프로젝트/프로젝트 A

# 020 Golem (1) (with. State)

효따 2025. 8. 25. 23:52

안녕하세요.😀

 

오늘은 대중교통을 이용하면서  '어떤 작업을 진행해 볼까?' 고민하다가,
정글 몬스터가 갑자기 떠올랐습니다.

 

우선 골램을 빠르게 만들어 보려고 했는데,


테스트 과정이 생각보다 길어져 시간이 조금 걸렸습니다.🤣

 

 


 

먼저 새로 생성한 GolemAI의 전체 스크립트입니다.

using UnityEngine;
using UnityEngine.AI;

public class GolemAI : MonoBehaviour
{
    float hp = 300f;    // 임시
    [SerializeField] private float attackCD = 3f;
    [SerializeField] private float attackRange = 1f;
    [SerializeField] private float aggroRange = 4f;
    [SerializeField] private GameObject player;
    [SerializeField] private NavMeshAgent agent;
    [SerializeField] private Animator animator;

    private Vector3 spawnPosition;
    float timePassed;
    float newDestinationCD = 0.5f;
 
    enum GolemState { Idle, Chase, Attack, Return }
    private GolemState currentState = GolemState.Idle;

    void Start()
    {
        spawnPosition = transform.position;
    }

    void Update()
    {
        float distanceToPlayer = Vector3.Distance(player.transform.position, transform.position);

        if (distanceToPlayer <= attackRange)
            currentState = GolemState.Attack;
        else if (distanceToPlayer <= aggroRange)
            currentState = GolemState.Chase;
        else if (Vector3.Distance(transform.position, spawnPosition) > 0.1f)
            currentState = GolemState.Return;
        else
            currentState = GolemState.Idle;

        animator.SetFloat("Speed", agent.velocity.magnitude / agent.speed);

        switch (currentState)
        {
            case GolemState.Attack:
                if (timePassed >= attackCD)
                {
                    animator.SetTrigger("IsAttack");
                    timePassed = 0f;
                }
                agent.SetDestination(transform.position);
                transform.LookAt(player.transform);
                break;

            case GolemState.Chase:
                if (newDestinationCD <= 0)
                {
                    newDestinationCD = 0.5f;
                    agent.SetDestination(player.transform.position);
                }
                transform.LookAt(player.transform);
                break;

            case GolemState.Return:
                if (newDestinationCD <= 0)
                {
                    newDestinationCD = 0.5f;
                    agent.SetDestination(spawnPosition);
                }
                transform.LookAt(spawnPosition);
                break;

            case GolemState.Idle:
                agent.SetDestination(transform.position);
                break;
        }

        timePassed += Time.deltaTime;
        newDestinationCD -= Time.deltaTime;
    }

    public void TakeDamage(float damage)
    {
        hp -= damage;
        animator.SetTrigger("IsDamage");
        if (hp <= 0)
        {
            animator.SetTrigger("IsDie");
            Destroy(gameObject);
        }
    }

    private void OnDrawGizmos()
    {
        Gizmos.color = Color.red;
        Gizmos.DrawWireSphere(transform.position, attackRange);
        Gizmos.color = Color.yellow;
        Gizmos.DrawWireSphere(transform.position, aggroRange);
    }
}

 


 

1. 변수 선언부

    float hp = 300f;    // 임시
    [SerializeField] private float attackCD = 3f;
    [SerializeField] private float attackRange = 1f;
    [SerializeField] private float aggroRange = 4f;
    [SerializeField] private GameObject player;
    [SerializeField] private NavMeshAgent agent;
    [SerializeField] private Animator animator;

    private Vector3 spawnPosition;
    float timePassed;
    float newDestinationCD = 0.5f;

 

 

hp : 몬스터의 체력입니다. 공격을 받으면 감소하며, 0이 되면 사망합니다.

attackCD : 공격 쿨다운(초)입니다. 한 번 공격 후 다음 공격까지 걸리는 시간이에요.

attackRange : 공격이 가능한 범위입니다. 플레이어가 이 범위 안에 들어오면 공격합니다.

aggroRange : 몬스터가 플레이어를 인식하고 추적을 시작하는 범위입니다.

player : 몬스터가 추적할 대상, 일반적으로 플레이어 캐릭터 오브젝트입니다.

agent : Unity의 NavMeshAgent로, 몬스터가 길을 찾아 이동할 수 있게 합니다.

animator : 몬스터 애니메이션을 제어하는 Animator 컴포넌트입니다.

spawnPosition : 몬스터가 생성된 원래 위치입니다. 플레이어를 따라갔다가 멀리 벗어나면 이 위치로 돌아옵니다.

timePassed : 공격 쿨다운 계산에 사용하는 시간 누적 변수입니다.

newDestinationCD : NavMeshAgent의 목적지를 자주 갱신하지 않기 위해 사용하는 짧은 쿨다운입니다.

 


 

 

2. AI 상태 정의

    enum GolemState { Idle, Chase, Attack, Return }
    private GolemState currentState = GolemState.Idle;

 

몬스터의 행동을 상태(State)로 나누어 관리합니다.

 

Idle : 가만히 있는 상태

