Unity で8番出口風ゲームを作る!
Unity Version 2022.3.16
歩くモブキャラとNavMesh
8番出口の特徴として、通路の先から歩いてくるモブ(おじさん)がある。今回はこのモブを作成する。と言っても、3D モデルを作るのは面倒なので、まずはフリーのアセットを活用する。利用するのは、Unity が無料で配布しているロボットのモデル(Kyle Robot)である。このアセットでは3D モデルのほか、歩く/走る/ジャンプするなどの基本的なアニメーションもセットになっているので、今回の目的にはちょうどよい。
assetstore.unity.com
ストアから購入(無料だが)後にPackage Manager からProject へインストールを行う。すると、Assets/Unity Technologies/SpaceRobotKyle/Models フォルダの配下に、KyleRobot というFBXファイル(3Dモデルのメッシュ、ボーン、マテリアル、アニメーションデータが一体になったファイル)が作成させるので、それをHierarchy 上にDrag & Drop してコピーする、さらにそれをAssets/Resources 配下のPrefab フォルダ(自分で作成)にDrag & Drop してPrefab化しておく。
まずはAnimator の設定から行う。3D モデルで"歩く"や"走る"などの動作をさせるときは3D モデルにアニメーションを適用する必要がある。アニメーションを適用するにはざっくり言うと、"Animator"、"Avatar"、"AnimationController"、"AnimationClip"が必要となる。
"Avatar" は、3D モデルのメッシュとボーン情報を紐づけているアセット。"AnimationController" は、3D モデルの状態別に何の"AnimationClip" を再生するかを管理するコンポーネント。"AnimationClip" は具体的なボーンのアニメーションデータを保持するアセット。最後の"Animator" は、"Avator" と"AnimationController" を紐づけて管理するためのコンポーネントとなっている。
今回利用するKyleRobot アセットには、これらのアセットやコンポーネントも付属しているので、そちらをそのまま利用すればよい。
Unity には、AnimationClip 間をパラメータに紐づけてシームレスにつなげるBlend Tree という仕組みがある。KyleRobot アセットでもこの仕組みを使って、立ちアニメ(Idle)、歩きアニメ(Walk_N)、 走るアニメ(Run_N)がBlend Tree で遷移するようになっている。各状態を遷移させるパラメータは"Speed" というfloat 型のパラメータで、これをスクリプト上から書き換えることで、アニメーションを遷移させることができる。例えば、Speed が 0の時はアイドル、0~1の時はその値の大きさに合わせてアイドルと歩くアニメのボージョン情報をブレンドしてアニメを作る。1の時は歩くアニメを適用するといったイメージ。こうすることで、アイドル状態から急に歩くアニメに1frameで遷移してギクシャクなることがなくなる。(もちろん、Speedの値を一気に変えると、ギクシャクなるが)
KyleRobot アセットのアニメーション制御でもう1点注意事項がある。"MotionSpeed" というパラメータがあり、これはAnimation State の"Speed"(左側のInspector内のパラメータ)に掛け合わされることになっている。この値が"0" のままだと、アニメーションが再生しないので注意が必要である。
Speed に"1.8"、MotionSpeedに"1.0"を設定したKyleRobotのアニメーションの結果がこちら。1.0 は歩くアニメーションになっている。
AI Navigation v1.1.5
Unity には、キャラクタがゲームのステージ内の移動可能な場所を自動で判断して動きまわれるようにするNavMesh という仕組みが存在する。
AI Navigation | AI Navigation | 1.1.5
NavMesh を利用するにはPackge Manager から"AI Navigation" というPackage をインストールする必要がある。今回は、Version 1.1.5 を利用する。
NavMesh の詳細な情報は、マニュアルを確認するとして、基本的なコンポーネントとしては、以下の3つである。
NavMesh Agent がアタッチされたキャラクタが移動できる領域を示す。領域は手動で付ける必要はなく、静的または動的に自動生成させる。生成された領域は、下図のようにSence view上では水色で表示される。領域の生成ロジックはよくわからないが、NavMesh Surface をアタッチしたオブジェクトの子オブジェクトが全て領域生成の対象となる。よって今回は、通路A とB を子に持つオブジェクトにNavMesh Surface コンポーネントを追加する。通路A とB は動的に生成するが、動的に領域を作成するには、NavMeshSurface クラスのBuildNavMesh メソッドをスクリプトからコールすることで可能となる。つまり、通路A/B を動的に生成した後に、BuildNavMesh メソッドを呼び出すと新しい経路を自動で再生成してくれる。
NavMesh Surface 上の領域をキャラクターが動くようにするには、NavMesh Agent コンポーネントをキャラクタのオブジェクトにアタッチする必要がある。このコンポーネントをアタッチし、destination プロパティ(Vector3)を設定するだけで、現在位置からdestinationまでのルートを自動計算し、Speed プロパティで設定した速度にしたがって勝手に移動を開始してくれる。今回はこのコンポーネントをKyleRobot オブジェクトにアタッチしておく。8番出口では、"おじさん" は常に特定の位置から移動を開始して、特定の位置で移動を終了するので、destination プロパティは事前に計算して決めておくことができる。
NavMesh Agent には、destination に到達したことを検知するコールバックが存在しないため(たぶん)、よってdestination に到達したかどうかは自分で確認する必要がある(Vector3.Distance を使う)。
NavMesh Agent が避ける障害物。このコンポーネントがアタッチされているオブジェクトを避けるようにNavMesh Agent は移動を行う。障害物に指定するオブジェクトは、動いていても問題ない。今回は、プレーヤーにこのコンポーネントをアタッチしておく。そうすると、プレーヤーがおじさんの前に立ちはだかったとしても、それをさけて目的地まで移動してくれるようになる。
Robot.cs
KyleRobot Prefab に以下のスクリプトをアタッチして操作を行う。クラス外からターゲット位置と歩行開始の指示を受けられるようにpublic メソッド SetTargetPosition と StartWalk を作り、Update メソッド内で、ターゲット位置と現在の自分の位置の距離を測定し(Vector3.Distance)、一定値以下になったら歩行をやめるということをしている(StopWalke)。
OnFootstep という空のメソッドは、KyleRobot アセットに付属しているアニメーションクリップで、コールバックイベントが設定されているため、特に使う必要がないが定義しておかないとランタイムエラーが発生したので空のメソッドを準備した。
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- using Unity.AI.Navigation;
-
- public class Robot : MonoBehaviour
- {
- [SerializeField] private Animator _animator;
- [SerializeField] private UnityEngine.AI.NavMeshAgent _navMeshAgent;
- private const float WALK_SPEED = 2.0f; // [m/s]
- private Vector3 _targetPosition;
- private bool _isWalking = false;
-
- public void SetTargetPosition(Vector3 targetPosition)
- {
- _targetPosition = targetPosition;
- }
-
- public void StartWalk()
- {
- _animator.SetFloat("Speed", 2.0f);
- _animator.SetFloat("MotionSpeed", 1.0f);
- _isWalking = true;
- _navMeshAgent.enabled = true;
- _navMeshAgent.speed = WALK_SPEED;
- _navMeshAgent.stoppingDistance = 0.5f;
- _navMeshAgent.destination = _targetPosition;
- }
-
- public void StopWalk()
- {
- _isWalking = false;
- _navMeshAgent.enabled = false;
- _animator.SetFloat("Speed", 0.0f);
- }
-
- public void OnFootstep()
- {
- }
-
- private bool IsArrived()
- {
- return Vector3.Distance(_targetPosition, this.transform.position) <= _navMeshAgent.stoppingDistance;
- }
-
- // Update is called once per frame
- void Update()
- {
- if (_isWalking)
- {
- if (IsArrived())
- {
- StopWalk();
- }
- }
- }
- }
-
ここまで試作した結果がこちら。
www.youtube.com
次の記事
from20150817.hatenablog.com
目次に戻る