0. 사전 준비

1) 메모리 누수 (Memory leak) 체크하기

// 메모리 릭 체크
#define _CRTDEBG_MAP_ALLOC
#include <cstdlib>
#include <crtdbg.h>

#ifdef _DEBUG
#define new new(_NORMAL_BLOCK, __FILE__, __LINE__);
#endif
// 메모리 릭을 체크하고 싶은 부분에 _CrtDumpMemoryLeaks(); 넣기
// 원래는 마지막 부분에 삽입해야함.
// 싱글톤이 소멸되는 코드가 Game의 소멸자 이후에 작동함.

pch.h에 추가한다.

Game의 소멸자에 _CrtDumpMemoryLeaks 함수를 넣으면 어떤 파일의 몇 번째 줄의 객체의 메모리가 소멸되지 않았는지 로그를 출력한다.

 

2) 도형을 그리는 함수 만들기

기존의 TextOut, Rectangle, Ellipse, MoveToEX - LineTo 함수를 쓰기에는 복잡하다.

따라서 더 간단하게 쓸 수 있도록 함수를 만든다.

 

2-1) Utils.h

#pragma once
#include <windows.h>
#include <string>
using namespace std;

class Utils
{
	// Typo 함수
	static void DrawText(HDC hdc, Pos pos, const wstring& str);

	static void DrawRect(HDC hdc, Pos pos, int32 w, int32 h);

	static void DrawCircle(HDC hdc, Pos pos, int32 radius);

	static void DrawLine(HDC hdc, Pos from, Pos to);

};

Pos는 x와 y로 구성되어 있고, Types 헤더에 아래 내용을 추가한다.

struct Pos
{
	float x = 0;
	float y = 0;
};

 

 

2-2) Utils.cpp

#include "pch.h"
#include "Utils.h"


// static_cast로 번거롭게 정수로 한 이유는?
// 세밀한 움직임을 구현하기 위해서 float로 만들어놨기 때문

void Utils::DrawText(HDC hdc, Pos pos, const wstring& str)
{
	::TextOut(hdc, static_cast<int32>(pos.x), static_cast<int32>(pos.y), str.c_str(), static_cast<int32>(str.size()));
}

void Utils::DrawRect(HDC hdc, Pos pos, int32 w, int32 h)
{
	::Rectangle(hdc, static_cast<int32>(pos.x - w / 2), static_cast<int32>(pos.y - h / 2), static_cast<int32>(pos.x + w / 2), static_cast<int32>(pos.y + h / 2));
}

void Utils::DrawCircle(HDC hdc, Pos pos, int32 radius)
{
	::Ellipse(hdc, static_cast<int32>(pos.x - radius), static_cast<int32>(pos.y - radius), static_cast<int32>(pos.x + radius), static_cast<int32>(pos.y + radius));
}

void Utils::DrawLine(HDC hdc, Pos from, Pos to)
{
	::MoveToEx(hdc, static_cast<int32>(from.x), static_cast<int32>(from.y), nullptr);
	::LineTo(hdc, static_cast<int32>(to.x), static_cast<int32>(to.y));

}

Pos 타입의 pos.x와 pos.y를 중심에 놓고 시작점과 끝점을 너비와 높이로 계산한다.

 

 

1. Scene 준비

1) enums.h

enum class SceneType
{
	None,
	DevScene,
	GameScene,
};

세가지 Scene의 종류를 만들어 관리할 예정이다.

 

2. SceneManager ; Scene을 관리하는 매니저

각 Scene마다 들어가는 Object(언리얼의 액터)들이 다를 수 있기 때문에 매니저로 관리하고 각 Object는 Scene 안에서 관리한다.

 

1) SceneManager.h

#pragma once


class SceneManager
{
	DECLARE_SINGLE(SceneManager);

public:
	void Init();
	void Update();
	void Render(HDC hdc);

	void Clear();
public:
	void ChangeScene(SceneType sceneType);


private:
	// 지금 출력 중인 Scene
	class Scene* _scene;
	SceneType _sceneType = SceneType::None;
};

지금 사용중인 Scene을 _scene으로 저장하여 관리할 것이다.

