반응형
[32회차 수업내용] 250701
1. DontDestroyOnLoad
2. MosterCore로 추상클래스, Goblin Idle, Patrol, Trace, Attack 정의
1. DontDestroyOnLoad
: 이 코드를 쓰면 gameObject가 씬 전환 시에도 파괴되지 않음
: 사운드 컨트롤러에 추가 (Awake에)
2. 몬스터 FSM
* FSM(Finite State Machine) = “지금 내가 어떤 상태인지” + “상태 간 전환 규칙”
2.1 모든 몬스터의 기본 동작을 정의하는 추상 클래스
// 추상 몬스터 코어 클래스: 공통 속성과 상태(FSM) 로직을 정의
public abstract class MonsterCore : MonoBehaviour
{
// 몬스터 상태 정의 (유한 상태 머신 - FSM)
public enum MonsterState { IDLE, PATROL, TRACE, ATTACK }
// 현재 몬스터 상태 (기본값은 IDLE)
public MonsterState monsterState = MonsterState.IDLE;
// 체력과 속도 (외부에서 Init으로 설정)
public float hp;
public float speed;
// 초기 설정 (자식 클래스에서 호출)
protected virtual void Init(float hp, float speed)
{
this.hp = hp;
this.speed = speed;
}
// 상태에 따라 매 프레임 행동 분기
void Update()
{
switch (monsterState)
{
case MonsterState.IDLE:
Idle(); // 대기 상태
break;
case MonsterState.PATROL:
Patrol(); // 순찰 상태
break;
case MonsterState.TRACE:
Trace(); // 추적 상태
break;
case MonsterState.ATTACK:
Attack(); // 공격 상태
break;
}
}
// 각 상태별 행동은 자식 클래스에서 구현해야 함
public abstract void Idle();
public abstract void Patrol();
public abstract void Trace();
public abstract void Attack();
}
*리마인드
| protected | 자신 + 자식만 접근 가능 |
| virtual | 오버라이딩 가능하게 허용 |
| override | 부모의 virtual 메서드를 재정의 |
2.2 애니메이터설정
- 고블린 애니메이터
- 부모 클래스: MonsterCore 공통된 동작과 상태 관리 담당
- 자식 클래스: Goblin 몬스터 개별 행동 세부 구현 담당

