今回はご依頼頂いた機能で、ゲームの主人公キャラクターが移動中に敵に捕まれ、身動きが取れなくなった後に特定のキーを押して主人公が脱出する機能です。
昔のバイオハザードにあるような機能ですね。
敵に抱きつかれる機能については
で作成していますが、
今回は敵に掴まれている間は主人公が身動き出来ない状態にし、特定のキーを数回押してまた移動出来る状態にします。
最小限の主人公キャラを準備
まずは最小限の主人公操作スクリプトHoldCharaMoveを作成します。
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 | using UnityEngine; using System.Collections; public class HoldCharaMove : MonoBehaviour { private Animator animator; private CharacterController cCon; private Vector3 velocity; private Vector3 input; [SerializeField] private float walkSpeed = 2f; void Start () { animator = GetComponent<Animator>(); cCon = GetComponent<CharacterController>(); velocity = Vector3.zero; } void Update () { // 地面に接地してる時は初期化 if (cCon.isGrounded) { velocity = Vector3.zero; input = new Vector3 (Input.GetAxis ("Horizontal"), 0f, Input.GetAxis ("Vertical")); // 方向キーが多少押されている if (input.magnitude > 0f) { animator.SetFloat ("Speed", input.magnitude); transform.LookAt (transform.position + input); velocity += input.normalized * walkSpeed; // キーの押しが小さすぎる場合は移動しない } else { animator.SetFloat ("Speed", 0f); } velocity.y += Physics.gravity.y * Time.deltaTime; cCon.Move (velocity * Time.deltaTime); } } } |
移動に関する最小限のスクリプトになっています。
主人公キャラ側の操作スクリプトはこのスクリプトに処理を追加していきます。
主人公キャラに移動機能であるCharacterControllerを取りつけたりする方法は
等を参考にしてください。
主人公キャラに設定するAnimatorControllerの状態は立っている状態のIdle、動いている状態のRunを作成しておきます。
アニメーターコントローラーの作成については
を参照してください。
主人公キャラにCharacterControllerの追加とスクリプトHoldCharacterMoveの取り付けが終わったらキーボード操作でキャラクターを動かし問題がない事を確認してください。
主人公キャラクターにはPlayerタグを取り付けておきます。
敵キャラのゾンビの準備
次に主人公キャラを捕まえるゾンビを準備します。
AssetStoreの検索窓でpxltiger zombieで検索し出てきたゾンビをインポートします。
pxltigerは作者名になります。
インポートが終了したらシーンに設置します。
ゾンビのアニメーションは別途ご用意して頂く必要があります。
自分自身でUnityで使用するアニメーションを作成するには
を参考にしてください。
ゾンビが主人公に向かっていくアニメーションは
↑のように地面にうつぶせで歩くアニメーションを使います。
ちょっと動きが速くてゴキブリのようですね・・・・(^_^;)
また主人公を捕まえた時のアニメーションには
↑のように地面に伏せたまま動かないアニメーションを使用します。
どちらのアニメーションも↑のようにキャラクターの土台を傾けて作成しておきます。
立った状態でアニメーションを作成しUnity側で傾けるという方法でも出来ますが、Unity側の管理が簡単になる為blender等でも傾けてアニメーションを作成しておきます。
ゾンビ用のアニメーターコントローラーを作成しこれらのアニメーションを状態として設定します。
Idleには地面に伏せたまま動かないアニメーションを設定し、Runには先ほどの地面に伏せて歩くアニメーション、Holdには地面に伏せたまま動かないアニメーションを設定します。
アニメーションパラメータにFloatのSpeed、BoolのHoldを作成します。
Idle→RunはSpeedがGreaterで0.1
Run→IdleはSpeedがLessで0.1
Run→HoldはHoldがtrue
Idle→HoldはHoldがtrue
Hold→IdleはHoldがfalse
という条件で遷移するようにします。
ゾンビにはHoldZombieスクリプトを作成し取りつけます。
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 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | using UnityEngine; using System.Collections; public class HoldZombie : MonoBehaviour { // ゾンビの状態 public enum ZombieState { wait, chase, hold, waitHold }; // ゾンビの状態変数 private ZombieState state; // ゾンビが目的地に着いたかどうか private bool arrived; // 主人公キャラクターのゲームオブジェクト [SerializeField] private Transform playerChara; // ゾンビのアニメーターコントローラー private Animator animator; // ゾンビのキャラクターコントローラー private CharacterController eCon; // ゾンビの向いている方向 private Vector3 direction; // ゾンビの移動速度 private Vector3 velocity = Vector3.zero; // ゾンビの移動の速さ [SerializeField] private float speed = 0.5f; // ゾンビが主人公に追いついたとする距離 [SerializeField] private float stopDistance = 1f; void Start () { eCon = GetComponent<CharacterController>(); animator = GetComponent<Animator>(); holdCharaMove = playerChara.GetComponent<HoldCharaMove>(); direction = Vector3.zero; state = ZombieState.chase; } void Update () { if(eCon.isGrounded) { velocity = Vector3.zero; // ゾンビが主人公を追いかけている状態 if(state == ZombieState.chase) { if(!arrived) { if(Vector3.Distance(playerChara.position, transform.position) < stopDistance) { SetState(ZombieState.hold); } animator.SetFloat("Speed", 2f); transform.LookAt(new Vector3(playerChara.position.x, transform.position.y, playerChara.position.z)); direction = (playerChara.position - transform.position).normalized; velocity += direction * speed; } } } // ゾンビが主人公を捕まえている状態 if (state == ZombieState.hold) { } velocity.y += Physics.gravity.y * Time.deltaTime; eCon.Move(velocity * Time.deltaTime); } // ゾンビの状態変更関数 public void SetState(ZombieState mode, Transform player = null) { state = mode; if(mode == ZombieState.chase) { arrived = false; playerChara = player; } else if(mode == ZombieState.hold) { arrived = true; animator.SetBool("Hold", true); animator.SetFloat("Speed", 0f); } } // ゾンビの状態取得関数 public ZombieState GetState() { return state; } } |
このスクリプトはゾンビが登場したら主人公を追いかけ、主人公と一定の距離まで来たらその場で止まるまでの処理です。
主人公キャラクターplayerに設定しておく必要があります。
ゾンビにはCharacterControllerを取りつけてコライダのサイズを調整します。
最初から地面にうつ伏せで動くのではなく、立って歩く状態からうつ伏せ状態へと遷移させる場合はスクリプトでコライダのサイズの調整が必要になりますが、
今回は寝そべったまま始まり立ちあがる事がないものとしておきます。
↑がCharacterControllerのコライダのサイズです。
CharacterControllerは基本縦に長くは出来ますが、横に長く出来なかったので↑のような感じにしました。
↑がゾンビのインスペクタになります。
speedに1.5、stopDistanceに1を設定します。
次にゾンビに主人公を検知するエリアとスクリプトを作成します。
ゾンビの子要素に右クリック→Create Emptyで空オブジェクトを作成し名前をSearchAreaとします。
↑がSearchAreaのインスペクタでAdd ComponentからPhysics→SphereColliderを選択します。
SphereColliderのサイズを調整し、主人公を検知する範囲を設定します。
SearchCharacterスクリプトを作成し取りつけます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | using UnityEngine; using System.Collections; public class SearchChara : MonoBehaviour { private HoldZombie holdZ; void Start() { holdZ = GetComponentInParent<HoldZombie>(); } void OnTriggerStay(Collider col) { if(col.tag == "Player" && holdZ.GetState() != HoldZombie.ZombieState.chase && holdZ.GetState() != HoldZombie.ZombieState.hold) { holdZ.SetState(ZombieState.chase, col.transform); } } void OnTriggerExit(Collider col) { if(col.tag == "Player") { holdZ.SetState(ZombieState.wait); } } } |
このスクリプトはSearchAreaの範囲にコライダが入ったり出ていったりした時にゾンビの状態を変更する処理です。
主人公のインスペクタを表示しタグにPlayerを設定します。
ここまでの設定が終わったらUnityを実行し、ゾンビが主人公の近くに来たら止まる事を確認します。
ここまではまだ準備段階です。(^_^;)
以前の記事で敵が主人公を追いかける機能などを簡易的に説明した結果になりますね。
なので、詳細についてはそちらの記事を参照して頂くと言う事で・・・・。
ゾンビが主人公に追いついたら主人公が身動き出来ないようにする
では、ゾンビが主人公に追いついた時に主人公が移動キーを押しても動けないようにしてみましょう。
主人公操作スクリプトHoldCharacterMoveに処理を追加する
まずは主人公にも列挙型で状態変数を作成し、その状態によっては移動キーで移動が出来ないようにしていきます。
主人公操作スクリプトHoldCharacterMoveスクリプトに処理を追記していきます。
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 | public enum MyState { normal, hold }; // 主人公の状態 private MyState state; // 主人公を掴んでいる敵 private GameObject holdEnemy; // 主人公が離れるまでにボタンを押す回数 [SerializeField] private int releaseCount = 10; // ボタンを押した回数 private int pushCount; void Start () { animator = GetComponent<Animator>(); cCon = GetComponent<CharacterController>(); velocity = Vector3.zero; state = MyState.normal; pushCount = 0; } |
↑のように主人公キャラクターの状態を作ります。
また主人公がゾンビに捕まれた場合SPACEキーを何回押したら脱出出来るかの回数を指定するreleaseCountを宣言します。
キーを押した回数はpushCount変数に入れていきます。
Startメソッド内でフィールドの初期化を行っています。
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 | void Update () { // 地面に接地してる時は初期化 if (cCon.isGrounded) { velocity = Vector3.zero; // ノーマル状態 if (state == MyState.normal) { input = new Vector3 (Input.GetAxis ("Horizontal"), 0f, Input.GetAxis ("Vertical")); // 方向キーが多少押されている if (input.magnitude > 0f) { animator.SetFloat ("Speed", input.magnitude); transform.LookAt (transform.position + input); velocity += input.normalized * walkSpeed; // キーの押しが小さすぎる場合は移動しない } else { animator.SetFloat ("Speed", 0f); } // 敵に掴まれている状態 } else if (state == MyState.hold) { // 指定回数以上キーを押したら離れる if(Input.GetKeyDown("space")) { pushCount++; if(pushCount >= releaseCount) { pushCount = 0; SetState(MyState.normal); holdEnemy.GetComponent<HoldZombie>().SetState(HoldZombie.ZombieState.waitHold); } } } } velocity.y += Physics.gravity.y * Time.deltaTime; cCon.Move (velocity * Time.deltaTime); } public void SetState(MyState state, GameObject enemy = null) { // 状態を変更 this.state = state; if(state == MyState.normal) { } else if(state == MyState.hold) { holdEnemy = enemy; animator.SetFloat("Speed", 0f); } } |
Updateメソッド内では主人公の状態に応じて処理を分け、今までの移動処理をしていた箇所は主人公が通常の状態(MyState.normal)の時だけ行う事にします。
主人公が捕まっている状態(MyState.hold)だった時はSPACEキーが押された回数を計測し、指定回数を超えたら自身の状態を変更し、捕まえているゾンビの状態も
変更しています。
捕まえているゾンビのゲームオブジェクトholdEnemyはSetStateメソッド内で引数として受け取ったゲームオブジェクトを入れています。
主人公のSetState関数はゾンビが主人公を捕まえた時に呼び出しその時にゾンビ自身のTransformを引数として渡しています。
ゾンビが主人公を捕まえた時にSetStateメソッドが呼ばれた時は主人公のアニメーションパラメータであるSpeedを0にしてIdle状態へと遷移させています。
これは捕まった時に主人公のアニメーションを立っている状態にさせる為です。
ゾンビ操作スクリプトHoldZombieに処理を追加する
次はゾンビ側のスクリプトに処理を追加していきます。
まずは宣言部、Startメソッド、Updateメソッド内です。
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 | // ゾンビが主人公を離してから動き出すまでの時間 [SerializeField] private float waitTime = 3f; // ゾンビが主人公を離してからの時間 private float currentTime; void Start () { eCon = GetComponent<CharacterController>(); animator = GetComponent<Animator>(); holdCharaMove = playerChara.GetComponent<HoldCharaMove>(); direction = Vector3.zero; state = ZombieState.wait; currentTime = 0f; } void Update () { // ゾンビが主人公を捕まえている状態 if (state == ZombieState.hold) { // ゾンビが主人公を離してからの待ち状態 } else if (state == ZombieState.waitHold) { currentTime += Time.deltaTime; if(currentTime >= waitTime) { SetState(ZombieState.wait); currentTime = 0f; } } } |
一部変数宣言は省略しています。
ゾンビが離れてから再び動き出すまでの時間をwaitTimeとします。
Update関数ではゾンビの状態がZombieState.waitHoldの時の処理を追加しています。
ZombieState.waitHoldはゾンビが主人公を捕まえた後、SPACEキーを特定の回数押して脱出した後のゾンビの状態です。
なぜこの状態が必要なのかと言うと離した後に一定の沈黙状態を作らないとすぐさまゾンビが主人公を捕まえてしまうからです(^_^;)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // ゾンビの状態変更関数 public void SetState(ZombieState mode, Transform player = null) { state = mode; if(mode == ZombieState.chase) { arrived = false; playerChara = player; } else if(mode == ZombieState.hold) { arrived = true; playerChara.GetComponent<HoldCharaMove>().SetState(HoldCharaMove.MyState.hold, this.gameObject); animator.SetBool("Hold", true); animator.SetFloat("Speed", 0f); } else if(mode == ZombieState.waitHold) { playerChara = null; arrived = false; animator.SetFloat("Speed", 0f); animator.SetBool("Hold", false); } else if(mode == ZombieState.wait) { animator.SetFloat("Speed", 0f); } } |
これでゾンビ側の操作スクリプトの修正は完了しました。
ゾンビが主人公をサーチするスクリプトSearchCharacterスクリプトの修正
主人公が捕まっている状態から脱出する事が出来るようになったので、SearchCharacterスクリプトで主人公を検知する処理も修正する必要があります。
なぜならこのままの状態だと主人公が離れた後すぐに捕まってしまうからです。
その為、ゾンビの状態によっては主人公を追いかける状態にしないようにします。
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 | using UnityEngine; using System.Collections; public class SearchChara : MonoBehaviour { private HoldZombie holdZ; void Start() { holdZ = GetComponentInParent<HoldZombie>(); } void OnTriggerStay(Collider col) { if(col.tag == "Player" && holdZ.GetState() != HoldZombie.ZombieState.chase && holdZ.GetState() != HoldZombie.ZombieState.hold && holdZ.GetState() != HoldZombie.ZombieState.waitHold) { holdZ.SetState(HoldZombie.ZombieState.chase, col.transform); } } void OnTriggerExit(Collider col) { if(col.tag == "Player") { holdZ.SetState(HoldZombie.ZombieState.wait); } } } |
ゾンビがZombieState.waitHold状態の時も追いかけないようにしています。
これで主人公がゾンビに捕まった時にSPACEキーを指定回数押したら脱出し、ゾンビの状態も待ち状態(ZombieState.waitHold)になりました。
ゾンビは指定の秒数を経過し、サーチエリアに主人公がいる場合は主人公を追いかける状態へと遷移します。
ではここまでの機能を確認してみましょう。
HoldCharacterMoveでreleaseCountの数値を10にします。
HoldZombieMoveでwaitTimeを3に設定します。
それではUnityの実行ボタンを押して確認してみましょう。
↑のようにゾンビに捕まると主人公は動けなくなり、SPACEキーを10回押すとゾンビから離れます。
ゾンビの方は3秒間の待ち時間の後、サーチエリア内に主人公がいる為再び主人公を追いかけ始めています。
これで主人公がゾンビに捕まって一定のキーを押した時に脱出する一連の流れが出来上がりました。
ゾンビに捕まった時の主人公のアニメーションとゾンビの手が足を掴むようにする
ここまでで主人公が捕まってから脱出するまでの機能は出来上がりましたが、ゾンビに捕まっているのかどうかわかりません。
そこで主人公が捕まった時は主人公のアニメーションを捕まった時の物に変更し、ゾンビの方は主人公の足を手で捕まえるようにしてみます。
主人公のアニメーションを用意し両足にゾンビがつかむ場所を作成する
主人公のアニメーションの用意とアニメーターコントローラーの設定
まずは主人公が捕まった時のアニメーションは
↑のようなアニメーションを用意しました。
うーむ・・・なぜかモデルをゾンビにしている・・・・(+_+)
主人公がゾンビに捕まった時は右足と左足、どちらを捕まれたかによってアニメーションを反対にします。
主人公のアニメーターコントローラーを開き、右足を捕まれた状態と左足を捕まれた状態を作成します。
↑のようにHoldRightとHoldLeftの状態を作り、先ほどのアニメーションを設定します。
アニメーションパラメータにBoolのHoldRightとHoldLeftを追加しておきます。
HoldRightとHoldLeftへの遷移条件は
HasExitTimeのチェックを外し
Idle→HoldRightはHoldRightがtrue
HoldRight→IdleはHoldRightがfalse
Idle→HoldLeftはHoldLeftがtrue
HoldLeft→IdleはHoldLeftがfalse
Run→HoldRightはHoldRightがtrue
Run→HoldLeftはHoldLeftがtrue
という条件を入れます。
アニメーションのブレンドは条件が成立したらすぐに捕まったアニメーションに遷移させるようにブレンドしておきます。
HoldLeftの状態を選択しインスペクタを表示したらMirrorの項目にチェックを入れます。
Mirror項目にチェックを入れると本来のアニメーションとは左右逆になるので、先ほどのアニメーションでは左足が後ろに来るようになります。
これで主人公のアニメーターコントローラーの設定は終了です。
主人公の足にゾンビが掴む場所を作成する
次にゾンビが主人公を捕まえた時に手がくる位置を主人公の足に作成します。
Hierarchy上で右クリック→3D Object→Sphereを選択し、右足と左足の子要素に設定します。
それぞれのインスペクタでScaleを調整し、足の大きさより少し大きめになるぐらいにしておきます。
このSphereColliderのサイズを使って手を置く位置を計算します。
SphereColliderのIs Triggerのチェックを入れて物理的に当たらないようにします。
↑が実際に設置したRightFootIKとLeftFootIKの位置と大きさになります。
わかりやすいように表示していますが、実際には見えないようにしたいのでRightFootIKとLeftFootIKのMeshRendererのチェックを外してください。
主人公操作スクリプトHoldCharaMoveにアニメーション操作処理を追加
1 2 3 4 5 6 7 | public enum MyState { normal, holdRight, holdLeft }; |
主人公の状態MyStateのholdを消して、holdRightとholdLeftという状態を作成します。
これに関連してUpdateメソッド内で主人公の状態がMyState.holdの時にしていた処理をMyState.holdRightとMyState.holdLeftの時に変更します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // 敵に掴まれている状態 } else if (state == MyState.holdRight || state == MyState.holdLeft) { // 指定回数以上キーを押したら離れる if(Input.GetKeyDown("space")) { pushCount++; if(pushCount >= releaseCount) { pushCount = 0; SetState(MyState.normal); holdEnemy.GetComponent<HoldZombie>().SetState(HoldZombie.ZombieState.waitHold); } } } |
次に状態変更関数SetStateの処理を変更します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public void SetState(MyState state, GameObject enemy = null) { // 状態を変更 this.state = state; if(state == MyState.normal) { animator.SetBool("HoldRight", false); animator.SetBool("HoldLeft", false); } else if(state == MyState.holdRight) { holdEnemy = enemy; animator.SetFloat("Speed", 0f); animator.SetBool("HoldRight", true); } else if(state == MyState.holdLeft) { holdEnemy = enemy; animator.SetFloat("Speed", 0f); animator.SetBool("HoldLeft", true); } } |
引数で受け取った状態がMyState.normalだった時は主人公が捕まった状態から脱出した時なのでアニメーションパラメータのHoldRightとHoldLeftをfalseにします。
MyState.holdRight、MyState.holdLeftだった時はアニメーションパラメータのHoldRightとHoldLeftをそれぞれtrueにしています。
これで主人公側の修正と追加が終わりました。
ここまででHoldZombieスクリプトで主人公の状態をMyState.holdに変える処理が残っているのでエラーが出ています。
これからHoldZombieMoveに修正を加えていく事でエラーが出なくなるので安心してください。
ゾンビが主人公を捕まえた時に手を主人公の足に付ける
次にゾンビが主人公を捕まえた時にゾンビの手を主人公の足に付ける方法を考えます。
現時点ではゾンビが主人公を捕まえた時のアニメーションはうつぶせになっているだけのもので手は地面に付いています。
そこでIKを使って手の位置を主人公に設置したRightFootIKとLeftFootIKの位置に持っていく事でこれを実現させます。
IKを使うと手や足の位置を本来のアニメーションから独立して位置や回転を設定する事が出来るようになります。
IKを使用する事が出来るようにする
IKに関しては
等のIKに関する記事を参照してください。
まずはゾンビのアニメーターコントローラーでIK Passにチェックを入れスクリプトでOnAnimatorIKが呼び出されるようにします。
ゾンビのアニメーターコントローラーのBase Layerの歯車を押してIK Passにチェックを入れます。
これでスクリプトにOnAnimatorIK関数を記述すると呼ばれるようになります。
ゾンビ操作スクリプトHoldZombieにIKの処理を追加する
アニメーターコントローラーでIKの設定をしたので、HoldZombieスクリプトにOnAnimatorIK関数を記述しIKが働くようにします。
まずは変数宣言を追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 | // 主人公との距離を計る位置 [SerializeField] private Transform head; // 主人公の右足 [SerializeField] private Transform rightFootIK; // 主人公の左足 [SerializeField] private Transform leftFootIK; // 右足をつかんでいるかどうか private bool catchRight; |
今までは主人公の位置とゾンビの位置がstopDistanceの距離より短くなった時に捕まえた状態にしてましたが、ゾンビの位置はtransform.positionとしており
ゾンビの足元からの距離になっていました。
そこでheadでゾンビの頭のボーンを指定し頭からの距離で計算するようにします。
またゾンビが掴む右足と左足も指定出来るようにし、headからrightFoot、leftFootまでの距離で捕まえたかどうかを判定するようにします。
catchRight変数は右足を掴んでいる場合にtrue、左足を掴んでいる場合はfalseにする変数で、どちらの足を掴んでいるか判定する時に使用します。
次はUpdateメソッド内のZombieState.chaseの状態の処理を変更します。
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 | // ゾンビが主人公を追いかけている状態 if(state == ZombieState.chase) { if(!arrived) { // ゾンビの頭から右足、左足の距離を計算しつかむかどうかを決定 if(Vector3.Distance(rightFootIK.position, head.position) < stopDistance) { SetState(ZombieState.hold); playerChara.GetComponent<HoldCharaMove>().SetState(HoldCharaMove.MyState.holdRight, this.gameObject); catchRight = true; } else if(Vector3.Distance(leftFootIK.position, head.position) < stopDistance) { SetState(ZombieState.hold); playerChara.GetComponent<HoldCharaMove>().SetState(HoldCharaMove.MyState.holdLeft, this.gameObject); catchRight = false; } animator.SetFloat("Speed", 2f); transform.LookAt(new Vector3(playerChara.position.x, transform.position.y, playerChara.position.z)); direction = (playerChara.position - transform.position).normalized; velocity += direction * speed; } } |
主人公の右足とゾンビの頭の距離がstopDistanceより短い時は右足を掴んだとし状態を変更します。
主人公のSetState関数を呼び出し掴んだ足によって主人公の状態も変更しています。
それ以外の時で主人公の左足とゾンビの頭の距離がstopDistanceより短かった時は左足を掴んだ状態としています。
これでゾンビが主人公を追いかけている時に距離が短くなったら右足か左足かを掴んだという状態に変更出来ます。
次にOnAnimatorIK関数を作成し、ゾンビが足を掴んだ時に手の位置を右足か左足に持っていく処理を記述します。
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 | void OnAnimatorIK() { if(state == ZombieState.hold) { // 右手と左手の位置と回転のIKのウエイトを1にする animator.SetIKPositionWeight(AvatarIKGoal.RightHand, 1f); animator.SetIKPositionWeight(AvatarIKGoal.LeftHand, 1f); animator.SetIKRotationWeight(AvatarIKGoal.RightHand, 1f); animator.SetIKRotationWeight(AvatarIKGoal.LeftHand, 1f); // 掴む足のTransform Transform foot; //位置調整 Vector3 offset; // 右足か左足かで分岐 if(catchRight) { foot = rightFootIK; offset = transform.right * rightFootIK.GetComponent<SphereCollider>().radius / 5f; } else { foot = leftFootIK; offset = transform.right * leftFootIK.GetComponent<SphereCollider>().radius / 5f; } // 右手と左手の位置と回転を設定 animator.SetIKPosition(AvatarIKGoal.RightHand, foot.position + offset); animator.SetIKPosition(AvatarIKGoal.LeftHand, foot.position - offset); animator.SetIKRotation(AvatarIKGoal.RightHand, Quaternion.FromToRotation(transform.up, transform.right) * transform.rotation); animator.SetIKRotation(AvatarIKGoal.LeftHand, Quaternion.FromToRotation(transform.up, -transform.right) * transform.rotation); } } |
IKのウエイトの設定や位置、回転の情報を設定する方法はIK関連の記事を見てください。
簡単に説明すると右手、左手を合わす時にどの程度のウエイトをかけるか設定し(ウエイトは0~1)、その後に実際に右手と左手の位置と回転を設定しています。
右足を掴んでいるか左足を掴んでいるかで処理が多少変わりますが、やることがほとんど同じなのでcatchRightの値によってあらかじめオフセット値を計算しておき、
同じ処理で済むようにして記述が少なくなるようにしています。
オフセット値で計算しているのはtransform.right(ゾンビの右側に1m)にrightFoot、leftFootのSphereColliderに設定したradius(半径)を5で割った値をかけたものです。
つまりrightFootIKとleftFootIKの球の周りに手の位置を設定する為の処理です。
rightFoot、leftFootのScaleは0.2にしたのでその影響で5で割る必要があります。
1 2 3 4 5 6 7 8 9 10 | // ゾンビが主人公を捕まえている状態 if (state == ZombieState.hold) { // ゾンビの向きを掴んでいる足の方向に向ける if (catchRight) { transform.LookAt (new Vector3 (rightFootIK.position.x, transform.position.y, rightFootIK.position.z)); } else { transform.LookAt (new Vector3 (leftFootIK.position.x, transform.position.y, leftFootIK.position.z)); } |
右手と左手のIKを設定した後にゾンビの向きを主人公の足の方向に向けるとよりリアルな感じになると思い向きを変更するようにしました。
これで処理が完成しました。
Unityを実行し主人公が捕まった時の状態を確認してみましょう。
↑のようにHoldZombieMoveスクリプトのheadにゾンビの頭のボーンをドラッグ&ドロップ、rightFootに主人公のrightFootIK、leftFootに主人公のleftFootIKをドラッグ&ドロップします。
また距離を計算する時の位置をゾンビの頭にしたのでstopDistanceは0.5に設定し直しました。
それでは実行してみましょう。
サンプルではすごくよく出来ているように見えますが・・・・(^_^;)
ゾンビが主人公を捕まえる条件の距離は走っている時の足の位置ですが、捕まえた状態になると主人公は捕まったアニメーションに変更されます。
(主人公の足の位置が走っている状態の場所から捕まっている状態の位置に移動します)
つまりゾンビの捕まえ方によっては手が足に届かないこともあります。
またゾンビのCharacterControllerのサイズを小さくしたことで主人公のCharacterControllerの設定によってはゾンビの上に乗る事が出来てしまいます。
そうなると主人公がゾンビの上にいる状態でゾンビが足を掴むことになり手があらぬ方向に曲がってしまいます。
こうならない為にはゾンビのCharacterControllerのサイズを少し高めに設定するか、主人公のCharacterControllerの設定で乗れる段差Step Offsetの値を小さくする事でゾンビの上には乗らないように出来ると思います。
ゾンビの上に乗るようにはしたいけれど足を掴ませない為にはまた別の処理が必要ですね・・・・これに関してはがんばって作成してみてください。(^_^;)
これですべての機能が完成!!
といきたい所ではありますが、別の所で問題がまだ残っています。
ゾンビのアニメーションが立って歩くもので立ったまま主人公を捕まえる場合は考慮しなくてもいい問題なんですが・・・・。
ゾンビが地面に合わせて角度を変えて歩くようにする
ゾンビが主人公を掴む機能と主人公が脱出する機能は出来ましたが、実はゾンビが主人公を追いかける時に坂やデコボコ道を動く時に同じように水平のまま移動してしまいます。
これは今回の機能とは関係ないですが・・・・デコボコ道をずっと水平のまま歩くのは残念な感じがします。
↑のように坂を上る時も水平なままです。
これはtransform.LookAtで自身のY座標と同じ高さを見ているからですね。
さらに水平なままなので坂の上で主人公を捕まえると手が変な方向に曲がってしまいます。
HoldZombieに処理を追加しゾンビが地面に合わせて角度を変えるようにしてみます。
1 2 3 4 5 6 | // ゾンビの下の地面の向き private Vector3 normalDirection; // 主人公操作スクリプト private HoldCharaMove holdCharaMove; |
ゾンビの下にある地面の向きをnormalDirectionに入れるようにします。
またゾンビが主人公の足を掴む時に毎回GetComponentでHoldCharaMoveスクリプトを取得してましたが、これをやめてStartメソッド内で取得しておきます。
1 2 3 4 5 6 7 8 9 10 | void Start () { eCon = GetComponent<CharacterController>(); animator = GetComponent<Animator>(); holdCharaMove = playerChara.GetComponent<HoldCharaMove>(); direction = Vector3.zero; state = ZombieState.wait; currentTime = 0f; } |
↑のようにplayerCharaを介してHoldCharaMoveスクリプトを取得出来るようにします。
次にUpdateメソッド内でゾンビから下方向にレイを飛ばし地面であったらその反射角度を求める処理を最初に入れます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | void Update () { if(eCon.isGrounded) { velocity = Vector3.zero; // 地面の角度に合わせる RaycastHit hit; // Fieldレイヤーの向いている方向を取得 if(Physics.Linecast(transform.position, transform.position + Vector3.down, out hit, LayerMask.GetMask("Field"))) { normalDirection = hit.normal; } else { normalDirection = Vector3.up; } |
Physics.Linecastでゾンビから下方向に1mレイを飛ばし、ぶつかったゲームオブジェクトにBlockレイヤーが設定されていたらその情報をhitに入れます。
地面にはFieldレイヤーを設定しておく必要があります。
hit.normalで衝突した面の向いている方向を取得できます。
次に今回のメインの処理であるゾンビの角度を地面の角度にする処理を追加します。
まずはUpdateメソッドのゾンビが主人公を追いかけている時の処理です。
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 | // ゾンビが主人公を追いかけている状態 if(state == ZombieState.chase) { if(!arrived) { // ゾンビの頭から右足、左足の距離を計算しつかむかどうかを決定 if(Vector3.Distance(rightFootIK.position, head.position) < stopDistance) { SetState(ZombieState.hold); playerChara.GetComponent<HoldCharaMove>().SetState(HoldCharaMove.MyState.holdRight, this.gameObject); catchRight = true; } else if(Vector3.Distance(leftFootIK.position, head.position) < stopDistance) { SetState(ZombieState.hold); playerChara.GetComponent<HoldCharaMove>().SetState(HoldCharaMove.MyState.holdLeft, this.gameObject); catchRight = false; } animator.SetFloat("Speed", 2f); transform.LookAt (playerChara); transform.rotation = Quaternion.FromToRotation(transform.up, normalDirection) * transform.rotation; direction = (playerChara.position - transform.position).normalized; velocity += direction * speed; } } |
ゾンビが歩いていたり、主人公を追いかけている時ゾンビの角度を変更します。
次は主人公を捕まえている状態の時です。
1 2 3 4 5 6 7 8 9 10 11 12 | // ゾンビが主人公を捕まえている状態 if (state == ZombieState.hold) { // ゾンビの向きを掴んでいる足の方向に向ける if (catchRight) { transform.LookAt (rightFootIK); transform.rotation = Quaternion.FromToRotation(transform.up, normalDirection) * transform.rotation; } else { transform.LookAt (leftFootIK); transform.rotation = Quaternion.FromToRotation(transform.up, normalDirection) * transform.rotation; } |
ゾンビの向きは主人公の掴んでいる足の方向を向かせ、ゾンビの角度は地面の角度に合わせます。
ゾンビの向いている方向はtransform.LookAtで主人公の方を向かせ、その後ゾンビの角度を地面の角度に合わせます。
1 2 3 | transform.rotation = Quaternion.FromToRotation(transform.up, rot) * transform.rotation; |
で上向きと地面の反射向きから角度を計算し、それに自身の角度をかけた後それを自身の角度にしています。
この処理はちょっと頭が混乱しますね・・・。
詳しくは
の記事や
を参照してください。
これで処理が出来ました。
ゾンビに坂を登らせて確認してみましょう。
↑のように坂に合わせてゾンビの角度が変わるようになりました。
平らな地面や坂だけのステージとは限りませんので、Terrainで作ったデコボコな地面で確認してみましょう。
↑のようにデコボコに合わせてゾンビの角度が変わりよりリアルな感じになりました!(^_^)v
これですべての機能が完成です!(^-^)
と言いたいところなんですが・・・、今回のゾンビを動かすスクリプトでは障害物があると引っかかって動けなくなってしまいます。
そこでゾンビを動かすスクリプトをナビゲーション機能を使った移動方法に変えて見ましょう。
ゾンビの移動をナビゲーション機能を使った移動に変更する
ナビゲーション機能を使うとナビメッシュした地面を効率よく移動する敵キャラを作成する事が出来ます。
ナビゲーション機能については
等のナビゲーションに関する記事を参照して頂くと詳しく記述しています。
ゾンビにNavMeshAgentコンポーネントを設定する
まずはCharacterControllerを使用して動かしていたゾンビをNavMeshAgentで動くようにコンポーネントを変更します。
ゾンビのインスペクタからAdd Component→Navigation→NavMeshAgentを追加します。
↑のようにゾンビのNavMeshAgentを設定します。
細かい設定をしてエージェントの動きを変更してみるといいと思います。
↑のようなNavMeshAgentのサイズになりました。
今まで使っていたCharacterControllerコンポーネントは使用しないので削除します。
ゾンビ操作スクリプトHoldZombieMoveをナビゲーション機能で移動するように変更
それではHoldZombieMoveスクリプトを変更していきます。
Unity5.5バージョン以降でC#でスクリプトを組んでいる場合は
1 2 3 | using UnityEngine.AI; |
というusingディレクティブを入れておくといいです。
1 2 3 4 | // ナビゲーションエージェント private NavMeshAgent agent; |
StartメソッドでNavMeshAgentを取得します。
1 2 3 4 5 | void Start () { agent = GetComponent <NavMeshAgent> (); } |
ゾンビの移動スピードに使用していたspeed変数は使用しなくなるので変数を削除し、NavMeshAgent変数を宣言してナビゲーション機能で移動するようにします。
またCharacterControllerのeCon変数も使用しなくなるので削除します。
Updateメソッド内の
1 2 3 4 | if(cCon.isGrouded) { } |
の条件を外します。
Updateメソッド内のCharacterControllerを使った移動処理の部分は使わなくなるので削除します。
1 2 3 4 | velocity.y += Physics.gravity.y * Time.deltaTime; eCon.Move(velocity * Time.deltaTime); |
またspeedを使っていた箇所であるWalkChase関数内の処理も使用しないので削除します。
1 2 3 4 | direction = (destination - transform.position).normalized; velocity += direction * speed; |
WalkChaseの↑の記述があった部分にはエージェントの目的地を設定するようにします。
1 2 3 | agent.SetDestination(playerChara.position); |
ゾンビの状態変更関数SetStateではエージェントの再開と停止の記述を追加します。
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 | // ゾンビの状態変更関数 public void SetState(ZombieState mode, Transform player = null) { state = mode; if(mode == ZombieState.chase) { arrived = false; playerChara = player; agent.SetDestination (playerChara.position); agent.Resume (); } else if(mode == ZombieState.hold) { arrived = true; animator.SetBool("Hold", true); animator.SetFloat("Speed", 0f); agent.Stop (); } else if(mode == ZombieState.waitHold) { arrived = false; animator.SetFloat("Speed", 0f); animator.SetBool("Hold", false); } else if(mode == ZombieState.wait) { animator.SetFloat("Speed", 0f); } } |
ゾンビが主人公を追いかける状態になった時は目的地を主人公に設定しエージェントの再開をします。
ゾンビが主人公を掴んでいる時はエージェントの動きを停止します。
これでスクリプトの修正が終わりました。
地面をNavMeshしてエージェントが移動出来るようにする
最後にNavMeshAgentはNavMeshされた地面しか移動できないので地面をNavMeshしましょう。
地面のゲームオブジェクトをすべてCtrlキーを押しながら選択し、インスペクタの右上にあるstaticのチェックを入れます。
staticは静的という意味で動かないゲームオブジェクトであることを意味します。
staticにはいくつか種類がありますが今回は大元のstaticにチェックを入れ全てチェックされるようにしています。
staticにチェックを入れたら
↑のようにUnityのメニューからNavigationを選択しNavigationタブを表示します。
NavMeshの設定をしてBakeボタンを押します。
↑がNavMeshされた地面です。
青くなっている部分がNavMeshAgentが動く場所になり白い部分は移動出来ません。
エージェントがうまく地面を動かない場合はその動けない部分がNavMeshされていない可能性があります。
さきほどのNavigationタブで設定を調整し再度Bakeする必要があります。
これでエージェントが動く地面の作成も終わりました。
ナビゲーション機能で動くゾンビを確認する
それでは最後にCharacterControllerで動くゾンビとナビゲーション機能で動くゾンビを比較して確認してみましょう。
まずはCharacterControllerを使ったゾンビの移動を見てみます。
↑のように障害物があっても主人公の方向に進もうとする為障害物にぶつかったまま動けなくなります。
次にNavMeshAgentを使ったゾンビの移動を見てみます。
↑のように障害物があってもそれを避けて主人公を追いかけていきます。
敵のAIを作成する上でナビゲーション機能を使うと非常に簡単に作成する事が出来るのがわかります。(^-^)
ただ、障害物があってもゾンビが主人公の方向を向いてしまうのは少し問題ですね。
ゾンビと主人公の間に障害物があった時は主人公の方向を見ないで次の移動先を見るようにさせるといいかもしれません。
Updateメソッド内の主人公を追いかけている処理を修正します。
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 | // ゾンビが主人公を追いかけている状態 if(state == ZombieState.chase) { if(!arrived) { animator.SetFloat("Speed", 2f); // ゾンビの向く方向を目的地にし、体の角度を地面と同じにする var offset = Vector3.up * 0.2f; Debug.DrawLine(transform.position + offset, playerChara.transform.position + offset, Color.yellow); if(!Physics.Linecast(transform.position + offset, playerChara.transform.position + offset, out hit, LayerMask.GetMask("Block"))) { transform.LookAt(playerChara); } transform.rotation = Quaternion.FromToRotation(transform.up, normalDirection) * transform.rotation; agent.SetDestination (playerChara.position); // ゾンビの頭から右足、左足の距離を計算しつかむかどうかを決定 if(Vector3.Distance(rightFootIK.position, head.position) < stopDistance) { SetState(ZombieState.hold); playerChara.GetComponent<HoldCharaMove>().SetState(HoldCharaMove.MyState.holdRight, this.gameObject); catchRight = true; } else if(Vector3.Distance(leftFootIK.position, head.position) < stopDistance) { SetState(ZombieState.hold); playerChara.GetComponent<HoldCharaMove>().SetState(HoldCharaMove.MyState.holdLeft, this.gameObject); catchRight = false; } } |
WalkChase関数内でゾンビから主人公にレイを飛ばし間にBlockレイヤーに設定したゲームオブジェクトがない場合だけキャラクターの方向を主人公に向けます。
ゾンビの位置や主人公の位置(transform.position)は足元になっているのでそこから少し上向きに調整した位置でレイを飛ばしています。
これは地面自体にレイが接触する間違いが発生する可能性もあった為、位置を調整しました(主人公とゾンビの段差が違いすぎる時は地面に接触する可能性あり)。
移動処理を先に実行し、その後に状態を変更する処理にしました。
これで障害物が間にあっても主人公は追いかけるが主人公の方を見なくなります。
↑のように障害物がある時は主人公の方を向かなくなりました。
捕まった後に主人公が脱出した場合に違う方向を向いてしまいますが・・・・。
1 2 3 4 5 6 7 8 9 10 11 12 13 | // ゾンビが主人公を離してからの待ち状態 } else if (state == ZombieState.waitHold) { currentTime += Time.deltaTime; if(currentTime >= waitTime) { SetState(ZombieState.wait); currentTime = 0f; } // 目的地(掴まえる前の目的地を向かせ、体の上向きは地面の向きにする transform.LookAt(agent.destination, normalDirection); } |
transform.LookAtを使ってゾンビがキャラクターを掴んだ位置(agent.destinationは掴んだ時の最後の目的地)を指定します。
ここで今まで使っていなかったLookAtの第2引数が出てきます。
第1引数は向く方向を指定しますが、第2引数はゲームオブジェクトの上向きの方向を指定します。
その為normalDirection(地面の向いている方向)を指定して体の向きを地面と合わせるようにしています。
同じような処理を使って障害物で主人公が見えなくなった時に追いかけるのをやめさせる事も出来ます。
↑の記事で障害物があった時に追いかけないようにする機能を作成しているので参考にしてください。
複数の敵がいた場合の対処
--2017/01/06に複数の敵がいた場合の処理を追加--
ここまでで機能としては出来ましたが、複数の敵がいる場合の対応をしていません。
そこで複数の敵がいた場合の対処をしていきます。
主人公が他のゾンビに掴まれていた時は主人公は追いかけるけど掴まないようにしていきましょう。
主人公操作スクリプトHoldCharaMoveに処理を追加します。
1 2 3 4 5 | public MyState GetState() { return state; } |
主人公の状態を返す関数を作成しておきます。
次にゾンビ操作スクリプトHoldZombieを修正していきます。
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 | // ゾンビが主人公を追いかけている状態 if(state == ZombieState.chase) { if(!arrived) { animator.SetFloat("Speed", 2f); // ゾンビの向く方向を目的地にし、体の角度を地面と同じにする var offset = Vector3.up * 0.2f; Debug.DrawLine(transform.position + offset, playerChara.position + offset, Color.yellow); if(!Physics.Linecast(transform.position + offset, playerChara.transform.position + offset, out hit, LayerMask.GetMask("Block"))) { transform.LookAt(playerChara); } transform.rotation = Quaternion.FromToRotation(transform.up, normalDirection) * transform.rotation; agent.SetDestination (playerChara.position); // 主人公が他のゾンビに掴まれていない時 if (holdCharaMove.GetState () != HoldCharaMove.MyState.holdRight && holdCharaMove.GetState () != HoldCharaMove.MyState.holdLeft) { // ゾンビの頭から右足、左足の距離を計算しつかむかどうかを決定 if (Vector3.Distance (rightFootIK.position, head.position) < stopDistance) { SetState (ZombieState.hold); playerChara.GetComponent<HoldCharaMove> ().SetState (HoldCharaMove.MyState.holdRight, this.gameObject); catchRight = true; } else if (Vector3.Distance (leftFootIK.position, head.position) < stopDistance) { SetState (ZombieState.hold); playerChara.GetComponent<HoldCharaMove> ().SetState (HoldCharaMove.MyState.holdLeft, this.gameObject); catchRight = false; } } else { if(Vector3.Distance(rightFootIK.position, head.position) < stopDistance) { SetState(ZombieState.waitHold); } else if(Vector3.Distance(leftFootIK.position, head.position) < stopDistance) { SetState(ZombieState.waitHold); } } } } |
Updateメソッド内の追いかける処理を記述している部分で主人公の状態を取得し他のゾンビに掴まれていなければ今までどおりの処理をします。
他のゾンビに掴まれている状態であり、指定距離より短くなったら、自分自身をZombieState.waitHoldに変更するようにします。
こうすることで主人公の近くにくるまでは追いかけるが足を掴まずその場で待機するようになります。
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 | // ゾンビの状態変更関数 public void SetState(ZombieState mode, Transform player = null) { state = mode; if(mode == ZombieState.chase) { arrived = false; playerChara = player; agent.SetDestination (playerChara.position); agent.Resume (); } else if(mode == ZombieState.hold) { arrived = true; animator.SetBool("Hold", true); animator.SetFloat("Speed", 0f); agent.Stop (); } else if(mode == ZombieState.waitHold) { arrived = false; animator.SetFloat("Speed", 0f); animator.SetBool("Hold", false); agent.Stop (); } else if(mode == ZombieState.wait) { animator.SetFloat("Speed", 0f); } } |
SetStateメソッド内でZombieState.waitHoldの時の処理にagent.Stop()を追加します。
これは他のゾンビが主人公を掴んでいる時に自身はwaitHoldの状態にするので主人公に追いついた時に主人公は掴まないけど移動は止める必要があるからです。
複数の敵を出現させるとゾンビ同士が重なってしまう事が起こりますが、NavMeshAgentのサイズを少し大きくして対処するといいと思います。
今回の対処は一人の敵が掴んでいる時は他の敵は近くで一定時間待機という方法にしていますが、複数の敵に掴まれるような対処も面白そうですね!
実現できるかどうかはさておいてですが・・・・(^_^;)
最後に複数のゾンビをコピーして試してみましょう。
↑のようにゾンビに掴まれている時は他のゾンビは近くまでくるだけになりました。
--2017/01/06に追加した複数の敵がいる場合の対処終わり--
ユーザーが一定時間SPACEキーを押さなかったら押した回数をリセットする
--2017/01/11に一定時間キーを押さないと押した回数を初期化する処理を追加始め
今までの処理だとreleaseCountの回数分SPACEキーを押したらゾンビから逃げられましたが、連続で押さなくても指定回数押しただけで脱出が出来ました。
そこで前にSPACEキーを押してから一定時間キーが押されなければキーを押した回数をリセットし最初からやり直しさせるように処理を変えてみましょう。
主人公操作スクリプトHoldCharaMoveを変更していきます。
1 2 3 4 5 6 7 | // pushCountを初期化する限界時間 [SerializeField] private float freezeTime; // 最後にキーが押された時間 private float lastTypeTime; |
最後にキーを押してから次のキーを押さなければいけない時間freezeTimeと最後にキーを押した時の時間lastTypeTimeを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // 敵に掴まれている状態 } else if (state == MyState.holdRight || state == MyState.holdLeft) { // 指定回数以上キーを押したら離れる if(Input.GetKeyDown("space")) { lastTypeTime = Time.time; pushCount++; if(pushCount >= releaseCount) { pushCount = 0; SetState(MyState.normal); holdEnemy.GetComponent<HoldZombie>().SetState(HoldZombie.ZombieState.waitHold); } } // 最後にキーを押してからの時間がfreezeTimeを超えた時はpushCountを初期化 if(Time.time - lastTypeTime > freezeTime) { pushCount = 0; } } |
Updateメソッド内の主人公が捕まっている時の処理でゲーム開始からの時間(Time.time)から最後にキーを押した時間を引いてfreezeTimeを超えたら押した回数(pushCount)をリセットしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public void SetState(MyState state, GameObject enemy = null) { // 状態を変更 this.state = state; if(state == MyState.normal) { animator.SetBool("HoldRight", false); animator.SetBool("HoldLeft", false); } else if(state == MyState.holdRight) { holdEnemy = enemy; animator.SetFloat("Speed", 0f); animator.SetBool("HoldRight", true); lastTypeTime = Time.time; } else if(state == MyState.holdLeft) { holdEnemy = enemy; animator.SetFloat("Speed", 0f); animator.SetBool("HoldLeft", true); lastTypeTime = Time.time; } } |
状態変更メソッドSetStateで主人公が捕まった時の処理にlastTypeTimeに初期時間を入れておきます。
これで機能が完成したので、インスペクタのfreezeTimeにキーを押さなければいけない時間(float値)を設定し確認してみてください。
--2017/01/11に一定時間キーを押さないと押した回数を初期化する処理追加終了--
これで本当にすべての機能が完成しました!
終わりに
敵が主人公の足を掴んで脱出するという機能の作成をする内に違う問題点(ゾンビが水平なまま移動してしまう)が発生して記事のボリュームが多くなりました・・・(^_^;)
キャラクターが立った状態で移動する場合は地面の角度を考慮しなくてもそんなに気にはなりませんが、
地面にうつ伏せになって歩く状態や人型キャラクター以外のモンスターなどを移動させる際には地面の角度とキャラクターの傾きを合わせる処理があるとリアルな感じが出ていいですね。
今回作成した機能のまま使用しても結構使えるとは思いますが、もっとこだわって作成するならゾンビの手がちゃんと主人公の足を掴むようにしたり、
主人公との位置関係によってゾンビの手があらぬ方向に曲がらないようにしていくといいですね。
今回の記事では他の記事へのリンクが多くあります。
それだけ色々な機能を把握しておく必要があるという事ですね。
キャラクターの移動、キャラクターのアニメーションの作成、アニメーションの制御、IK、角度の計算、ナビゲーション機能。
でも、これだけの機能を使いこなせれば他の複雑な機能を作ろうと思った時に応用が利くようになると思います。
それらの機能について当ブログで記事を書いているのでそちらも参考にしつつ、今回の機能の作成に取り組んでみてください(^^)/