이어서 DevScene, GameScene, None의 정보를 가지고 있는 _sceneType을 None으로 지정한다.

 

3. Scene ; 언리얼 엔진에서의 Level

Scene은 DevScene, GameScene으로 분화되므로, 공통적인 기능을 순수 가상함수로 만들고, 소멸자를 가상함수로 구현한다.

#pragma once
class Scene
{
public:
	Scene();
	virtual ~Scene();

	virtual void Init() abstract;
	virtual void Update() abstract;
	virtual void Render(HDC hdc) abstract;

protected:
};

기본 상속구조를 위한 부모 클래스라 내용이 비어있긴 하지만 생성자와 소멸자를 .cpp 파일에서 정의하지 않으면 오류가 발생할 수 있으므로 꼭 정의를 해주어야 한다.

 

3-1) DevScene.h (GameScene도 유사하게 작성)

#pragma once
#include "Scene.h"

class DevScene : public Scene
{

public:
	DevScene();
	virtual ~DevScene() override;

	virtual void Init() override;
	virtual void Update() override;
	virtual void Render(HDC hdc) override;

public:
	//TEST
	Pos _playerPos = { 300, 300 };
};

 

3-2) DevScene.cpp (GameScene도 유사하게 작성)

#include "pch.h"
#include "DevScene.h"
#include "Utils.h"
#include "InputManager.h"
#include "TimeManager.h"

DevScene::DevScene()
{

}

DevScene::~DevScene()
{

}

void DevScene::Init()
{

}

void DevScene::Update()
{
	if (GET_SINGLE(InputManager)->GetButton(KeyType::A))
	{
		_playerPos.x -= 1;
	}

	if (GET_SINGLE(InputManager)->GetButton(KeyType::D))
	{
		_playerPos.x += 1;
	}

	if (GET_SINGLE(InputManager)->GetButton(KeyType::W))
	{
		_playerPos.y -= 1;
	}

	if (GET_SINGLE(InputManager)->GetButton(KeyType::S))
	{
		_playerPos.y += 1;
	}
}

void DevScene::Render(HDC hdc)
{
	Utils::DrawCircle(hdc, _playerPos, 50);
}

 

4. SceneManager.cpp

더보기
#include "pch.h"
#include "SceneManager.h"
#include "DevScene.h"
#include "GameScene.h"

void SceneManager::Init()
{

}

void SceneManager::Update()
{
	if (_scene)
		_scene->Update();
}

void SceneManager::Render(HDC hdc)
{
	if (_scene)
		_scene->Render(hdc);
}

void SceneManager::Clear()
{
	SAFE_DELETE(_scene);
}

void SceneManager::ChangeScene(SceneType sceneType)
{
	// 변경하려는 Scene과 현재 Scene이 일치하면 종료
	if (_sceneType == sceneType)
		return;

	// 변경하려는 Scene을 newScene이라 명명
	Scene* newScene = nullptr;

	switch (sceneType)
	{
	case SceneType::DevScene:
		newScene = new DevScene;
		break;

	case SceneType::GameScene:
		newScene = new GameScene;
		break;
	}

	// Scene 변경을 위해 기존 scene 제거
	SAFE_DELETE(_scene);

	// 기존 Scene을 변경하려는 Scene으로 대체
	_scene = newScene;
	_sceneType = sceneType;

	newScene->Init();
}

Scene을 전환해주는 함수인 ChangeScene 함수를 만든다.

 

5. Game.cpp에서 Scene 교체하기

SceneManager.h를 include 한다.

더보기
#include "pch.h"
#include "Game.h"
#include "TimeManager.h"
#include "InputManager.h"
#include "SceneManager.h"
Game::Game()
{

}

Game::~Game()
{

}

void Game::Init(HWND hwnd)
{
	_hwnd = hwnd;
	_hdc = ::GetDC(hwnd);

	GET_SINGLE(TimeManager)->Init();
	GET_SINGLE(InputManager)->Init(hwnd);
	GET_SINGLE(SceneManager)->Init();

	GET_SINGLE(SceneManager)->ChangeScene(SceneType::DevScene);
}

void Game::Update()
{
	GET_SINGLE(TimeManager)->Update();
	GET_SINGLE(InputManager)->Update();
	GET_SINGLE(SceneManager)->Update();
}

