안녕하세요.😀
오늘은 대중교통을 이용하면서 '어떤 작업을 진행해 볼까?' 고민하다가,
정글 몬스터가 갑자기 떠올랐습니다.
우선 골램을 빠르게 만들어 보려고 했는데,
테스트 과정이 생각보다 길어져 시간이 조금 걸렸습니다.🤣
먼저 새로 생성한 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
작업을 완료하도록 하겠습니다.
감사합니다.🙂
밖에는 천둥이 치고,
비가 얼마나 올지 걱정이 앞서네요. 하하…
모두 좋은 하루 되세요.💪💪💪