1. 오브젝트 틀 설계

a. Object 생성하기

  • 게임의 각 객체를 Scene처럼 체계적으로 관리하기 위한 기반 오브젝트 클래스를 생성합니다.
  • Object 클래스는 다양한 게임 요소(Player, Monster, Missile 등)의 최상위 부모 역할을 하며, 추상 클래스 형태로 구현됩니다.
  • 폴더 구조: 📁 03.Object > 📄 ObjectType.h
    • 폴더 구조를 통해 프로젝트의 계층 구조를 명확히 정리합니다.
  • enum class ObjectType: 게임 내 객체 유형(예: None, Player, Monster, Projectie)을 구분하여 타입 체크와 로직 처리를 용이하게 합니다.
  • 기본 정보 정의: 객체의 ID, 위치, 상태 등을 포함하며, 능력치(Stat)는 별도의 Types.h에 구조체로 정의해 재사용성을 높입니다.

b. 자식 클래스 작성하기: Player

  • Object 클래스를 기반으로 Player, Monster, Missile 등 구체적인 객체 클래스를 작성합니다.
  • Player 클래스는 📁 03.Object > 📁 Game > 📄 Player 경로에 생성되어 구조를 체계적으로 관리합니다.
  • Player는 Object를 상속받아 공통 메서드와 속성을 재사용하며, 플레이어 전용 기능을 추가로 구현합니다.
  • Init 메서드:
    • 체력, 속도, 위치 등 고정값을 초기화합니다.
    • 데이터 시트(Excel, JSON)에서 가져올 수 있지만, 예제에서는 하드코딩으로 설정합니다.
  • Update 메서드:
    • WASD 키 입력을 처리해 플레이어의 위치를 업데이트합니다.
    • 스페이스바를 누르면 미사일을 생성하고 현재 플레이어 위치에서 발사되도록 설정합니다.
  • Render 메서드:
    • Utils 라이브러리를 사용해 플레이어를 원형으로 렌더링합니다.

c. 자식 클래스 작성하기: Missile

  • Missile 클래스는 플레이어가 발사하는 투사체를 관리합니다.
  • Missile 객체는 📁 03.Object > 📁 Game > 📄 Missile에 작성되며, 초기화(Init), 업데이트(Update), 렌더링(Render)을 처리합니다.
  • Init 메서드:
    • 미사일의 초기 속도, 체력 등 고정값을 설정합니다.
  • Update 메서드:
    • 미사일은 매 프레임 위쪽으로 이동하며, 충돌 처리와 화면을 벗어난 경우 제거됩니다.
    • 충돌 처리는 미사일과 몬스터 간의 거리를 계산해 특정 범위 내에서 발생하며, 충돌 시 객체를 제거합니다.
  • Render 메서드:
    • 미사일은 화면에 작은 원형으로 렌더링됩니다.

d. 자식 클래스 작성하기: Monster

  • Monster 클래스는 📁 03.Object > 📁 Game > 📄 Monster 경로에 생성됩니다.
  • Init 메서드: 몬스터의 체력, 속도, 위치 등의 기본값을 설정합니다.
  • Render 메서드: Utils 라이브러리를 사용해 몬스터를 사각형으로 렌더링합니다.

2. 기능 구현하기

a. GameScene으로 교체하기

  • 초기 테스트용 DevScene을 제거하고, 실제 게임 플레이를 위한 GameScene을 활성화합니다.
  • GameScene에서 플레이어를 초기화하고, Update와 Render 메서드를 호출하여 객체 상태를 갱신 및 렌더링합니다.

b. 오브젝트 매니저 만들기

  • 미사일을 스폰하거나 관리하기 위한 오브젝트 매니저(ObjectManager)를 작성합니다.
  • 관리 방식:
    • 객체를 Scene에서 관리하거나 별도의 매니저에서 관리할 수 있습니다.
    • 이번 예제에서는 ObjectManager를 통해 모든 객체를 관리합니다.
  • ObjectManager는 객체 추가(Add), 제거(Remove), 초기화(Clear), 생성(CreateObject) 등의 기능을 제공합니다.
    • CreateObject 템플릿: 객체 생성 시 타입 검사를 통해 컴파일 타임에서 오류를 방지하고, 생성 후 초기화(Init)를 호출합니다.
    • 메모리 관리: 제거된 객체는 반드시 메모리를 해제하여 누수를 방지합니다.

