【バイオハザード風のゲームを作ってみよう。その12】
今回も下記動画をやっていきます。
youtu.be
今回はゾンビのアタック動作を実装していきます。
・ゾンビは、ラン後に攻撃モーションをします。mixamoから該当するアニメーションをダウンロードし、Zombieアニメーターコントローラーのoverrideレイヤーに下記のように設定します。
・ScriptableObject を作成します。
using System.Collections; using System.Collections.Generic; using UnityEngine; //CreateAssetMenu()を記述することで、Projectビュー右クリック時のメニューに、指定したクラスのScriptableObjectを作成する項目を手っ取り早く追加できる。 [CreateAssetMenu(menuName = "A.I/Actions/Zombie Attack Action")] public class ZombieAttackAction : ScriptableObject //EP12追加 { [Header("Attack Animation")] public string attackanimation; [Header("Attack Cooldown")] public float attackCooldown = 5f; //The time before the zombie can perform another attack (After performing this attack) [Header("Attack Angles & Distances")] public float maxmumAttackAngle=20f; //The maximum angle of sight needed to perform THIS attack public float minimumAttackAngle=-20f; //The minimum angle of sight needed to perform THIS attack public float minimumAttackDistance=1f; //The minimum distance from the current target needed to perform THIS attack public float maxmumAttackDistance=3.5f; //The maxmum distance from the current target needed to perform THIS attack }
・スクリプトを追加します。
public class AttackState : State //EP10追加 ゾンビ直下のstatesオブジェクトにアタッチ { PurseTargetState purseTargetState; //EP12追加 [Header("Zombie Attacks")] public ZombieAttackAction[] zombieAttackActions; //EP12追加 [Header("Potential Attacks Performed Right Now")] public List<ZombieAttackAction> potentialAttacks; //EP12追加 [Header("Current Attack Being Performed")] public ZombieAttackAction currentAttack; //EP12追加 [Header("Stage Flags")] public bool hasPerformAttack; //EP12追加 private void Awake() { purseTargetState = GetComponent<PurseTargetState>(); //EP12追加 } public override State Tick(ZombieManager zombieManager) { zombieManager.animator.SetFloat("Vertical", 0f, 0.2f, Time.deltaTime); //if the zombie is being hurt, or is in some action, pause the state if (zombieManager.isPerformingAction) //EP11追加 { zombieManager.animator.SetFloat("Vertical", 0f, 0.2f, Time.deltaTime); return this; } if(!hasPerformAttack && zombieManager.attackCoolDownTimer <= 0) //EP12追加 { if(currentAttack == null) { //GET A NEW ATTACK BASED ON DISTANCE AND ANGLE FROM CURRENT TARGET GetNewAttack(zombieManager); } else { //ATTACK THE CURRENT TARGET AttackTarget(zombieManager); } } if (hasPerformAttack) //EP12追加 { ResetStateFlags(); return purseTargetState; } else { return this; } } //EP12追加 private void GetNewAttack(ZombieManager zombieManager) { for(int i = 0; i < zombieAttackActions.Length; i++) { ZombieAttackAction zombieAttack = zombieAttackActions[i]; //Check for attackdistanced needed to perform the potential attack if(zombieManager.distanceFromCurrentTarget <= zombieAttack.maxmumAttackDistance && zombieManager.distanceFromCurrentTarget >= zombieAttack.minimumAttackDistance) { //Check for attack angles, needed to perform the potential attack if(zombieManager.viewAngleFromCurrentTarget<=zombieAttack.maxmumAttackAngle && zombieManager.viewAngleFromCurrentTarget >= zombieAttack.minimumAttackAngle) { //if the attack passes the distance and angle, add it to the list of attacks we MAY preform right now potentialAttacks.Add(zombieAttack); } } } int randomValue = Random.Range(0, potentialAttacks.Count); //EP12追加 if (potentialAttacks.Count > 0) //EP12追加 { currentAttack = potentialAttacks[randomValue]; potentialAttacks.Clear(); } } //EP12追加 private void AttackTarget(ZombieManager zombieManager) { if (currentAttack != null) { hasPerformAttack = true; zombieManager.attackCoolDownTimer = currentAttack.attackCooldown; zombieManager.ZombieAnimationManager.PlayTargetAttackAnimation(currentAttack.attackanimation); } else { //Debug.Log の派生で警告メッセージをコンソール出力 Debug.LogWarning("WARNING: ZoMbie is attempting to perform an attack,but has no attack"); } } //EP12追加 private void ResetStateFlags() { hasPerformAttack = false; } }
public class ZombieManager : MonoBehaviour //EP8追加 ゾンビオブジェクトにアタッチ { public ZombieAnimationManager ZombieAnimationManager; //EP12追加 //The state this character begins on public IdleState startingState; [Header("Flags")] public bool isPerformingAction; //EP11追加 [Header("Current State")] //The state this character is currently on [SerializeField] private State currentState; [Header("Current Target")] public PlayerManager currentTarget; //EP9追加 public Vector3 targetDirection; //EP12追加 public float distanceFromCurrentTarget; //EP10追加 public float viewAngleFromCurrentTarget; //EP12追加 [Header("Animator")] //EP10追加 public Animator animator; [Header("Navmesh Agent")] public NavMeshAgent zombieNavmeshAgent; //EP10追加 [Header("RigidBody")] public Rigidbody zombieRigidbody; //EP10追加 [Header("Locomotion")] public float rotationSpeed = 5f; //EP10追加 [Header("Attack")] public float attackCoolDownTimer; //EP12追加 public float minimumAttackDistance = 1f; //EP10追加 SET THIS TO THE NUMBER OF YOUR MINIMIM ATTACK DISTANCE,OF THE SHOREST RANGE ATTACK public float maximumAttackDistance = 3.5f; //EP12追加 SET THIS TO THE NUMBER OF YOUR MAXMIM ATTACK DISTANCE,OF THE LONGEST RANGE ATTACK private void Awake() { currentState = startingState; zombieNavmeshAgent = GetComponentInChildren<NavMeshAgent>(); //EP10追加 animator = GetComponent<Animator>(); //EP10追加 zombieRigidbody = GetComponent<Rigidbody>(); //EP10追加 ZombieAnimationManager = GetComponent<ZombieAnimationManager>(); //EP12追加 } private void FixedUpdate() { HandleStateMachine(); } private void Update() //EP10追加 { zombieNavmeshAgent.transform.localPosition = Vector3.zero; if (attackCoolDownTimer > 0) //EP12追加 { attackCoolDownTimer = attackCoolDownTimer - Time.deltaTime; } if (currentTarget != null) //EP10追加 { targetDirection = currentTarget.transform.position - transform.position; //EP12追加 viewAngleFromCurrentTarget = Vector3.SignedAngle(targetDirection, transform.forward, Vector3.up); //EP12追加 //ターゲットと自身の距離を計算 distanceFromCurrentTarget = Vector3.Distance(currentTarget.transform.position, transform.position); } } private void HandleStateMachine() { State nextState; if (currentState != null) { nextState = currentState.Tick(this); if (nextState != null) { currentState = nextState; } } } }
using System.Collections; using System.Collections.Generic; using UnityEngine; public class ZombieAnimationManager : MonoBehaviour //EP12追加 ゾンビにアタッチ { ZombieManager zombieManager; private void Awake() { zombieManager = GetComponent<ZombieManager>(); } public void PlayTargetAttackAnimation(string attackAnimation) { //appapplyRootMotion = trueで座標移動が含まれるアニメーションをそのまま再生する zombieManager.animator.applyRootMotion = true; zombieManager.isPerformingAction = true; zombieManager.animator.CrossFade(attackAnimation, 0.2f); } }
public class PurseTargetState : State //EP8追加 ゾンビ直下のstatesオブジェクトにアタッチ { AttackState attackState; //EP10追加 private void Awake() { attackState = GetComponent<AttackState>(); //EP10追加 } public override State Tick(ZombieManager zombieManager) { //if the zombie is being hurt, or is in some action, pause the state if (zombieManager.isPerformingAction) //EP11追加 { zombieManager.animator.SetFloat("Vertical", 0f, 0.2f, Time.deltaTime); return this; } Debug.Log("Running pursue target state"); MoveTowardsCurrentTarget(zombieManager); //EP10追加 RotateTowardsTarget(zombieManager); //EP10追加 if (zombieManager.distanceFromCurrentTarget <= zombieManager.maximumAttackDistance) //EP12追加 { zombieManager.zombieNavmeshAgent.enabled = false; //EP12追加 return attackState; } else { return this; } } private void MoveTowardsCurrentTarget(ZombieManager zombieManager) //EP10追加 { //walkアニメーションに切り替え zombieManager.animator.SetFloat("Vertical", 1f, 0.2f, Time.deltaTime); } private void RotateTowardsTarget(ZombieManager zombieManager) //EP10追加 { zombieManager.zombieNavmeshAgent.enabled = true; //移動先に移動開始 zombieManager.zombieNavmeshAgent.SetDestination(zombieManager.currentTarget.transform.position); //ターゲット方向に回転 zombieManager.transform.rotation = Quaternion.Slerp(zombieManager.transform.rotation, zombieManager.zombieNavmeshAgent.transform.rotation, zombieManager.rotationSpeed / Time.deltaTime); } }
プレイ動画はこんな感じです。
youtu.be
今回は以上です。