프로그래밍 공부
작성일
2023. 12. 5. 20:46
작성자
WDmil
728x90

AnimationInstancing에서 각 객체에 애니메이션을 분할적용하고, 원하는 애니메이션을 원하는 객체에 실행하도록 바꾸어보자.

 

우선. Instancing을 사용해서 데이터를 조절함으로 Buffer한개로 같은 객체 100개를 조절하는것을 알 수 있다.

 

그럼으로, ObjectMaster를 한개 두고. 해당 객체를 사용해서 나머지 객체의 행동사항을 조절해야함을 이해할 수 있을것이다.

 

한번 작업해보자.

 

// MonsterManager 클래스는 Singleton 패턴을 따르며 게임 내 몬스터를 관리하는 역할을 수행합니다.
class MonsterManager : public Singleton<MonsterManager>
{
private:
    // 몬스터 풀의 크기를 나타내는 상수
    const UINT POOL_SIZE = 100;

    // 몬스터 생성 간격을 나타내는 상수 (초 단위)
    const float SPAWN_INTERVAL = 1.0f;

    // 몬스터 생성 위치의 범위를 나타내는 상수
    const float SPAWN_RANGE = 20.0f;

private:
    // Singleton 클래스에 대한 friend 선언
    friend class Singleton;

    // 생성자 - 외부에서 인스턴스화를 방지하며 필요한 초기화를 수행합니다.
    MonsterManager();

    // 소멸자 - 메모리 누수를 방지하기 위해 몬스터 관리에 사용된 자원을 정리합니다.
    ~MonsterManager();

public:
    // 게임 업데이트 시 호출되는 함수
    void Update();

    // 몬스터 렌더링을 처리하는 함수
    void Render();

    // GUI에서 몬스터 정보를 렌더링하는 함수
    void GUIRender();

    void Play(UINT index, UINT clip);

    // 몬스터 매니저의 대상(Transform)을 설정하는 함수
    void SetTarget(Transform* target) { this->target = target; }
    // 몬스터 매니저의 대상(Transform)을 반환하는 함수
    Transform* GetTarget() { return target; }
    ModelAnimatorInstancing* GetInstancing() { return modelInstancing; }

private:
    // 몬스터 생성을 처리하는 내부 함수
    void Spawn();

    void Collision();

private:
    // 몬스터 인스턴싱에 사용되는 3D 모델 애니메이터
    ModelAnimatorInstancing* modelInstancing;

    // 몬스터 객체를 저장하는 벡터 컨테이너
    vector<TopViewMonster*> monsters;

    // 몬스터의 대상(Transform)
    Transform* target;

    // 몬스터 생성 간격을 추적하기 위한 변수
    float spawnTime = 0.0f;
};

 

Monster객체이다. Vector형태를 사용해서 원하는 오브젝트를 저장하고, ModelAnimatorInstancing이 객체의 에니메이션을 조정한다.

 

우리는 modelInstancing을 각 monster가 참조하여. 자기자신의 번호에 접속하고, 해당 번호의 애니메이션을 변경하도록 작성할 것 이다.

 

#include "Framework.h"

// MonsterManager 생성자
MonsterManager::MonsterManager()
{
    // 몬스터에 사용될 3D 모델 애니메이터 초기화
    modelInstancing = new ModelAnimatorInstancing("Monster");
    modelInstancing->ReadClip("Walk");
    modelInstancing->ReadClip("Run");
    modelInstancing->ReadClip("Attack1");
    modelInstancing->ReadClip("Hit1");
    modelInstancing->ReadClip("death");
    modelInstancing->CreateTexture();

    // 몬스터 배열 크기 설정
    monsters.resize(POOL_SIZE);

    // 몬스터 객체 초기화 및 모델 인스턴스에 추가
    FOR(POOL_SIZE)
    {
        monsters[i] = new TopViewMonster(modelInstancing->Add(), modelInstancing, i);
    }
}

// MonsterManager 소멸자
MonsterManager::~MonsterManager()
{
    delete modelInstancing;

    // 할당된 몬스터 객체들을 메모리에서 해제
    for (TopViewMonster* monster : monsters)
        delete monster;
}