2.3 코드 해석
-부모클래스 (전체코드)
// 몬스터의 기본 동작과 상태를 정의하는 추상 클래스 (기반 뼈대)
public abstract class MonsterCore : MonoBehaviour
{
// 몬스터 행동 상태를 표현하는 열거형 (FSM의 상태들)
public enum MonsterState { IDLE, PATROL, TRACE, ATTACK }
// 현재 몬스터 상태, 기본값은 IDLE
public MonsterState monsterState = MonsterState.IDLE;
// 몬스터 공통 컴포넌트들 (자식 클래스 접근 가능하도록 protected)
protected Animator animator; // 애니메이션 조작용 컴포넌트
protected Rigidbody2D monsterRb; // 물리적 움직임 처리용 Rigidbody2D
protected Collider2D monsterColl; // 충돌 감지용 Collider2D
// 추적 대상 (주로 플레이어 Transform)
public Transform target;
// 몬스터 스탯 (체력, 이동 속도, 공격 간격)
public float hp, speed, attackTime;
// 몬스터 상태 관련 정보
protected float moveDir; // 이동 방향 (1 또는 -1)
protected float targetDist; // 플레이어와의 거리
protected bool isTrace; // 플레이어 추적 여부 플래그
// 초기화 함수: 스탯 설정, 주요 컴포넌트 연결, 플레이어 자동 할당
protected virtual void Init(float hp, float speed, float attackTime)
{
this.hp = hp; // 체력 초기화
this.speed = speed; // 이동 속도 초기화
this.attackTime = attackTime; // 공격 쿨타임 초기화
// 플레이어 태그로 자동 탐색 후 Transform 할당
target = GameObject.FindGameObjectWithTag("Player").transform;
// 현재 게임 오브젝트에서 컴포넌트들 가져오기 (Animator, Rigidbody2D, Collider2D)
animator = GetComponent<Animator>();
monsterRb = GetComponent<Rigidbody2D>();
monsterColl = GetComponent<Collider2D>();
}
// 매 프레임 실행되는 업데이트 함수
void Update()
{
// 플레이어와 몬스터 간 거리 계산
targetDist = Vector3.Distance(transform.position, target.position);
// 몬스터가 바라보는 방향 벡터 (moveDir 방향)
Vector3 monsterDir = Vector3.right * moveDir;
// 몬스터에서 플레이어 방향으로 향하는 벡터 (정규화)
Vector3 playerDir = (transform.position - target.position).normalized;
// 두 벡터 간 내적 계산 (cosine 유사도)
float dotValue = Vector3.Dot(monsterDir, playerDir);
// dotValue가 -0.5 ~ -1 이면, 몬스터가 플레이어를 향해 바라보고 있다고 판단
isTrace = dotValue < -0.5f && dotValue >= -1f;
// 현재 상태에 따라 해당 상태 함수 호출 (FSM 실행)
switch (monsterState)
{
case MonsterState.IDLE:
Idle(); // 대기 상태 행동
break;
case MonsterState.PATROL:
Patrol(); // 순찰 상태 행동
break;
case MonsterState.TRACE:
Trace(); // 플레이어 추적 행동
break;
case MonsterState.ATTACK:
Attack(); // 공격 행동
break;
}
}
// 벽 또는 경계 오브젝트(태그 "Return")에 닿으면 방향 전환 처리
private void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Return"))
{
moveDir *= -1; // 이동 방향 반전
// 스프라이트 좌우 반전 처리 (moveDir에 따라 x축 크기 변경)
transform.localScale = new Vector3(moveDir, 1, 1);
}
}
// 몬스터 상태 변경 함수: 중복 상태 변경 방지
public void ChangeState(MonsterState newState)
{
if (monsterState != newState)
monsterState = newState;
}
// 상태별 행동은 자식 클래스에서 반드시 구현해야 하는 추상 메서드들
public abstract void Idle(); // 대기 상태 행동 정의
public abstract void Patrol(); // 순찰 상태 행동 정의
public abstract void Trace(); // 추적 상태 행동 정의
public abstract void Attack(); // 공격 상태 행동 정의
}
-자식클래스(고블린전체코드)
// Goblin 몬스터의 구체 동작을 정의하는 클래스 (MonsterCore를 상속)
public class Monster_Goblin : MonsterCore
{
private float timer; // 상태 지속 시간 측정용 타이머
private float idleTime, patrolTime; // 각각 IDLE 및 PATROL 상태 지속 시간
private float traceDist = 5f; // 추적을 시작할 거리 기준
private float attackDist = 1.5f; // 공격을 시작할 거리 기준
private bool isAttack; // 공격 중인지 여부 (공격 중복 방지용)
// 게임 시작 시 초기화 (체력, 속도, 공격 쿨타임 설정)
void Start()
{
Init(10f, 3f, 2f); // 체력 10, 속도 3, 공격 딜레이 2초
}
// IDLE 상태: 제자리 대기하다가 랜덤 방향으로 순찰 시작
public override void Idle()
{
timer += Time.deltaTime; // 대기 시간 측정
// 대기 시간이 충분히 지나면 순찰 시작
if (timer >= idleTime)
{
timer = 0f;
// 랜덤 방향 지정: 1(오른쪽), -1(왼쪽)
// 조건식 ? 참일 때 값 : 거짓일 때 값
moveDir = Random.Range(0, 2) == 1 ? 1 : -1;
// 방향에 맞춰 스프라이트 반전
transform.localScale = new Vector3(moveDir, 1, 1);
// 순찰 시간 무작위 설정
patrolTime = Random.Range(1f, 5f);
// 달리기 애니메이션 재생
animator.SetBool("isRun", true);
// 순찰 상태로 전환
ChangeState(MonsterState.PATROL);
}
// 플레이어가 추적 범위에 있고, 바라보는 방향이면 추적 상태로 전환
if (targetDist <= traceDist && isTrace)
{
timer = 0f;
animator.SetBool("isRun", true);
ChangeState(MonsterState.TRACE);
}
}
// PATROL 상태: 일정 시간 동안 지정 방향으로 이동
public override void Patrol()
{
// moveDir 방향으로 계속 이동
transform.position += Vector3.right * moveDir * speed * Time.deltaTime;
timer += Time.deltaTime;
// 순찰 시간 종료 → IDLE로 복귀
if (timer >= patrolTime)
{
timer = 0f;
// 다음 대기 시간 무작위 설정
idleTime = Random.Range(1f, 5f);
// 달리기 애니메이션 해제
animator.SetBool("isRun", false);
// IDLE 상태로 전환
ChangeState(MonsterState.IDLE);
}
// 플레이어가 감지 범위 안에 들어오면 추적 시작
if (targetDist <= traceDist && isTrace)
{
timer = 0f;
ChangeState(MonsterState.TRACE);
}
}
// TRACE 상태: 플레이어 쪽으로 계속 이동
public override void Trace()
{
// 플레이어를 향한 방향 벡터 계산
var targetDir = (target.position - transform.position).normalized;
// x축 방향으로만 이동 (y축 이동 없음)
transform.position += Vector3.right * targetDir.x * speed * Time.deltaTime;
// 이동 방향에 따라 스프라이트 좌우 반전
var scaleX = targetDir.x > 0 ? 1 : -1;
transform.localScale = new Vector3(scaleX, 1, 1);
// 플레이어가 너무 멀어지면 추적 포기 → IDLE로 전환
if (targetDist > traceDist)
{
animator.SetBool("isRun", false);
ChangeState(MonsterState.IDLE);
}
// 일정 거리 이하로 가까워지면 공격 상태로 전환
if (targetDist < attackDist)
{
ChangeState(MonsterState.ATTACK);
}
}
// ATTACK 상태: 코루틴을 통해 일정 시간 공격 애니메이션 재생
public override void Attack()
{
if (!isAttack)
StartCoroutine(AttackRoutine()); // 한 번만 실행되도록 제한
}
// 실제 공격 로직 실행 코루틴
IEnumerator AttackRoutine()
{
isAttack = true;
// 공격 애니메이션 재생
animator.SetTrigger("Attack");
yield return new WaitForSeconds(1f); // 공격 모션 시간 (1초)
// 이동 애니메이션 해제
animator.SetBool("isRun", false);
// 남은 공격 쿨타임 대기
yield return new WaitForSeconds(attackTime - 1f);
// 공격 종료
isAttack = false;
// 다시 IDLE 상태로 전환
ChangeState(MonsterState.IDLE);
}
}
기본 컴포넌트 자동 초기화 (Animator, Rigidbody, Collider)
animator = GetComponent<Animator>();
monsterRb = GetComponent<Rigidbody2D>();
monsterColl = GetComponent<Collider2D>();
- 부모에서 한 번만 처리 → 자식 클래스는 컴포넌트 설정 고민 ❌
- 추상화된 Init()에서 모두 통합 관리
Vector3 monsterDir = Vector3.right * moveDir;
여기서 몬스터 방향을 벡터화. 추후 내적에 활용하기 위함
자세한 내용은 주석 형태로 정리해 놓았다.
후기
*colldier 를 2D로 안넣는 실수를 했다. 주의할것
*애니메이터 Transition 조건과 시간 조절을 잘해서 자연스럽게 보이게 하자.
여러 코드 짜기 학습을 반복하다 보니 눈에 점점 익는 기분이 든다. 그래도 이걸 내 도구로 만들기 위해선 수업의 진도를 따라가는 것 외의 혼자 무언갈 구현해보는 시간이 필요할 것 같다. 고블린이 내가 코딩한대로 움직이는걸 보니 귀엽다(?)

반응형
'프로그래밍 > 유니티 부트캠프' 카테고리의 다른 글
| 유니티(몬스터 행동패턴 (인벤토리 기능/ 플랫포머게임 마무리 / 2D Rigging) _ 멋쟁이사자처럼 유니티 부트캠프 후기 34회차 (3) | 2025.07.05 |
|---|---|
| 유니티(공격 판정 / 체력바 / 아이템 드롭) _ 멋쟁이사자처럼 유니티 부트캠프 후기 33회차 (1) | 2025.07.05 |
| 유니티(플랫포머 맵 만들기 ) _ 멋쟁이사자처럼 유니티 부트캠프 후기 31회차 (5) | 2025.07.03 |
| 유니티(사운드매니저 / 설정창 / 씬 전환) _ 멋쟁이사자처럼 유니티 부트캠프 후기 30회차 (0) | 2025.07.03 |
| 유니티(Camera Follow / 표지판 팝업 UI 인터랙션/ 씬 내부 맵 이동/타이핑 코루틴) _ 멋쟁이사자처럼 유니티 부트캠프 후기 29회차 (0) | 2025.06.27 |