[멋쟁이사자차럼 부트캠프] 유니티 게임 개발 5기 - 32일차 : DontDestroyOnLoad / 몬스터 FSM 설계

2025. 7. 1. 17:28·멋쟁이사자처럼/부트캠프

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 상속

Goblin은 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
'멋쟁이사자처럼/부트캠프' 카테고리의 다른 글
  • [멋쟁이사자차럼 부트캠프] 유니티 게임 개발 5기 - 34일차 : 인벤토리 기능 / 2D Rigging
  • [멋쟁이사자차럼 부트캠프] 유니티 게임 개발 5기 - 33일차 : 공격 판정 / 체력바 / 아이템 획득
  • [멋쟁이사자차럼 부트캠프] 유니티 게임 개발 5기 - 31일차 : 탐험 씬(Adventure Scene) 레벨 디자인
  • [멋쟁이사자차럼 부트캠프] 유니티 게임 개발 5기 - 29일차 : Camera Follow / 건물 이동 / NPC
khybin154
khybin154
khybin154 님의 블로그 입니다.
    티스토리 홈
    |
  • khybin154
    khybin154
    khybin154
  • 글쓰기 관리
  • 전체
    오늘
    어제
  • 공지사항

  • 인기 글

    • 분류 전체보기 N
      • 멋쟁이사자처럼 N
        • 부트캠프 N
  • 블로그 메뉴

    • 태그

      unity book
      인터페이스
      상속
      인벤토리 기능
      c#
      git
      unity
      p316 ~ p375
      유니티
      2d rigging
      unity 게임 개발 5기
      컴포넌트
      p149 ~ p247
      배열 폭탄
      오브젝트 풀 패턴
      실글턴 패턴
      게임 수학
      멋쟁이사자처럼후기
      charatercontroller
      고양이 게임 마무리
    • 링크

    • 최근 글

    • 최근 댓글

    • hELLO· Designed By정상우.v4.10.3
    khybin154
    [멋쟁이사자차럼 부트캠프] 유니티 게임 개발 5기 - 32일차 : DontDestroyOnLoad / 몬스터 FSM 설계
    상단으로

    티스토리툴바