반응형
이번 글에서는 주변을 배회하다가 캐릭터를 발견하면 캐릭터를 따라다니고 멀어지면 다시 주변을 배회하는 몬스터의 AI 를 간단한 StateMachine 을 이용해 구현하는 방법에 대해 알아보도록 하겠습니다. 이 글은 이전에 진행한 다음의 글에 이어서 작성하도록 하겠습니다.
▶ 에셋 정보
위의 링크 글에 있긴 하지만 귀찮으신 분들을 위해 여기를 클릭하시고 다운로드 받으시면 됩니다.
▶ 몬스터 만들기
몬스터는 위의 에셋에 있는 몬스터 Dog를 사용하겠습니다.
- Project 창에서 경로 [Assets > SunnyLand Artwork > Sprites > Enemies > Dog] 로 이동합니다.
- dog1 ~ 4 의 Pixels Per Unit 을 33 으로 설정합니다. (파일 4개를 모두 선택하고 Inspector 창에서 한 번에 변경할 수 있습니다.)
- dog1 을 Hierarchy 창으로 드래그하고 이름을 Monster-Dog 로 변경합니다.
- Monster-Dog를 선택하고 Inspector 창에서 [Add Component > Miscellaneous] 에서 Animator 를 추가합니다.
- Animator의 Controller 필드에 [Assets > SunnyLand Artwork > Sprites > Enemies > Dog] 에 있는 dog1 controller 를 연결해 줍니다.
- [Add Component > Physics2D] 에서 Rigidbody2D 와 BoxCollider2D 를 추가합니다.
- Player를 감지하기 위해 Sensor를 만듭니다.
- Monster-Dog 객체에 빈 자식 객체를 만들고 이름을 Sensor 로 변경합니다. 이 객체에 [Add Component > Physics2D] 에 있는 CircleCollider2D 를 추가합니다.
- CircleCollider2D의 Is Trigger 필드를 체크하고 Radius 필드를 3으로 설정합니다.
- 이름을 Monster 로 해서 새 스크립트를 추가합니다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Monster : MonoBehaviour
{
int HP = 1;
Rigidbody2D rig;
SpriteRenderer sr;
float move = -1;
enum State
{
Idle,
MoveLeft,
MoveRight,
Chase
}
State state;
Player player;
bool cancelWait;
// Start is called before the first frame update
IEnumerator Start()
{
rig = GetComponent<Rigidbody2D>();
sr = GetComponent<SpriteRenderer>();
cancelWait = false;
while (HP > 0)
{
yield return StartCoroutine(state.ToString());
}
}
void StateChange(State next)
{
state = next;
cancelWait = true;
}
void SetNextRoaming()
{
// 현재의 state 가 Idle, MoveLeft, MoveRight 이면 다음 로밍 설정
if (state == State.Idle || state == State.MoveLeft || state == State.MoveRight)
{
state = (State)Random.Range(0, 3);
}
}
IEnumerator CancelableWait(float t)
{
var d = Time.deltaTime;
cancelWait = false;
// 지정한 시간 t 만큼 대기
// cancelWait == true 면 중단
while (d < t && cancelWait == false)
{
d += Time.deltaTime;
yield return null;
}
if (cancelWait == true) print("Cancled");
}
IEnumerator Idle()
{
// 움직이지 않음
move = 0;
// 1~2 초 대기
yield return StartCoroutine(CancelableWait((Random.Range(1f, 3f))));
SetNextRoaming();
}
IEnumerator MoveLeft()
{
move = -1;
sr.flipX = false;
yield return StartCoroutine(CancelableWait((Random.Range(3f, 6f))));
SetNextRoaming();
}
IEnumerator MoveRight()
{
move = 1;
sr.flipX = true;
yield return StartCoroutine(CancelableWait((Random.Range(3f, 6f))));
SetNextRoaming();
}
IEnumerator Chase()
{
if (player != null)
{
Vector3 vec;
do
{
yield return new WaitForSeconds(0.5f);
// Player와의 방향 벡터
vec = player.transform.position - transform.position;
// Player 가 오른쪽에 있으면
if (vec.x > 0)
{
sr.flipX = true;
move = 2f;
}
// Player 가 왼쪽에 있으면
else
{
sr.flipX = false;
move = -2f;
}
}
// 거리가 6 이내인 동안 따라 다니기
while (vec.magnitude < 6f);
print("End of chase");
// Player 가 멀어지면 State 를 Idle 로 변경
StateChange(State.Idle);
}
}
// Update is called once per frame
void FixedUpdate()
{
// move 값에 따라 x 축으로 이동
rig.velocity = new Vector2(move, rig.velocity.y);
}
// Sensor 객체의 CircleCollider2D와 충돌시 발생하는 이벤트
private void OnTriggerEnter2D(Collider2D collision)
{
// 충돌한 객체의 이름이 Player 가 아니면 return
if (collision.gameObject.name != "Player") return;
player = collision.gameObject.GetComponent<Player>();
if (state != State.Chase) StateChange(State.Chase);
}
}
- StateMachine 의 구현은 유니티에서 제공하는 Coroutine 을 이용했습니다.
- IEnumerator Start() 로 선언하면 Start 함수가 코루틴 함수로 동작합니다.
- Idle, MoveLeft, MoveRight 는 로밍용 state 입니다.
- Chase 중에 Player 객체와의 거리가 6 보다 멀어지면 Idle state 가 되고 이 후 근처를 로밍합니다.
반응형
'게임 프로그래밍 > 유니티 활용' 카테고리의 다른 글
[유니티 활용] ScriptableObject 로 HP Bar 만들기 - #2 (3) | 2022.12.30 |
---|---|
[유니티 활용] ScriptableObject 로 HP Bar 만들기 - #1 (1) | 2022.12.29 |
[유니티 활용] 부드러운 카메라 이동과 경계 값 설정 (0) | 2022.12.25 |
[유니티 활용] BlendTree 를 이용한 2D 점프 애니메이션 (2) | 2022.12.23 |
[유니티 활용] Platform Effector 2D 를 이용한 관통 플랫폼 만들기 (1) | 2022.12.22 |
댓글