오브젝트설계
오브젝트 상속구조의 필요성
현재까지 진행한 내용은 Scene안에서 물체를 그리고 InputManager를 물체를 움직이게 한것이였다. 하지만 게임에서는 한 씬안에 여러가지 오브젝트들이 존재하며 오브젝트가 생성될때마다 일일이 로직을 추가하는 것은 현실적으로 불가능하다. 그렇기에 오브젝트를 관리하는 오브젝트 클래스가 필요로 하게 된다. 언리얼에서는 오브젝트->엑터->폰->캐릭터와 같은 상속구조로 오브젝트를 관리하고있으며, 이와 유사한 형태의 상속구조로 오브젝트를 관리하고자 한다.
오브젝트 생성
먼저 물체들의 최상위 클래스인 오브젝트 클래스가 필요하다. 그 안에는 오브젝트 타입과 위치, 기타 정보들 또한 넣어주는것이 좋다.
//Object.h
enum class ObjectType
{
None,
Player,
Monster,
Projectile,
Env,
};
class Object
{
public:
Object(ObjectType type);
virtual ~Object();
virtual void Init()abstract;
virtual void Update()abstract;
virtual void Render(HDC hdc)abstract;
public:
ObjectType GetObjectType() { return _type; }
Pos Getpos() { return _pos; }
void Setpos(Pos pos) { _pos = pos; }
protected:
ObjectType _type = ObjectType::None;
Pos _pos = {}; //위치 정보
Stat _stat = {}; //HP,speed와 같은 기타 정보
};
플레이어 생성
게임설계 하면서 가장 먼저 해야될 부분은 플레이어 생성이라고 생각한다. 일단 플레이를 할 수 있어야 그에 맞는 게임을 기능들을 추가할 수 있기 때문이다. 먼저 플레이어를 생성하고 플레이어를 대표할만한 원을 하나 그리도록 한다. 플레이어는 오브잭트를 상속받았기에 기본적인 구조는 오브젝트와 동일하다.
//Player.h
#pragma once
#include "Object.h"
class Player :public Object
{
public:
Player();
virtual ~Player()override;
virtual void Init()override;
virtual void Update()override;
virtual void Render(HDC hdc)override;
};
//Player.cpp
#include "pch.h"
#include "Player.h"
#include "InputManager.h"
#include "TimeManager.h"
#include "Projectile.h"
Player::Player():Object(ObjectType::Player)
{
}
Player::~Player()
{
}
void Player::Init()
{
_stat.hp = 100;
_stat.maxHp = 100;
_stat.speed = 500;
_pos.x = 400;
_pos.y = 500;
}
void Player::Update()
{
}
void Player::Render(HDC hdc)
{
Utils::DrawCircle(hdc,_pos,50);
}
이런식으로 플레이어가 생성되게 된다.
이제 이 플레이어가 움직이도록 설정해준다. 저번 포스팅에서 InputManger를 이용하여 사각형을 움직인것을 이용하여 플레이어에 적용시키도록 한다.
//Player.cpp
void Player::Update()
{
float deltaTime = GET_SINGLE(TimeManager)->GetDeltaTime();
// 거리 = 시간 * 속도
//언리얼의 경우 Tick(DeltaTime)으로 함수내에서 DeltaTime을 받는다.
if (GET_SINGLE(InputManager)->GetButton(KeyType::A))
{
_pos.x -= _stat.speed * deltaTime;
}
if (GET_SINGLE(InputManager)->GetButton(KeyType::D))
{
_pos.x += _stat.speed * deltaTime;
}
if (GET_SINGLE(InputManager)->GetButton(KeyType::W))
{
_pos.y -=_stat.speed * deltaTime;
}
if (GET_SINGLE(InputManager)->GetButton(KeyType::S))
{
_pos.y += _stat.speed * deltaTime;
}
if (GET_SINGLE(InputManager)->GetButton(KeyType::SpaceBar))
{
}
}
GameScene적용
새로 만든 오브젝트들을 새로운 Scene에 적용하여 확인하고자 한다.
//GameScene.h
private:
class Player* _player=nullptr;
//GameScene.cpp
void GameScene::Init()
{
_player = new Player();
_player->Init();
}
void GameScene::Update()
{
if (_player)_player->Update();
}
void GameScene::Render(HDC hdc)
{
if (_player)_player->Render(hdc);
}
적용하면 이런식으로 플레이어를 움직일수 있다.
오브젝트 관리
이런 식으로 플레이어라는 오브젝트를 생성하였다. 그런데 오브젝트는 플레이어 하나만 존재하지 않고 몬스터나 발사체와 같은 다수의 오브젝트들이 존재해야 게임이 진행될 것이다. 그렇다면 이러한 다수의 오브젝트들은 어디서 관리를 해야되는 것인가? 상용 엔진의 경우 Scene에서 오브젝트들을 관리하곤 한다. 하지만 온라인 게임일 경우 유저를 빠르게 찾아야되는 경우도 종종 발생한다. 만약 Scene에서만 오브젝트를 관리한다면 속도가 느려질수도 있다. 그렇기에 비효율적이지만 이중으로 오브젝트를 관리하는 방법도 알아두어야 한다 생각한다.
//ObjectManager.h
class ObjectManager
{
public:
DECLARE_SINGLE(ObjectManager);
~ObjectManager();
void Add(class Object* object);
void Remove(class Object* object);
void Clear();
const vector<Object*>& GetObject() { return _object; }
template<typename T>
T* CreateObject()
{
T* object = new T();
object->Init();
return object;
}
private:
vector<class Object*> _object;
};
//ObjectManager.cpp
ObjectManager::~ObjectManager()
{
}
void ObjectManager::Add(Object* object)
{
if (object == nullptr)return;
auto findit = find(_object.begin(), _object.end(), object);
if (findit != _object.end())return;
_object.push_back(object);
}
void ObjectManager::Remove(Object* object)
{
auto it = remove(_object.begin(), _object.end(), object);
_object.erase(it,_object.end());
delete object;
}
void ObjectManager::Clear()
{
for_each(_object.begin(), _object.end(), [=](Object* obj) {delete obj; });
_object.clear();
}
//Player.cpp
void Player::Update()
{
if (GET_SINGLE(InputManager)->GetButton(KeyType::SpaceBar))
{
Projectile* projectile = GET_SINGLE(ObjectManager)->CreateObject<Projectile>();
}
}
오브젝트 매니저를 사용하여 플레이어를 생성해준다.
//GameScene.cpp
void GameScene::Init()
{
Player* player=GET_SINGLE(ObjectManager)->CreateObject<Player>();
player->Setpos(Pos{ 400, 400 });
GET_SINGLE(ObjectManager)->Add(player);
}
void GameScene::Update()
{
const vector<Object*>&object=GET_SINGLE(ObjectManager)->GetObject();
for (Object* obj : object)
{
obj->Update();
}
void GameScene::Render(HDC hdc)
{
const vector<Object*>&object = GET_SINGLE(ObjectManager)->GetObject();
for (Object* obj : object)
{
obj->Render(hdc);
}
}
}
발사체 생성
플레이어가 공격을 한다는 의미에서 특정 버튼을 누르면 발사체 오브젝트가 생성되어 이동하는 것을 만들어 보고자 한다.
//Projectile.h
class Projectile :public Object
{
public:
Projectile();
virtual ~Projectile()override;
virtual void Init()override;
virtual void Update()override;
virtual void Render(HDC hdc)override;
};
//Projectile.cpp
Projectile::Projectile():Object(ObjectType::Projectile)
{
}
Projectile::~Projectile()
{
}
void Projectile::Init()
{
_stat.hp = 0;
_stat.maxHp = 0;
_stat.speed = 500;
}
void Projectile::Update()
{
float deltaTime = GET_SINGLE(TimeManager)->GetDeltaTime();
_pos.y -= deltaTime * _stat.speed;
}
void Projectile::Render(HDC hdc)
{
Utils::DrawCircle(hdc, _pos, 25);
}
적용하기
발사체까지 완성되었다면 미사일을 추가하고 실행하면 잘 작동이 되는것일까?
//Player.cpp
void Player::Update()
{
if (GET_SINGLE(InputManager)->GetButton(KeyType::SpaceBar))
{
Projectile* projectile = GET_SINGLE(ObjectManager)->CreateObject<Projectile>();
projectile->Setpos(_pos);
GET_SINGLE(ObjectManager)->Add(projectile);
}
}
보는 바와 같이 SpaceBar를 누르고 Projectile이 생성이 되면 크래쉬가 일어난다. 왜 이런 현상이 일어나는걸까?
문제점
현재 ObjectManager에서는 백터를 이용하여 생성된 Object들을 저장하고있다. 벡터와 같은 컨테이너를 사용할때 삽입과 삭제를 조심해서 사용해야되는데, 현재 코드에서는 원본에 대해서 추가와 삭제를 하고 있는 상황이다. 이럴경우 어떤식으로 처리를 하면 좋은 방식일까? DirectX에서 일부 배울 내용이지만 이럴 경우 일감을 분배하는 형식으로 처리를 하는것도 좋은 방법중 하나이다. 가장 쉬운 방법은 원본을 복사하여 다루는 방식일 것이다. 복사방식의 경우 문제가 없는 것일까? 복사 방식의 경우 추가의 경우 문제가 되지않는다. 다만 삭제의 경우 문제점이 발생하는데, 이미 복사한 시점에서 만약 삭제가 일어나게 된다면 원본과 복사된 데이터는 다르게 된다는 것이다.
발사체 생성
위와 같은 문제점이 있으나, 우선은 그점을 인지하고 복사형식으로 발사체를 생성하도록 하자.
다만 한가지 주의할 점은 삭제시에는 반드시 별도의 행동을 취하지말고 바로 return을 해야된다. 그렇지 않을 경우 댕글링포인터의 문제가 발생할수도 있다.
//Projectile.h
#include "Object.h"
class Projectile :public Object
{
public:
Projectile();
virtual ~Projectile()override;
virtual void Init()override;
virtual void Update()override;
virtual void Render(HDC hdc)override;
};
//Projectile.cpp
Projectile::Projectile():Object(ObjectType::Projectile)
{
}
Projectile::~Projectile()
{
}
void Projectile::Init()
{
_stat.hp = 0;
_stat.maxHp = 0;
_stat.speed = 500;
}
void Projectile::Update()
{
float deltaTime = GET_SINGLE(TimeManager)->GetDeltaTime();
_pos.y -= deltaTime * _stat.speed;
if (_pos.y < -200)
{
GET_SINGLE(ObjectManager)->Remove(this);
return; //삭제후 바로 종료해야된다.
}
}
void Projectile::Render(HDC hdc)
{
Utils::DrawCircle(hdc, _pos, 25);
}
몬스터 생성
발사체가 있다면 맞을 몬스터도 존재해야한다.
//Monster.h
class Monster :public Object
{
public:
Monster();
virtual ~Monster()override;
virtual void Init()override;
virtual void Update()override;
virtual void Render(HDC hdc)override;
};
//Monster.cpp
Monster::Monster():Object(ObjectType::Monster)
{
}
Monster::~Monster()
{
}
void Monster::Init()
{
_stat.hp = 100;
_stat.maxHp = 100;
_stat.speed = 10;
}
void Monster::Update()
{
}
void Monster::Render(HDC hdc)
{
Utils::DrawRect(hdc,_pos,50,50);
}
테스트
상용엔진에서는 콜리젼과 같은 충돌체를 이용하여 충돌을 계산한다. 이번 포스팅에서는 간단하게 발사체에 근접하면 충돌되었다고 판단하여 둘다 사라지는 식으로 충돌을 구현하였다.
//Projectile.cpp
void Projectile::Update()
{
float deltaTime = GET_SINGLE(TimeManager)->GetDeltaTime();
_pos.y -= deltaTime * _stat.speed;
const vector<Object*>obj = GET_SINGLE(ObjectManager)->GetObject();
for (Object* object : obj)
{
if (object == this)continue;
if (object->GetObjectType() != ObjectType::Monster)continue;
Pos p1=Getpos();
Pos p2=object->Getpos();
const float dx = p1.x - p2.x;
const float dy = p1.y - p2.y;
float dist = sqrt(dx*dx+ dy*dy);
if (dist < 25)//피격했을시 둘다 삭제
{
GET_SINGLE(ObjectManager)->Remove(object);
GET_SINGLE(ObjectManager)->Remove(this);
return;
}
}
if (_pos.y < -200)
{
GET_SINGLE(ObjectManager)->Remove(this);
return;
}
}
'게임엔진 > 윈도우API' 카테고리의 다른 글
윈도우API)8. 벡터 (0) | 2022.12.18 |
---|---|
윈도우API)7. 삼각함수 (0) | 2022.12.18 |
윈도우API)3. Scene과 SceneManager (0) | 2022.12.16 |
윈도우API)2. 프레임워크 제작 (0) | 2022.12.15 |
윈도우API)1. 기본템플릿 분석 (0) | 2022.12.10 |
댓글