1. DontDestroyOnLoad
Unity에서 씬 전환 시에도 오브젝트를 유지하고 싶을 때 사용하는 함수입니다.
주로 데이터 매니저, 오디오 매니저, 플레이어 오브젝트 등에 활용됩니다.
using UnityEngine;
public class ExampleClass : MonoBehaviour
{
void Awake()
{
DontDestroyOnLoad(gameObject);
}
}
Awake()
또는Start()
에서 호출 시, 해당 오브젝트는 씬이 바뀌어도 사라지지 않습니다.- 단, 중복 생성을 방지하기 위한 싱글톤 구조와 함께 사용하는 것이 일반적입니다.
2. 몬스터 FSM 설계
2-1. Init 구현 방식
초기화를 위한 Init()
함수는 다음과 같이 두 가지 방식으로 구현할 수 있습니다.
방법 1: 자식 클래스에서 직접 구현
void Start()
{
hp = 10f;
speed = 3f;
attackTime = 2f;
}
- 자식 클래스에서 필요한 초기화 내용을 모두 정의합니다.
💡 코드가 단순하지만, 자식 클래스마다 중복되는 코드가 발생할 수 있습니다.
방법 2: 부모 클래스에서 가상 함수로 구현 → 자식 클래스에서 호출
protected virtual void Init(float hp, float speed, float attackTime)
{
this.hp = hp;
this.speed = speed;
this.attackTime = attackTime;
target = GameObject.FindGameObjectWithTag("Player").transform;
animator = GetComponent<Animator>();
monsterRb = GetComponent<Rigidbody2D>();
monsterColl = GetComponent<Collider2D>();
}
- 공통 초기화는 부모 클래스에서 처리하고, 자식 클래스에서는 base.Init()을 호출합니다.
💡 코드 재사용성과 유지보수 측면에서 효율적입니다.
2-2. MonsterCore 클래스 - FSM 구현
MonsterCore
는 모든 몬스터의 기본 동작을 정의하는 추상 클래스입니다.
상태 기반 동작을 처리하기 위해 FSM(Finite State Machine) 구조를 사용합니다.
핵심 상태 열거형
public enum MonsterState { IDLE, PATROL, TRACE, ATTACK }
상태별 처리 구조
void Update()
{
targetDist = Vector3.Distance(transform.position, target.position);
Vector3 monsterDir = Vector3.right * moveDir;
Vector3 playerDir = (transform.position - target.position).normalized;
float dotValue = Vector3.Dot(monsterDir, playerDir);
isTrace = dotValue < -0.5f && dotValue >= -1f;
switch (monsterState)
{
case MonsterState.IDLE: Idle(); break;
case MonsterState.PATROL: Patrol(); break;
case MonsterState.TRACE: Trace(); break;
case MonsterState.ATTACK: Attack(); break;
}
}
IDLE, PATROL, TRACE, ATTACK
네 가지 상태를 각각 추상 메서드로 선언하고 자식 클래스에서 구체적으로 구현합니다.
💡 ChangeState()
메서드를 통해 상태를 전환할 수 있습니다.
2-3. Goblin 클래스 - MonsterCore 상속
Gobli
n은MonsterCore
를 상속한 실제 몬스터 클래스입니다.
랜덤한 시간의 대기 → 이동 → 추적 → 공격을 FSM을 통해 제어합니다.
초기화
void Start()
{
Init(10f, 3f, 2f); // 체력, 속도, 공격 딜레이 설정
}
상태별 구현
1. Idle 상태
전체 코드 (C#)
더보기
더보기
public override void Idle()
{
timer += Time.deltaTime;
if (timer >= idleTime)
{
timer = 0f;
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);
}
}
1-1. 대기 시간 측정 및 다음 상태 준비
timer += Time.deltaTime;
if (timer >= idleTime)
{
timer = 0f;
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);
}
설명:
- 대기 시간이 끝나면 방향을 랜덤 설정하고, 이동 애니메이션을 켭니다.
- 이후 PATROL 상태로 전환됩니다.
1-2. 플레이어 탐지 시 추적 상태 전환
if (targetDist <= traceDist && isTrace)
{
timer = 0f;
animator.SetBool("isRun", true);
ChangeState(MonsterState.TRACE);
}
설명:
- 플레이어가 일정 거리 이내에 있고, 시야 범위 안에 있다면 TRACE 상태로 전환합니다.
Idle 상태 핵심 코드
moveDir = Random.Range(0, 2) == 1 ? 1 : -1;
ChangeState(MonsterState.PATROL);
ChangeState(MonsterState.TRACE);
기능 설명
moveDir
을 랜덤하게 설정해 이동 방향을 좌우로 결정합니다.transform.localScale
로 방향에 따라 캐릭터를 좌우 반전시킵니다.- 일정 시간이 지나면 상태를
PATROL
로 전환합니다.
2. Patrol 상태
전체 코드 (C#)
더보기
더보기
public override void Patrol()
{
transform.position += Vector3.right * moveDir * speed * Time.deltaTime;
timer += Time.deltaTime;
if (timer >= patrolTime)
{
timer = 0f;
idleTime = Random.Range(1f, 5f);
animator.SetBool("isRun", false);
ChangeState(MonsterState.IDLE);
}
if (targetDist <= traceDist && isTrace)
{
timer = 0f;
ChangeState(MonsterState.TRACE);
}
}
2-1. 현재 방향으로 이동
transform.position += Vector3.right * moveDir * speed * Time.deltaTime;
설명:
- 정해진 방향으로 일정 속도로 이동합니다.
2-2. 이동 시간 종료 시 대기 상태로 전환
timer += Time.deltaTime;
if (timer >= patrolTime)
{
timer = 0f;
idleTime = Random.Range(1f, 5f);
animator.SetBool("isRun", false);
ChangeState(MonsterState.IDLE);
}
설명:
- 이동 시간이 끝나면 애니메이션을 멈추고
IDLE
상태로 돌아갑니다.
2-3. 플레이어 탐지 시 추적 상태 전환
if (targetDist <= traceDist && isTrace)
{
timer = 0f;
ChangeState(MonsterState.TRACE);
}
설명:
- Idle 상태와 동일하게 플레이어가 근처에 있다면 추적을 시작합니다.
Patrol 상태 핵심 코드
transform.position += Vector3.right * moveDir * speed * Time.deltaTime;
ChangeState(MonsterState.TRACE);
기능 설명
- 현재 방향(moveDir)으로 이동을 지속합니다.
- 타이머가 끝나면 다시 IDLE로 전환하며 휴식 상태에 들어갑니다.
- 플레이어가 일정 거리 안으로 들어오면 TRACE 상태로 전환됩니다.
3. Trace 상태
전체 코드 (C#)
더보기
더보기
public override void Trace()
{
var targetDir = (target.position - transform.position).normalized;
transform.position += Vector3.right * targetDir.x * speed * Time.deltaTime;
var scaleX = targetDir.x > 0 ? 1 : -1;
transform.localScale = new Vector3(scaleX, 1, 1);
if (targetDist > traceDist)
{
animator.SetBool("isRun", false);
ChangeState(MonsterState.IDLE);
}
if (targetDist < attackDist)
{
ChangeState(MonsterState.ATTACK);
}
}
3-1. 플레이어 방향 계산 후 이동
var targetDir = (target.position - transform.position).normalized;
transform.position += Vector3.right * targetDir.x * speed * Time.deltaTime;
설명:
- 플레이어와의 위치 차이를 계산하고, 그 방향으로 이동합니다.
3-2. 방향에 따라 캐릭터 좌우 반전
var scaleX = targetDir.x > 0 ? 1 : -1;
transform.localScale = new Vector3(scaleX, 1, 1);
설명:
- 방향에 따라 스프라이트를 좌우 반전시켜 자연스럽게 이동하게 합니다.
3-3. 추적 범위 이탈 시 Idle로 전환
if (targetDist > traceDist)
{
animator.SetBool("isRun", false);
ChangeState(MonsterState.IDLE);
}
설명:
- 플레이어가 멀어지면 추적을 멈추고 대기 상태로 돌아갑니다.
3-4. 공격 범위 진입 시 Attack 상태 전환
if (targetDist < attackDist)
{
ChangeState(MonsterState.ATTACK);
}
설명:
- 공격 가능 거리까지 접근하면 ATTACK 상태로 전환됩니다.
Trace 상태 핵심 코드
transform.position += Vector3.right * targetDir.x * speed * Time.deltaTime;
ChangeState(MonsterState.IDLE);
ChangeState(MonsterState.ATTACK);
기능 설명
- 플레이어의 방향을 계산한 뒤, 그 방향으로 이동합니다.
- 이동 방향에 따라 캐릭터의 좌우 방향을 전환합니다.
- 일정 거리 이상 멀어지면 추적을 멈추고 IDLE 상태로 전환합니다.
- 가까워지면 공격(ATTACK) 상태로 전환됩니다.
4. Attack 상태
전체 코드 (C#)
더보기
더보기
public override void Attack()
{
if (!isAttack)
StartCoroutine(AttackRoutine());
}
IEnumerator AttackRoutine()
{
isAttack = true;
animator.SetTrigger("Attack");
yield return new WaitForSeconds(1f);
animator.SetBool("isRun", false);
yield return new WaitForSeconds(attackTime - 1f);
isAttack = false;
ChangeState(MonsterState.IDLE);
}
4-1. 공격 중복 방지
if (!isAttack)
StartCoroutine(AttackRoutine());
설명:
- 이미 공격 중이면 중복 실행하지 않도록 막습니다.
4-2. 공격 루틴 실행
IEnumerator AttackRoutine()
{
isAttack = true;
animator.SetTrigger("Attack");
yield return new WaitForSeconds(1f);
animator.SetBool("isRun", false);
yield return new WaitForSeconds(attackTime - 1f);
isAttack = false;
ChangeState(MonsterState.IDLE);
}
설명:
- 공격 애니메이션 재생 후,
attackTime
이 지나면 IDLE로 상태를 전환합니다.
Attack 상태 핵심 코드
StartCoroutine(AttackRoutine());
animator.SetTrigger("Attack");
ChangeState(MonsterState.IDLE);
기능 설명
isAttack
플래그로 공격 중복을 방지합니다.- 공격 애니메이션을 실행하고 일정 시간 후 다시
IDLE
상태로 돌아갑니다. attackTime
만큼 대기한 후 다시 다른 상태로 전환 가능해집니다.
5. 마무리 요약
MonsterCore
는 FSM 기반 구조로 설계되어 유지보수와 확장이 용이함- 각 상태는 독립적으로 구성되어 상태 전이 로직이 명확함
Goblin
과 같은 자식 클래스는Init()
과 각 상태만 정의하면 쉽게 구현 가능- 상태별 기능을 분리하면 디버깅과 설계 구조 파악에 도움이 됨
- 향후 공격 범위, 체력 시스템, 리스폰 처리 등 추가 확장 가능
'멋쟁이사자처럼 > 부트캠프' 카테고리의 다른 글
[멋쟁이사자차럼 부트캠프] 유니티 게임 개발 5기 - 34일차 : 인벤토리 기능 / 2D Rigging (1) | 2025.07.03 |
---|---|
[멋쟁이사자차럼 부트캠프] 유니티 게임 개발 5기 - 33일차 : 공격 판정 / 체력바 / 아이템 획득 (1) | 2025.07.02 |
[멋쟁이사자차럼 부트캠프] 유니티 게임 개발 5기 - 31일차 : 탐험 씬(Adventure Scene) 레벨 디자인 (0) | 2025.06.30 |
[멋쟁이사자차럼 부트캠프] 유니티 게임 개발 5기 - 29일차 : Camera Follow / 건물 이동 / NPC (1) | 2025.06.26 |
[멋쟁이사자차럼 부트캠프] 유니티 게임 개발 5기 - 27일차 : 조이스틱 구현, 애니메이션 블렌드 트리 (3) | 2025.06.24 |