Unity上で動く履帯の作り方 in Blender 2.82
はじめに
こんにちは、部員の杉山です。今回は新入生向けコンテンツを作り始めた際にひっかかった所を記事にしていきます。
事の起こり
履帯を作るにあたり私はいつも通りArrayモディファイアとCurveモディファイアを併用していました。そしてリグを打ち、ポーズモードでアニメーションを作るぞ!と思ったとき。
履帯が丸ごと動いてしまうことに気づきました、これではいけませんね。
本題
この問題を解決するにあたり、Youtubeで検索したところこのような動画が!
ここからは動画を参考に作っていきます。
動画 0:35~ 履帯部分作成
キューブの中心にループカットを挿入して、下半分を削除、面を作成し埋めます。
こうすることで、キューブの中心座標を下面に合わせているんですね。
上面を移動ツールで下げ、スケールツールでⅹ軸方向の厚みを調整。(筆者はY軸方向に厚み調節しました)
この時、Ctrl キーを押しながらだと綺麗に動かせます。
動画 1:23~ ボーンと関連付け
Amatureを作成し、CubeをBoneのTailに中心部分が重なるように移動させます。(キューブを動かす際は3Dカーソルを使うとうまく動かせます、Shift+Sのショートカットを上手く使いましょう)
移動出来たら、Shift キーを押しながらCube→Armatureの順で選択し、Ctrl+P、「自動のウェイト」を選択
動画 1:50~ 複製
平行投影で作業するのがよいでしょう。先ほど作成したArmatureとCubeのセットを選択、Shift+D で複製し動画の通りに並べていきます。数はお好きに。
動画 5:10~ アニメーション作成
今しがた作ったArmatureをすべて選択してPoseModeにし、履帯の動きを作っていきましょう。ここも動画の通りにしていれば問題ありません。
動画 8:22~ 複製
履帯を複製します。ObjectModeに戻し、全選択した後、複製しましょう。
動画 8:45~ 後退アニメーション
先ほど作ったアニメーションに後退を追加します。画面を増やして、DopeSheetに変更しましょう。開始時のモーションをShift+Dで複製し等間隔に配置します。
動画 10:13~ ループアニメーション
ここまで作ったアニメーションを再生するとゆっくりと加速減速し、ループ感が出ません。画面をGraphEditorに切り替えます。グラフが緩やかな曲線を描いているのが見て取れると思います。Aキーで全選択した後に、Vキーを押し「ベクトル」の項目を選択しましょう。再生すると直線的な緩急のない動きになったと思います。
動画 11:10~ FBX書き出し
FBX形式で書き出しします。
動画 11:23~ Unityに持っていく
ここからはUnityでの作業になります。出力したFBX形式のファイルをUnity上に持っていき、Scene上に配置します。Scene上に配置したモデルにAnimatorControllerを設定しておきましょう。
動画 11:40~ アニメーション
Unityに持ってきたモデルのPrefabを選択し、InspectorビューでAnimationの長さを調整します。前進部分のみを選択しLoopTimeにチェックを入れてください。分かりやすい様に名前を付けたらApplyを押しましょう。次に+マークを押し同じように後退のアニメーションも切り抜きます。
動画 13:40~ アニメーションコントロール
Animatorビューを開き、先程作った前進のアニメーションをドラッグ&ドロップしましょう。綺麗に前進できていますか?できていたら成功です。
まとめ
動画で紹介されているのはここまででしたが、自分で調べて動画終わりのように挙動に合わせた履帯の動きも作れればいいなと思います。
2019年度活動報告
はじめに
こんにちは, 部長の木村です. 昨年に続いて, 2019年度の振り返りをしたいと思います.
普段の活動
2019年度の活動では, 「ゲーム部」と「新規開発部」という2つの部署に分かれて活動していました. 昨年度まではチームでのVRゲームの開発をメインに活動してきましたが, 個人での活動やゲームだけではなく新しいデバイスや技術を使った開発もしたとの声があり, このような体制での活動をする事に決定しました.
全体
前期の間は新入生向けにxR開発に必要な技術を学ぶためのワークショップを開きました.
また, 毎週第4金曜日には全体での定例会を開いてLT会などを行いました.
ゲーム部
ゲーム部では, 展示イベントに向けて全員で1つのVRゲームの制作を行いました. スケジュールは以下の通りです.
オープンキャンパスやデジゲー博で出展した作品です.
新入生歓迎会に向けての開発は現在も進行中です.
新規開発部
新規開発部では個人開発の他, 次のような活動を行いました.
- xR関連の映像作品の試聴会
- 某xR界隈の履修科目リストの必修科目の1つを履修しました.
- VR界隈のワード紹介
- VR界隈でよく目にするワードについて調べ, 毎週交代でLTのような形式で発表し合いました.
また, 現在はバーチャルマーケット4への出展に向けての準備と新入生歓迎会に向けてのチーム開発が進行中です.
イベント
VRキャラバン活動 (5/18,19)
HTC Vive ProやゲーミングPC等の機材の運搬・電力供給をすべて電気自動車で行い, 野外でVRコンテンツの展示をしました.
道の駅、湯川会津坂下にてVRの展示を行います!コンテンツはiPadとVRでプレイする対戦ゲームと魔法アクションRPGです、是非お越し下さい! pic.twitter.com/f4A7VyKMbo
— Aizu-Progressive xr Lab (@aizu_PxL) May 18, 2019
詳しくはこちらの記事をご覧ください.
Oculus Quest体験会 (6/16)
Oculus Questが発売され, A-PxLでも多くの部員が購入したので大学の体育館を貸し切ってQuestの体験会を開きました.
[Oculus Quest体験会]
— Aizu-Progressive xr Lab (@aizu_PxL) June 9, 2019
6/16日(日)14:00~16:00にOculus Quest体験会をやります!
場所は大学の体育館です。
部員でなくても参加可能なので、VRやQuestに興味のある方やスペースやケーブルを気にせずにVRゲームで遊んでみたい方はお気軽にお越し下さい! pic.twitter.com/cBfkkgy7OE
この広い空間で近未来の機器を付けて謎の動きをする集団#A_PxL#OculusQuest体験会 pic.twitter.com/IYv9OoZzQs
— グッチー (@guchimoriVR82) June 16, 2019
新入生対抗ハッカソン (7/13,14)
新入生向けのワークショップが一通り終わり, 新入生同士のチームでのハッカソンを行いました. テーマは「既存のゲームを面白くハックしよう」ということで, Unity公式のサンプルプロジェクトや事前に用意したシンプルなゲームの改造をしてもらいました.
オープンキャンパス VR作品体験会 (8/11)
大学のオープンキャンパスで, 部員の制作した作品の体験会をやりました.
[オープンキャンパス VR体験会]
— A-PxL (@aizu_PxL) August 11, 2019
本日のオープンキャンパスで部員の制作したコンテンツの体験会をやっています!
場所は大学の会津大学 LiCTIAです。
午前は12時まで、午後は13時から15時までやっていますのでぜひお越し下さい! pic.twitter.com/z6pT3HBvKR
学祭
学祭でも制作物の展示・体験会を予定していたのですが, 今年度は台風の影響で学祭自体が中止となってしまいました...
デジゲー博
今年も抽選に当選し, 参加させて頂く事ができました!
会津大VR部(A-PxL)のデジゲー博での展示もまもなく終わります pic.twitter.com/ZQGCSPt2Hf
— ヤミみみ (@MtYamaniko) November 17, 2019
その他
これまでに部で制作した作品をまとめたポートフォリオです。
UniTaskとUniRxで、待ちの処理を実装する(後編:UniRx)
前編では、非同期処理のライブラリとしてUniTaskを軽く解説しました
UniTaskとUniRxで、待ちの処理を実装する(前編:UniTask) - Aizu-Progressive xr Lab blog
今回はUniRxというものを解説します
UniRxに関しては文章で説明するより、コードを見せる方が早そうです
void Start() { var count = 0; this.UpdateAsObservable() .Subscribe(_ => { if (Input.GetKeyDown(KeyCode.Space)) { print(count++); } }); }
はい
このコードだけで、スペースを押すごとにその回数を出力できます
フィールドに値を保持する必要がありません
正確にはライブラリの目的は、イベントハンドリングであり、イベントという概念をうまく扱う為のものですが
学習コストが高いので詳しくは触れない事にします
UniTaskと組み合わせるととても便利な部分と、UI等の処理には使えるかなという部分を紹介します
UniRx
まずUniRxの導入について
UniRxはAssetStoreにあるのでそちらから
UniRx - Reactive Extensions for Unity - Asset Store
コードで使うときは次の物が必要です
using UniRx; using UniRx.Triggers;
最初に見せたコードの事もそれなのですが
取り敢えず、好きなところでUpdate処理を書ける事を見せます
this.UpdateAsObservable() .Where(_ => Input.GetKeyDown(KeyCode.Space)) // フィルタ .Subscribe(_ => { print("スペースキーが押された"); });
ここでのthisは、自分自身・・・書いているコンポーネントのクラスの事ですね
そこから、UpdateAsObservable()というものを取得できて、Subscribeという物に、ラムダ式でUpdate処理を書くことになります
Whereという物を使うと、条件がtrueのときのみSubscribeの処理を実行できます
ここのラムダ式では、引数は存在するのですが、
値が無いを意味するUnit型という物であるため、
使わないので_
にしています
他にも
this.OnCollisionEnterAsObservable() .Subscribe(collision => { var renderer = collision.collider.GetComponent<Renderer>(); if (renderer != null) { renderer.enabled = false; } });
これで、OnCollisionEnter時にぶつかったColliderにRendererがあれば、表示を消す処理
this.OnCollisionStayAsObservable(); this.OnCollisionExitAsObservable(); this.OnTriggerEnterAsObservable(); this.OnTriggerStayAsObservable(); this.OnTriggerExitAsObservable();
これらのように、UniRxによって、
Updateの他にも一通りの
「決まったメソッドを定義して書かなければならないもの」
を任意の位置で書くことができるようになります
UniTaskと組み合わせる
ではUniTaskと組み合わせたら何ができるのか
見ていきましょう
いきなりちょっと複雑な例になりますが
例えば、
ボタンを押しているかつゲージが0より大きい場合にとある動作をし続けるというのがあるとします
そして、ゲージが0になったら動作をやめて、ゲージが回復し始めます
ゲージはUIとして表示されなくとも
ゲームの走る動作なんかはそういうのがよくありますよね
なので走る動作として実装してみます
using System; using System.Threading; using System.Collections; using System.Collections.Generic; using UnityEngine; using UniRx; using UniRx.Triggers; using UniRx.Async; using UniRx.Async.Triggers; public class Player : MonoBehaviour { public float walkSpeed; public float runSpeed; public float statminaRecoverySpeed; public float staminaDecreaseSpeed; public float staminaMax; public float stamina; [NonSerialized] public float currentSpeed; void Start() { Run(this.GetCancellationTokenOnDestroy()).Forget(); // 初期化 stamina = staminaMax; currentSpeed = walkSpeed; // 移動処理の登録(前後のみ) this.UpdateAsObservable() // UniRx .Subscribe(_ => { Vector3 moveDir = Vector3.zero; if (Input.GetKey(KeyCode.W)) { moveDir += Vector3.forward; } if (Input.GetKey(KeyCode.D)) { moveDir += Vector3.back; } transform.position += moveDir * currentSpeed * Time.deltaTime; // スタミナの回復は常にしている stamina += statminaRecoverySpeed * Time.deltaTime; if (stamina > staminaMax) stamina = staminaMax; }); } async UniTask Run(CancellationToken token) { // 走り始めの条件判定 await UniTask.WaitUntil(() => { var staminaRate = stamina / staminaMax; var b1 = Input.GetKeyDown(KeyCode.LeftShift); var b2 = staminaRate > 0.3f; return b1 && b2; }, cancellationToken: token); // 走り始める currentSpeed = runSpeed; var disposable = // スタミナ減少処理 this.UpdateAsObservable() // UniRx .Where(_ => Input.GetKey(KeyCode.W)) .Subscribe(_ => stamina -= staminaDecreaseSpeed * Time.deltaTime); // SubscribeしたのをCancellationTokenでCancelしたときに止めたいならこう書く token.Register(() => disposable.Dispose()); // 止まる条件判定 await UniTask.WaitUntil(() => { var b1 = Input.GetKeyUp(KeyCode.LeftShift) || Input.GetKeyUp(KeyCode.W); var b2 = stamina < 0f; return b1 || b2; }, cancellationToken: token); // 止まる disposable.Dispose(); // スタミナ減少処理の停止 currentSpeed = walkSpeed; if (stamina < 0f) stamina = 0f; // 繰り返す Run(token).Forget(); } }
このコードをGameObjectにアタッチして、
このような感じで値を設定すると、
WキーとShiftキーを押すと、移動速度が上がり、
スタミナが無くなるか、WキーやShiftキーが離されれば移動速度が下がるところが確認できます
Staminaも正しく上がったり下がったりするのが確認できます
UniRxを使ったところは2か所ありますが、
二つ目のところでdisposable
という物を取得しています。
このdisposable
をDispose
することで、登録した処理を止めることが出来ます
このように、UniTaskとUniRxを組み合わせることで、
async/awaitの非同期的な処理によって、Update動作を始めたり止めたりできるので
複雑そうな処理を割と簡単に書く事ができました
UIの更新
UniRxにある、ReactivePropertyというものを使うと
主にUI等の更新処理を書けます
テキストの更新は毎フレーム行うと、メモリの使い方が悪くなりますから
public FloatReactiveProperty hp; public Text text; void Start() { this.UpdateAsObservable() .Subscribe(_ => hp.Value += Time.deltaTime); // hpを増やすとする // hpの更新処理。hpの値が変更されたら処理が実行される // AddToを付けないとメモリに悪いので一応付けた方が良い hp.Subscribe(v => text.text = v.ToString()).AddTo(this); }
UniRxについてとアニメーション実装してみた
ここからちょっと難易度が上がります
需要があるかはわからないですがUniRxとUniTaskを用いて、ちょっとスクリプトのみでアニメーションを実装してみます
まず先に、
UniRxの型について解説します
UniRxではIObservable
という型が重要です
this.UpdateAsObservable()
の型や
this.OnTriggerEnterAsObservable()
の型は
それぞれ、
IObservable<Unit>
と
IObservable<Collision>
であり、
これらの型はWhere
やSubscribe
を使うことができます
Where
やSubscribe
時のラムダ式の引数の型はIObservable<T>
のT
の部分の型です
UniRxを使うと、次のような処理も楽に書けます
コルーチンが前提知識になるので知らない人は調べて欲しいかなってところですが
分からないなら飛ばしても構いません
好きなところから、指定した時間毎フレーム経過時間と指定した時間の割合を取って何か処理をしたい場合
void Start() { // コルーチンに渡したobserverから流された値が流れてくる // oはobserver Observable.FromCorountine<float>(o => Timer(3f, o)) .Subscribe(t => print(t)).AddTo(this); } IEnumerator Timer(float actTime, IObserver<float> observer) { var sTime = Time.time; // 最初の時間 while(true) { var rate = (Time.time - sTime) / actTime; // 経過時間 / 指定した時間 if (rate > 1f) break; // 割合が1より大きいなら終了 observer.OnNext(rate); // 割合を送る yield return null; } observer.OnNext(1f); // 最後に1だけ送る observer.OnCompleted(); // Observableを終了させる }
これで、3秒間0から1までの値を出力してくれます
ここでは、コルーチンに渡されたobserverに
OnNextで経過時間の割合を流します
FromCorountineの型はIObservable
IObserver
Subscribe時に流した値を使うことが出来ます
ここでは、経過時間の割合が出力されますね
OnCompletedすると、Observable側で値が流れるのが終了されたというのを検知できます
AddToしているのは、コンポーネントがDestroyしたときに処理をキャンセルする為です
UniTaskと組み合わせて、
アニメーションさせてみます
using System; using System.Threading; using System.Collections; using System.Collections.Generic; using UnityEngine; using UniRx; using UniRx.Triggers; using UniRx.Async; using UniRx.Async.Triggers; public class SphereAnimation : MonoBehaviour { void Start() { PlayAnimation(this.GetCancellationTokenOnDestroy()).Forget(); } async UniTask PlayAnimation(CancellationToken token) { await UniTask.WaitUntil(() => Input.GetKeyDown(KeyCode.Space) , cancellationToken: token); await Animation(token); PlayAnimation(token).Forget(); } async UniTask Animation(CancellationToken token) { IObservable<float> timer; IDisposable disposable; // 弾む、1回目 timer = Observable.FromCoroutine<float>(o => Timer(0.5f, o)); disposable = timer.Subscribe(t => transform.position = Vector3.up * (-(8f*t*t) + (8f*t))); // 放物線 token.Register(() => disposable.Dispose()); await timer.ToUniTask(token); // 弾む、2回目 // 同じObservableの場合、設定しなおさなくていい(例外あり) disposable = timer.Subscribe(t => transform.position = Vector3.up * (-(8f*t*t) + (8f*t))); token.Register(() => disposable.Dispose()); await timer.ToUniTask(token); // 1回転 disposable = timer.Subscribe(t => transform.position = new Vector3( Mathf.Sin(Mathf.PI * 2f * t), 0f, Mathf.Cos(Mathf.PI * 2f * t) )); token.Register(() => disposable.Dispose()); await timer.ToUniTask(token); // 早く弾む disposable = timer.Subscribe(t => transform.position = Vector3.up * (-Mathf.Sin(Mathf.PI * t * 4f) + 1f)); token.Register(() => disposable.Dispose()); await timer.ToUniTask(token); } IEnumerator Timer(float actTime, IObserver<float> observer) { var sTime = Time.time; while(true) { var rate = (Time.time - sTime) / actTime; if (rate > 1f) break; observer.OnNext(rate); yield return null; } observer.OnNext(1f); observer.OnCompleted(); } }
このコードを球にアタッチして
シーンを再生してSpaceキーを押すと
次のアニメーションが動きます
最後の例はずいぶん難しいかもしれませんが、
UniTaskとUniRxを組み合わせると結構良い感じにコードが書ける事がわかったと思います
最後に
UniRxは基本的にはイベントハンドリングというのが目的で扱う物です
一応、UniRxはいろんな書き方ができてしまい、
UniRxで非同期処理もできなくはないのですが、
UniRxは非同期処理の為に使ってはいけません
(↑これは僕の経験談でもある)
"待ち"が入る非同期処理はUniTask
動作を登録したいときはUniRx
のような使い分けが大事です
それを分かったうえで、UniRxを学習するのも悪くは無いでしょう
とりさんの記事とかで、しっかり学べます
UniRx入門 その1 - Qiita
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で標準に用意されているウィンドウの自分好みのものを作成したり、インスペクターの表示を編集しやすいようにいじることができます