20210101のUnityに関する記事は3件です。

『UniTask v2』の色々な .ToUniTask

はじめに

UniTask にはあらゆるものを UniTask に変換する .ToUniTaskというメソッドがあります
同じものを変換するときでもオーバーロードがあったりでややこしいので備忘録としてまとめてみました
GetAwaiterWithCancellation なども書いておきます(実装を書くのは省きます)

UniTask/Github

環境

以下の環境で動作しています

  • Unity 2019.4.15f1
  • UniTask 2.0.37

目次

準備

ダミー変数
var token = this.GetCancellationTokenOnDestroy();
var uniTask = UniTask.Run(() => "hoge");

こんな感じで変数tokenは何かしらのCancellationTokenが、
変数uniTaskには何かしらのUniTaskが入っていると考えてください

CancellationToken

CancellationToken を UniTask に変換できます

ToUniTask

// 実装
public static (UniTask, CancellationTokenRegistration) ToUniTask(this CancellationToken cancellationToken)

// 使用例
var (task,registration) = token.ToUniTask();
registration.Dispose(); // CancellationTokenRegistrationから破棄もできる
await task;

CancellationToken がキャンセル状態になるまで await される UniTask が返ってきます
返り値は (UniTask, CancellationTokenRegistration) というタプルです

WaitUntilCanceled

UniTask型ではありませんが、awaitableなCancellationTokenの型が返ってきます

// 実装
public static CancellationTokenAwaitable WaitUntilCanceled(this CancellationToken cancellationToken)

// 使用例
await token.WaitUntilCanceled(); // 直接awaitできる。ToUniTaskはできない

逆変換

UniTask を CancellationToken に変換できます

// 実装
public static CancellationToken ToCancellationToken<T>(this UniTask<T> task)
public static CancellationToken ToCancellationToken<T>(this UniTask<T> task, CancellationToken linkToken)

// 使用例
var ct = uniTask.ToCancellationToken();
var ct2 = uniTask.ToCancellationToken(linkToken);

コルーチン

ダミーのコルーチン
private IEnumerator HogeCoroutine(){
    yield break;
}

ToUniTask , GetAwaiter , WithCancellation

UniTask では StartCoroutine を使わずにコルーチンを起動することができます

// 実装
public static UniTask ToUniTask(this IEnumerator enumerator, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken))

// 使用例
await HogeCoroutine(); // コルーチン起動
await HogeCoroutine().ToUniTask(PlayerLoopTiming.FixedUpdate, token);
await HogeCoroutine().WithCancellation(token);

下記は MonoBehaviour とコルーチンを結び付けるためのメソッドです
MonoBehaviourで StartCoroutine をするように、ゲームオブジェクトの寿命とコルーチンが結びつきます

// 実装
public static UniTask ToUniTask(this IEnumerator enumerator, MonoBehaviour coroutineRunner)

// 使用例
await HogeCoroutine().ToUniTask(this); // ゲームオブジェクトと結び付けられる

逆変換

UniTask を IEnumerator に変換できます

// 実装
public static IEnumerator ToCoroutine<T>(this UniTask<T> task, Action<T> resultHandler = null, Action<Exception> exceptionHandler = null)
public static IEnumerator ToCoroutine(this UniTask task, Action<Exception> exceptionHandler = null)

// 使用例
StartCoroutine(uniTask.ToCoroutine(Debug.Log,Debug.LogError));
StartCoroutine(uniTask.ToCoroutine(Debug.LogError));

エラーハンドリングや結果を得るためのデリゲートを渡せるオーバーロードが用意されてます

AsyncOperation

AsyncOperation を UniTask に変換できます

UniTask には 様々な AsyncOperation が awaitable になっています。

  • AsyncOperation
  • ResourceRequest
  • AssetBundleRequest
  • AssetBundleCreateRequest
  • UnityWebRequest

シグネチャは拡張メソッドのthisキーワードが付いた引数の型の違いしかなく、使い方は全部一緒なので省きます。

// 実装
public static UniTask ToUniTask(this AsyncOperation asyncOperation, IProgress<float> progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken))

