0. 들어가기 전에
이번엔 몬스터가 알아서 맵을 돌아다니도록 했다.
1. 게임 오브젝트
일단 하이어라키 창에 몬스터 게임 오브젝트를 만들었다. 다음에 이 몬스터를 프리팹으로 만들어서 사용할 것이다.
1.1 Monster
몬스터 게임 오브젝트의 정보는 다음과 같다.
1. Monster 스크립트
직접 만든 스크립트이다. 몬스터의 움직임을 관리하기 위해 만들었다.
2. Rigidbody 2D
몬스터 게임 오브젝트가 물리 작용을 받도록 하기 위해 부착한 컴포넌트이다.
3. Circle Collider 2D
몬스터 게임 오브젝트가 다른 물체와 충돌한 것을 감지하기 위해 부착한 컴포넌트이다.
4. Animator
몬스터의 애니메이션을 관리하기 위해 부착한 컴포넌트이다.
2. 애니메이션&애니메이터
이번에 새로 만든 애니메이터와 애니메이션은 다음 사진과 같다. 일단 Orage 몬스터의 애니메이션과 애니메이터만 만들었는데 나머지 몬스터들도 만들어줄것이다.
Die 는 무한 반복하면 안 되고 한 번만 수행되어야 하기 때문에 Loop 를 비활성화 했다. Idle 과 Run 은 무한반복 하도록 했다.
구조는 똑같고 애니메이션 스프라이트만 다르기 때문에 Orage 컨트롤러를 오버라이딩해서 만들 예정이다.
2.1 애니메이터 상태
애니메이터 상태는 다음과 같다.
Move 파라미터의 값을 통해 Idle, Run 애니메이션을 수행하도록 했다. Move 값이 true 이면 Run, false 이면 Idle 애니메이션을 수행하도록 했다.
이때 Die 신호가 들어오면 Die 애니메이션으로 간 후, 끝나면 Exit 로 빠져나가도록 했다.
3. 스크립트
이번에 새로 만든 스크립트는 Monster 이다.
3.1 Monster 스크립트
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Monster : MonoBehaviour
{
public enum MoveDir
{
Left = -1, Idle = 0, Right = 1
}
[Header("Monster info")]
public float speed = 1f; // 이동 속도
public MoveDir dir = MoveDir.Idle; // 점토 몬스터가 움직이는 방향
public Coroutine monsterMoveCoroutine; // 몬스터는 계속해서 움직이는데 그 동작을 코루틴으로 만들 것. 코루틴 시작한 거 저장하기 위한 용도
private Animator anim;
private Rigidbody2D rigid;
private SpriteRenderer spriteRenderer;
private void Start()
{
// 컴포넌트 할당
anim = GetComponent<Animator>();
rigid = GetComponent<Rigidbody2D>();
spriteRenderer = GetComponent<SpriteRenderer>();
monsterMoveCoroutine = StartCoroutine(MonsterMove());
}
private void Update()
{
// 몬스터가 땅에 있으면 이동 방향에 따라 움직일 수 있도록
transform.Translate(new Vector2((int)dir, 0) * speed * Time.deltaTime * 0.5f);
// 애니메이션 수행 & 스프라이트 관리
SetMonsterSprite();
// 몬스터 레이캐스트
MonsterRaycast();
}
private void MonsterRaycast()
{
// 방향에 맞게 레이 쏴야함
// 근데 아래 방향으로 쏴야함 -> 몬스터보다 조금 더 앞에서 밑에 방향으로 레이 쏠 것
Vector2 front = new Vector2(rigid.position.x + (int)dir, rigid.position.y);
Debug.DrawRay(front, Vector2.down, new Color(0, 1, 0));
RaycastHit2D rayHit = Physics2D.Raycast(front, Vector3.down, 1, LayerMask.GetMask("Ground"));
if (rayHit.collider == null)
{
Debug.Log("밑에 땅이 업어여;;");
// 자기 앞이 낭떠러지면 move 코루틴 중지하고 GotoReverse 시작
StopCoroutine(monsterMoveCoroutine);
StartCoroutine(GotoReverse());
}
}
private IEnumerator MonsterMove()
{
while (true)
{
dir = (MoveDir)Random.Range(-1, 2);
yield return new WaitForSeconds(2f); // 2초마다 방향 바꿀 수 있도록..
}
}
private IEnumerator GotoReverse()
{
// 이동 방향을 반대로 바꾸는 코루틴
int reverseDir = -(int)dir; // 현재 이동 방향에 반대
// 점토가 가는 방향의 밑이 낭떠러지면 방향 바꿔야함
dir = (MoveDir)reverseDir;
yield return new WaitForSeconds(2f); // 2초 기다령
monsterMoveCoroutine = StartCoroutine(MonsterMove()); // 코루틴 다시 시작
}
private void SetMonsterSprite()
{
// 애니메이션 수행
if ((int)dir == 0)
anim.SetBool("Move", false);
else
{
anim.SetBool("Move", true);
// 움직이는 방향이 오른쪽이면 스프라이트 뒤집도록..
if ((int)dir == -1)
spriteRenderer.flipX = false;
else
spriteRenderer.flipX = true;
}
}
}
3.2 Monster 스크립트 설명
Monster 스크립트에 대한 설명은 다음과 같다.
1. 열거체
몬스터의 이동 방향을 쉽게 알기 위해서 임의로 만들었다. -1 은 Left, 0 은 Idle, 1 은 Right 에 대응시켰다.
public enum MoveDir
{
Left = -1, Idle = 0, Right = 1
}
2. 변수
선언한 변수는 다음과 같다. 몬스터가 맵을 돌아다니는 속도를 관리하기 위해 speed 변수를 만들었고, 이동 방향을 저장하기 위해 dir 변수를 만들었다.
그리고 몬스터가 방향을 2초마다 바꾸는 것을 구현하기 위해 코루틴을 이용했는데 시작한 코루틴을 저장하기 위한 monsterMoveCoroutine 변수를 만들었다.
그 다음에는 몬스터 게임 오브젝트에 부착된 컴포넌트를 스크립트 상에서 이용하기 위해 anim, rigid, spriteRenderer 변수를 만들었다.
[Header("Monster info")]
public float speed = 1f; // 이동 속도
public MoveDir dir = MoveDir.Idle; // 점토 몬스터가 움직이는 방향
public Coroutine monsterMoveCoroutine; // 몬스터는 계속해서 움직이는데 그 동작을 코루틴으로 만들 것. 코루틴 시작한 거 저장하기 위한 용도
private Animator anim;
private Rigidbody2D rigid;
private SpriteRenderer spriteRenderer;
3. Start()
선언한 변수에 컴포넌트를 할당했다.
그리고 2초 마다 이동 방향을 바꾸는 코루틴을 시작하고 monsterMoveCoroutine 에 넣었다. 변수에 넣는 이유는 나중에 코루틴을 강제 종료 할 때 필요하기 때문이다.
StopCoroutine 메서드에 monsterMoveCoroutine 을 넣어주면 성공적으로 종료시킬 수 있다.
private void Start(){
// 컴포넌트 할당
anim = GetComponent<Animator>();
rigid = GetComponent<Rigidbody2D>();
spriteRenderer = GetComponent<SpriteRenderer>();
monsterMoveCoroutine = StartCoroutine(MonsterMove());
}
4. Update()
몬스터의 이동 방향에 맞게 움직이도록 했다.
이에 더해 애니메이션과 스프라이트를 관리하는 메서드와 몬스터의 레이캐스트를 관리하는 메서드를 호출했다.
private void Update(){
// 몬스터가 땅에 있으면 이동 방향에 따라 움직일 수 있도록
transform.Translate(new Vector2((int)dir, 0) * speed * Time.deltaTime * 0.5f);
// 애니메이션 수행 & 스프라이트 관리
SetMonsterSprite();
// 몬스터 레이캐스트
MonsterRaycast();
}
5. MonsterRaycast()
몬스터가 레이를 쏘는 메서드이다.
몬스터는 자신이 현재 움직이는 방향의 밑으로 광선을 쏴야한다. 점토에서 바로 밑으로 쏘는 것이 아니고 현재 점토의 위치 x 값에 이동방향을 더해서 점토의 앞에서 광선이 시작되도록 했다. 이유는 현재 점토의 앞이 낭떠러지인지를 판단해야 하기 때문이다.
Debug.DrawRay 는 진짜로 광선을 쏘는 것은 아니고 사람이 광선이 나가는 모습을 볼 수 있도록 하기 위함이다. 즉, 실질적으로 광선을 쏘는 역할은 Physics2D.Raycast 가 담당한다. 몬스터가 쏜 광선이 감지하는 대상을 Ground 로 설정해서 땅을 감지할 수 있도록 했다.
만약 rayHit.collider 의 값이 null 이면 몬스터의 앞이 낭떠러지임을 의미하므로 move 코루틴을 중지하고 GotoReverse() 코루틴 수행을 시작했다.
private void MonsterRaycast()
{
// 방향에 맞게 레이 쏴야함
// 근데 아래 방향으로 쏴야함 -> 몬스터보다 조금 더 앞에서 밑에 방향으로 레이 쏠 것
Vector2 front = new Vector2(rigid.position.x + (int)dir, rigid.position.y);
Debug.DrawRay(front, Vector2.down, new Color(0, 1, 0));
RaycastHit2D rayHit = Physics2D.Raycast(front, Vector3.down, 1, LayerMask.GetMask("Ground"));
if (rayHit.collider == null)
{
// 자기 앞이 낭떠러지면 move 코루틴 중지하고 GotoReverse 시작
StopCoroutine(monsterMoveCoroutine);
StartCoroutine(GotoReverse());
}
}
6. MonsterMove()
몬스터가 2초 마다 방향을 바꾸도록 하는 코루틴이다. 얘는 밖에서 직접 Stop 을 통해 종료시킬 수 있다. 스스로는 빠져나갈 수 없다. while 에 계속 묶여있기 때문에..
private IEnumerator MonsterMove()
{
while (true)
{
dir = (MoveDir)Random.Range(-1, 2);
yield return new WaitForSeconds(2f); // 2초마다 방향 바꿀 수 있도록..
}
}
7. GotoReverse()
점토의 앞이 낭떠러지라면 방향을 반대로 바꾸도록 하는 코루틴이다. 방향을 바꾼 후 2초 기다린 후에 다시 move 코루틴을 시작하도록 했다.
private IEnumerator GotoReverse()
{
// 이동 방향을 반대로 바꾸는 코루틴
int reverseDir = -(int)dir; // 현재 이동 방향에 반대
// 점토가 가는 방향의 밑이 낭떠러지면 방향 바꿔야함
dir = (MoveDir)reverseDir;
yield return new WaitForSeconds(2f); // 2초 기다령
monsterMoveCoroutine = StartCoroutine(MonsterMove()); // 코루틴 다시 시작
}
8. SetMonsterSprite()
몬스터의 모습을 관리하는 메서드이다.
현재 몬스터가 움직이는 방향에 맞게 스프라이트를 뒤집을지 말지 결정했고, Idle 이 아니라면 Move 의 값을 true 로 설정해서 Run 애니메이션이 수행되도록 했다.
private void SetMonsterSprite()
{
// 애니메이션 수행
if ((int)dir == 0)
anim.SetBool("Move", false);
else
{
anim.SetBool("Move", true);
// 움직이는 방향이 오른쪽이면 스프라이트 뒤집도록..
if ((int)dir == -1)
spriteRenderer.flipX = false;
else
spriteRenderer.flipX = true;
}
}
4. 결과물
2초에 한 번씩 방향을 바꿔 움직이는 걸 확인할 수 있다. 방향은 Left, Idle, Right 로 총 세 가지이고 이 중 랜덤으로 방향을 설정한다.
몬스터는 자신의 이동방향에서 아래로 레이를 쏘는데 만약 밑에 땅이 없으면 기존 방향에서 반대로 움직이도록 했다.
임의로 몬스터를 벼랑끝으로 내몰았을 때 방향을 바꾸는 것을 확인할 수 있다.
5. 참고자료
이번엔 레이캐스트를 사용했다. 또 까먹지 않기 위해 참고자료로 공부한 내용을 남겨놓으려고 한다.
5.1 RayCast
유니티 레이캐스트 Raycast 충돌 / Ray의 모든 것 :: Chameleon Studio
유니티 레이캐스트 Raycast 충돌 / Ray의 모든 것
해당 티스토리 페이지는 필자가 유니티 C# 개발을 하면서 학습한 내용들을 기록하고 공유하는 페이지입니다 ! - 틀린 부분이 있거나, 수정된 부분이 있다면 댓글로 알려주세요 ! - 해당 내용을 공
chameleonstudio.tistory.com
2D 플랫포머 - 몬스터 AI 구현하기 [유니티 기초 강좌 B18]