[내배캠] 유니티 숙련 팀프로젝트#2

Sungchan Ahn(안성찬)·2024년 11월 4일

내일배움캠프

목록 보기
54/104

Creature 상태 관리

Creature의 상태를 갖고 상태의 전환을 담당하는 StateContext

public class StateContext
{
    public IState currentState;

    public IdleState idle;
    public WanderState wander;
    public AlertedState alerted;
    public ChaseState chase;
    public AttackState attack;
    public EscapeState escape;
    public DeadState dead;

    public StateContext(Creature _creature)
    {
        idle = new IdleState(_creature);
        wander = new WanderState(_creature);
        alerted = new AlertedState(_creature);
        chase = new ChaseState(_creature);
        attack = new AttackState(_creature);
        escape = new EscapeState(_creature);
        dead = new DeadState(_creature);
    }

    public void Transition(IState newState)
    {
        if (currentState == newState) return;
        if (currentState != null) currentState.OnStateExit();
        currentState = newState;
        currentState.OnStateEnter();
    }
}

상태 예시(WanderState)

해당 상태에서의 행동을 담당.
WanderState : 랜덤한 곳으로 이동하는 상태, 활동 영역 밖에 있을 경우 처음 스폰된 포지션으로 이동

using UnityEngine;
using UnityEngine.AI;

public interface IState
{
    public void OnStateEnter();
    public void OnStateUpdate();
    public void OnStateExit();
}

public class WanderState : IState
{
    private Creature creature;

    public WanderState(Creature _creature)
    {
        creature = _creature;
    }

    public void OnStateEnter()
    {
        creature.agent.isStopped = false;

        if (IsOutArea())
        {
            creature.animator.SetBool("Walk", false);
            creature.animator.SetBool("Run", true);
            creature.agent.speed = creature.data.runSpeed;
            creature.agent.SetDestination(creature.spawnPoint.position);
        }
        else
        {
            creature.animator.SetBool("Walk", true);
            creature.animator.SetBool("Run", false);
            creature.agent.speed = creature.data.walkSpeed;
            creature.agent.SetDestination(GetWanderLocation());
        }

        //creature.animator.speed = creature.agent.speed / creature.data.walkSpeed;
    }

    public void OnStateUpdate()
    {
        
    }

    public void OnStateExit()
    {
        creature.animator.SetBool("Walk", false);
        creature.animator.SetBool("Run", false);
    }

    private bool IsOutArea()
    {
        return Vector3.Distance(creature.transform.position, creature.spawnPoint.position) > creature.data.activityAreaRadius;
    }

    private Vector3 GetWanderLocation()
    {
        NavMeshHit hit;

        NavMesh.SamplePosition(creature.transform.position + (Random.onUnitSphere * Random.Range(creature.data.minWanderDistance, creature.data.maxWanderDistance)),
                                out hit, creature.data.maxWanderDistance, NavMesh.AllAreas);

        int i = 0;

        while (Vector3.Distance(creature.transform.position, hit.position) < creature.data.detectDistance)
        {
            NavMesh.SamplePosition(creature.transform.position + (Random.onUnitSphere * Random.Range(creature.data.minWanderDistance, creature.data.maxWanderDistance)),
                                out hit, creature.data.maxWanderDistance, NavMesh.AllAreas);
            i++;
            if (i == 30) break;
        }

        return hit.position;
    }
}

동물 상태 변경 예시(PassiveAnimal)

Aggressive, Passive, Nonattack 각각 변경되는 상태가 다르다.
Passive의 경우 아래와 같은 상태를 가진다.

  • 기본적인 상태인 Idle, Wander 상태
  • 탐지 범위 내에서 플레이어가 시야 안에 있을 때 공격 당하지 않았다면 Alerted 상태
  • 공격 당한 경우,
    • 기본적으로 Chase 상태
    • 플레이어가 공격 범위, 시야 내에 있으면 Attack 상태
    • 특정체력(여기선 5) 이하면 Escape 상태
      위와 같은 조건에 따라 상태를 변경해준다.
public class PassiveAnimal : Creature
{
    protected override void Awake()
    {
        base.Awake();
    }

    protected override void Start()
    {
        stateContext.Transition(stateContext.wander);
    }

    protected override void Update()
    {
        base.Update();
        if (creatureState == CreatureState.Dead) return;

        if (playerDistance <= data.detectDistance)
        {
            if (!data.isAttacked && IsPlayerInFieldOfView())
            {
                isAlerted = true;
                ChangeState(CreatureState.Alerted);
            }
            else if (data.isAttacked)
            {
                isChasing = true;
                if (data.health < 5)
                {
                    isEscaping = true;
                    isChasing = false;
                    ChangeState(CreatureState.Escape);
                }
                else if (playerDistance < data.attackDistance && IsPlayerInFieldOfView())
                {
                    ChangeState(CreatureState.Attack);
                }
                else
                {
                    NavMeshPath path = new NavMeshPath();
                    if (agent.CalculatePath(PlayerManager.Instance.Player.transform.position, path))
                    {
                        ChangeState(CreatureState.Chase);
                    }
                }
            }
        }
        else
        {
            isAlerted = false;
            isChasing = false;
            isEscaping = false;
            data.isAttacked = false;
            if (creatureState == CreatureState.Wander && agent.remainingDistance < 0.1f)
            {
                ChangeState(CreatureState.Idle);
                wanderRate = Random.Range(data.minWanderWaitTime, data.maxWanderWaitTime);
            }
            else if (Time.time - lastWanderTime > wanderRate)
            {
                lastWanderTime = Time.time;
                ChangeState(CreatureState.Wander);
            }
        }

        stateContext.currentState.OnStateUpdate();
    }

    private void ChangeState(CreatureState _creatureState)
    {
        creatureState = _creatureState;
        switch (creatureState)
        {
            case CreatureState.Idle:
                stateContext.Transition(stateContext.idle);
                break;
            case CreatureState.Wander:
                stateContext.Transition(stateContext.wander);
                break;
            case CreatureState.Alerted:
                stateContext.Transition(stateContext.alerted);
                break;
            case CreatureState.Chase:
                stateContext.Transition(stateContext.chase);
                break;
            case CreatureState.Attack:
                stateContext.Transition(stateContext.attack);
                break;
            case CreatureState.Escape:
                stateContext.Transition(stateContext.escape);
                break;
        }
    }
}

---

public class Creature : MonoBehaviour, IDamagable
{
    public CreatureData data;
    protected int currentHealth;

    protected StateContext stateContext;
    protected float playerDistance;

    public Transform spawnPoint;
    public CreatureState creatureState;
    public NavMeshAgent agent;
    public Animator animator;
    protected SkinnedMeshRenderer[] meshRenderers;

    protected float wanderRate;
    protected float lastWanderTime;

    public bool isAlerted;
    public bool isChasing;
    public bool isEscaping;

    protected virtual void Awake()
    {
        spawnPoint = CreatureSpawnManager.Instance.pool.Pools[data.id].spawnPoint;
        agent = GetComponent<NavMeshAgent>();
        animator = GetComponent<Animator>();
        meshRenderers = GetComponentsInChildren<SkinnedMeshRenderer>();
        stateContext = new StateContext(this);
        isAlerted = false;
        isChasing = false;
        isEscaping = false;
        currentHealth = data.health;
    }
    (생략)    
}
profile
게임 개발 기록

0개의 댓글