// 使用例
await SceneManager.LoadSceneAsync("Game");
await SceneManager.LoadSceneAsync("Game").ToUniTask(cancellationToken : token);
await SceneManager.LoadSceneAsync("Game").WithCancellation(token);
await SceneManager.LoadSceneAsync("HogeScene")
                  .ToUniTask(Progress.Create<float>(n => {
                      Debug.Log($"現在{n * 100}%");
                  }), PlayerLoopTiming.Update, token);

SceneManager.LoadSceneAsync の例です。
.ToUniTaskに Progress を入れて進行状況を取ることができます。

AsyncLazy

UniTask に用意されている AsyncLazy型です。ToUniTask ではなく .Taskプロパティ から取得できます

.Taskプロパティ

var lazy = UniTask.Lazy(() => uniTask);
var task = lazy.Task; // UniTaskに変換

逆変換

UniTask から AsyncLazy に変換できます

uniTask.ToAsyncLazy(); // AsyncLazyに変換

IObservable

IObservable を UniTask に変換できます。

ToUniTask

// 実装
public static UniTask<T> ToUniTask<T>(this IObservable<T> source, bool useFirstValue = false, CancellationToken cancellationToken = default)

// 使用例
await subject.ToUniTask(cancellationToken:token); // OnCompletedが発行されるまで待つ
await subject.ToUniTask(true,token); //次に発行される最初のメッセージを待つ

第一引数をtrueにすると、次に発行される最初のメッセージだけを待機できます。

逆変換

UniTask を IObservable に変換できます。

uniTask.ToObservable();

JobHandle

ジョブの実行が終了するまで待機できます

// 実装
public static UniTask ToUniTask(this JobHandle jobHandle, PlayerLoopTiming waitTiming)
public static async UniTask WaitAsync(this JobHandle jobHandle, PlayerLoopTiming waitTiming, CancellationToken cancellationToken = default)

// 使用例
await jobHandle;
 // CancellationTokenが指定できない
await jobHandle.ToUniTask(PlayerLoopTiming.FixedUpdate);
 // 指定したタイミングに切り替えてjobHandle.Complete();される
await jobHandle.WaitAsync(PlayerLoopTiming.FixedUpdate,token);

実装を見ればどれも違うのがわかりますが、自分はJobHandleに詳しくないので有識者の方、コメントや編集リクエストをお願いします!

AsyncGPUReadbackRequest

AsyncGPUReadbackRequest を UniTask に変換できます。

ToUniTask

// 実装
public static UniTask<AsyncGPUReadbackRequest> ToUniTask(this AsyncGPUReadbackRequest asyncOperation, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken))

await AsyncGPUReadback.Request(src);
await AsyncGPUReadback.Request(src).ToUniTask(cancellationToken : token);
await AsyncGPUReadback.Request(src).WithCancellation(token);

逆変換

UniTask を AsyncGPUReadbackRequest に変換できます

var asyncGpuReadbackRequest = AsyncGPUReadback.Request(src);
asyncGpuReadbackRequest.ToUniTask(PlayerLoopTiming.FixedUpdate,token);

おわりに

文章書くの下手すぎて読みにくいかもです。すいません。
間違いがあったら優しく教えてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity - boltでの敵AIセオリーまとめ 〜 bolt kit Third Personからわかること

あけおめです。
かなり久しぶりに記事を書きます、asiramでございます。
ちょっと思うこともあり、これからちょいちょい投稿できればなーと思っております。

さて本題ですが、2020年に無償化・Unityファミリー入りしたビジュアルスクリプティングツール「bolt」について、敵AIセオリーがサンプルプロジェクトからある程度読み取れたので、まとめてみようかなと思います。

その前にちょっとだけboltについて