// 게임 업데이트 시 호출되는 함수
void MonsterManager::Update()
{
    // 몬스터 생성 간격 업데이트
    spawnTime += DELTA;

    // 일정 간격마다 몬스터 생성 함수 호출
    if (spawnTime >= SPAWN_INTERVAL)
    {
        spawnTime -= SPAWN_INTERVAL;
        Spawn();
    }

    Collision();

    // 모델 인스턴싱 업데이트
    modelInstancing->Update();

    // 각 몬스터의 업데이트 함수 호출
    for (TopViewMonster* monster : monsters)
        monster->Update();
}

// 몬스터 렌더링을 처리하는 함수
void MonsterManager::Render()
{
    // 모델 인스턴싱 렌더링
    modelInstancing->Render();

    // 각 몬스터의 렌더링 함수 호출
    for (TopViewMonster* monster : monsters)
        monster->Render();
}

// GUI에서 몬스터 정보를 렌더링하는 함수
void MonsterManager::GUIRender()
{
    // 모델 인스턴싱의 GUI 렌더링
    modelInstancing->GUIRender();

    // 각 몬스터의 GUI 렌더링 함수 호출
    for (TopViewMonster* monster : monsters)
        monster->GUIRender();
}

void MonsterManager::Play(UINT index, UINT clip)
{
    modelInstancing->PlayClip(index, clip);
}

// 몬스터 생성을 처리하는 함수
void MonsterManager::Spawn()
{
    // 비활성화된 몬스터를 찾아 생성
    for (TopViewMonster* monster : monsters)
    {
        if (!monster->IsActive())
        {
            // 몬스터를 활성화하고 생성 위치 설정
            monster->SetActive(true);
            Vector3 pos = target->GetGlobalPosition();
            Vector3 direction;
            direction.x = MATH->Random(-1.0f, 1.0f);
            direction.z = MATH->Random(-1.0f, 1.0f);

            pos += direction.GetNormalized() * SPAWN_RANGE;
            monster->SetLocalPosition(pos);

            // 생성 후 바로 리턴하여 하나의 몬스터만 생성
            return;
        }
    }
}

void MonsterManager::Collision()
{
    for (TopViewMonster* monster : monsters)
    {
        if (FireBallManager::Get()->IsCollision(monster))
        {
            monster->SetAction(TopViewMonster::DAMAGE);
        }
    }
}

 

Manager는 각 객체의 생성과 업데이트, 애니메이션을 정의하고 구현한다음. 업데이트, 렌더역할을 가진다.

 

vertor로 생성된 Monsters는 삭제되지 않으며, 종료시 할당해제된다.

 

생성된 Monsters는 사망시, IsActive를 비활성화 하여 렌더타겟에서 사용되지 않는다.

 

Monster는 생성시 애니메이션 정의를 위해서 생성위치값, 애니메이션 정의체, 자신의 인덱스번호 를 부여받는다.

 

이로써, Monster는 객체관계를 조금 희생하지만, 자기스스로 애니메이션을 재정의하고 바꿀 수 있게되었다.

 

Monster의 코드를 살펴보자.

 

#pragma once

// TopViewMonster 클래스는 CapsuleCollider를 상속받아 몬스터의 기본 동작을 정의합니다.
class TopViewMonster : public CapsuleCollider
{
public:

    enum AnimState
    {
        WALK, RUN, ATK, HIT, DEAD
    };

    enum ActionState
    {
        PATROL, // 순찰 상태
        TRACE,  // 추적 상태
        ATTACK, // 공격 상태
        DAMAGE,    // 맞는 상태
        DIE,   // 죽는 상태
        NONE    // 상태 없음
    };

private:

    const float TRACE_RANGE = 10.0f;
    const float ATTACK_RANGE = 2.0f;

    const float DELETETIME = 5;
    const float ATTACKDELAY = 1;
    // 몬스터의 상태를 표현하는 열거형

public:
    // 생성자 - 몬스터의 초기 설정을 수행하며 Transform을 전달 받습니다.
    TopViewMonster(Transform* transform, ModelAnimatorInstancing* instancing, UINT num);

    // 소멸자 - 몬스터 관련 자원을 정리합니다.
    ~TopViewMonster();

    // 몬스터의 업데이트를 처리하는 함수
    void Update();

    // 몬스터를 렌더링하는 함수
    void Render();

    // 몬스터의 정보를 GUI로 렌더링하는 함수
    void GUIRender();

    void Hit(float input);

    // 몬스터의 행동 상태를 설정하는 함수
    void Play(AnimState clip);
    void SetAction(ActionState state);

