
임의로 넣은 랜덤 값으로 적이 멈추고, 순찰하고, 추적하며 공격할수있도록 만들어보겠습니다.
상태패턴을 작용할것이라
Enemy.cs 그리고 내가 원하는 상태 enum { IDLE, MOVE, ATTACK, DEATH} 등을 적고 원하는 패턴에 대한 스크립트를 만들어서 작성을하는데... IDLE.cs, MOVE.cs, ATTACK.cs 등... 근데 여러 스크립트를 짜기엔 대형 프로젝트는 아닌지라 한 스크립트에 작성을 했습니다.
제가원하는 몬스터의 패턴은 다음과 같습니다
public enum State
{
IDLE, //멈춤
PATROL, //순찰
CHASE, //추적
ATTACK, //공격
EVADE, //회피
DEATH //사망
}
//현 상태를 알수있도록
public State currentState
들어가는 상태, 현재 상태, 빠져나오는 상태 작성을위해
private void ChangeState(State newState)
{
if (currentState != newState)
{
//진행중이던 상태 종료
ExitState(currentState);
//현재 상태
currentState = newState;
//새로 들어갈 상태 실행
EnterState(newState);
}
}
private void ExitState(State state)
{
switch (currentState)
{
case State.IDLE:
ExitIdle();
break;
case State.PATROL:
ExitPatrol();
break;
case State.CHASE:
ExitChase();
break;
case State.ATTACK:
ExitAttack();
break;
case State.EVADE:
ExitEvade();
break;
case State.DEATH:
ExitDeath();
break;
}
}
private void EnterState(State state)
{
Debug.Log(" 현재 상태 : " + currentState);
switch (currentState)
{
case State.IDLE:
EnterIdle();
break;
//...생략
case State.DEATH:
EnterDeath();
break;
}
}
private void ExecuteState()
{
switch (currentState)
{
case State.IDLE:
ExcuteIdle();
break;
//...생략
case State.DEATH:
ExcuteDeath();
break;
}
}
private void EnterIdle()
{
}
private ExcuteIdle()
{
}
private ExitIdle()
{
}
//... 나머지도 같은식으로
이런식으로 모든 패턴에 대한 메서드를 Alt + Enter로 싹다 생성해준다.
순찰 실행 로직
순찰할 지점을 정했다면, 그 방향을 바라보며 이동합니다.
//컴포넌트
private Animator animator;
private Transform playerTransform;
//순찰을 위한 코드
[SerializeField] private float speed = 3f; //이동속도
[SerializeField] private float patrolRadius = 5f; //순찰 반경
[SerializeField] private float patrolDuration = 5f; //한 위치에 멈춰있을 시간
[SerializeField] private float detectionRadius = 8f; //플레이어 감지 거리
private Vector3 nextPatrolPoint; //순찰 지점
//초기화
private void Awake()
{
ChangeState(State.PATROL();
animator = GetComponent<Animator>();
playerTransform = GameObject.FindGameObjectWithTag("Player").transform;
}
//순찰 실행
private void ExcutePatrol()
{
animator.SetFloat("Speed", 1f); //블렌드 트리를 사용해 1이면 Walk 애니메이션 실행
//플레이어를 찾는거리
float distanceToPlayer = Vector3.Distance(transform.position, playerTransform.Position)
//다음 움직일 지점 찾기
float distanceToPatrolPoint = Vector3.Distance(transform.position, nextPatrolPoint);
//감지 거리 내에 있다면 추적상태로 전환
if (distanceToPlayer <= detectionRadius)
{
ChangeState(State.CHASE);
return;
}
//목표지점에 거의 도달했다면 코루틴 실행
if (distanceToPatrolPoint <= 0.1f)
{
StartCoroutine(StayForAWhile());
return;
}
else
{
//바라볼 방향 구하기
Vector3 moveDirecion = (nextPatrolPoint - transform.position).normalized;
//회전이 적용될 x y z 값
Quaternion lookRotation = Quaternion.LookRotation(new Vector3(moveDirection.x, 0f, moveDirection.z));
//부드럽게 회전시키기
transform.rotation = Quaternion.Slerp(transform.rotation, lookRotation, speed * Time.detlaTime);
//nextPatrol에서 구한 순찰지점거리로 이동
transform.position = Vector3.MoveTowards(transform.position, nextPatrolPoint, speed * Time.deltaTime);
}
}
//순찰지점은 y값을 제외한 나머지 랜덤으로 이동
private void ChooseNextPatrolPoint()
{
float randomX = UnityEngine.Random.Range(-patrolRadius, patrolRadius);
float randomZ = UnityEngine.Random.Range(-patrolRadius, patrolRadius);
nextPatrolPoint = transform.position + new Vector3(randomX, 0f, randomZ);
}
//순찰 지점 도착시 잠깐 쉬어주기
private IEnumerator StayForAWhile()
{
Debug.Log("Stay...");
//IDLE상태로 바꿔 잠깐 멈춰주고, 순찰 휴식후 다음 지점찾아 다시 순찰
ChangeState(State.IDLE);
yield return new WaitForSeconds(patrolDuration);
ChooseNextPatrolPoint();
ChangeState(State.PATROL);
}
추적 실행 로직
추적 상태로 전환시, 플레이어를 바라보며 이동합니다.
private vodi ExcuteChase()
{
animator.SetFloat("Speed", 1.5f);
//플레이어 발견시 쳐다보도록함. (플레이어 위치 - 내 위치)
Vector3 directionToPlayer = (playerTransform.position - transform.position).normalized;
//회전 방향 설정
Quaternion lookRotation = Quaternion.LookRotation(new Vector3(directionToPlayer.x, 0f, directionToPlayer.z)
//부드럽게 회전시키기
transform.rotation = Quaternion.Slerp(transform.rotation, lookRotation, enemyStats.speed * Time.deltaTime);
//플레이어 추적하기
float distanceToPlayer = Vector3.Distance(transform.position, playerTransform.transform.position);
transform.position = Vector3.MoveTowards(transform.position, playerTransform.transform.position, speed * Time.deltaTime);
//추적중에 공격 사거리내로 들어오면 공격실행을 위해 공격상태로 전환
if (distanceToPlayer <= 2f)
{
ChangeState(State.ATTACK);
return;
}
//추적중에 플레이어가 멀어졌다면 기존 순찰모드로 돌아감
if (distanceToPlayer > detectionRadius)
{
ChangeState(State.PATROL);
return;
}
}
공격 실행 로직
추적 중에 공격 범위에 들어와 공격이 실행되는 상태
[SerializeField] private float attackInterval = 1.5f; //공격 간격
private Coroutine attackRoutine; //현재 공격을 코루틴에 저장한다.
private bool isAttacking = false;
private void ExcuteAttack()
{
//enemy와 플레이어 사이의 거리
float distanceToPlayer = Vector3.Distance(transform.position, playerTransform.transform.position);
//공격 범위 내에 들어오면서, 조건 충족시
if (distanceToPlayer <= 2f && !isAttacking)
{
//공격중엔 움직이지않음
animator.SetFloat("Speed", 0f);
//공격 실행하는 코루틴을 저장함 (나중에 공격 취소 등을 위하여)
attackRoutine = StateCoroutine(AttackPlayer());
isAttacking = true;
}
//공격 상태인데 공격 범위에 벗어나버리면 공격 중지
else if (distanceToPlayer > 2f && isAttacking)
{
StopCoroutine(attackRoutine);
animator.SetBool("isAttacking", false); //파라미터 이름 임의로
isAttacking = false;
ChangeState(State.CHASE);
}
}
//공격 코루틴 실행중
private IEnumerator AttackPlayer()
{
while (true)
{
//공격 범위 내에 들어와있다면 공격 실행
animator.SetBool("isAttacking", true);
//공격 재사용 대기 시간
yield return new WaitForSeconds(attackInterval);
//공격 애니메이션 종료
animator.SetBool("isAttacking", false);
//애니메이션 길이 (공격 간격)을 고려하여 살짝 대기
yield return new WaitForSeconds(attackInterval - 1f);
}
}'Unity Engine > Unity 3D.' 카테고리의 다른 글
| [Unity] Rigidbody로 움직임 줄때 (0) | 2023.11.04 |
|---|---|
| [Unity 3D] Floating Capsule 경사면 해결 (0) | 2023.11.04 |
| [Unity 3D] Creating Monster State Machine (0) | 2023.10.21 |
| [유니티 3D] 메기도 구현 (무한히 튕기는 광선) (1) | 2023.10.21 |
| 일정한 간격으로 오브젝트 생성 (0) | 2023.10.20 |