- 投稿日:2020-07-22T22:43:50+09:00
URP下でのSteamVR PluginV2を使用したVR開発メモ。
はじめに
これは毎回自分のPCの中をたどりながらVR開発をするのがめんどくさくなったがための開発覚書です。後々書き足す可能性有り。
-追記
タイトルにURP下でのと入れていますがコントローラーのシェーダーを変えることしかやってません、ごめんなさい本題
- もろもろのアセットを入れたらCameraRigを設置する。CameraRigの子のmodelのシェーダーはURP対応ではないので手動でLitシェーダーに変える。
- SteamVR Inputからdefaultのjsonファイルを作る。コントローラーの割り当てを変えたい場合はOpen binding UI。
自分だけのコントローラーの設定を行いたい場合
- 自分だけのコントローラーの設定を行いたい場合SteamVR Inputで設定を作ってあげる
- そのあとOpen Binding UIからbindの設定を行う こんな感じ(アクションポーズ、Haptic、スケルトンも下から設定する。)
- デフォルトバインドの置換を押す(必要かはわからないけど押したところで支障はない。)
- デフォルトであるコンポーネントのBehaviour_PoseのPose Actionを自分で作った設定に適用する。
またスクリプトでコントローラーにイベントを登録する際も自分で作った設定にすること。
注意事項Poseの設定は必須なのでアクションポーズの設定から左手未加工にPoseを割り当てる。
Skeltonの設定は一回ミラーを外してからスケルトンの設定で割り当てる。
- これでPlayすると移動できないがコントローラーが見えるようになる。
- Input系列のコードを書く
以下使用例using Valve.VR; //押すボタン、Inspectorから選ぶ public SteamVR_Action_Boolean actionButton; //handtypeはInspectorから右か左かで選ぶ public SteamVR_Input_Sources handtype; //判定 if(actionButton.GetStateDown(handtype)){} //コントローラーの動きをとる場合 SteamVR_Behaviour_Pose controllerPose; //角度、方向 Vector3 vec3 = controllerPose.GetAngularVelocity(); //振っている力 Vector3 power = controllerPose.GetVelocity();コードの例
Teleport
using System.Collections; using System.Collections.Generic; using UnityEngine; using Valve.VR; public class Teleport : MonoBehaviour { public SteamVR_Input_Sources handtype; public SteamVR_Behaviour_Pose controllerPose; public SteamVR_Action_Boolean teleportAction, triggerAction; public GameObject laserPrefab; private GameObject laser; private Transform laserTransform; private Vector3 hitPoint; public Transform cameraRigTransform; public GameObject teleportReticlePrefab; private GameObject reticle; private Transform teleportReticleTransform; public Transform headTransform; public Vector3 teleportReticleOffset; public LayerMask layerMask; private bool shouldTeleport; // Start is called before the first frame update void Start() { laser = Instantiate(laserPrefab); laserTransform = laser.transform; reticle = Instantiate(teleportReticlePrefab); teleportReticleTransform = reticle.transform; } // Update is called once per frame void Update() { if (teleportAction.GetState(handtype)) { RaycastHit hit; if (Physics.Raycast(controllerPose.transform.position, transform.forward, out hit, 100, layerMask)) { hitPoint = hit.point; ShowLaser(hit); reticle.SetActive(true); teleportReticleTransform.position = hitPoint + teleportReticleOffset; shouldTeleport = true; if (triggerAction.GetStateDown(handtype) && shouldTeleport) { TeleportPlayer(); } } } else { laser.SetActive(false); reticle.SetActive(false); } } private void ShowLaser(RaycastHit hit) { laser.SetActive(true); laserTransform.position = Vector3.Lerp(controllerPose.transform.position, hitPoint, 0.5f); laserTransform.LookAt(hitPoint); laserTransform.localScale = new Vector3(laserTransform.localScale.x, laserTransform.localScale.y, hit.distance); } private void TeleportPlayer() { shouldTeleport = false; reticle.SetActive(false); Vector3 difference = cameraRigTransform.position - headTransform.position; difference.y = 0; cameraRigTransform.position = hitPoint + difference; } }物をつかむGrabObject
- この場合掴みたい物に対して投げた時に力を加えたい&&FixedJointで掴むものとをくっつけたいので、Rigidbodyを付与しておく
- 掴むものにもRigidbodyを付けておく。
using System.Collections; using System.Collections.Generic; using UnityEngine; using Valve.VR; public class GrabObject : MonoBehaviour { public SteamVR_Input_Sources handtype; public SteamVR_Behaviour_Pose contollerPose; public SteamVR_Action_Boolean grabAction; private GameObject collidingObject; private GameObject objectInHand; // Update is called once per frame void Update() { if (grabAction.GetStateDown(handtype)) { if (collidingObject) { Grab(); } } if (grabAction.GetStateUp(handtype)) { if (objectInHand) { ReleaseObject(); } } } void OnTriggerEnter(Collider other) { SetCollidingObject(other); } void OnTriggerStay(Collider other) { SetCollidingObject(other); } void OnTriggerExit(Collider other) { if (!collidingObject) { return; } collidingObject = null; } private void Grab() { objectInHand = collidingObject; collidingObject = null; var joint = AddFixedJoint(); joint.connectedBody = objectInHand.GetComponent<Rigidbody>(); } private void ReleaseObject() { if (GetComponent<FixedJoint>()) { GetComponent<FixedJoint>().connectedBody = null; Destroy(GetComponent<FixedJoint>()); objectInHand.GetComponent<Rigidbody>().velocity = contollerPose.GetVelocity(); objectInHand.GetComponent<Rigidbody>().angularVelocity = contollerPose.GetAngularVelocity(); } objectInHand = null; } private FixedJoint AddFixedJoint() { FixedJoint fixedJoint = this.gameObject.AddComponent<FixedJoint>(); fixedJoint.breakForce = 20000; fixedJoint.breakTorque = 20000; return fixedJoint; } private void SetCollidingObject(Collider col) { if (collidingObject || !col.GetComponent<Rigidbody>()) { return; } collidingObject = col.gameObject; } }
- 投稿日:2020-07-22T11:50:11+09:00
RiderにUnity Log Windowが出なくなった
- 投稿日:2020-07-22T11:12:09+09:00
UnityEditor上でgitのヒストリーを表示させるUniGitHistoryViewerを作りました
経緯
チーム開発をしていると作業ファイルごとのgitの履歴を知りたくなる事があります。
特に非エンジニアがUnityプロジェクトに対して画像の追加やPrefabの修正などをした時です。
ソースコードであればRiderなどのエディタに履歴閲覧機能が備わっている場合があるので問題はありません。
しかし画像などのバイナリファイルを調査する時は少し時間がかかります。
- シェルからgit logコマンドを実行して調査
- SourceTreeなどのGUIツールからgit log相当の機能を実行して調査
など
Riderでもバイナリファイルのgit履歴は調査可能ですが、Riderがインストールされていない非エンジニアは困ります。非エンジニアにgit logさせるのもハードルが高め。。。
理想はUnityEditor上からgitの履歴をサクッと確認できる事です。
ということで非エンジニア向けの「UniGitHistoryViewer」というものを作りました。
UniGitHistoryViewerとは
UniGitHistoryViewerとはUnityEditor上でサクッと任意のファイルまたはディレクトリのgit履歴を閲覧するエディタ拡張です。
使い方
Tools > UniGitHistoryViewer
から開きます。プロジェクトウィンドウでファイルまたはディレクトリを選択して
Check History
ボタンをクリックします。
すると履歴が以下のgif動画のようにリストアップします。表示内容は以下です。
- commit日時(年-月-日)
- author
- コミットメッセージ
Countに最大表示履歴数を設定でき、また、左端のCボタンをクリックするとコミットハッシュをクリップボードにコピーする事ができます。
インストール方法
Unity2019.3.4f1以降1の場合はUnityPackageManagerでインストール可能です。
- UnityPackageManager(以下:UPM)からインストール
- manifest.jsonを書き換えてインストール
Unity2019.3.4f1未満の場合は従来のunitypackageをインポートしてインストール出来ます。
- unitypackageをインポート
このどれかを選んで頂ければと思います。
UPMからインストールする方法
以下のパッケージURLを入力します。
https://github.com/baobao/UniGitHistoryViewer.git?path=Assets/UniGitHistoryViewer
を入力してAddボタンをクリックします。するとインストールが完了します。manifest.jsonを書き換えてインストールする方法
UPMを使わない場合はmanifest.jsonのdependenciesに以下を追加してインストールしてください。
"info.shibuya24.uni-git-history-viewer": "https://github.com/baobao/UniGitHistoryViewer.git?path=Assets/UniGitHistoryViewer"unitypackageをインポートしてインストールする方法
Unity2019.3.4f1未満の環境の場合はunitypackageをReleaseページからダウンロードできますので、そちらをインポートしてインストールしてください。
UniGitHistoryViewerを使うメリット
このツールを使うメリットは複数の非エンジニアメンバーがUnityを触る場合に生まれます。
非エンジニア同士、誰がどのファイルを触ったかという事実は重要で、何かトラブルが発生した際に、非エンジニアメンバーのみで安全に調査できる情報(ここでは履歴情報)が増える事で、エンジニアが介入することなく原因の特定をする可能性が上がります。僕はエンジニア、非エンジニアがそれぞれ並列して仕事できている状態がベストだと考えています(時と場合にもよりますが)。
その状態を促進させるツールの1つになれば良いなと思っています。最後に
よろしければ使って頂きバグがあればPR、issueをお待ちしております。
https://github.com/baobao/UniGitHistoryViewer
よろしければStarをください。
Unity2019.3.4f1以降でないとPackage URLのクエリパラメータに対応していないため ↩
- 投稿日:2020-07-22T10:42:17+09:00
UnityのPanda BTでシンプルにBehaviour Treeを実装する
前提
この記事では、UnityでシンプルにBehaviour Treeが実装できるPanda BTというアセットについて書きます。
Panda BT Free
Panda BT Pro
Panda BTのドキュメントBehaviour Treeとは
AIのふるまいをツリー構造に細かく分けていくことで実装するAI技術です。
例えば目玉焼きを作る場合、以下のように処理を細かく分けていってそのふるまいを記載することができます。目玉焼きを作る フライパンを温める フライパンを持つ フライパンをコンロにかける コンロに火をつける 卵を割る 卵をフライパンに入れる 五分間待つテキストの各行は処理のノードを表します。Tab文字による字下げによりノード間の親子関係を表します(字下げされると子ノードになる)。
Panda BTでは、上のような処理の手順を構造化ノード、ビルトインノード、カスタムノードを組み合わせて書くことでAIのふるまいとして記述していきます(各ノードの説明については後から詳述します)。
Panda BTではBehaviour Treeがどのように実現されるのか
実際にPanda BT Freeに含まれるサンプルシーンを見ながら、Panda BTの使い方を解説していきます。無料でアセットストアからダウンロードできますので、自分でも動かしてみてください。
では、Panda BT Freeに含まれるPlayTagというサンプルシーンを見てみます。
これは、プレイヤーとコンピューター(AI)が鬼ごっこをするサンプルシーンです(Tagというのは英語で鬼ごっこという意味)。最初はプレイヤーが鬼です。プレイヤーがコンピューターに触ると、鬼を交代してコンピューターが鬼になります。下の画像を見れば分かるように、プレイヤーは丸〇、コンピューターは四角□です。鬼が赤、追われる方は青です。プレイヤーはマウスで丸〇を動かし、コンピューターはPanda BTで四角□を動かしています。
コンポーネントの構成
それでは、コンピューターのゲームオブジェクトにアタッチされているゲームオブジェクトを見てみましょう(下図参照)。
Mesh Filter、Mesh Renderer、Rigidbody、BoxColliderはごく普通のコンポーネントで、どんなキャラクターオブジェクトにもアタッチされている基本的なコンポーネントです。キャラクターの表示と接触判定を行うためにアタッチされています。
Panda BTでBehaviour Treeを実現するコンポーネントが残りの2つです。Panda Behaviourというコンポーネントと、Computerという自作スクリプトです。
2つのコンポーネントのそれぞれを見てみます。Panda Behaviourコンポーネント
まずPanda Behaviourコンポーネントですが、こちらでBehaviour Treeのロジックを記述します。記述はBTスクリプトという独自のフォーマットのテキストファイルで記述して、そのBTスクリプトをPanda Behaviourコンポーネントにアサインします。Panda Bahaviourコンポーネントには複数のBTスクリプトをアサインできます。今回は2つのBTスクリプトがアサインされています(Computer.BT.TxtとPlayTag.commented.BT.txtの2つ)。
ちなみにUnityエディタのプレイモード中は、BTスクリプトの各ノードは以下の画像のように色付けされます。ノードの各色は、そのノードがどのような状態にあるかを表します。Runningが青色、Succeededが緑色、Failedが赤色、ノードが実行されていない場合灰色となります。ノードの状態(Running、Succeeded、Failed)については後述します。
このようにPanda BTでは、インスペクタ上の字下げ表記だけでBehaviour Treeを表現します。実行時には、各ノードの状態は色で分かりますので動作状況も容易に把握できます。また、Pro版の機能からはインスペクタ上からのBTスクリプト編集、ブレークポイントの挿入などもできます。こういったアセットのコンパクトさのおかげで、とてもシンプルに開発・運用できるところがPanda BTの最大の魅力です。
tree("Root")の解説
Panda BTではまず初めにtree("Root")という構造化ノードから処理が始まります。なので、今回はComputer.BTの以下の
tree
が最初に実行されます。Computer.BT.txttree("Root") //Play Tag, check for collision and Talk at the same time. parallel repeat mute tree("PlayTag") repeat mute tree("CheckCollision") repeat mute tree("Talk")
tree
という構造化ノードは、ツリーの一番上の親に必ず一つ書く決まりになっています。最初に実行されるtree
には"Root"
という名前を付けます。処理はtree
ノードから順番に子ノードへと移動します。子ノードはタブ文字による字下げで表現します。// の行はコメント行なので無視されて、その次の行に移ります。
次の行は
parallel
ノードです。Parallel
ノードはPanda BTに付属の構造化ノードで、全ての子ノードを並列に実行するという機能があります。今回の場合はtree("PlayTag")
、tree("CheckCollision")
、tree("Talk")
という3つのツリーを並列に実行しています。parallel : 子ノード全てを並列に実行する
最後に
repeat mute
ですが、repeat
もmute
も構造化ノードでそれぞれ以下の機能があります。repeat:子ノードが失敗(Failed)するまで、子ノードの処理をリピートする
mute:子ノードの失敗(Failed)・成功(Succeeded)に関わらず、その結果を成功(Succeeded)に変更する。子ノードが実行中(Running)の場合は実行中(Running)を返す。
という意味があります。ここでFailed(失敗)、Scceeded(成功)、Running(実行中)という概念が出てきました。これは各ノードの状態を表すもので、BTスクリプト上で実行されたノードは必ずこの3つのどれかの状態を返します。
では
repeat mute tree("PlayTag")
を考えてみましょう。この行のrepeat
、mute
は(子ノードが必ず1つであるので)略式記載されていて、実際には以下のようなツリー構造となっています。repeat mute tree("PlayTag")まず、
tree("PlayTag")
からその実行状態が返ってきます(Failed、Succeeded、Runningのいずれか)。ですがmute
により、その結果がFailedの場合でもSucceededに変更されます。mute
の影響でrepeat
以下の実行状態は常にSucceeded(またはRunning)なので、repeat
は永遠にmute
以下のツリー構造をrepeat
し続けます。要するに、repeat mute tree
と書けばtree
以下の処理を無限に繰り返し実行するということです。それでは、
tree("Root")
全体をもう一回見てみましょう。tree("Root")
はtree("PlayTag")
、tree("CheckCollision")
、tree("Talk")
を並列に無限回繰り返す処理であることがわかります。コンピューターが、鬼ごっこをプレイする(Play)、衝突判定を行う(CheckCollision)、話す(Talk)という3つのタスクを並列処理していることが分かりますね。tree("CheckCollision")の解説
では、3つのツリー
tree("PlayTag")
、tree("CheckCollision")
、tree("Talk")
を並列実行するということだったので、その中の一つで衝突判定を行っているtree("CheckCollision")
を見てみましょう。tree("CheckCollision") //Tag when we collide with the player. //Leave 1 sec of cooldown between each "Tag". sequence IsColliding_Player Tag Wait(1.0)
tree("CheckCollision")
では、まずsequence
という構造化ノードが処理されます。sequence
は以下の機能があります。sequence : 子ノードの処理が失敗するまで、子ノードを順次実行する(子ノードが成功したら、次の子ノードを実行する)。子ノードが実行中(Running)の場合は、sequenceもRunningの状態を返す。子ノードが失敗(Failed)した時点で、sequenceもFailedを返して処理を終了する。全ての子ノードが成功(Succeeded)したら、sequenceもSucceededを返して処理を終了する。
ですので処理の手順を日本語で説明すると、以下のようになります。
まずIsColliding_Playerノードで、コンピューターがPlayerと接触したかを判定します。
接触していない場合、IsColliding_PlayerノードはFailedを返します。子ノードがFailedを返したのでsequenceはそこで処理を終了して自らもFailedを返します。ですが、tree("CheckCollision")はrepeat muteされているので繰り返し処理されます。結果、次のフレームで再度IsColliding_Playerノードが実行されます(毎フレーム衝突判定を行うことになります)。
接触している場合、IsCollinding_PlayerノードはScucceededを返します。成功したので次のノードに移ります。Tagノードが実行されます。ここでは鬼のフラグ(IsIt)を反転させます。また青・赤の色も変更します。このノードは必ず成功します(スクリプトでそう記述しています)。Tagノードが成功したのでWait(1.0)ノードに移ります。WaitノードはPanda BTのビルトインノードでして、指定した秒数だけ処理を待ちます(待っている間はRunning、待ちが終了した状態でSucceededを返す)。Waitノードをここで実行することで、鬼の交代をした後の1秒間は衝突判定を行わないようになっています。Waitによる待ちを完了しsequenceを抜けたあとは、tree("CheckCollision")がrepeat muteされているため、再度sequenceの先頭から処理が開始されます。
このように、
sequence
を用いれば子ノードの成功・失敗により、次のノードを実行するかどうかを選択するという、if文のような条件分岐が実現できます。tree("PlayeTag")の解説
では次に
tree("PlayTag")
を見てみましょう。tree("PlayTag") //Do whatever is appropriate: //Chase the player, Avoid the player or just Idle. fallback tree("ChasePlayer") tree("AvoidPlayer") tree("Idle")
tree("PlayTag")
ではfallback
という構造化ノードを使っています。fallback
ノードは以下のような機能があります。fallback : 子ノードの処理が成功を返すまで、子ノードを順次実行する。子ノードの実行中(Running)の場合は、fallbackもRunningを返す。子ノードが成功(Succeeded)したら、fallbackもSucceededを返してそこで処理を終了する。子ノードが失敗したら次の子ノードを実行する。すべての子ノードが失敗(Failed)したら、fallbackもFailedを返して処理を終了する。
fallback
の子ノードは、tree("ChasePlayer")
、tree("AvoidPlayer")
、tree("Idle")
の3つのtreeです。
sequence
は複数の成功を積み重ねて順次処理を行うようなものでしたが、fallback
の場合は子ノードは1つしか成功しません。今回の場合は、Playerを追いかける処理(ChasePlayer)が成功した場合は、Playerをよける処理(AvoidPlayer)とアイドル状態(Idle)は実行されない。ChasePlayerが失敗してAvoidPlayerが成功した場合は、Idleは実行されない。ChasePlayerとAvoidPlayerが失敗した場合は、Idleが実行される。のようになります。このようにfallback
を使うと、複数の状態を持つキャラクターコントローラのようなロジックが簡単に記述できます。tree("Talk")の解説
次に
tree("Talk")
を見てみます。tree("Talk") //Say something when changing. fallback while IsIt sequence Say("...I'm it.") Running while not IsIt sequence Say("Tag, you're it!") Running
fallback
は先程説明しました。子ノードのwhile IsIt
、while not IsIt
のどちらかが実行されます。
while
ノードという新しい構造化ノードが出てきました。while
ノードは必ず2つの子ノードを持ちます。1つ目のノードが条件(condition)を表し、2つ目のノードがアクション(action)を表します。while codition action別の書き方として、以下のようにも書けます。
while codition action
while
ノードには以下のような機能があります。while : conditionノードがFailedになるまで、conditionノードとactionノードの処理を実行する(conditionノードがSucceededであることを確認後にactionノードは処理実行される)。whileノードは、conditionノードがSucceededかつactionノードがSucceededの場合Succeededを返す。conditionノードとactionノードのいずれかがFailedの場合Failedを返す。conditionノードとactionノードのいずれかがRunningの場合Runningを返す。
要するにC#プログラミングのwhileと同じような機能を果たします。
次に
not
ノードですが、こちら子ノードの結果を反転させます。not childchildノードがSucceededの場合は
not
ノードはFailedを返し、childノードがFailedの場合はnot
ノードはSucceededを返すわけです。not
ノードは(必ず子ノードが1つなので)以下のように簡略化できます。not childでは、改めて
tree("Talk")
の内容を見てみます。鬼かどうかを表す
IsIt
ノードがSucceeded
を返す場合、while IsIt
ノード以下のsequence
ノードが実行されます。sequence
ノードでは、Say("...I'm it.")
というカスタムノードで"...I'm it."
というテキストメッセージを表示した後、ビルトインのRunning
ノードが実行されます。Running
ノードは常に実行中(Running)の状態を返します。ですのでsequence
ノードはそこで処理を実行中のままで保留します。この書き方はあるフラグがたったら一回だけ処理を実行したい場合に有効です。余談ですが
Running
ノードと同様のノードにSucceed
ノード、Fail
ノードがあります。Succeed
ノードは常に成功(Succeeded)を返し、Fail
ノードは常に失敗(Failed)を返します。while IsIt sequence //子ノードRunningで実行中の状態でとまる(永遠にSucceededしない) Say("...I'm it.") //IsItフラグが変化すると1回だけ処理されるノード Running //常にRunningを返すビルトインノード
fallback
+while
+while not
の書き方も大事です。このように書くと、フラグの状態により2つに分岐するツリーができます。これで、Behaviour Treeをステートマシーンのように扱うこともできます。// フラグによるステートマシーンを実現する書き方 tree("StateMachine") fallback while condition action1 while not condition action2さて、解説していないtreeは残り
tree("ChasePlayer")
、tree("AvoidPlayer")
、tree("Idle")
だけとなりました。これらのtreeの説明は省略します。これらのtreeの内容は今まで説明した知識で読める内容になっていますので、実際にどのような動きをするかをご自分で解析し、理解してみてください。カスタムノードを自作する
さて、今までの説明でPanda Behaviourコンポーネントの説明がほぼ終わったのですが、1つだけまだ説明していないことがあります。それがカスタムノードです。Panda BehaviourコンポーネントのBTスクリプトで登場した
IsColliding_Player
、Tag
、IsIt
、Say("...I'm it.")
などのことです。このカスタムノードの中身はどこにあるでしょう?察しがついている方もいるかと思いますが、これらカスタムノードの内容はゲームオブジェクトにアタッチされているComputer.csという自作スクリプトに書いてあります。
それではスクリプトComputer.csを見てみましょう。
まず、
IsIt
カスタムノードですが、スクリプトComputer.csでは以下のように書いてあります。Computer.cs[Task] bool IsIt = true; // Whether the agent is "It".非常に簡単です。bool変数に
[Task]
という属性を加えてあげるだけでPanda Behaviour上のカスタムノードとして機能します。bool変数がtrue
の場合Succeededを返し、false
の場合Failedを返すわけです。次に
IsColliding_Player
カスタムノードです。以下のようにboolプロパティも[Task]
属性でカスタムノード化出来ます。Computer.cs[Task] bool IsColliding_Player { get { return _IsColliding_Player; } }次は
Say
カスタムノードを見てみましょう。こちらは関数での実装になっています。boolを返り値とする関数に対して[Task]
属性を付与するとカスタムノードとしてPanda Behaviourコンポーネント上から利用できます。関数がtrue
を返すとカスタムノードはSucceededを返し、false
を返すとカスタムノードはFailedを返すわけです。引数には int/float/bool/string などが使えます(複数引数も可能です。GameObject、Transformなどは引数にできないようです。)Computer.cs[Task] bool Say(string text) { //ここにカスタムノード内でやりたい処理を書く tagDialogue.SetText(text); tagDialogue.speaker = this.gameObject; tagDialogue.ShowText(); //最後にbool値を返す return true; //常にSucceededを返す場合 }関数を使えば、Succeeded/Failedをカスタムノード上で決定できるようになります。
[Task] bool SomeFunk(string text) { if(何かの条件) return true; //Succeeded else return false; //Failed }関数を使ったカスタムノードはもう1つ作り方があります。Computer.csの
MoveToDestination
関数を見てください。[Task] void MoveToDestination() { var task = Task.current; var delta = destination - transform.position; if (transform.position != destination) { // 目的地に近づく処理 // ... // ... } //目的地に到着したら実行される if (transform.position == destination) task.Succeed(); }この
MoveToDestination
関数の返り値はbool型でなくてvoid型です。void型の関数をカスタムノードとする場合、以下のルールによりそのカスタムノードの状態が変わります。Task.current.Succeed()が呼び出されたとき、カスタムノードはSucceededを返す。Task.current.Fail()が呼び出されたとき、カスタムノードはFailedを返す。どちらも呼び出されなかったとき、カスタムノードはRunningを返す。
要するに、void型の関数でカスタムノードを作るとRunning状態を表現できます。上のコードの例で言うと、目的地に到達していない状態ではRunning状態にしながら目的地に近づいていき、目的地に到着したら
Task.current.Succeed()
を呼び出してSucceededとするなどの処理ができます。もう1つカスタムノードを作るときに重要な
Task.current.isStarting
について触れておきます。Task.current.isStarting
は、カスタムノードに入った直後だけtrue
を返します。カスタムノードに入った時に一度だけ初期化処理を行いたいときなどに便利です。下のコードはTask.current.isStarting
の使用例です。// Task.current.isStartingの使用例 // numberOfFramesのフレーム数だけRunning状態でWaitするカスタムノードの例 private int counter; [Task] public void WaitFrames(int numberOfFrames) { var task = Task.current; if (task.isStarting) { // このカスタムノードに入った時に一回だけ実行される counter = 0; } // Running状態でカウンターをプラスしていく counter++; if (counter >= numberOfFrames) { // counterが10以上になったら // Succeededを返してこのカスタムノードを抜ける task.Succeed(); } }最後に、
[Task]
属性を使う場合はスクリプトの最初にusing Panda;
と書くのを忘れないようにしてください。using Panda;ここまでのまとめ
さて、Panda BTのデモシーンを題材に説明をしてきましたがいかがだったでしょうか。Panda Behaviourコンポーネントとカスタムノード用のスクリプト1つで、シンプルかつ可読性がよくBehaviour Treeが実現できていることが分かっていただけたのではないでしょうか。
ここまでの話をまとめてみます。
- ゲームオブジェクトにPanda Behaviourコンポーネントとカスタムノード用のスクリプトを1つアタッチすれば良い
- Panda Behaviourコンポーネント用にBTスクリプトを書き、登録する
- BTスクリプトは構造化ノード、ビルトインのノード、カスタムノードの3つで構成される
- 構造化ノードの機能を理解してBehaviour Treeのロジックを組み立てる
- 実際の処理はビルトインのノードの活用、カスタムノードの自作により実現する。
- カスタムノードは、C#スクリプト上の変数、プロパティ、関数で表現できる。
ここまで話したことの詳細は、公式のドキュメントでより詳細に記載されていますのでご参考になさってください。
構造化ノードのまとめ
BTスクリプトでBehaviour Treeのロジックの要を担うのは構造化ノードです。
構造化ノードを把握しておくことはとても重要ですのでここでまとめておきます。
構造化ノードは全部で10種類あります。
- tree
- sequence
- fallback
- parallel
- race
- random
- repeat
- while
- not
- mute
以下に、それぞれの構造化ノードの機能を文章でまとめました。ご参照ください。
また、公式サイトに構造化ノードの動作をデモしたUnity WebGLがあります。
こちらもあわせて確認してみて下さい。→ Structual Nodes Demotree
tree
ノードはツリー構造の一番上に必ず必要(ツリー構造をまとめる意味がある)tree
ノードはstringの引数を必ず一つとる。その引数がtree
の名前になる(ex.tree("Patrol")
など)tree("Root")
は必ず必要。tree("Root")
から処理が開始される。tree
ノードは子ノードは必ず1つ。複数の子ノードを持つとエラーになる。tree("Name") child
返り値 条件 Running 子ノードがRunningの時 Succeeded 子ノードがSucceededを返したとき Failed 子ノードがFailedを返したとき sequence
sequence
ノードは子ノードを何個でも持てる。sequence
ノードは子ノードを上から順に順番に実行していく。- 順番に実行していく途中で、子ノードがFailedを返した時点で
sequence
ノードもFailedを返し処理は終了する。- 順番に実行していき、全ての子ノードがSucceededを返したら、
sequence
ノードもSucceededを返し処理は終了する。sequence child_0 child_1 ... child_k
返り値 条件 Running 子ノードがRunningの時 Succeeded 全ての子ノードがSucceededを返したとき Failed いずれかの子ノードがFailedを返したとき fallback
fallback
ノードは子ノードを何個でも持てる。fallback
ノードは子ノードを上から順に順番に実行していく。- 順番に実行していく途中で、子ノードがSucceededを返した時点で
fallback
ノードもSucceededを返し処理は終了する。- 順番に実行していき、全ての子ノードがFailedを返したら、
fallback
ノードもFailedを返し処理は終了する。fallback child_0 child_1 ... child_k
返り値 条件 Running 子ノードがRunningの時 Succeeded いずれかの子ノードがSucceededを返したとき Failed 全ての子ノードがFailedを返したとき parallel
parallel
ノードは子ノードを何個でも持てる。parallel
ノードは全ての子ノード並列に実行する。- 全ての子ノードがSucceededを返した時点で
parallel
ノードもSucceededを返し処理は終了する。- いずれかの子ノードがFailedを返したら、
parallel
ノードもFailedを返し処理は終了する。parallel child_0 child_1 ... child_k
返り値 条件 Running 少なくとも1つの子ノードがRunningの時 かつ Failedを返した子ノードが存在しないとき Succeeded 全ての子ノードがSucceededを返したとき Failed いずれかの子ノードがFailedを返したとき race
race
ノードは子ノードを何個でも持てる。race
ノードは全ての子ノード並列に実行する。- 全ての子ノードがFailedを返した時点で
race
ノードもFailedを返し処理は終了する。- いずれかの子ノードがSucceededを返したら、
race
ノードもSucceededを返し処理は終了する。race child_0 child_1 ... child_k
返り値 条件 Running 少なくとも1つの子ノードがRunningの時 かつ Succeededを返した子ノードが存在しないとき Succeeded いずれかの子ノードがSucceededを返したとき Failed 全てのの子ノードがFailedを返したとき random
random
ノードは子ノードを何個でも持てる。random
ノードは全ての子ノードの中から1つの子ノードをランダムに選び、実行する。random
ノードの状態は、ランダムに選択された子ノードの状態と同じなる。random
ノードの引数でランダム選択の確率的な重みを指定できる。例えば子ノードが4つで、それぞれの確率を10%、20%、30%、40%としたい場合、random(10.0, 20.0, 30.0, 40.0)
とする(合計100にする必要はない)。random(w_0, w_1, ... ,w_k) //ウェイトは省略可能 child_0 child_1 ... child_k
返り値 条件 Running ランダム選択された子ノードがRunningの時 Succeeded ランダム選択された子ノードがSucceededを返したとき Failed ランダム選択された子ノードがFailedを返したとき repeat
repeat
ノードは整数の引数を1つ持ち、子ノードを引数の数だけリピート実行する。- 引数を省略すると子ノードを無限回リピート実行する。
repeat
ノードは、子ノードを必ず1つだけ持つrepeat
ノードは、子ノードを引数回Succeededさせた場合にSucceededを返す(子ノードが引数以下の回数のSucceededであるならば、repeat
ノードはRunningを返す)(引数を省略した場合、repeat
ノードはSucceededを返すことはない。ずっとRunnging返す。Failedを返す場合はある。)repeat
ノードは、子ノードがFailedを返した場合にFailedを返す。repeat(n) //引数は省略可能 child_0
返り値 条件 Running 子ノードのSucceeded回数が引数よりも小さいとき Succeeded 子ノードのSucceeded回数が引数回数になった時(引数を省略した場合Succeededにはならない。) Failed 子ノードがFailedを返したとき while
while
ノードは必ず2つの子ノードを持つ- 1つ目のノードは条件(
condition
)を表す。2つ目のノードはアクション(action
)を表す。condition
ノードがFailedを返すまで、condition
ノードとaction
ノードが繰り返し実行される。action
ノードは、condition
ノードの成功判定後に実行される。action
ノードがSucceededを返した場合、while
ノードもSucceededを返す。condition
ノードまたはaction
ノードがFailedを返した場合、while
ノードもFailedを返す。while condition action // もう一つの書き方 while condition action
返り値 条件 Running いずれかの子ノードがRunning状態のとき(conditionがRunningでactionが未実行の場合とconditionがSucceededでactionがRunningの場合の2パターンがある) Succeeded actionノードがSucceededを返したとき(conditionがSucceededでactionがSucceededの場合) Failed conditionノードまたはactionノードがFailedを返したとき(conditionがFailedでactionが未実行の場合とconditionがSucceededでactionがFailedの場合の2パターンがある) not
not
ノードは必ず1つの子ノードを持つnot
ノードは子ノードの状態を反転させる- 子ノードがSucceededを返したとき、
not
ノードはFailedを返す- 子ノードがFailedを返したとき、
not
ノードはSucceededを返す- 子ノードがRunningを返したとき、
not
ノードもRunningを返すnot child // もう一つの書き方 not child
返り値 条件 Running 子ノードがRunning状態のとき Succeeded 子ノードがFailedを返したとき Failed 子ノードがSucceededを返したとき mute
mute
ノードは必ず1つの子ノードを持つmute
ノードは子ノードがFailedの場合でもSucceededを返す(Failedには絶対にならなくなる)- 子ノードがSucceededを返したとき、
mute
ノードはSucceededを返す- 子ノードがFailedを返したとき、
mute
ノードはSucceededを返す- 子ノードがRunningを返したとき、
mute
ノードもRunningを返すmute child // もう一つの書き方 mute child
返り値 条件 Running 子ノードがRunning状態のとき Succeeded 子ノードがSucceededまたはFailedを返したとき Failed 絶対にFailedにはならない ビルトインのノードを少し紹介
Panda BTに最初から用意されている便利なノードについても少しまとめておきます。
完全なビルトインのノードのリストは以下で確認できます。http://www.pandabehaviour.com/?page_id=23#Built-in_Tasks
Status系
単に状態を返すだけのノードがあります。たまに使います。
Succeed //Succeededを返すだけ Fail //Failedを返すだけ Running //Runningを返すだけWait系
一定時間処理をWaitする場合に使います。
Wait(3.0) //3.0秒waitする Wait(10) //10フレームwaitする RealtimeWait(3.0) //3.0秒waitする。TimeScaleを無視する(Unscaled)Debug系
Debug.Logを仕込むこともできます。
DebugLog("StringMessage") //コンソールにログを表示したときに利用するアセットに含まれるサンプル紹介
最後にアセットに含まれるサンプルシーンをご紹介します。
Hotlineマイアミのようなサンプルです。
敵キャラとプレイヤー両方ともPanda Behaviourコンポーネントを利用して実装されています。
無料で全機能が使えます
Panda BTにはPro版とFree版があります。
Pro版では以下の3つの機能が強化されます。
1. BehaviourTree内にBreakPointが設定できる(デバッグ機能の向上です。)
2. インスペクタ上のドラッグ&ドロップでBTスクリプトを編集できる(編集機能の向上です。エディタで修正する手間が省けるので割と便利です。)
3. 全ソースコードが付属する(Free版はDllとして提供される)(アセットの保守、改造が自分で出来るようになります。)Pro版では、Free版よりもデバッグ/編集/保守などのゲーム制作ワークフロー上の機能が強化されるだけです。なので、Free版とPro版の間で出来上がったゲームの性能に違いは生まれません。より効率的に作業したくなったらPro版の購入を検討しましょう。
UnityでシンプルにBehaviour Treeをやってみたい!と思う人はぜひPanda BTを使ってみてください!
Panda BT Free
Panda BT Pro
Panda BTのドキュメント補足
この記事は、Unity アセット真夏のアドベントカレンダー 2020 Summer! 8月14日のために書かれた記事です。
http://assetstore.info/eventandcontest/adventcalendar/summer-advent-calendar-2020/
- 投稿日:2020-07-22T07:11:19+09:00
Unityでのゲーム制作時、オブジェクトの移動処理が重い場合に行った事とその効果
筆者の環境:OS:Windows10, Unity 2019.3.5f1 使用言語:C#
利用Asset:DOTween,Destructible2D
筆者のプログラミング歴:2020年1月よりUnity上でC#の勉強を開始。現在開発中のゲーム(2D)のメインが『落下してくるオブジェクトを切断する』といった内容なのですが、切断後に異常に動きが遅くなる現象が多発し、ゲーム性を著しく損なう為、対策を調べつつ対処した結果を、ざっくりではありますが、備忘録として残そうと思います。
まず、UnityのProfilerを起動し、どこの処理が重いのかを調べました。
結果、『Physics2D.FindNewContacts』という部分に多大な時間を取られていました(数百~千数百ms)。
調べてみると、どうやらRigidbody2Dが良くないようです。
しかし、色々と試しましたが、Rigidbody2Dを外したり、Kinematicにすると思った動きが出来なかった為、以下の事を試しました。
※筆者の環境で、効果が高かったと思われる順に表記。●切断されるオブジェクトのSpriteのサイズを小さくした。
Spriteを選択した時のInspectorから、MaxSizeを小さく、Pixels Per Unitの値を大きくして調整しました。●オブジェクトのGravity Scaleの値を小さくした。
これは、単純に動きが早すぎて切断し辛かったのでスピードを遅くする目的でやったのですが、カクカク感が軽減されました。●Update関数をFixedUpdateに変更。
数値としては反映されているか分かりませんでしたが、見た目のカクカクを軽減する効果があったように感じました。●Project SettingsのTime Fixed Timestepの値を”小さく”した。
処理を軽くする為には、値を大きくすべきだそうですが、大きくすると思ってもない動きをしたり、若干ワープしたような動きをするので、逆に小さくしてみると、スムーズに動くようになったので、小さくしてしまいました。結果、目的がブレたような気もしますが、見た目のスムーズさは向上したのと、固まったかのような現象はほとんど起こらなくなったので、とりあえずはこのまま開発を進めようと思います。
新情報等見つけたら、再度試してこちらに追記します。ここまで読んでいただき、ありがとうございました。