さて、前回記事に引き続いて、さっそく敵キャラ(NPCキャラクター)を設定する。いずれはプレイヤーを追いかけ回すようになる衛兵をイメージすると良いとおもう。
関連記事
では例のごとく、Unity Gemsからの翻訳をどうぞ!
http://unitygems.com/basic-ai-character/
November 22, 2012
最初にNPCの設定
NPCキャラクターを扱う上で真っ先にやると良いのはキャラクター コントローラ (以下、CC) の使用だ。これによりNPCキャラクターが壁をすり抜ける問題がシンプルに解決出来る。キャラクター コントローラ(CC)は単に、Rigidbodyなしでもコリジョンを取得してくれる複雑なコンポーネントだ。プレイヤーを包み、コリジョンを検知して通知するカプセルから主に構成されている。
NPC キャラクターで CC を使用すればワールドに存在するコライダ全ての上にぶつかって乗ることが出来ます。CCがベストの解決策とならないようなケースも発生し、例えばマリオギャラクシーの世界を真似して重力が継続的に変化するような場合、上手く行かない。もしPrey (2006) のようなゲームを遊ぶ場合、キャラクターは壁を歩くことご出来るけどCCでは当然にできない。CCで大いなる問題の一つは物理挙動と連動されないことで、自身でスクリプト対応が必要となる。ただし多くのの場合はそのままでも十分だ。
NPCにアタッチして、最初にやることはCCをフェッチして修正出来るようにすることだ。コンポーネントをなぜ、どのようにしてフェッチするかについてはここを参照のこと。さらにオブジェクトがCCを保有するかどうかをチェックするため、クラスの前の属性を記述する。
二つのコンポーネントが相互作用する場合はRequireComponent属性を使用する。例えばスクリプトA がスクリプトBの関数または変数に依存するとして次のようにスクリプトA に追加する:
[RequireComponent (typeof (ScriptB))]
同様に物理計算のスクリプトがあったとして、Rigidbody コンポーネントご必要となる可能性が高い。
次のように記述する:
using UnityEngine; using System.Collections; // 以下の属性により、CCがオブジェクトにアタッチされていることを保証し、削除出来ないようになる // 実際にオブジェクトからCCコンポーネントを削除を試みてどうなるか見ると良い [RequireComponent (typeof (CharacterController))] public class AiScript : MonoBehaviour { CharacterController _controller; Transform _transform; void Start(){ _controller = GetComponent_controller 変数を用いてキャラクターコントローラを制御する。二行目は単に最適化のためのTransform コンポーネントのキャッシュだ。これでNPCが動くようになる。(); _transform = GetComponent (); } }
float speed = 5f; float gravity = 20f; Vector3 moveDirection; void Update(){ moveDirection = _transform.forward; moveDirection *= speed; moveDirection.y -= gravity * Time.deltaTime; _controller.Move(moveDirection * Time.deltaTime); }まだ大したことはしなのでスクリプトを慌てて書き取り始める必要性はない。上記はほとんどUnityのCCドキュメントからのコピーで、それに追加する形でvector3型のmoveDirectionでオブジェクトのforwardを取得し、各メンバにspeedを乗算して、y メンバが一定の重力を受けてNPCがupdateのなかで実際に重力を受けます。最後にCCクラスからMoveメソッドをコールして、moveDirectionベクトルに渡す。Time.deltaTime を使用するのは単にゲームがフレームレートに依存しないようにするためだ。
現時点ではNPCは真っ直ぐ前方しか進まず、つまらないゲームでしか使用出来ない。面白くするためにはNPCが向きを変えられるようにする。次のチュートリアルによりこれは実現出来るため、ここでは記載内容をそのまま反映する。
float speed = 5f; Vector3 _target; float maxRotSpeed = 200.0f; float minTime = 0.1f; float _velocity; void Update () { //移動のためのコード var newRotation = Quaternion.LookRotation(_target - _transform.position).eulerAngles; var angles = _transform.rotation.eulerAngles; _transform.rotation = Quaternion.Euler(angles.x, Mathf.SmoothDampAngle(angles.y, newRotation.y, ref _velocity, minTime, maxRotSpeed), angles.z); }まだ辛抱する必要があって、このキャラにはまだ抜けている細かい設定が残っている。それでも、CC を動かす基本的な部分は出来た。ターゲットがないだけなのだ。忘れちゃいけないのは、このキャラは賢くない前提ということでこれからどこに向かうか指示したら単純にその通りに従うということだ(本当に、その通りにね)。次のパートではNPCがターゲットとして出来るように_targetに値を設定する。
可読性を高めるために、二つの箇所(移動と回転)をひとつのMove() 関数に変更する。
シンプルなスクリプトでNPCが動きまわれるようにする:
bool change; float range; void Start () { range = 2f; _target = GetTarget(); InvokeRepeating ("NewTarget",0.01f,2.0f); } void Update () { if(change)_target = GetTarget (); if(Vector3.Distance(_transform.position,_target)>range){ Move(); animation.CrossFade("walk"); }else animation.CrossFade ("idle"); } Vector3 GetTarget(){ return new Vector3(Random.Range (0,300),0,Random.Range (0,300)); } void NewTarget(){ int choice = Random.Range (0,3); switch(choice){ case 0: change = true; break; case 1: change = false; break; case 2: _target = transform.position; break; } }このAIには高度な思考能力をもたせないため、どこに移動するか知らせる必要がある。GetTarget() はこの目的で使用する。単純にターゲット変数にセットするVector3を戻り値とする関数だ。
InvokeRepeating関数にコールされるNewTarget関数 (関数実行の詳細についてはここを参照してほしい) は単純に新しいターゲットを割り当てたかどうか判断するのみだ。0 から 2 の間のランダムな数で次の態度を決めるスイッチとしている。
0. 新しいターゲットを取得
1. 前回のターゲットを継続
2. 止まって待つ
NPCキャラクターの動きはターゲットとの距離に依存する。もし十分近い距離にいない場合 (rangeより大きい場合)、NPCはターゲットに近づき、そうでない場合は待機する。2 の場合にターゲットに transform.position をセットするのはこのためであり、NPCキャラクターが十分近い場合は待機する。移動する際は歩行アニメーションが使用され、待機する、という場合はアイドル状態のアニメーションを使用する。
初めてのAIでのFSMは次の図のようなものである:
この図からDecision Center (NewTarget関数、判断を行う中心地点) で次の行動が決定されることが分かる。判断の際に歩くことを選択せずターゲットを、提供することのみ選択していることに注目してほしい。Update によりターゲットを使用してこの転換を行う。
この種のAIは "人間タイプ" のNPCでは不十分だが、動物タイプであれば十分だ。例えば森で彷徨うイノシシとして使用出来る。まだ問題はひとつあって、地形には自然環境による障害物はまだ作成していない。しかし、もし崖や池がある場合、そこにNPCキャラクターがいると不自然になります。これを回避するためにNPCキャラクターに事前設定したターゲットを設定する。
さらに、すぐ出てくるもうひとつの問題は地形の問題の解決だ。ランダムな値を割り当て、この場合はy を 0に保持しているため平らな地形でしかこのアルゴリズムが適用出来ないということだ。例えばy = 10で小さな丘があったとしても、現在のランダムの値どはキャラクターが0の地点から移動するには遠いため、永遠にターゲットが変更されないことになる。そうでなければ、範囲を変更することになりますが、あまり好ましいことではない。
----
次回は巡回する方法についてとりあげるぜ!
0 件のコメント:
コメントを投稿