6-5. 15조 MonsterController 리팩토링, Item, ItemTable

keubung·2024년 12월 2일

1. MonsterController 리팩토링

불필요한 변수 및 코드 삭제

  • 아직 더 깔끔하게 정리도 하고 싶고 컨트롤러니까 안에 다른 곳으로 좀 나눠야 될 것 같고...

    using Common.Timer;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.AI;
    
    public class MonsterController : MonoBehaviour
    {
        [Header("Stats")]
        [SerializeField]private float walkSpeed;
        [SerializeField] private float runSpeed;
    
        [Header("AI")]
        [SerializeField] private float detectDistance;
        [SerializeField] private float safeDistance;
        [SerializeField] private float lookAtPlayerDistance;
        private AIStateType aiState;
    
        [Header("Wandering")]
        [SerializeField] private float minWanderDistance;
        [SerializeField] private float maxWanderDistance;
        private int wanderingCount;
        [SerializeField] private int minWanderingCount;
        [SerializeField] private int maxWanderingCount;
    
        [Header("Combat")]
        [SerializeField] private float attackDistance;
        private const float fieldOfView = 120f;
    
        private float playerDistance;
        private bool isHiding;
    
        private Transform playerTransform;
        private NavMeshAgent agent;
    
        private Monster monster;
        private Coroutine timer;
        private bool canWander = true;
    
        void Start()
        {
            agent = GetComponent<NavMeshAgent>();
            monster = GetComponent<Monster>();
            playerTransform = Player.Instance.transform;
            SetState(AIStateType.Idle);
            ResetWanderingCount();
        }
    
        void Update()
        {
            playerDistance = Vector3.Distance(transform.position, playerTransform.position);
    
            switch (aiState)
            {
                case AIStateType.Idle:
                case AIStateType.Wandering:
                    PassiveUpdate();
                    break;
                case AIStateType.Attacking:
                    AttackingUpdate();
                    break;
                case AIStateType.Fleeing:
                    FleeingUpdate();
                    break;
            }
    
            if (!monster.RendererActive)
            {
                LookingAtPlayerUpdate();
            }
        }
    
        public void SetState(AIStateType state)
        {
            if (aiState == state)
                return;
    
            aiState = state;
            switch (aiState)
            {
                case AIStateType.Wandering:
                    agent.speed = walkSpeed;
                    break;
                case AIStateType.Attacking:
                case AIStateType.Fleeing:
                    agent.speed = runSpeed;
                    break;
            }
        }
    
        void PassiveUpdate()
        {
            if (!monster.RendererActive)
            {
                return;
            }
    
            if (playerDistance < detectDistance && !isHiding) // 플레이어가 감지 범위 안에 있고 숨지 않은 경우
            {
                SetState(AIStateType.Attacking);
            }
            else if ((isHiding || playerDistance > detectDistance) && aiState != AIStateType.Wandering) // 플레이어를 놓친 경우 Wandering으로 전환
            {
                SetState(AIStateType.Wandering);
            }
    
            if (AIStateType.Wandering == aiState && agent.remainingDistance < 0.1f && canWander)
            {
                canWander = false;
                WanderToNewLocation();  // 새 위치로 이동
    
                // 이동 횟수를 모두 소진하면 투명화 상태로 전환
                if (wanderingCount <= 0)
                    ResetCycle();
            }
        }
    
        private void LookingAtPlayerUpdate()
        {
            if (playerDistance > lookAtPlayerDistance)
            {
                SetState(AIStateType.Wandering);
            }
            else
            {
                Vector3 directionToPlayer = (playerTransform.position - transform.position).normalized;
                Quaternion lookRotation = Quaternion.LookRotation(directionToPlayer);
                transform.rotation = Quaternion.Slerp(transform.rotation, lookRotation, Time.deltaTime * 5f);
            }
        }
    
        void AttackingUpdate()
        {
            if (playerDistance < detectDistance)
            {
                NavMeshPath path = new NavMeshPath();
                if (agent.CalculatePath(playerTransform.position, path))
                {
                    agent.SetDestination(playerTransform.position);
                }
            }
            else
            {
                agent.SetDestination(transform.position);
                SetState(AIStateType.Wandering);
            }
        }
    
        void FleeingUpdate()
        {
            if (agent.remainingDistance < 0.1f)
            {
                agent.SetDestination(GetFleeLocation());
            }
            else
            {
                SetState(AIStateType.Wandering);
            }
        }
    
        void ResetWanderingCount()
        {
            wanderingCount = Random.Range(minWanderingCount, maxWanderingCount);
        }
    
        void ResetCycle()
        {
            monster.Invisible();
            StopCoroutine(timer);
            timer = null;
            ResetWanderingCount();
            canWander = true;
        }
    
        void WanderToNewLocation()
        {
            timer = StartCoroutine(CoTimer.Start(0.5f, () =>
            {
                agent.SetDestination(GetWanderLocation());
                wanderingCount--;
                canWander = true;
            }));
    }
    bool IsPlayerInFieldOfView()
    {
        Vector3 directionToPlayer = playerTransform.position - transform.position;
        float angle = Vector3.Angle(transform.forward, directionToPlayer);
        return angle < fieldOfView * 0.5f;
    }
    
    Vector3 GetFleeLocation()
    {
        NavMeshHit hit;
    
        int i = 0;
        do
        {
            NavMesh.SamplePosition(transform.position + (Random.onUnitSphere * safeDistance), out hit, maxWanderDistance, NavMesh.AllAreas);
            i++;
            if (i == 30)
                break;
        } while (GetDestinationAngle(hit.position) > 90 || playerDistance < safeDistance);
    
        return hit.position;
    }
    
    Vector3 GetWanderLocation()
    {
        NavMeshHit hit;
    
        int i = 0;
        do
        {
            NavMesh.SamplePosition(transform.position + (Random.onUnitSphere * Random.Range(minWanderDistance, maxWanderDistance)), out hit, maxWanderDistance, NavMesh.AllAreas);
            i++;
            if (i == 30)
                break;
        } while (Vector3.Distance(transform.position, hit.position) < detectDistance);
    
        return hit.position;
    }
    
    float GetDestinationAngle(Vector3 targetPos)
    {
        return Vector3.Angle(transform.position - playerTransform.position, transform.position + targetPos);
    }

    }

2. Item (수정될 수 있음)

  1. HideableObject - 숨을 수 있는 오브젝트(숨어있는지 확인 및 위치값)
      public class HideableObject : InteractableItem
    {
        private bool isHidden = false;
        private Transform hidingSpot;
    
        public override GameObject Interact()
        {
            if (isHidden)
            {
                isHidden = false;
            }
            else
            {
                isHidden = true;
                hidingSpot = transform;
            }
    
            return gameObject;
        }
    }
  2. DestroyableObject - 파괴할 수 있는 오브젝트(파괴되었는지 확인 및 위치값)
      public class DestroyableObject : InteractableItem
    {
        private bool isDestroyed = false;
        private Transform destroySpot;
    }

3. ItemTable (임시)

ID(key)로 데이터를 읽어서 사용
1. ItemTable

2. ItemTextTable

3. Enum

  • ItemType
    - None(들 수 없는 오브젝트)
    - OneHanded(한 손으로 들 수 있는 오브젝트)
    - TwoHanded(두 손으로 들 수 있는 오브젝트)
profile
김나영(Unity_6기)

0개의 댓글