    void SetEvent(int clip, Event event, float timeRatio);
    void ExcuteEvent();

    ActionState GetAction() { return curState; }

    UINT Getnumber() { return index; }
    bool NowChangeMotion() { return ChangeMotion; }


private:
    // 몬스터의 현재 행동 상태를 결정하는 함수
    void CheckAction();

    // 몬스터의 다양한 행동을 생성하는 함수
    void CreateActions();

    void EndDamage();
    void EndAttack();

private:
    // 몬스터의 3D 모델 Transform
    Transform* meshTransform;

    // 몬스터의 추적 대상(Transform)
    Transform* target = nullptr;

    // 몬스터의 번호.
    UINT index = 0;
    // 몬스터의 이동 속도
    float moveSpeed = 5.0f;

    // 몬스터의 회전 속도
    float rotSpeed = 10.0f;

    float HP = 100;
    float DeadTime = 0;
    float Attime = 0;
    bool ChangeMotion = false;
    // 몬스터의 현재 속도 벡터
    Vector3 velocity;

    // 몬스터의 현재 행동 상태
    ActionState curState = NONE;
    // 몬스터의 현재 애니메이션 상태
    AnimState animState = RUN;

    ModelAnimatorInstancing* instancing;
    ModelAnimatorInstancing::Motion* motion;
    
    // 몬스터의 다양한 행동을 담는 벡터
    vector<MonsterAction*> actions;

    vector<map<float, Event>> totalEvent;
    vector<map<float, Event>::iterator> eventIters;
};

 

Monster이다.

 

Monster는 자기자신의 Event처리를 위해 함수포인터객체인 totalEvent와 해당 함수포인터를 탐색하기 위한 eventIters를 가진다.

 

해당 객체가 vector형태로 정의되는 이유는, 여러개의 Event가 해당 Object에 정의될 수 있기 때문이다.

 

객체를 map형태로 하고, Event로 하는이유는, map은 작은수부터 큰수 순으로 정리됨으로. Animation의 실행시간에 따라 원하는 동작을 실행시킬 수 있기 때문이다.

 

#include "Framework.h"
#include "TopViewMonster.h"

// TopViewMonster 클래스 생성자
TopViewMonster::TopViewMonster(Transform* transform, ModelAnimatorInstancing* instancing, UINT num)
    : meshTransform(transform), index(num), instancing(instancing)
{
    // 전달받은 Transform을 부모로 설정하고 필요한 초기화 수행
    transform->SetParent(this);
    transform->SetTag("TopViewMonster");
    transform->Load();

    // 몬스터는 처음에는 비활성화 상태로 시작
    isActive = false;

    motion = instancing->GetMotion(index);
    totalEvent.resize(instancing->GetClipSize());
    eventIters.resize(instancing->GetClipSize());

    // 초기 행동 상태를 PATROL로 설정
    SetEvent(HIT, bind(&TopViewMonster::EndDamage, this), 0.8f);
    SetEvent(ATK, bind(&TopViewMonster::EndAttack, this), 0.8f);
    // 몬스터의 다양한 행동을 생성하는 함수 호출
    CreateActions();
}

// TopViewMonster 클래스 소멸자
TopViewMonster::~TopViewMonster()
{
    // 몬스터의 소멸자에서는 추가적인 정리 작업이 필요하지 않음
}

// 몬스터의 업데이트를 처리하는 함수
void TopViewMonster::Update()
{
    // 몬스터가 비활성화 상태인 경우 업데이트를 수행하지 않음
    if (!IsActive()) return;

    // 부모 클래스의 UpdateWorld 함수 호출
    CheckAction();
    ExcuteEvent();

    actions[curState]->Update();

    UpdateWorld();
}

// 몬스터를 렌더링하는 함수
void TopViewMonster::Render()
{
    // 몬스터가 비활성화 상태인 경우 렌더링을 수행하지 않음
    if (!IsActive()) return;
    // CapsuleCollider의 Render 함수 호출
    __super::Render();
}

// 몬스터의 정보를 GUI로 렌더링하는 함수
void TopViewMonster::GUIRender()
{
    // 몬스터가 비활성화 상태인 경우 GUI 렌더링을 수행하지 않음
    if (!IsActive()) return;

    // CapsuleCollider의 GUIRender 함수 호출
    __super::GUIRender();
}