Chase : 플레이어를 발견하고 추적하는 상태

Attack : 공격 범위 안에 들어와 공격하는 상태

Return : 플레이어가 범위 밖으로 나가거나 Idle 상태로 돌아갈 때, 원래 위치로 돌아가는 상태

 


 

3. Start 함수

    void Start()
    {
        spawnPosition = transform.position;
    }

 

 

몬스터가 처음 생성될 때 위치를 저장합니다.

Return 상태에서 원래 위치로 돌아갈 때 필요합니다.

 

 


 

4. Update 함수: 매 프레임마다 AI 상태 결정 및 행동

    void Update()
    {
        float distanceToPlayer = Vector3.Distance(player.transform.position, transform.position);

 

 

매 프레임마다 플레이어와 몬스터 사이의 거리를 계산합니다.

이 거리를 기준으로 상태를 결정합니다.


 

4-1. 상태 결정

        if (distanceToPlayer <= attackRange)
            currentState = GolemState.Attack;
        else if (distanceToPlayer <= aggroRange)
            currentState = GolemState.Chase;
        else if (Vector3.Distance(transform.position, spawnPosition) > 0.1f)
            currentState = GolemState.Return;
        else
            currentState = GolemState.Idle;

 

 

 

플레이어가 공격 범위 안이면 Attack

공격 범위 밖이고 aggro 범위 안이면 Chase

플레이어가 멀리 있고, 몬스터가 원래 위치에서 벗어나 있으면 Return

그 외에는 Idle

 


 

4-2.  애니메이션 속도 적용

        animator.SetFloat("Speed", agent.velocity.magnitude / agent.speed);

 

몬스터가 이동할 때 애니메이션 속도를 현재 속도에 맞춰 변경합니다.


 

4-3. 상태별 행동

        switch (currentState)
        {
            case GolemState.Attack:
                if (timePassed >= attackCD)
                {
                    animator.SetTrigger("IsAttack");
                    timePassed = 0f;
                }
                agent.SetDestination(transform.position);
                transform.LookAt(player.transform);
                break;

 

Attack

공격 쿨다운이 끝나면 애니메이션 실행

이동을 멈추고 플레이어를 바라봅니다


 

            case GolemState.Chase:
                if (newDestinationCD <= 0)
                {
                    newDestinationCD = 0.5f;
                    agent.SetDestination(player.transform.position);
                }
                transform.LookAt(player.transform);
                break;

 

Chase

일정 간격으로 플레이어 위치로 이동 명령 갱신

이동 중에 플레이어를 바라봅니다


            case GolemState.Return:
                if (newDestinationCD <= 0)
                {
                    newDestinationCD = 0.5f;
                    agent.SetDestination(spawnPosition);
                }
                transform.LookAt(spawnPosition);
                break;

 

Return

원래 위치로 이동

이동 중 원래 위치를 바라봅니다


            case GolemState.Idle:
                agent.SetDestination(transform.position);
                break;
        }

 

Idle

이동하지 않고 가만히 있음


 

4-4. 시간 누적

        timePassed += Time.deltaTime;
        newDestinationCD -= Time.deltaTime;

 

공격 쿨다운과 NavMeshAgent 목적지 갱신 쿨다운 계산합니다.


 

5. TakeDamage 함수

    public void TakeDamage(float damage)
    {
        hp -= damage;
        animator.SetTrigger("IsDamage");
        if (hp <= 0)
        {
            animator.SetTrigger("IsDie");
            Destroy(gameObject);
        }
    }

 

 

몬스터가 피해를 받으면 체력 감소,

피해 애니메이션 실행,

체력이 0 이하이면 사망 애니메이션 실행 후 오브젝트 삭제

를 할 예정입니다!


 

 

6. OnDrawGizmos 함수

    private void OnDrawGizmos()
    {
        Gizmos.color = Color.red;
        Gizmos.DrawWireSphere(transform.position, attackRange);
        Gizmos.color = Color.yellow;
        Gizmos.DrawWireSphere(transform.position, aggroRange);
    }

 

Scene 뷰에서 공격 범위(빨강), aggro 범위(노랑) 시각화

(단순 디버깅용으로, 플레이어와 몬스터 간 거리 확인용 입니다.)


 

 

 

사실,

작업을 하던 도중 내일 새벽에 일어나 급히 이동해야 하는 일정이 생각나서 급하게 마무리했습니다. 😂

내일 이어서 Golem의 AI를 보완하고,

 

Damage → Die

Attack → PlayerHit

 

작업을 완료하도록 하겠습니다.

 

감사합니다.🙂


 

밖에는 천둥이 치고,

비가 얼마나 올지 걱정이 앞서네요. 하하…

 

모두 좋은 하루 되세요.💪💪💪

 

 

 

'프로젝트 > 프로젝트 A' 카테고리의 다른 글

# 022 Map Design  (4) 2025.08.28
# 021 Golem (2) (with. PlayerHpBar)  (7) 2025.08.26
#019 Enemy (3) (whit. Combat)  (6) 2025.08.24
# 018 Login (with. Firebase)  (2) 2025.08.23
# 017 Enemy (2) (with. Turret)  (0) 2025.08.23