【バイオハザード風のゲームを作ってみよう。その10】
今回も下記やっていきましょう。
youtu.be
今回はNavMeshを使用してプレイヤーをゾンビが追いかけさせるようにしていきます。
NavMesh概要は下記参照。
www.midnightunity.net
・ゾンビにNavMeshAgentコンポーネントとRigidBodyコンポーネントをアタッチします。なお、前回の【その9】内でRayCastHit
使用してゾンビはプレイヤーを探知していますが、RigidBodyはひとつのオブジェクトと見なされるようで、ゾンビ自身のRigidBodyが干渉してプレイヤー探知が出来ません。そこで、ゾンビレイヤーを追加しゾンビレイヤーを無視するようにします。また、下記スクリプトを変更します。
public class IdleState : State //EP8追加 ゾンビ直下のstatesオブジェクトにアタッチ { (省略) //This setting determins where our linecast starts on the Y Axis of the character(used for line of sight) [Header("Line of Sight Detection")] [SerializeField] float characterEyeLevel = 1.8f; //EP10追加 [SerializeField] LayerMask ignoreForLineOfSightDetection; //EP10追加 (省略) private void FindATargetViaLineOfSight(ZombieManager zombieManager) { //We are searching ALL colliders on the layer of PLAYER within a certain radius //Physics.OverlapSphere戻り値: Collider[] 球体の内部や触れたすべてのコライダーの配列を取得します. Collider[] colliders = Physics.OverlapSphere(transform.position, detectionRadius, detectionLayer); // Debug.Log(colliders.Length); Debug.Log("we are checking for colliders"); //For every collider that we find, that is on the same layer pf the player, we try and serch it for PlayerManager script for(int i = 0; i < colliders.Length; i++) { PlayerManager player = colliders[i].transform.GetComponent<PlayerManager>(); //If rhe playerManager is detected, we then check for line of sight if (player != null) { Debug.Log("we have found the player collider"); //The target mudt be infront of us Vector3 targetDirection = transform.position - player.transform.position; float viewableAngle = Vector3.Angle(targetDirection, transform.forward); // Debug.Log("VIEWABLE ANGLE=" + viewableAngle); if(viewableAngle>minimumDetectionRadiusAngle && viewableAngle < maximumDetectionRadiusAngle) { Debug.Log("we have passed the field of view check"); RaycastHit hit; Vector3 playerStartPoint = new Vector3(player.transform.position.x, characterEyeLevel, player.transform.position.z); Vector3 zombieStartPoint = new Vector3(transform.position.x, characterEyeLevel, transform.position.z); Debug.DrawLine(playerStartPoint, zombieStartPoint,Color.yellow); //CHECK ONE LAST TIME OUR //Linecast:始点と終点を設定してそこに線を張り、コライダーがヒットした場合 true を返します. if (Physics.Linecast(playerStartPoint,zombieStartPoint,out hit, ignoreForLineOfSightDetection)) //EP10変更 { Debug.Log("There is something iin the way"); //Cannot find the target, there is an object in the way } else { Debug.Log("We have a target, switching states"); zombieManager.currentTarget = player; } } } } } }
・ゾンビがプレイヤーを追いかけるようにします。そして、両者が一定距離となるとゾンビがIdle状態なるようにします。
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; //EP10追加 public class ZombieManager : MonoBehaviour //EP8追加 ゾンビオブジェクトにアタッチ { //The state this character begins on public IdleState startingState; [Header("Current State")] //The state this character is currently on [SerializeField] private State currentState; [Header("Current Target")] public PlayerManager currentTarget; //EP9追加 public float distanceFromCurrentTarget; //EP10追加 [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 minimumAttackDistance = 1f; //EP10追加 private void Awake() { currentState = startingState; zombieNavmeshAgent = GetComponentInChildren<NavMeshAgent>(); //EP10追加 animator = GetComponent<Animator>(); //EP10追加 zombieRigidbody = GetComponent<Rigidbody>(); //EP10追加 } private void FixedUpdate() { HandleStateMachine(); } private void Update() //EP10追加 { zombieNavmeshAgent.transform.localPosition = Vector3.zero; if (currentTarget != null) //EP10追加 { //ターゲットと自身の距離を計算 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 PurseTargetState : State //EP8追加 ゾンビ直下のstatesオブジェクトにアタッチ { AttackState attackState; //EP10追加 private void Awake() { attackState = GetComponent<AttackState>(); //EP10追加 } public override State Tick(ZombieManager zombieManager) { //logic to follow current target goes here Debug.Log("Running pursue target state"); MoveTowardsCurrentTarget(zombieManager); //EP10追加 RotateTowardsTarget(zombieManager); //EP10追加 //ゾンビとプレイヤーの距離が設定値(zombieManager.minimumAttackDistance)よりも近い場合 if (zombieManager.distanceFromCurrentTarget <= zombieManager.minimumAttackDistance) //EP10追加 { 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); } }
using System.Collections; using System.Collections.Generic; using UnityEngine; public class AttackState : State //EP10追加 ゾンビ直下のstatesオブジェクトにアタッチ { public override State Tick(ZombieManager zombieManager) { Debug.Log("ATTACK"); //Idleアニメーションに切り替え zombieManager.animator.SetFloat("Vertical", 0f, 0.2f, Time.deltaTime); return this; } }
今回の結果がこんな感じです。
youtu.be
今回は以上です。