void TopViewMonster::Hit(float input)
{
    HP -= input;
    if (HP <= 0)
    {
        curState = DIE;
        HP = 100;
    }
}

void TopViewMonster::Play(AnimState clip)
{
    MonsterManager::Get()->Play(index, clip);

    this->animState = clip;
    eventIters[clip] = totalEvent[clip].begin();
}

void TopViewMonster::SetEvent(int clip, Event event, float timeRatio)
{
    if (totalEvent[clip].count(timeRatio) > 0)
        return;

    totalEvent[clip][timeRatio] = event;
}

void TopViewMonster::ExcuteEvent()
{
    int clip = curState;


    if (totalEvent[clip].empty()) return;
    if (eventIters[clip] == totalEvent[clip].end()) return;

    // 모션 진행중인 비율
    float ratio = motion->runningTime / motion->duration;

    if (eventIters[clip]->first > ratio) return;

    eventIters[clip]->second();
    eventIters[clip]++;

}

// 몬스터의 행동 상태를 체크하는 함수
void TopViewMonster::CheckAction()
{
    if (curState == DAMAGE) return;

    SetColor(Float4(0, 1, 0, 1));

    // 대상이 설정되어 있지 않다면 MonsterManager에서 대상을 가져옴
    if (target == nullptr)
        target = MonsterManager::Get()->GetTarget();

    // 몬스터와 대상 간의 거리 계산
    float distance = (localPosition - target->GetLocalPosition()).Length();

    // 거리에 따라 행동 상태 설정
    if (distance < ATTACK_RANGE)
        SetAction(ATTACK);
    else if (distance < TRACE_RANGE)
        SetAction(TRACE);
    else
        SetAction(PATROL);
}

// 몬스터의 행동 상태를 설정하는 함수
void TopViewMonster::SetAction(ActionState state)
{
    // 현재 상태와 동일한 상태로 설정되면 무시
    if (curState == state) return;
    // 상태 변경 시 현재 상태 업데이트 및 해당 상태의 행동 시작
    curState = state;
    actions[state]->Start();
}

// 몬스터의 다양한 행동을 생성하는 함수
void TopViewMonster::CreateActions()
{
    // 순찰 및 추적 동작을 담은 행동 객체들을 생성하고 벡터에 추가
    actions.push_back(new MonsterPatrol(this));
    actions.push_back(new MonsterTrace(this));
    actions.push_back(new MonsterAttack(this));
    actions.push_back(new MonsterDamage(this));
}

void TopViewMonster::EndDamage()
{
    SetAction(PATROL);
}

void TopViewMonster::EndAttack()
{
    SetAction(PATROL);
}

 

Monster객체이다.

 

각 애니메이션은 정의된 조건하에 실행되나, 애니메이션이 정의되는곳은, actions에 객체형태로 저장한다.

 

그리고, 객체가 바뀔 때. Start가 한번 실행되고, 그 이후Update함수에서 바뀐 객체의 Update가 실행된다.

 

이로써 우리는 원하는 객체의 행동패턴을 각 다른 객체로 정의하고 사용할 수 있게되었다.

 

객체를 분리함으로써 좀더 정의하기 편하고. 원하는 행동을 다른방식으로 추가하기 매우 간편하다.

 

#pragma once

class TopViewMonster;

// MonsterAction 클래스는 몬스터의 다양한 행동을 정의하는 추상 클래스입니다.
class MonsterAction
{
public:
    // 생성자 - TopViewMonster 포인터를 전달받아 초기화
    MonsterAction(TopViewMonster* monster)
        : monster(monster) {};

    // 순수 가상 함수로써 몬스터의 행동 업데이트를 담당
    virtual void Update() = 0;

    // 행동이 시작될 때 호출되는 가상 함수
    virtual void Start() {};

protected:
    // 몬스터를 주어진 방향으로 이동시키는 함수
    void Move(Vector3 direction, float MoveSpeed, float rotSpeed);

    // 몬스터가 주어진 방향으로 회전하도록 하는 함수
    void LookAtRotate(Vector3 direction, float rotSpeed);

protected:
    // 몬스터 객체에 대한 포인터
    TopViewMonster* monster;
};

 

MonsterAction이다. Action을 통해 상속받아. 원하는 행동을 정의하고 실행할 수 있다.

 

Monster의 함수를 참조함으로 monster의 행동을 제어할 수 있다.

 

 

 

728x90