今回は主人公キャラクターが敵に攻撃を受けた時に、敵に抱きつかれる機能を作りたいと思います。
Rigidbodyを使ってキャラクターを動かしている場合はJointの機能を使うと敵が主人公にくっつく機能が作れそうです。
スクリプトからRigidbodyを付けてもいいんですが、うまく出来ませんでした。
Rigidbody自体がIsKinematicにチェックが入っているとスクリプト上でしか操作出来ないのでダメなのかも
今回の機能を作成すると、
↑のような感じになります。
敵の抱きつき時のアニメーションは昔適当に作ったやつなので主人公キャラのサイズとも合ってません・・・・・(^_^;)
そこら辺もちゃんと作りこむとなかなか良くなるかもしれません。
自前のくっつき機能を作成
ということでJoint機能でくっつける機能を作るのをやめて、自前のくっつき機能を作ります。
敵キャラクターの作成
まずは敵キャラを準備します。
他の敵キャラでも同じように設定すればいいのでどんなキャラでも人型なら大丈夫です。
敵キャラを動かすスクリプトの作成
敵にはAnimatorにHugEnemyというアニメーターコントローラーを作り設定し、敵キャラの動きはNavMeshAgentで動かします。
敵キャラを動かすスクリプトHugEnemyを作成します。
NavMeshAgentに関しては、
を参考にしてください。
HugEnemyスクリプトは以下のように作成しました。
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 91 92 93 94 95 96 97 98 99 100 101 102 103 | using UnityEngine; using System.Collections; public class HugEnemy : MonoBehaviour { public enum HugEnemyState { Normal, Hug, Freeze } private HugEnemyState state; // エージェント private UnityEngine.AI.NavMeshAgent agent; // アニメーター private Animator animator; // 主人公キャラ private GameObject player; // くっついている時間 [SerializeField] private float touchTime = 2f; // 経過時間 private float nowTime = 0f; // フリーズ状態から抜け出すまでの時間 [SerializeField] private float freezeTime = 1f; // 主人公キャラに抱き着いた時の距離(1より下の値を設定) [SerializeField] private float hugDistance = 0.5f; // 抱き着くスピード [SerializeField] private float hugSpeed = 3f; void Start() { agent = GetComponent<UnityEngine.AI.NavMeshAgent>(); player = GameObject.FindWithTag("Player"); agent.SetDestination(player.transform.position); // エージェントの目的地設定 animator = GetComponent<Animator>(); SetState(HugEnemyState.Normal); } void Update() { // くっついていない時 if (state == HugEnemyState.Normal) { nowTime += Time.deltaTime; agent.SetDestination(player.transform.position); animator.SetFloat("Speed", agent.desiredVelocity.magnitude); // くっついている時 } else if (state == HugEnemyState.Hug) { nowTime += Time.deltaTime; // くっついている時間を超えていない時 if (nowTime < touchTime) { // 主人公が動いていれば敵キャラの向きを主人公の向きにする if (player.GetComponent<HugChara>().IsMove()) { transform.LookAt(player.transform); } // 主人公からの相対位置を敵の位置とする var pos = player.transform.position + -transform.forward * hugDistance; transform.position = Vector3.MoveTowards(transform.position, pos, hugSpeed * Time.deltaTime); // くっついている時間を超えた時 } else { SetState(HugEnemyState.Freeze); player.GetComponent<HugChara>().SetState(HugChara.HugCharaState.Normal); } } else if (state == HugEnemyState.Freeze) { nowTime += Time.deltaTime; if (nowTime >= freezeTime) { SetState(HugEnemyState.Normal); } } } public void SetState(HugEnemyState state) { this.state = state; nowTime = 0f; if (state == HugEnemyState.Normal) { agent.SetDestination(player.transform.position); //agent.Resume (); // Unity5.6バージョン以降の再開処理 agent.isStopped = false; } else if (state == HugEnemyState.Hug) { //agent.Stop (); // Unity5.6バージョン以降の停止処理 agent.isStopped = true; animator.SetBool("Hug", true); animator.SetFloat("Speed", 0f); transform.LookAt(player.transform); } else if (state == HugEnemyState.Freeze) { //agent.Stop (); // Unity5.6バージョン以降の停止処理 agent.isStopped = true; animator.SetBool("Hug", false); } } public HugEnemyState GetState() { return state; } } |
敵が主人公に抱きついていない時は目的地を主人公に設定し追いかけさせます。
抱きついている時は敵キャラの位置を主人公の位置に補正値である敵キャラの後方にhugDistanceをかけて加えて位置に移動させます。
また主人公が動いている時は敵キャラの向きを主人公の向きに向けさせるようにします。
抱きつきタイム(謎)が終了したら敵の状態をフリーズ状態とし、すぐに主人公に抱きつかないようにします。
フリーズ状態で一定時間が経過したらノーマル状態へと遷移させています。
SetStateでHugEnemyState.Hug状態の時はアニメーションパラメータのHugをtrueにして抱きつき時のアニメーションへと遷移させます。
敵キャラ用のアニメータコントローラーの作成
次に敵キャラ用のアニメーターコントローラーHugEnemyを作成します。
アニメーションパラメータ-にFloatでSpeed、BoolでHugを作成します。
Idle→WalkをSpeedが0.1以上
Walk→IdleをSpeedが0.1以下
Idle→HugをHugがtrue
Walk→HugをHugがtrue
を設定します。
それぞれの遷移ではHas Exit Timeのチェックを外しておきます。
Walk→IdleのSettingsでInterruption SourceをNext Stateに設定し、歩き状態でHugがtrueになった時すぐにHug状態に遷移出来るようにします。
主人公キャラに抱きつく範囲の作成
次に敵のゲームオブジェクトの子要素に空のゲームオブジェクトを作成し、名前をHugAreaとし主人公に抱きつく範囲を作成します。
↑のような階層ですね。
HugAreaにはSphereColliderを取り付けIs Triggerにチェックを入れ検知範囲を作ります。
実際の抱きつく範囲は、
↑のようになりました。
HugAreaにはHugAreaスクリプトを作成し取り付けます。
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 | using UnityEngine; using System.Collections; public class HugArea : MonoBehaviour { private HugEnemy hugEnemy; private void Start() { hugEnemy = transform.parent.GetComponent<HugEnemy>(); } void OnTriggerStay(Collider col) { if (col.tag == "Player") { // フリーズ状態、抱きつき状態でなければ抱きつき状態にする if (hugEnemy.GetState () != HugEnemy.HugEnemyState.Freeze && hugEnemy.GetState () != HugEnemy.HugEnemyState.Hug) { // 敵キャラ自身の状態を変更 transform.root.GetComponent <HugEnemy> ().SetState (HugEnemy.HugEnemyState.Hug); // 主人公キャラの状態を変更 col.GetComponent <HugChara>().SetState (HugChara.HugCharaState.CaughtByEnemy); } } } } |
敵自身の状態がフリーズ、抱きつき状態でない時に敵を抱きつき状態にし、主人公を抱きつかれた状態に設定します。
主人公キャラクターの作成
次に主人公キャラクターを作成します。
主人公キャラ操作スクリプトの作成
主人公キャラの操作スクリプトはHugCharaとしました。
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 | using UnityEngine; using System.Collections; public class HugChara : MonoBehaviour { public enum HugCharaState { Normal, CaughtByEnemy } private CharacterController characterController; private Animator animator; private Vector3 velocity = Vector3.zero; [SerializeField] private float walkSpeed = 1.5f; private HugCharaState state; // Use this for initialization void Start () { characterController = GetComponent <CharacterController> (); animator = GetComponent <Animator> (); SetState (HugCharaState.Normal); } // Update is called once per frame void Update () { if (characterController.isGrounded) { velocity = Vector3.zero; var input = new Vector3 (Input.GetAxis ("Horizontal"), 0f, Input.GetAxis ("Vertical")); if (input.magnitude > 0f) { animator.SetFloat ("Speed", input.magnitude); if (state == HugCharaState.Normal) { velocity = transform.forward * walkSpeed; } else if (state == HugCharaState.CaughtByEnemy) { velocity = transform.forward * walkSpeed * 0.5f; } transform.LookAt (transform.position + input); } else { animator.SetFloat ("Speed", 0f); } } velocity.y += Physics.gravity.y * Time.deltaTime; characterController.Move (velocity * Time.deltaTime); } public void SetState(HugCharaState state) { this.state = state; if (state == HugCharaState.Normal) { animator.SetLayerWeight (animator.GetLayerIndex ("OverrideLayer"), 0f); } else if (state == HugCharaState.CaughtByEnemy) { animator.SetLayerWeight (animator.GetLayerIndex ("OverrideLayer"), 1f); } } public bool IsMove() { return (!Mathf.Approximately(velocity.x, 0f) || !Mathf.Approximately(velocity.z, 0f)); } } |
キャラクターを動かす処理の部分ではノーマル状態か抱きつかれている状態かで歩くスピードを変化させています。
抱き着かれている状態では通常の半分の移動スピードにしています。
SetStateメソッドでは受け取った状態に主人公の状態を変化させますが、その時にAnimatorに設定しているアニメーターコントローラーのOverrideLayerというレイヤーのウエイトを変化させています。
OverrideLayerレイヤーはこの後作成しますが、ウエイトが1だと完全にOverrideLayerに設定したアニメーションを再生させます。
0だとベースとなるレイヤー(デフォルトで作成されるBase Layer)のアニメーションが再生されます。
この辺りは
で詳しくやっておりますので、そちらを参照してみてください。
IsMoveメソッドは主人公キャラクターを動かしているかどうかを返すメソッドです。
主人公キャラのアニメーターコントローラーの作成
主人公キャラに設定するアニメーターコントローラーを作成します。
AnimatorタブのLayersで+を押し新しいレイヤーを作成して名前をOverrideLayerとします。
OverrideLayerの右の歯車を押し、
↑のように設定します。
このレイヤーはウエイトが変更されるとSource Layerに設定したレイヤーを上書きします。
Syncにチェックを入れるとSource Layerとシンクロし、Source Layerに設定されているアニメーションの遷移条件等と同じものがOverride Layerに表示されます。
Override LayerのWalk状態に抱きつかれた状態の時の歩行アニメーションを設定します。
Override Layerのウエイトが1になると完全にOverride LayerのWalk状態のモーションが再生されます。
これで敵キャラが主人公キャラにくっつく処理が完成しました。
Unityの実行ボタンを押して確認してみましょう。
↑は改造前の抱きつき機能なのでアニメーションは変化していません。
簡易ではありますが、敵が主人公キャラクターにくっつくようになりました。
くっついている状態の時は主人公キャラが動けないようにしたり、敵キャラの手や足の位置をIKで調整して
主人公キャラクターにめり込まないようにする必要もありますねぇ・・・・。