본문 바로가기

게임 프로그래밍

몬스터 행동 패턴 구현 정리 (2D)

몬스터가 여러 동작을 수행할 때 애니메이션과, 동작에 대한 상태 변경에 대해서 간단하게 작성한 글입니다.

 

1. 애니메이션 자료구조

✅ AnimInfo

  • 애니메이션 스프라이트의 상태 정보 저장용 구조체입니다.
typedef struct AnimInfo
{
	int iFrameStartInit = 0; 	// 스프라이트의 시작 프레임
	int iFrameEndInit   = 0;	// 스프라이트의 끝 프레임
	int iFrameEnd = 0;			// 스프라이트 이미지의 끝 
	int iCurrentFrame = 0;	    // 현재 스프라이트의 프레임 (열 정보)
	int	iMotion		= 0;     // 해당 스프라이트의 몇번째 줄을 사용할 것인지? (행 정보)
	float fChangeTime   = 0.f;  // 현재 애니메이션이 어느정도 시간이 지났을 때 전환되는지.
	bool  bIsReverse = false;   // 현재 스프라이트를 뒤집어서 실행할 것인지.
}ANIMINFO;

 

✅ AnimProcessInfo

  • 애니메이션이 시간에 따라 동작하게 만들기 위한 정보.
typedef struct AnimProcessInfo
{
	float fCurTime      = 0.f;		// 현재 애니메이션의 진행 시간.
	int iAnimRepeatEndCnt = 0;		// 애니메이션을 몇번 반복할 것인지? -1이면 무한 반복, 
	int iAnimRepeatCurCnt = 0;		// 현재 몇번째 반복 중인지.
}ANIMPROCESS;

 

 

2. 애니메이션 처리 함수

int Class자료형::MoveAnimation(float fDeltaTime)
{
	// 0. 애니메이션 진행 중임을 반환, 1. 애니메이션이 종료되었음을 반환.

	if (!m_AnimInfo.bIsReverse)
    {
    	// 스프라이트 역재생이 아닌 경우
        
        // 1. AnimProcess의 CurTime에 fDeltaTime을 게임의 프레임마다 더해줍니다.
        // 2. AnimProcess의 CurTime이 Animation Frame의 전환 시간보다 커질 경우. 
        //   애니메이션 프레임을 증가시켜줍니다.
        // - 이때 Animation Frame이 마지막값에 도달했는지 체크하고, 마지막인 경우 아래 처리를 수행합니다.
        // - 애니메이션이 무한 반복인 경우 => 프레임을 시작 값으로 다시 초기화 해줍니다. return 0;
        // - 애니메이션이 반복이 끝난 경우 return 1;
        // - 반복횟수가 안채워졌을 경우 => 프레임을 시작 값으로 다시 초기화 해줍니다. return 0;
        // - 마지막이 아닌 경우에는 다음 프레임으로 전환합니다.
        
    }
    else
    {
    	// 스프라이트 역재생인 경우.
    }

	return 0;
}

 

  • 게임의 프레임마다 호출되어 애니메이션을 업데이트합니다.
  • 주요 동작:
    1. 누적 시간(fCurTime)에 fDeltaTime을 더함.
    2. fCurTime이 fChangeTime을 넘으면 다음 프레임으로 이동.
    3. 종료 조건에 따라 반복 또는 종료 처리.

 

 

3. 상태 기반 몬스터 AI 설계

✅ 상태 클래스 (State 클래스 계층)

  • 몬스터의 다양한 행동 패턴을 상태(State)로 나눠서 관리.

🔹 CSwordManState (SwordMan 상태에 대한 부모 클래스)

#pragma once
#include "CState.h"
#include "CSwordMan.h

class CSwordManState :
    public CState
{
public:   
	enum STATE : uint8_t
	{
		STATE_SPAWN = 0,
		STATE_IDLE,
		STATE_WALK,
		STATE_ATTACK,
		STATE_HIT,
		STATE_DEATH,
		STATE_END
	};

	const STATE& GetState() { return m_eState; }

protected:
	CSwordMan* m_pSwordMan;
	STATE	   m_eState = STATE_END;

public:
	CSwordManState(CSwordMan* pSwordMan) : m_pSwordMan(pSwordMan) {};
	virtual ~CSwordManState() {};
};

 

 

