前回までで敵キャラが自動で設定された目的地に移動するところまで作成出来ました。
今回は敵キャラクターが目的地に到着したら数秒の待機をした後、別の目的地を設定し、そこに移動する機能を作ります。
まず初めに前回までに作ったスクリプトを分割してみます。
移動のスクリプトと目的地の設定スクリプトが混在していたので、目的地の設定スクリプトを取り出して別の名前にします。
こうすることでスクリプトを機能毎に分ける事が出来、処理の把握がしやすくなると思います。
目的地設定はMoveEnemyと機能を共有しているので、特別分ける必要もなくMoveEnemyメソッドにSetPositionというメソッドを追加して対応した方がいいです。
があえて他のスクリプトを取得するという事を勉強する為にやってみます・・・・(^_^;)
名前は安易にSetPostionという名前にしました。
忘れずにゾンビ(敵キャラ)にSetPositionを設定しておきます。
ゾンビの位置を設定するスクリプトSetPosition
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | using UnityEngine; using System.Collections; public class SetPosition : MonoBehaviour { //初期位置 private Vector3 startPosition; //目的地 private Vector3 destination; void Start () { // 初期位置を設定 startPosition = transform.position; SetDestination(transform.position); } // ランダムな位置の作成 public void CreateRandomPosition() { // ランダムなVector2の値を得る var randDestination = Random.insideUnitCircle * 8; // 現在地にランダムな位置を足して目的地とする SetDestination(startPosition + new Vector3(randDestination.x, 0, randDestination.y)); } // 目的地を設定する public void SetDestination(Vector3 position) { destination = position; } // 目的地を取得する public Vector3 GetDestination() { return destination; } } |
CreateRandomPositionメソッドが呼ばれるとランダム値を加えた目的地が設定され、このスクリプトで値を保持します。
設定された目的地destinationはprivateで宣言されていますので、外部のスクリプトからは値の取得が出来ません。
その為Getter(privateで宣言されている変数の値を取得するだけのメソッド)を用意し、GetDestinationメソッドが呼ばれたら値を取得出来るようにしておきます。
メソッドの戻り値の指定は
1 2 3 4 5 | 戻り値の型 メソッド名 { return 戻り値; } |
↑のようにメソッド名の前に戻り値の型を書きます。
敵キャラ移動スクリプトMoveEnemyの修正
次にMoveEnemyの修正をします。
目的地の設定をしていた個所を修正する必要があります。スクリプトもコンポーネントなので取得はGetComponentで出来ます。
1 2 3 4 | // SetPositionスクリプト private SetPosition setPosition; |
上記のようにスクリプト名型の変数を宣言します。
1 2 3 | setPosition = GetComponent <SetPosition> (); |
Startメソッド内でGetComponentでスクリプトを取得しフィールドに入れておきます。
これでsetPositionを介してSetPositionスクリプト内のメソッドを使用出来ます。
1 2 3 4 5 6 | // ランダム位置の作成と設定 setPosition.CreateRandomPosition(); // 目的地の取得 destination = setPosition.GetDestination(); |
setPosition.CreateRandomPositionでランダム位置の作成と設定をし、setPosition.GetDestinationで目的地を取得します。
あとは目的地に到着した後に待機して新たな目的地を設定、到着フラグをoffにするという処理を加えるだけです。
敵キャラが見回りをするスクリプト
MoveEnemyスクリプトに待機時間の設定と待機してから何秒たったかを記憶するフィールドを宣言します。
1 2 3 4 5 6 7 | // 待ち時間 [SerializeField] private float waitTime = 5f; // 経過時間 private float elapsedTime; |
Startメソッド内で初期値を設定します。
1 2 3 | elapsedTime = 0f; |
到着してからその場で待っていた時間(elapsedTime)を増やしていきたいので、
1 2 3 4 5 6 7 8 9 10 11 | elapsedTime += Time.deltaTime; // 待ち時間を越えたら次の目的地を設定 if(elapsedTime > waitTime) { setPosition.CreateRandomPosition(); destination = setPosition.GetDestination(); arrived = false; elapsedTime = 0f; } |
上記のようにTime.deltaTimeを足していきます。
Time.deltaTimeは1フレームの時間を取得出来ます(厳密には前回Updateメソッドを実行してから現在のUpdateメソッドを実行するまでの時間)。
つまりelapsedTimeに1フレーム毎に時間を足していき、
それが待機時間を超えた時に、新しい目的地の設定、取得、到着フラグのoffをし、経過時間を0にリセットします。
到着フラグがリセットされたので、また新しい目的地へと移動を開始するはずです。
以下MoveEnemyの全文です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | using UnityEngine; using System.Collections; public class MoveEnemy : MonoBehaviour { private CharacterController enemyController; private Animator animator; // 目的地 private Vector3 destination; // 歩くスピード [SerializeField] private float walkSpeed = 1.0f; // 速度 private Vector3 velocity; // 移動方向 private Vector3 direction; // 到着フラグ private bool arrived; // SetPositionスクリプト private SetPosition setPosition; // 待ち時間 [SerializeField] private float waitTime = 5f; // 経過時間 private float elapsedTime; // Use this for initialization void Start () { enemyController = GetComponent <CharacterController> (); animator = GetComponent <Animator> (); setPosition = GetComponent <SetPosition> (); setPosition.CreateRandomPosition (); velocity = Vector3.zero; arrived = false; elapsedTime = 0f; } // Update is called once per frame void Update () { if (!arrived) { if (enemyController.isGrounded) { velocity = Vector3.zero; animator.SetFloat ("Speed", 2.0f); direction = (destination - transform.position).normalized; transform.LookAt (new Vector3 (destination.x, transform.position.y, destination.z)); velocity = direction * walkSpeed; Debug.Log (destination); } velocity.y += Physics.gravity.y * Time.deltaTime; enemyController.Move (velocity * Time.deltaTime); // 目的地に到着したかどうかの判定 if (Vector3.Distance (transform.position, destination) < 0.5f) { arrived = true; animator.SetFloat ("Speed", 0.0f); } // 到着していたら } else { elapsedTime += Time.deltaTime; // 待ち時間を越えたら次の目的地を設定 if(elapsedTime > waitTime) { setPosition.CreateRandomPosition(); destination = setPosition.GetDestination(); arrived = false; elapsedTime = 0f; } Debug.Log(elapsedTime); } } } |
これで敵キャラが見回りをするような動きを表現できました。
スクリプトが長くなってきたので、全文を載せるのはそろそろやめようかな・・・。
戻り値を使わない時はメソッド実行命令だけ送る事も可能
今回はGetComponentでスクリプトを取得し、setとgetのメソッドを呼び出して使用しました。
しかし戻り値を使わない場合は別の方法もあります。
SendMessage(“メソッド名”)
とします。同じオブジェクトに設定されたスクリプトであれば上記のように記述し、メソッド名を指定するだけでメソッドを実行してくれます。
第2引数(引数はメソッドに渡す値の事)に値を設定する事は出来ますが、値を複数渡したい時、第3引数以降は使えないので、第2引数に配列として渡すなどをすれば複数の値の引き渡しが出来ます。
メソッドを実行してくれ!というメッセージを送るだけなので、戻り値は取れません。
スクリプトの実行順序で不具合!?
以下Updateメソッドの実行順を変更する方法が載っていますが、不具合の原因は単純にキャラクターの位置と目的地のどれだけ近づいたか?の距離が短すぎた事が原因でした。
1 2 3 | if(Vector3.Distance(transform.position, destination) < 0.3f) { |
↑の部分ですね。
0.3fのところの数値を多少大きくすれば問題ありません。
もしくは
1 2 3 | if(Vector3.Distance(transform.position, new Vector3(destination.x, transform.position.y, destination.z)) < 0.5f) { |
上のようにY軸の高さをゾンビと主人公の位置を合わせてしまいます。
原因が別の所にあったので、以下の内容はUpdateメソッドの順序をスクリプト毎でどうしても変えたい時のTipsとして使い、安易に変更しない方がいいです。
またUpdateメソッドの後に実行したい時はLateUpdateメソッドを使うとUpdateと同じように毎フレーム呼ばれるのでこちらを使うといいです。
-- 以下Updateメソッドの実行順序を変えるTips--
Updateメソッドの実行順序をスクリプト毎に変更したい時もあります。
そんな時は、
Editメニュー→Project Setting→Script Exection Order
を選択します。
+をクリックしSetPositionを選択します。
SetPositionがDefault Timeより前に実行するように移動します。
するとSetPositionスクリプトのUpdateメソッドの方が他のスクリプトのUpdateメソッドよりも早く実行されます。
次回は主人公キャラが敵キャラに接近したら近づいてくるというようなことをやってみたいと思います。