c. GameScene에 ObjectManager 등장시키기

  • ObjectManager를 사용해 Player와 Monster를 관리합니다.
  • 플레이어 추가:
    • GameScene 초기화 시 플레이어를 생성하고 ObjectManager에 추가합니다.
  • 몬스터 생성:
    • GameScene 초기화 시 몬스터 5마리를 생성하고 ObjectManager에 추가합니다.
    • 각 몬스터의 위치는 일정한 간격으로 배치됩니다.

d. 미사일 코드 작성하기

  • 미사일 생성:
    • Player의 Update 메서드에서 스페이스바 입력 시 미사일을 생성하고 ObjectManager에 추가합니다.
  • 미사일 소멸:
    • 화면을 벗어나거나 몬스터와 충돌 시 미사일을 제거합니다.
    • 충돌 처리 시 복사된 객체 리스트를 순회하여 동시성 문제를 방지합니다.
  • 몬스터 적중 처리:
    • 미사일과 몬스터 간의 거리를 계산해 충돌 여부를 확인하고, 충돌 시 두 객체를 모두 제거합니다.

4. ObjectManager 상세 설명

  • Add 메서드:
    • 중복되지 않은 객체를 리스트에 추가합니다.
  • Remove 메서드:
    • 객체를 제거하고 메모리를 해제합니다.
  • Clear 메서드:
    • 모든 객체를 삭제하며, 객체의 메모리를 해제하고 리스트를 초기화합니다.

다음은 게임 오브젝트 설계와 구현을 설명하는 강의와 예제 코드를 분석하여 작성된 완성된 문서입니다. 주요 내용과 각 단계의 코드 분석이 포함되어 있습니다.


5. 오브젝트 틀 설계

a. Object 생성하기

Object 클래스는 게임 내 모든 요소(Player, Monster, Missile 등)의 최상위 부모 역할을 합니다. Scene처럼 오브젝트를 관리하며, 다른 게임 객체들이 상속받아 기능을 추가하도록 설계되었습니다.

#pragma once

enum class ObjectType
{
	None,
	Player,
	Monster,
	Projectie,
};

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;
	Stat _stat = {};
	Pos _pos = {};
};
  • enum class ObjectType: 게임 내 객체 유형을 구분합니다. 예를 들어, Player, Monster, Projectie는 각각 플레이어, 몬스터, 투사체를 의미합니다.
  • Init, Update, Render: 순수 가상 함수로 선언되어, 상속받는 클래스에서 구현해야 합니다.
  • _type, _stat, _pos: 객체의 타입, 능력치, 위치 정보를 저장합니다.

b. Object 구현

#include "pch.h"
#include "Object.h"

Object::Object(ObjectType type) : _type(type)
{
}

Object::~Object()
{
}
  • 생성자: 객체 생성 시 _type을 초기화합니다.
  • 소멸자: 가상 소멸자로 선언되어 상속받는 클래스의 소멸자가 올바르게 호출됩니다.

6. 자식 클래스 구현

a. Player 클래스

Player는 Object를 상속받아 플레이어 전용 기능을 추가한 클래스입니다.

#pragma once

#include "Object.h"

class Player : public Object
{
public:
	Player();
	virtual ~Player();

	virtual void Init() override;
	virtual void Update() override;
	virtual void Render(HDC hdc) override;
};
#include "pch.h"
#include "Player.h"
#include "TimeManager.h"
#include "InputManager.h"
#include "Missile.h"
#include "ObjectManager.h"

Player::Player() : Object(ObjectType::Player) {}
Player::~Player() {}

void Player::Init()
{
	_stat.hp = 100;
	_stat.maxHp = 100;
	_stat.speed = 500.0f;
	_pos.x = 400;
	_pos.y = 500;
}

void Player::Update()
{
	float deltaTime = GET_SINGLE(TimeManager)->GetDeltaTime();

	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)->GetButtonDown(KeyType::SpaceBar))
	{
		Missile* missile = GET_SINGLE(ObjectManager)->CreateObject<Missile>();
		missile->SetPos(_pos);
		GET_SINGLE(ObjectManager)->Add(missile);
	}
}

void Player::Render(HDC hdc)
{
	Utils::DrawCircle(hdc, _pos, 50);
}
  • Init: 플레이어의 능력치와 위치를 초기화합니다.
  • Update: 키 입력 처리(WASD 이동 및 스페이스바로 미사일 발사).
  • Render: 플레이어를 원형으로 렌더링.

