UniTaskとUniRxで、待ちの処理を実装する(前編:UniTask)
来年度で学部4年になるユムルと言います
UnityのC#でUniTaskとUniRxという便利なライブラリがあるので、
前編と後編に分けて紹介したいと思います
最初に断っておきますが、取りあえずC#を使って、Unity上でプログラムを書ける人向けの記事です
ラムダ式
前提知識としてラムダ式が必要なので説明します
次の構文がこの記事では何回か登場するので、その時は理解できるまでここに戻ってきて欲しいと思います
引数 => 処理と返り値
この構文は、関数を簡単に書く書き方です
関数を定義するところの引数に当たる変数を=>
の左側に書いて、処理と返り値は右側に書きます
x => x*2; // 引数がxの変数に渡され、x*2を返す () => 3; // 引数が無い場合は()で表す () => { // 処理を挟みたければ{}で囲む var a = 3; return a; // returnで値を返す }; (a, b) => { // 引数が複数ある場合は括弧とカンマで指定 var c = a + b; print(c); // 返り値が必要なければreturnはいらない }; () => print(0); // 返り値も{}も無いバージョン
引数や返り値の型はどうするの?ってなりますが
ラムダ式とは別のところで指定されているので、
そういうところで使える構文という事です
前編で紹介するのはUniTaskです
もともとプログラミングでasync/awaitという非同期処理の為の構文で、
C#でも言語機能として使えるものがあるのですが、
それをUnityに特化させて便利にしたものになります。
async/awaitというのはUniTaskと同時にここにまとめますが、
その前に非同期処理について
非同期処理
非同期処理というのは、簡単に言えば何か別の事を待って行う処理になります
まず同期処理というのが、プログラムを書いた順番に処理がされていくものです
それに対して非同期処理になると、必ずしも処理を順番に行うわけではなくて
条件が整うのを待って処理を行う必要がある場合に、別の処理をしようという"待ち"が絡んだ処理の事です
例えば、ゲームではグレネードを投げた場合、
時限式ならば一定時間後に
接触式なら別の物に衝突した場合に
爆発するわけですが、
この場合それぞれ、一定時間が経つ, 衝突するという待ちの動作が必要になります
これらの動作を適切に行うのが非同期処理になります
これから紹介するUniTaskはこの非同期処理を効果的に実装するための便利なライブラリです
UniTask
ではUniTaskについて解説をします
ライブラリの導入はこのリンクからダウンロードできるUnityPackageから
Releases · Cysharp/UniTask · GitHub
まだAssetStoreにはありません
このライブラリの機能を使う為には、C#のソースコードのusing UnityEngine等が並んでいるところに、
using UniRx.Async;
が必要です
まず、一番最初の例として2秒後にprint出力をさせます
void Update() { if (Input.GetKeyDown(KeyCode.A)) { Method(); } } async UniTask Method() { await UniTask.Delay(2000); // 千分の一秒単位で2秒 print("2秒たった"); }
これでAキーを押してから2秒経つとprint出力できます
連打したら、すべての連打に対して2秒ずつ待って出力してくれます
メソッドの最初にasyncを付けることで、
そのメソッドは待ちの処理をする事ができる
awaitを付けるとその文で処理が終わるのを待つよという意味になります
using System;
をファイルの最初につけて、
UniTask.Delay(TimeSpan.FromSeconds(2f)
と書くと、ミリ秒ではなくて普通に秒数で指定できます
using System;
を書いてしまった場合、UnityEngine.Random
とSystem.Random
が競合してRandom.value
がそのまま書けなくなってしまうので
using Rand = UnityEngine.Random;
も書いておくと
Rand.value
と書けるようになります
UniTaskには便利な機能があって、
その機能を使うと、Aキーを押したあとに、B、C、D、キーを順番に押すと処理が行われる等の処理も簡単に書くことが出来ます
ラムダ式使ってるので分からなければこの記事の最初に説明が書かれています。ここでは引数無し、boolを返すラムダ式です
void Start() { Method(); } async UniTask Method() { await UniTask.WaitUntil(() => Input.GetKeyDown(KeyCode.A)); await UniTask.WaitUntil(() => Input.GetKeyDown(KeyCode.B)); await UniTask.WaitUntil(() => Input.GetKeyDown(KeyCode.C)); await UniTask.WaitUntil(() => Input.GetKeyDown(KeyCode.D)); print("A, B, C, Dキーが順番に押された"); }
WaitUntilは独自にUpdateのタイミングで条件判定を行ってくれます
また、async/awaitは値を返す事もできます
void Update() { if (Input.GetKeyDown(KeyCode.A)) { // ラムダ式にもasyncを付けれる UniTask.Void(async () => { var time = await GetTimeUntilKeyPushDown(KeyCode.B); print("Aを押してからBを押すまで" + time + "秒"); }); } } async UniTask<float> GetTimeUntilKeyPushDown(KeyCode code) { var from = Time.time; await UniTask.WaitUntil(() => Input.GetKeyDown(code)); return Time.time - time; }
UniTask<返り値の型>と書くことでasync内での値を
return 返り値の型の値;
で返すことが出来ます
UniTask.Voidは、UniTask型を返すラムダ式を渡すことでasyncの関数を実行することができ、その返り値は無視します。
async関数はループさせる事ができます
void Start() { Loop(); } async UniTask Loop() { await UniTask.WaitUntil(() => Input.GetKeyDown(KeyCode.A)); print("Test"); Loop(); }
これでAを押すたびに出力がされるのが確認できます
ところで、awaitでは何を待っているのだという疑問がありますが
それは、UniTaskの値が確定されるのを待っています
従って、次のように書けば
UniTask<int> waitValue; void Start() { waitValue = new UniTask(async () => { await UniTask.Delay(2000); return 3; }); Test(); } async UniTask Test() { var v = await waitValue; print(v); }
2秒後に3が出力されるのが確認でき
フィールド等にUniTaskを置いておくことができるという事がわかります
これを利用すると、特定の値の初期化が終わるのを待つ等の処理ができるでしょう
UniTaskは便利な非同期処理のライブラリであることはよくわかるでしょう。しかし、"待ち"の処理をキャンセルする事を考える必要があります
例えばこのような処理で、
void Start() { Test(); } async UniTask Test() { await UniTask.WaitUntil(() => Input.GetKeyDown(KeyCode.A)); print("A"); }
シーンを再生し、Aキーを押す前にGameObjectを削除してみましょう
そして、Aキーを押してみましょう・・・
Aが出力されてしまいましたね。アタッチしたGameObjectがDestroyされたのですから、処理はされないという感覚を持ってしまうところですが、そういうわけにはいきませんでした
これはUniTask.WaitUntil
で使ってるUpdateがGameObjectに紐づくものではないからです
ということで、取り敢えずはGameObjectのDestroyに合わせてWaitUntilをキャンセルさせてみましょう
using UniRx.Async.Triggers
が必要です
void Start() { Test(this.GetCancellationTokenOnDestroy()).Forget(); } async UniTask Test(CancellationToken token) { await UniTask.WaitUntil(() => Input.GetKeyDown(KeyCode.A), cancellationToken: token); print("A"); }
このように、CancellationTokenというものを指定することになります
Destroy時のTokenはthisから取得できます
この場合のthisは(Componentを継承したMonoBehaviorを継承した)実装時のクラスになります
すべてのComponentから取得できます
asyncメソッド呼び出し時に、Forgetを書いていますが
これを消すと、Cancel時にエラーが起きます。
まあ、いちいちcancellationTokenを指定するのは面倒と感じるかもしれませんが、便利な機能を使っている分多少の面倒は仕方なしと割り切る必要はあるかもしれません
さて、次のように自分のタイミングでキャンセルをすることもできます
CancellationTokenSource cancellation = null; void Update() { if (Input.GetKeyDown(KeyCode.A)) { cancellation?.Cancel(); cancellation = new CancellationTokenSource(); Test(cancellation.Token).Forget(); } } async UniTask Test(CancellationToken token) { await UniTask.Delay(2000, cancellationToken: token); print("A"); }
cancellation?.Cancel()
の?.
はnullチェックをして、nullでなければ続きの部分を実行するものです
Aキーを連打したときに、一番最後に押したタイミングから2秒後に出力があります
ここらで前編は終わりにします
UniTaskについては検索すれば記事は結構出てくるので
後はそれで学べます
後編ではUniRxというものについて話をします
UniRx単体ではちょっと便利くらいのものかもしれませんが
UniTaskと組み合わせると強いです
UniTaskとUniRxで、待ちの処理を実装する(後編:UniRx) - Aizu-Progressive xr Lab blog
[追記 03/29]
後編ではUniRxと組み合わせる事で、UpdateはCollisionEnter等の動作を絡めたコードの解説をしているのですが、UniTaskのみで十分にそのようなコードが書ける事が分かってきたので続けて書きます
UniTaskのみで、そのようなループをするコードを書くときはwhile文を使います
async UniTask UpdateAndCollisionLoop(CancellationToken token) { // 指定のTagの物体と衝突があるまで待つ var trigger = this.GetAsyncCollisionTrigger(); while(true) { var col = await trigger.OnCollisionEnterAsync(token); if (col.gameObject.tag == "Test") break; } var count = 0; // このループ内がUpdateループになる while(true) { print(count++); if (Input.GetKeyDown(KeyCode.Space)) { break; } await UniTask.DelayFrame(0, cancellationToken:token); } // Updateでは0からインクリメントされた値が出力される // Spaceキーを押すとUpdateループを抜ける }
このようにすると、衝突を検知しつつUpdateループに繋げる事ができます
とは言え、UniTaskのみでは
ループをやめるタイミングをもう少し変わったものにすることができないですし、
毎ループごとにUniTask側でクラスのインスタンスを生成することにはなりますから、
そういうのを気にするのなら後編で書いているUniRxを使うほうがよかったりします
[さらに追記 04/01]
注意点があります
UniTask
をawait
せずに、呼んでしまった場合にはNullReferenceException
等のエラーが起きても、Unityのエラーとして出力されないようです。警告の方に少しだけ形を変えて出力されるので、場合によってはデバッグがしにくくなってしまいます
その場合、UniTaskScheduler
のUnobservedExceptionWriteLogType
というところを、UnityEngine.LogType.Warning
から、UnityEngine.LogType.Exception
に書きかえてしまうば直ってくれるようです
Unityのエディタ拡張で快適に開発を進めよう
皆さんこんにちは。A-PxLゲーム部所属の森口です。
もう3月ですね新生活が迫っている方もいるのではないのでしょうか。ついこの間始まったと思っていた私の大学生活も最終学年に突入しようとしています。
今日はタイトルにもある通りUnityのエディタ拡張に触れてコンテンツの開発をより良いものにしていこうという記事です。
- エディタ拡張とは
- エディタ拡張を始める前に
- ウインドウを作る
- インスペクタ拡張
- 話が長い。3行でまとめて
- 参考記事
エディタ拡張とは
「そもそもエディタ拡張ってなんだよ?」とか「名前は知ってる」という方向けに簡単にエディタ拡張について説明していきたいと思います。
Unityエディタ上にはアニメーションを作成するウインドウやプロジェクトの設定をするウインドウ、はたまたオブジェクトの情報が乗っているインスペクターなどが多く存在します。
Unityはとても良く作られているので基本的にこれらのものに対して不便に感じることは少ないと思います。しかし例えばゲームで敵のレベルやその数などを再生して確認→不満だったらスクリプトをいじって修正→再生して確認→不満d(ry みたいにするのは少し面倒ですよね。他にも再生しないでエディタ上で動作を確認して見たいと思うこともあると思います。そんなときに使えるのがエディタ拡張(インスペクタ拡張)です。
具体的にどんなことができるかというとUnityで標準に用意されているウィンドウの自分好みのものを作成したり、インスペクターの表示を編集しやすいようにいじることができます
バーチャルを「仮想」と訳す君に申したい
こんにちは! A-PxL部員の橋本です。もうそろそろ大学生活の3/4が過ぎようとしていることに驚きを隠せない今日この頃です。さて、他の部員がIT分野(主にUnity)に関してのブログを多数執筆しているので、今回は私が世間のVRの認知に対して思うことを書いていきたいと思います。
今回の記事で私が言いたいことは
VRを仮想現実と訳すのは間違いだ!
ということです。では、なぜそう言えるのか順に解説していきましょう。
バーチャルの意味
バーチャル(virtual)は形容詞で、名詞はバーチュー(virtue)となっています。というわけで、まずはバーチューの意味を辞書を引いて調べてみます。バーチューは「徳」、「善行」、「効力」などを意味しますが、さらに原義に戻ってみると、「そのものをそのものとして在らしめる本来的な力」という意味を持ちます。米国継承英語辞典(The American Heritage Dictionary)では、「Existing in essence or effect though not in actual fact or form」と定義されています。これは 「みかけや形はそうじゃないけど、本質や効果としてはそのもの」 という意味を表します。つまり、「バーチャル〇〇」は「みかけや形は〇〇じゃないけど、実質的には〇〇」を意味することになります。
例えば、「バーチャルマネー」はオンライン上で使用することができるお金(MMORPG内の通貨や電子決済)を表しますが、実際に硬貨や紙幣として目で見たり触ったりすることはできません。しかし、現金と同じように価値を有しており、取引に使うことができる。すなわち、「見かけはお金じゃないけど、効果的にはお金」。だからバーチャルマネーなのです。
他にも、2016年から活動開始したキズナアイを筆頭に今でも増え続けている「バーチャルYouTuber」も同じように考えられますね。すなわち「みかけはYouTuberじゃないけど、実質的にはYouTuber」ってことです。ちなみに、私はそこまでバーチャルYouTuberに詳しくはありません。せいぜい四天王の5人+αくらいです。
(四天王なのに5人とはこれいかに...)
バーチャルの類義語、反意語、そして「仮想」
バーチャルの意味が分かったところで、次にバーチャルの類義語、反意語を調べてみたいと思います。まずはじめにバーチャルの反意語ですが、これはノミナル(nominal)「名目上の」となっています。では、ノミナルの反意語はどうなっているかというと、実はこれはリアル(real)「現実の」が相当します。これが意味することはリアルの反対はバーチャルじゃないくて、リアル=バーチャルなんだよということです。ちなみに、リアルの対を成す言葉はイマジナリ(imaginary)「架空の」が相当すると考えられています。
バーチャルの反意語、類義語について理解していただいたところで、「仮想」について考えてみましょう。仮想は読んでそのまま「仮に想定した」という意味を表します。これは英語にするならsupposedなどと表すべきであり、これはvirtualとは異なる概念であると言えます。つまり、概念的な違いによりバーチャルは「仮想」と訳すことができないのです。
バーチャルリアリティの意味と訳し方
以上のことより、バーチャルリアリティとは「見た目的には現実じゃないけど、実質的、効果的には現実」を意味し、「現実に似せた偽物の現実」ではありません。そして、「仮想現実」と訳すこともできません。では一体どう訳すればいいのかということですが、これはそのまま「バーチャルリアリティはバーチャルリアリティだ」で十分です。どうしても日本語に訳したいという方は「人工現実感」と訳すのがベストということになっています。
また、話は変わりますが、世間では「VR=アタマになんか被ってやるアレ」という認識をされている方も多くいらっしゃることと思います。確かにOculus RiftやHTC ViveといったHMD(ヘッドマウントディスプレイ)の登場によりVRの人気が出たといっても過言ではないので一概に間違いとは言えないのですが、VRの概念から考えると、HMDはあくまで「VRを体験するための道具」なのでVR=HMDとはなりません。このことについても頭の片隅に置いといてもらえればと思っています。
さらに余談ですが、VR技術とは、「コンピュータなどを使って現実と同じ、もしくは現実では起こりえないことを再現する技術」と言えます。それゆえ、VRの研究対象は心理学からロボティクス、CGやヒューマンインターフェースなど多岐に渡っており、とても広大です。将来的に大学ではVRについて研究したいと思っている高校生は、VRの中のどの分野について研究したいのか高校在学中に決めておくことをお勧めします。
まとめ
- バーチャルはリアルとほぼ同義語である。そして「仮想」と訳すことができない。そのまま「バーチャル」と言いましょう
- バーチャルリアリティを日本語にするなら「人工現実」と訳しましょう
特に、今A-PxLに所属している部員、そしてこれからA-PxLに所属することになる未来の部員たちには以上のことを最低限知っておいて欲しいものです。
参考
- 作者:
- 出版社/メーカー: コロナ社
- 発売日: 2010/12/16
- メディア: 単行本(ソフトカバー)
以上の本の内容を試験としたVR技術者認定試験も開かれております。現在のA-PxLにも何名かの合格者がいます。興味がある方はぜひトライしてみましょう!
大学のモーションキャプチャを触ってみた その一
ブログ記事を見なくなって久しい会津大学OBの平川です。
大学を卒業し、老害社会人になり、モーションキャプチャ関係の知識が増えたので、
大学のモーションキャプチャシステムであるViconを夏*1に五日間ほど触ってみました。
それについての所感やフローをつらつらと書いていこうと思います。その三ぐらいまでは続くかもしれません。
*1:随分と立っていますがご容赦ください。
Blenderで簡単ボーンアニメーション
こんにちは!学部一年生の吾妻です。初めてのブログ投稿で緊張しています。今回はBlenderで簡単な犬の作り方、ボーンアニメーション(犬が歩いているアニメーション)の付け方を紹介しようと思います。Blender初心者なため、「ここはもっとこうした方がいい」という点もあるかもしれませんが、暖かい目で見てくださると幸いです。
犬を作る
アニメーションをつけるための犬を作成します。アニメーションをつけるということが目的なので簡単な犬を作ります(言い訳)。正方形からループカット機能「Ctrl + Rキー」と面の押し出し機能「Eキー」を使って作成しました。
ボーンを付ける
まず、作成したボーンが見えるように、Zキーからワイヤーフレームを選択します。 次にオブジェクトモードに切り替えた後に「Shift + Aキー」でアーマチュアを選択します。 するとボーンが3Dカーソルの位置にできるので、それを背骨あたりの位置に調整して大きさも調整します。 また、このままではソリッド表示に切り替えた時にボーンが見えなくなってしまうので、プロパティエディタのオブジェクトデータ→ビューポート表示→最前面を選択してソリッド表示でもボーンが見えるようになります。 編集モードに切り替え、ボーンの先端か末端を選択して「Eキー」でボーンを追加していきます。この時にX,Y,Zを押すとその軸に伸ばすことができます。また、x軸ミラーを適用して、「Shift + Eキー」で左右対称にボーンを出すことができるので便利です。 このようにボーンを作ってみました。 これだけだとただボーンがあるだけなので、オブジェクトモードに切り替え、メッシュ→ボーンの順で(Shiftを押しながら)選択した後に「Ctrl + Pキー」から「自動のウェイト」でボーンとメッシュを関連づけます これでボーンをつけることができました。
アニメーションを作る
下準備が終わり、いよいよアニメーションを作っていきます。まずトップバーのAnimationを選択します。 これでアニメーションを作るのが楽になります。 次にフレームレートについて設定します。デフォルトではフレームレートは24FPSに設定されています。24FPSというのは、1秒間に24フレーム進むということです。歩くという動作は、直立→足を前に出す→戻す→足を後ろに引く→戻すという一連の動作を繰り返しています。今回はこの一連の動作を1秒間に行うことにするので、開始フレームを0、終了を24にします。 そして画面下のドープシートをアクションに切り替えます。 新規を押してアニメーションを作っていきます。 足が揃っている状態で足4本のボーンを選択し、0フレーム目に「Iキー」でキーフレームを挿入していきます。今回挿入するのは回転だけです。※この時ポーズモードにしておくことを忘れずに! 12、24フレーム目も同様に足が揃っている状態でキーフレームを挿入します。 次に6フレーム目に移動して左の前足と、後ろ足を「Rキー」で回転させて前に出します。「Ctrl」を押しながらマウスを動かすと5°ずつ動きます。 同様に右側の前足、後ろ足を「Rキー」で後ろに引き、4本の足を選択して「Iキー」でキーフレームを挿入します。 次に18フレーム目に飛んで今度は逆に、左側の前足、後ろ足を後ろ、右側の前足、後ろ足を前に出してキーフレームを挿入します。 簡単なアニメーションですがこれで完成です。
終わりに
拙い文章で長々と書いてあるにも関わらずここまで見てくださり、ありがとうございます。今後は段階を踏んでもっと複雑なモデルや、アニメーションを作れるように日々精進していく所存でございます。