✅ 상태 매니저 (CSwordManStateMgr)

  • 현재 상태를 관리하고 전환하며, 상태 업데이트를 위임합니다.
#pragma once
#include "CSwordManState.h"

class CSwordManStateMgr
{
public:
	CSwordManStateMgr(CSwordMan* pSwordMan) : m_pSwordMan(pSwordMan), m_pCurState(nullptr)
	{
	}

	~CSwordManStateMgr()
	{
		SafeDelete(m_pCurState);
	}

	const CSwordManState::STATE& GetCurState() const { return m_pCurState->GetState(); }
	void ChangeState(CSwordState* pNewState);
	void Update();
	void LateUpdate();

private:
	CSwordMan* m_pSwordMan = nullptr;
	CSwordManState* m_pCurState = nullptr;
};

 

  • ChangeState: 현재 상태 제거 후 새로운 상태로 전환하고 Enter() 호출.
  • Update / LateUpdate: 매 프레임마다 현재 상태의 함수 실행.

 

 

 

4. 예시: IDLE 상태 구현

✅ CSwordManStateIdle 클래스

  • 몬스터가 대기 중일 때의 상태를 정의합니다.

🔹 Enter() 함수

void CSwordManStateIdle::Enter()
{
	// IDLE상태에 진입하면 해야할 일
	// 1. 애니메이션 초기화
	// 2. 애니메이션 프로스세스 초기화
	// 3. 이미지 DC 변경.
	// 4. 현재 이미지 크기조정

	// Animation 초기화.
	m_AnimInfo.iCurrentFrame = 0;
	m_AnimInfo.iFrameEnd = 0;
	m_AnimInfo.iFrameStartInit = 0;
	m_AnimInfo.iFrameEndInit = 0;
	m_AnimInfo.iMotion = 0;
	m_AnimInfo.fChangeTime = 0.f;

	// 프로세스 초기화
	m_AnimProcess.fCurTime = 0.f;
	m_AnimProcess.iAnimRepeatCurCnt = 0;
	m_AnimProcess.iAnimRepeatEndCnt = 0;

	if (m_pSwordMan->GetDirection() == CSwordMan::DIRECTION::DIR_RIGHT) // 왼쪽 오른쪽 방향 확인
	{
		m_pFrameKey = m_pSwordMan->GetFrameKey(CSwordMan::ANIM_STATE::STATE_RIGHT);
	}
	else
	{
		m_pFrameKey = m_pSwordMan->GetFrameKey(CSwordMan::ANIM_STATE::STATE_LEFT);
	}


	HDC hMemDC = CGameMgr::GetBmpMgr()->Find_Img(m_pFrameKey);
	if (hMemDC != nullptr)
	{
		m_pSwordMan->SetNewImageDC(hMemDC);
	}

	m_fPatternTime = 0.2f;
	m_pSwordMan->Set_ImageSize({ SWORDMAN_X, SWORDMAN_Y });
	m_pSwordMan->SetNewAnimInfo(m_AnimInfo);
	m_pSwordMan->SetNewAnimProcess(m_AnimProcess);
	m_pSwordMan->SetCanHit(true);
	m_pSwordMan->Set_Speed(0.f);
}
 

🔹 Update() 함수

void CSwordManStateIdle::Update()
{
	if (m_fPatternTime > 0.f)
	{
		m_fPatternTime -= DELTA_TIME;
	}
	else
	{
		// 시간이 지나면 WALK 상태로 전환
		m_pSwordMan->GetSwordStateMgr()->ChangeState(new CSwordStateWalk(m_pSwordMan));
	}
}

 

5. 전체 흐름 정리

[ 게임 프레임마다 실행되는 흐름 ]
1. CSwordMan::LateUpdate()
    → SwordStateMgr->Update()
        → 현재 State의 Update()
            → 애니메이션 또는 행동 처리