b. Missile 클래스

Missile은 Object를 상속받아 투사체 전용 기능을 추가한 클래스입니다.

#pragma once

#include "Object.h"

class Missile : public Object
{
public:
	Missile();
	virtual ~Missile();

	virtual void Init() override;
	virtual void Update() override;
	virtual void Render(HDC hdc) override;
};
#include "pch.h"
#include "Missile.h"
#include "TimeManager.h"
#include "ObjectManager.h"

Missile::Missile() : Object(ObjectType::Projectie) {}
Missile::~Missile() {}

void Missile::Init()
{
	_stat.hp = 1;
	_stat.maxHp = 1;
	_stat.speed = 600;
}

void Missile::Update()
{
	float deltaTime = GET_SINGLE(TimeManager)->GetDeltaTime();
	_pos.y -= deltaTime * _stat.speed;

	const vector<Object*> objects = GET_SINGLE(ObjectManager)->GetObjects();
	for (Object* object : objects)
	{
		if (object == this || 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;
	}
}

void Missile::Render(HDC hdc)
{
	Utils::DrawCircle(hdc, _pos, 25);
}
  • Init: 미사일의 속도와 체력을 초기화합니다.
  • Update: 미사일 이동 및 충돌 처리. 화면을 벗어난 경우 제거.
  • Render: 미사일을 작은 원형으로 렌더링.

7. 기능 구현

a. GameScene 구현

#pragma once
#include "Scene.h"

class GameScene : public Scene
{
public:
	GameScene();
	virtual ~GameScene() override;

	virtual void Init() override;
	virtual void Update() override;
	virtual void Render(HDC hdc) override;
};
#include "pch.h"
#include "GameScene.h"
#include "Player.h"
#include "Monster.h"
#include "ObjectManager.h"

GameScene::GameScene() {}
GameScene::~GameScene() {}

void GameScene::Init()
{
	Player* player = GET_SINGLE(ObjectManager)->CreateObject<Player>();
	player->SetPos(Pos{ 400, 400 });
	GET_SINGLE(ObjectManager)->Add(player);

	for (int i = 0; i < 5; ++i)
	{
		Monster* monster = GET_SINGLE(ObjectManager)->CreateObject<Monster>();
		monster->SetPos(Pos{ static_cast<float>(i + 1) * 100, 100 });
		GET_SINGLE(ObjectManager)->Add(monster);
	}
}

void GameScene::Update()
{
	const vector<Object*> objects = GET_SINGLE(ObjectManager)->GetObjects();
	for (Object* object : objects)
		object->Update();
}

void GameScene::Render(HDC hdc)
{
	const vector<Object*> objects = GET_SINGLE(ObjectManager)->GetObjects();
	for (Object* object : objects)
		object->Render(hdc);
}
  • Init: 플레이어와 몬스터 초기화 및 추가.
  • Update: 모든 객체의 Update 호출.
  • Render: 모든 객체의 Render 호출.

b. ObjectManager 구현

#pragma once

class ObjectManager
{
public:
	DECLARE_SINGLE(ObjectManager);
	~ObjectManager();

	void Add(Object* object);
	void Remove(Object* object);
	void Clear();
	const vector<Object*>& GetObjects() { return _objects; }

	template<typename T>
	T* CreateObject()
	{
		static_assert(std::is_convertible_v<T*, Object*>);
		T* object = new T();
		object->Init();
		return object;
	}

private:
	vector<Object*> _objects;
};
#include "pch.h"
#include "ObjectManager.h"
#include "Object.h"

ObjectManager::~ObjectManager()
{
	Clear();
}

void ObjectManager::Add(Object* object)
{
	if (!object || std::find(_objects.begin(), _objects.end(), object) != _objects.end())
		return;

	_objects.push_back(object);
}

void ObjectManager::Remove(Object* object)
{
	if (!object) return;

	auto it = std::remove(_objects.begin(), _objects.end(), object);
	_objects.erase(it, _objects.end());
	delete object;
}

void ObjectManager::Clear()
{
	for (Object* object : _objects)
		delete object;

	_objects.clear();
}
  • Add: 객체를 중복 없이 추가.
  • Remove: 객체를 제거하고 메모리를 해제.
  • Clear: 모든 객체를 삭제하며 메모리를 정리.

profile
李家네_공부방

0개의 댓글