boltの何がいいの?

  • 視覚的に挙動を組むことができる
  • 実行中に処理の流れをGUI上で確認できて、エラー個所もわかりやすくデバッグしやすい
  • プログラムを書かないので、プログラミング知識が乏しくても組みやすい(かもしれない

勉強するには?
unity公式のサンプルがとてもわかりやすいです。
定番のボールを転がすやつです。インストールから一連の機能を触れるのでオススメですよ。
https://learn.unity.com/project/bolt-roll-a-ball-tutorial

本題。bolt kit Third Personから読み取れる敵AIセオリー

今回はフリーアセット「Bolt Kit: Third Person for Unity」から読み取れる敵AIのセオリーになりそうなフローグラフを見ていきましょう。
ちなみに、このアセットはUnity Technologies公式から提供されており、他にもFPS版や2Dゲーム版もあるので興味がある方は見てみてくださいねー(bolt kitでasset storeにて検索すれば出てきます)。

今回の環境
Unity 2020.2.1f1
bolt 1.4.13

bolt kit Third Personの敵AIの挙動

敵はこんな見た目
スクリーンショット 2021-01-01 14.36.01.png
主な挙動は以下の通りです。

  • パトロール - 指定された範囲をランダムで巡回
  • 追跡 - プレイヤーを追跡
  • 攻撃 - プレイヤーに向かって弾を撃って攻撃

この他にも、頭上にあるHP的なものの制御やダメージを受けた時の処理などもフローグラフで表現されていますが、今回は上記3つの挙動についてみていきます。

敵オブジェクトにはState Machineが設定されており、EnemyAiというマクロが設定されています。
上記3つの動作はこのマクロで定義されています。
当然(?)ですが、各種Variables(変数)も定義されてます。
スクリーンショット 2021-01-01 14.42.26.png

状態遷移図(ステートグラフ)

スクリーンショット 2021-01-01 14.51.37.png
こちらが状態遷移設定です。
「パトロール(Patrol)」「追跡(Chase)」「攻撃(Attack)」が状態遷移条件によってそれぞれ結ばれています。
シンプルなそれぞれの状態の中には、挙動が定義されています。

状態遷移条件

よくある「敵の視界にプレイヤーが入ったら/出たら」というものと「プレイヤーが攻撃範囲に入ったら/出たら」が状態遷移の条件として定義されています。
スクリーンショット 2021-01-01 14.58.43.png
これは、「敵の視界にプレイヤーが入ったら次の状態に遷移する」処理です。
「パトロール」→「追跡」に遷移する時の条件ですね。
Updateイベントで「IsWithinRange」のbool戻り値で判定しています。
ちなみに、Player変数はプレイヤーGameObject、ChaseRangeはfloat値です。

基本的に他の状態遷移条件の処理も同じように、「IsWithinRange」で距離を測って判定しています。

再利用可能なSuperUnit

状態遷移条件処理で出てきた「IsWithinRange」は、SuperUnitといって、関数のように扱えるフローグラフです。
スクリーンショット 2021-01-01 15.06.59.png
使いまわせる関数として、ファイルそのものを別で用意しておいて、他のフローグラフ内でinputを受け取ってoutputを返す処理として使えます。
boltを使う場合、頻出する処理はもちろん出てくるでしょうから、このようにSuperUnitで切り出すのは必須でやったほうが効率が良さそうですね。グラフもスッキリします。

今回の「IsWithinRange」はこんな定義になっています。
スクリーンショット 2021-01-01 15.10.34.png
自分のpositionと入力のオブジェクトのpositionのDistanceが指定float値より小さければtrueが返る感じですね。

パトロールステート

次はそれぞれの状態に書かれている処理を見ていきます。
まずは指定された範囲をランダム巡回するパトロールステートです。
二つに分けて見ていきます。

巡回する目的地の設定

スクリーンショット 2021-01-01 15.32.33.png
一言で言うとこんな感じでしょうか。
「指定された範囲内のランダム値秒おきに、ナビメッシュのdestination(宛先)を決定」
destinationに設定する値(緑色の矢印)は、この後触れます。

ナビメッシュは、簡単に言うとあらかじめキャラクターが通れる場所を定義しておいて、宛先を指定すると最適な経路を通って移動してくれる便利な機能です。(詳しくは調べてくださいませ!

while loopで無限ループにしてるのはちょっと意外でしたが、よく考えるとこの場合はupdateイベントで呼ぶより確かに良さそうです。

  • Waitで待ち時間を設定しているので、必要以上に処理が呼ばれない(updateにすると、都度呼ばれてしまう

目的地の算出

スクリーンショット 2021-01-01 15.53.39.png
後半部分(ランダムで目的地を設定する挙動のうちの、目的地を算出する部分)です。

こちらも一言で言うと
「指定されたパトロールエリア(BoxCollider)の端から端までのxyz座標をランダムで算出してVector3を返す」
と言う感じでしょうか。

colliderからboundsにアクセスして算出しちゃうなんてダイナミックですね。
unityの機能をカバーできちゃうboltだからこその組み方ですねー。

追跡ステート

追跡ステートはとてもシンプルです
スクリーンショット 2021-01-01 16.02.39.png
updateで毎フレームプレイヤー位置を目的地として、ナビメッシュのdestinationに設定しているだけですね。

攻撃ステート

攻撃ステートも二つに分けてみてみます。

プレイヤーの方向に体(頭)を向ける

スクリーンショット 2021-01-01 16.05.42.png
やっていることはシンプルで、基本的にはプレイヤーの座標に向けてLookAtを呼んでいるだけですね。
ちょっとした注意点としては、y座標だけは自分の座標を使って、体を傾かせないように工夫しているところですね。

プレイヤーに向けて弾を撃つ

スクリーンショット 2021-01-01 16.08.35.png
こちらの処理は、先ほどのLookAtの処理の次に実行されるように設定されています(白い矢印)。

ここでもSuperUnitが使用されています。
実は、プレイヤーも弾を撃てるんですが、そこで再利用したいのもありSuperUnitになっているんだと思われます。

必要な変数を持ってきて、Fireに渡していますね。
スクリーンショット 2021-01-01 16.12.17.png
これがFireの中身です。

一言だと
「指定オブジェクトの向きに回転させてから、Instantiateを行う(クールタイムあり)」
ですかね。

このままだとInstantiateして終わりになってしまう...と思いきや、ちゃんとその先があります。
弾オブジェクトに設定されているフローグラフでまっすぐにvelocityを加えてやる記述があり、それで弾は飛んでいくといった具合です。

まとめ

bolt kit Third Personから読み取れる敵AIセオリーを紹介しました。
パトロール - 追跡 - 攻撃の遷移の流れは、いろんなゲームにそのまま使えそうですね。
パトロール、追跡は特に一般的なゲームに見られるものにも近い気がします。(とはいえ改造は必要そう

本当は、敵AIがもう少し複雑になっている例を見てみたかったんですが、シンプルな例でわかりやすく良いサンプルでした。
個人的には敵AIは複雑になりがちなので、boltだけでやろうとするとかなり複雑になりそうだなあと思っており、ビヘイビアツリーを使う方法(こっちだとboltでは未対応)と合わせて模索中です。
知見が出来てきたらそこらへんも投稿できればなあと思っています。

OMAKE

おまけですよ。
若干愚痴入りですが...

bolt kit Third Personを導入に少し手間取ったんです。
2020.2.1f1という最先端でやったせいかもしれませんが...
同じ境遇の人もいるかもなのでちょっとだけ書きます

asset storeから落としてきて導入。
そのままだとboltが入っていないのでboltも一緒に導入。(Package Managerで管理されてないので一緒に入ってこない)
サンプルシーンを開くと...、bolt周りの設定が全て消えてる...(フローグラフの割り当てとか変数の設定が消えてる
なぜか元プレハブを開いて編集モードにすると設定が蘇るので、そこでComponentのコピーをして変数を復元したり、フローグラフの割り当てをしました。

また、「Use Deterministic Compilation」をオフにしてねというエラーが出ているので、Player settingsからチェックを外すのも行いました。

あとは、パトロールステートの目的地を設定するフローグラフは、そもそもエラーが出ていて、つなぎ直してあげると直る箇所がありました。

boltのプロジェクトを外部から持ってくるときは要注意かもしれない...

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Sceneの切り替え方法

Sceneの切り替えをざっくりですがまとめました。
自分用メモです。

①まず、SampleSceneとNextSceneを作る。

②sampleSceneに移動し、UIからButtonを作る。
(positionX 0; Y 0; で真ん中にくる)

③project部分で右クリックしcreate→C#ScriptでScriptの作成。

④下記のコードを追加する。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

↓ここから下の部分を追記
using UnityEngine.SceneManagement;

public class Test : MonoBehaviour
{
public void MoveScene()
{
SceneManager.LoadScene("NextScene");
}
}

⑤ヒエラルキーにCreateEmptyでからのオブジェクトを作成。

⑥作成済のC#Scriptをドラッグ。

⑦ButtonのインスペクタのOnClick部分の+を押し、
GameObjectをドラッグ。
右の選択部分からTest→Test.MoveSceneを選択する。

⑧ビルドセッティングを開き、NewSceneを追加する。

動作確認する。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む