[Unity]一人称FPSゲーム-3Living Entity-2


1. Ememy


1) Unity Object


  • Animator
    ControllerとAvatarはSimpleApoicase Assetを使用しています.(Asset Storeにある)
  • Skinned Mesh Renderer
    MaterialsもSimpleApoicase Assetを使用しています.
  • Enemy Script
  • Nav Mesh Agent
    (AgentのサイズはNavigationタブで調整することもできます)
  • Capsule Collider
  • 2) C# Code


    敵には偵察・追跡・攻撃の3つの状態がある.
    ステータスを0.25秒おきに更新するにはcoluceneを使用します.
  • 偵察:周期ごとにランダムポイントを選択して位置に移動します.移動不可領域の場合は、次のサイクルを待って場所を再選択します.
  • 追跡:プレイヤーがSight Rangeに入ると実行する.プレイヤーが再び視野から離れても、追いかけ続けます.
  • (Bool SetChaseを使用)
  • 攻撃:プレイヤーが攻撃範囲に入ったときに実行します.Time Between Attackと同じ間隔で攻撃する.
  • using UnityEngine.AI;
    using UnityEngine;
    using System.Collections;
    
    
    public class EnemyAI : LivingEntity
    {
        public NavMeshAgent agent;
    
        public Transform playerPos;
        public Player player;
    
        public float damage = 1; //좀비가 주는 데미지
    
        public LayerMask whatIsGround, whatIsPlayer, whatIsObstacle;
    
        GameObject gameManager;
    
        // 모델링 넣기 전 테스트 용으로 넣은 코드
        Material skinMaterial; //적의 메테리얼
        Color OriginColor;
       
    
        float refreshRate = 0.25f; //path 업데이트 갱신 간격
    
        //Patroling
        public Vector3 walkPoint; //위치
        bool walkPointSet = false; //해당 위치가 이미 세팅 되어있는지 확인
        public float walkPointRange; //범위
        
        //Chasing
        bool setChase = false;
    
    
        //Attacking
        //bool setAttack;
        public float timeBetweenAttacks;
        bool alreadyAttacked;
    
        //States
        public float sightRange, attackRange;
        public bool playerInSightRange, playerInAttackRange;
    
    
        //충돌범위
        float myCollisionRadius; //적의 충돌범위
        float targetCollisionRadius;// 타겟 충돌범위
    
    
        private void Awake()
        {
        
        }
    
    
       
        protected override void Start()
        {
            base.Start();
    
    
    
            gameManager = GameObject.Find("GameManager");
    
            dead = false;
            //플레이어 위치 할당
            playerPos = GameObject.Find("Player(Clone)").transform;
            player.dead = false;
            //nav mesh agent 할당
            agent = GetComponent<NavMeshAgent>();
    
            //적과 플레이어 겹침 방지 위해
            myCollisionRadius = GetComponent<CapsuleCollider>().radius;
            targetCollisionRadius = playerPos.GetComponent<CapsuleCollider>().radius;
    
    
            //상태 변화 시 색상 변화 위해
            skinMaterial = GetComponent<Renderer>().material;
            OriginColor = skinMaterial.color;
            
            //Player의 초기 체력 값이 자꾸 스크립트로 리셋이 안되어서 Enemy 쪽에서 강제로 세팅 -> 추후 수정 필요
            player.setHealth(player.startingHealth);
            StartCoroutine(UpdatePath());
        }
    
        private void Update()
        {
            if (gameManager.GetComponent<GameManager>().isClear)
            {
                this.gameObject.SetActive(false);
            }
    
        }
    
    
    
        IEnumerator UpdatePath()
        {
            Debug.Log("enemy updatePath");
            Debug.Log(player.getHealth());
            while (playerPos!=null && !player.GetComponent<Player>().dead)
            {
                //Debug.Log(!player.GetComponent<Player>().dead);
                //Debug.Log(!dead);
                if (!dead)
    
                {
                    //Debug.Log("Player exist");
                    //시야와 공격범위 확인
                    playerInSightRange = Physics.CheckSphere(transform.position, sightRange, whatIsPlayer);
                    //Debug.Log(playerInSightRange);
                    playerInAttackRange = Physics.CheckSphere(transform.position, attackRange, whatIsPlayer);
                    //Debug.Log(playerInAttackRange);
    
                    if (!playerInSightRange && !playerInAttackRange && !setChase)
                        Patroling();
    
                    if ((playerInSightRange && !playerInAttackRange) || setChase)
                        ChasePlayer();
    
                    if ((playerInAttackRange && playerInSightRange))
                    {
                        setChase = false;
                        AttackPlayer();
                        
                    }
    
                    if (gameManager.GetComponent<GameManager>().isClear)
                    {
                        this.gameObject.SetActive(false);
                    }
                }
    
                yield return new WaitForSeconds(refreshRate);
    
            }
            
    
        }
    
        private void Patroling()
        {
            skinMaterial.color = OriginColor;
           //Debug.Log("Patroling");
            if (!walkPointSet)
                SearchWalkPoint();
    
            if (walkPointSet)
            {
                agent.SetDestination(walkPoint);
    
                Vector3 distanceToWalkPoint = transform.position - walkPoint;
    
                //Walkpoint에 도달 (거리 1 미만일 때)
                if (distanceToWalkPoint.magnitude < 0.1f)
                    walkPointSet = false;
            }
        }
    
        private void ChasePlayer()
        {
    
            setChase = true;
            if (setChase)
            {
            //chasing할 때 색 바뀌도록 -> 모델링 입히기 전 테스트 위해
                skinMaterial.color = Color.yellow;
    
                 //원래는 Collider의 Is Trigger를 체크하고 콜라이더 범위만큼 빼고 그 앞까지 오는 걸로 했었다
                 //그런데 계속 오류나서 Is Trigger를 해제하고 플레이어 위치로 찾아오도록 변경
                //Vector3 dirToTarget = (playerPos.position - transform.position).normalized;
                //Vector3 targetPosition = playerPos.position - dirToTarget * (myCollisionRadius + targetCollisionRadius);
    
                walkPoint = new Vector3(playerPos.position.x, transform.position.y, playerPos.position.z);
                if (agent.SetDestination(walkPoint))
                {
                    Debug.Log("Follow Player");
                }
            }
    
    
        }
    
        private void AttackPlayer()
        {
            skinMaterial.color = Color.red;
            Debug.Log("Attack");
            setChase = false;
    
    
            //플레이어 계속 추적
            //agent.SetDestination(playerPos.position);
            Vector3 dirToTarget = (playerPos.position - transform.position).normalized;
            Vector3 targetPosition = playerPos.position - dirToTarget * (myCollisionRadius + targetCollisionRadius);
            agent.SetDestination(targetPosition);
    
            transform.LookAt(playerPos);
    
    
            //Attack
            if (!alreadyAttacked) {
    
                Debug.Log("Attacking....");
                //Attack Code 여기에
    
                //Attack();
    
                //IDamageble damagebleObject = player.GetComponent<IDamageble>();
    
                   
    
                if (player.dead != true)
                {
                    //Attack();
                    //Debug.Log("Attacking....");
                    Debug.Log(player.getHealth());
                    player.TakeHit(damage);
                    Debug.Log(player.getHealth());
    
                }
    
    
                alreadyAttacked = true;
    
                Invoke(nameof(ResetAttack), timeBetweenAttacks);
            }
    
        }
    
       
    
        //랜덤으로 순찰할 위치 선정
        private void SearchWalkPoint()
        {
            float randomZ = Random.Range(-walkPointRange, walkPointRange);
            float randomX = Random.Range(-walkPointRange, walkPointRange);
    
    
            
            //랜덤하게 패트롤할 위지 범위 안에서 선청
            walkPoint = new Vector3(transform.position.x + randomX, transform.position.y, transform.position.z + randomZ);
        
            //맵밖으로 안나갔는지 확인
            if (Physics.Raycast(walkPoint, -transform.up, 2f, whatIsGround))
            {
                Vector3 tmpPoint = new Vector3(walkPoint.x, 20, walkPoint.z);
                if (!Physics.Raycast(tmpPoint, -transform.up, Mathf.Infinity, whatIsObstacle))
                {
                    walkPointSet = true;
                //    return;
                }
              
            }
            else
            {
                Debug.Log("Can't find");
                //SearchWalkPoint();
            }
    
           
            
    
        }
    
        protected override void Die()
        {
            base.Die();
            gameManager.GetComponent<GameManager>().IncreaseKillCount();
            Destroy(this.gameObject);
        }
    
    
        private void ResetAttack()
        {
            alreadyAttacked = false;
        }
    
    
    //Sight Range, AttackRange를 EDITOR에서 확인하기 위해
    #if UNITY_EDITOR
        private void OnDrawGizmosSelected()
        {
            Gizmos.color = Color.red;
            Gizmos.DrawWireSphere(transform.position, attackRange);
            Gizmos.color = Color.yellow;
            Gizmos.DrawWireSphere(transform.position, sightRange);
    
        }
    #endif
    }
    

    2.発生した問題


    1)Enemy Animator接続


    できあがったAssetをどうやって貼ればいいのか分からないので、ちょっと苦労しました.
    最初は、Assetのゾンビプリセット要素をコピーして予め作成したEnemy Prefabに貼り付けましたが、アニメーションコントローラにマッピングされていないかどうか分かりません.
    だからAssetのゾンビプリセットをそのまま持ってきて、そこにEnemyスクリプトとNav Mesh Agentを加えます.
    そしてそのままMesh Renderを使用したのは素材を着用していないのでSkind Mesh Renderを使用しました
  • Skinned Mesh Renderer
    Unityでは、Skind Mesh Render構成部品を使用してBoneアニメーションをレンダリングします.これらのBoneアニメーションは、事前に定義されたアニメーションシーケンスに基づいてメッシュ形状をレンダリングします.これは、キャラクタ(回転軸機能を持つマシンとは異なる)だけでなく、他のベンドジョイントのオブジェクトにも適用できる非常に有用な方法です.
  • 2)Enemy偵察と交換問題


    =敵が偵察や迎撃を行わない問題
    これはコーラマシンとnavmesh agentの問題です.
    敵のコーラ機と地上コーラ機が衝突し、敵は地面に貼られず、navmesh agentはこのルートに入ることができなかった.
    これは,地図上のコーラ初期化器の初期化時に0.5の高さのスクリプトを記述したためである.
    また、CollayderのIs Triggerをチェックした場合、プレイヤーと敵の直径を抜き、敵をプレイヤーの位置に移動させるというエラーも発生し続け、Is Triggerを解除し、敵がプレイヤーの位置に移動することを修正しました.

    3)プレイヤーが攻撃するとすぐに死亡する


    実はこれはEnemy側の問題ではなく、Playerスクリプトの問題ですが、Enemyが一時的に修正したのでここに書きます.
    Attack関数を呼び出すと、プレイヤーがヒットしてすぐに死亡するので、ログを撮りましたが、プレイヤーが死亡してから再生成したとき、初期体力値は初期化されませんでした.
    ただしプレイヤーと敵がprefabになってしまったためか同期が取れず、一時的な方法でEnemyを生成した際に、プレイヤーの体力を強制リセットした.