void Game::Render()
{
	uint32 fps = GET_SINGLE(TimeManager)->GetFps();
	float deltaTime = GET_SINGLE(TimeManager)->GetDeltaTime();

	{ // 마우스 위치 출력
		POINT mousePos = GET_SINGLE(InputManager)->GetMousePos();
		wstring str = std::format(L"Mouse({0}, {1})", mousePos.x, mousePos.y);
		::TextOut(_hdc, 20, 10, str.c_str(), static_cast<int32>(str.size()));
	}

	{ // FPS, Delta Time 출력
		wstring str = std::format(L"FPS({0}), DT({1} ms)", fps, static_cast<int32>(deltaTime * 1000));
		::TextOut(_hdc, 650, 10, str.c_str(), static_cast<int32>(str.size()));
	}

	GET_SINGLE(SceneManager)->Render(_hdc);

}

 

5-1) SceneManager Init

void Game::Init(HWND hwnd)
{
	_hwnd = hwnd;
	_hdc = ::GetDC(hwnd);

	GET_SINGLE(TimeManager)->Init();
	GET_SINGLE(InputManager)->Init(hwnd);
	GET_SINGLE(SceneManager)->Init();

	GET_SINGLE(SceneManager)->ChangeScene(SceneType::DevScene);
}

SceneManager를 초기화하고 Scene을 내가 원하는 Scene으로 변경한다.

 

5-2) SceneManager Update

void Game::Update()
{
	GET_SINGLE(TimeManager)->Update();
	GET_SINGLE(InputManager)->Update();
	GET_SINGLE(SceneManager)->Update();
}

 

5-3) SceneManager Render

void Game::Render()
{
	// ~~~~~~~~
	GET_SINGLE(SceneManager)->Render(_hdc);
}

Game에서 HDC를 _hdc로 받아와서 사용하고 있다.

해당 _hdc를 SceneManager의 Render 함수에 넘겨주면

void SceneManager::Render(HDC hdc)
{
	if (_scene)
		_scene->Render(hdc);
}

해당 부분이 True가 되면서 위에서 ChangeScene에서 DevScene으로 변경하였으므로

DevScene의 Render 함수가 실행된다.

 

 

5. 속도 문제 (1) - FPS에 의해 이동 속도가 달라지는 문제

매 프레임마다 도형의 이동을 계산하고, 초당 프레임 수는 모든 컴퓨터마다 각기 다르기 때문에 발생하는 문제이다.

'거리 = 속력 * 시간'의 관계를 이용한다.

 

- DevScene.h

// 추가
	float _speed = 1000;
}

 

- DevScene.cpp

숫자로 하드코딩된 부분을 공식으로 대체한다.

void DevScene::Update()
{
	float deltaTime = GET_SINGLE(TimeManager)->GetDeltaTime();
	//거리 = 시간 * 속도

	if (GET_SINGLE(InputManager)->GetButton(KeyType::A))
	{
		_playerPos.x -= _speed * deltaTime;
	}

	if (GET_SINGLE(InputManager)->GetButton(KeyType::D))
	{
		_playerPos.x += _speed * deltaTime;
	}

	if (GET_SINGLE(InputManager)->GetButton(KeyType::W))
	{
		_playerPos.y -= _speed * deltaTime;
	}

	if (GET_SINGLE(InputManager)->GetButton(KeyType::S))
	{
		_playerPos.y += _speed * deltaTime;
	}
}

 

6. 속도 문제 (2) - 대각선으로 이동할 때 빨라지는 문제

x축 이동속도와 y축 이동속도, 두 벡터 합의 크기가 루트 2배이다.

당분간은 대각선 이동을 못하게 막거나 두 키가 동시에 눌렸을 때 속도를 1.414 (루트 2)로 설정한다.

 

 

7. 이동할 때 잔상이 남는 문제

-> 더블 버퍼링 ( 그래픽스 용어 )

; 추가적으로 버퍼를 하나 더 두고(백 버퍼), 화면에 출력하기(프론트 버퍼) 전에 (백 버퍼에)도형을 그린 후 프론트 버퍼로 넘기는 방식.