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. 이동할 때 잔상이 남는 문제
-> 더블 버퍼링 ( 그래픽스 용어 )
; 추가적으로 버퍼를 하나 더 두고(백 버퍼), 화면에 출력하기(프론트 버퍼) 전에 (백 버퍼에)도형을 그린 후 프론트 버퍼로 넘기는 방식.
'Windows API & 게임 수학' 카테고리의 다른 글
1-6. 오브젝트(Object) 설계 (1) - 플레이어, 투사체, ObjectManager (0) | 2023.10.17 |
---|---|
1-5. 더블 버퍼링 (Double Buffering) (0) | 2023.10.17 |
1-3. 게임을 위한 프레임워크 제작하기(2) - TimeManager, InputManager (1) | 2023.10.16 |
1-2. 게임을 위한 프레임워크 제작하기(1) (0) | 2023.10.16 |
1-1. 기본 템플릿 분석 (1) | 2023.10.16 |