WIN32API FrameWork/원본
220503_WIN32API_Framework_3-2_입력받은대로 캐릭터 이동
hyrule
2022. 5. 5. 22:24
<Gameobject.h 변경사항>
* WIN32API에서 플레이어를 출력하려면 HDC 인자가 필요하므로 Render() 함수에 인자로 넘겨주도록 추가해 주었음.
- 상속받는 모든 자식 클래스도 변경해 줄것
virtual void Render(HDC hDC, float DeltaTime);
* 최상위 클래스인 GameObject에 변수 추가
- 오브젝트의 위치를 나타내는 m_Pos
- 오브젝트의 크기 m_Size
- 오브젝트의 중심점을 잡을 수 있게 해주는 m_Pivot
//Class CGameObject
//GameObject.h
#pragma once
//Include/GameObject/GameObject.h -> Include/GameInfo.h 이므로
#include "../GameInfo.h"
/*
* 게임오브젝트: 상속구조로 설계할 예정.
게임오브젝트 - 캐릭터 - 플레이어 - 나이트
- 아처
- 매지션
- ...
- NPC - ...
- ...
- 몬스터 -
- 아이템
*/
/*
* 앞으로는 클래스가 매우 많아질 거라 추가적인 정리정돈이 필요하다.
- GameObject 폴더를 만들어서 GameObject 관련 클래스들은 GameObject 폴더 안에 생성되게 할 예정
- Include 폴더 안으로 들어가서 GameObject 폴더를 생성한 뒤, 관련 클래스를 만들 때
- 클래스 cpp와 헤더의 이름 앞에 GameObject/를 붙여준다.
*/
class CGameObject
{
public:
CGameObject();
//상속할 것이므로 가상함수로 소멸자 선언
virtual ~CGameObject();
protected:
Vector2 m_Pos;
Vector2 m_Size;
//m_Size를 그리기 위한 중간점
Vector2 m_Pivot;
public:
const Vector2& GetPos() const
{
return m_Pos;
}
void SetPos(float x, float y)
{
m_Pos.x = x;
m_Pos.y = y;
}
void SetPos(const Vector2& Pos)
{
m_Pos = Pos;
}
const Vector2& GetSize() const
{
return m_Size;
}
void SetSize(float x, float y)
{
m_Size.x = x;
m_Size.y = y;
}
void SetSize(const Vector2& Size)
{
m_Size = Size;
}
const Vector2& GetPivot() const
{
return m_Pivot;
}
void SetPivot(float x, float y)
{
m_Pivot.x = x;
m_Pivot.y = y;
}
void SetPivot(const Vector2& Pivot)
{
m_Pivot = Pivot;
}
public:
virtual bool Init();
virtual void Update(float DeltaTime);
virtual void Render(HDC hDC, float DeltaTime);
//충돌 함수는 별도로 구현할 것이므로 만들지 않을 것임.
//virtual void Collision();
};
//Class Cplayer: public CCharacter
//Player.cpp
#include "Player.h"
CPlayer::CPlayer()
{
}
CPlayer::~CPlayer()
{
}
bool CPlayer::Init()
{
return true;
}
void CPlayer::Update(float DeltaTime)
{
//VK_RETURN: Enter키
//VK_F1: F1키
//VK_LBUTTON: 왼쪽 마우스 버튼
//GetAsyncKeyState: 키의 상태를 체크할 수 있다.
/*
<반환값>
0: 해당 키가 눌러진 상태가 아니다.
0x8000: 해당 키를 지금 누르고 있는 상태이다.
0x1: 해당 키를 이전 프레임에 눌렀다.
하지만 키를 눌렀는지에 대한 시스템은 나중에 따로 개별적으로 만들 것이고
지금 당장은 그냥 '눌렸는지'(0x8000)만 체크할 것이다.
*/
if (GetAsyncKeyState('W') && 0x8000)
{
m_Pos.y -= 300.f * DeltaTime;
}
if (GetAsyncKeyState('S') && 0x8000)
{
m_Pos.y += 300.f * DeltaTime;
}
if (GetAsyncKeyState('A') && 0x8000)
{
m_Pos.x -= 300.f * DeltaTime;
}
if (GetAsyncKeyState('D') && 0x8000)
{
m_Pos.x += 300.f * DeltaTime;
}
}
void CPlayer::Render(HDC hDC, float DeltaTime)
{
Vector2 RenderLT;
//렌더링을 시작할 위한 좌측 상단 점 구하기
//이유: 윈도우즈 좌표계는 맨 왼쪽 위가 0, 0이므로
//도형을 그릴때 해당 점부터 시작해야 한다.
RenderLT = m_Pos - m_Pivot * m_Size;
Ellipse(hDC, (int)RenderLT.x, (int)RenderLT.y,
(int)(RenderLT.x + m_Size.x), (int)(RenderLT.y + m_Size.y));
}
이후 GameManager에서 m_Player 클래스를 생성해 주고, Render 함수에 m_Player의 Render 함수 등록, m_Update에도 m_Player의 Update 함수를 등록한다.
//Class CGameManager
//GameManager.h
#pragma once
#include "GameInfo.h"
#include "SingletonMacro.h"
/*
[프레임과 게임 처리]
내 컴퓨터는 100프레임
PC방 컴퓨터는 200프레임
게임 코드는 1프레임당 한번 게임 로직을 처리
만약 게임 로직이 1프레임에 5씩 캐릭터를 이동시킨다면
내 컴퓨터에서 500 움직일 동안 PC방 컴퓨터는 1000을 움직이게 된다.
아주 심각한 문제
<해결법 - DeltaTime>
* 매 프레임마다 한 프레임을 그리는 데 걸렸던 시간을 변수에 저장한다.
* 그리고 다음 프레임을 그릴때 시간 단위를 위에서 저장한 변수로 처리한다.
* 그러면 프레임 수에 게임 속도가 빨라지거나 느려지는 문제가 발생하지 않는다.
* 그러므로 앞으로 모든 게임 로직 처리 함수에
인자로 DeltaTime을 넣어줄 것.
*/
//윈도우 클래스를 생성하고 전체 게임을 관리하는 클래스
//이 클래스틑 객체를 여러 개 만들 필요가 없으므로 싱글턴 패턴으로 만든다.
class CGameManager
{
//매크로를 활용해 싱글턴 패턴으로 생성
DECLARE_SINGLETON(CGameManager)
private:
/*
< 윈도우 핸들 >
* 일종의 번호로 되어 있음.
- 이 번호들은 커널 오브젝트에 속함.
* 커널: 운영체제가 사용하는 특수한 영역 및 핵심 부분. 사용자는 컨트롤 불가능
* 커널 오브젝트: 커널에 있는 메모리 - 커널에 값을 넣어 놓음
* 핸들은 각자 자신이 부여받은 번호에 속한 오브젝트들을 컨트롤하는 역할을 한다.
ex) HWND(윈도우 핸들): 윈도우 창을 컨트롤하기 위한 용도
HDC: WIN32에서 제공하는 그리기 도구. 윈도우 창의 클라이언트와 연결 됨.
*/
HINSTANCE m_hInst;
HWND m_hWnd;
//HDC는 GetDC 함수에 HWND를 인자로 집어넣어 운영체제로부터 할당받을 수 있다.
//사용을 다 했으면 ReleaseDC 함수를 통해 할당을 해제해 주어야 한다.
//DC = Device Context
HDC m_hDC;
static bool m_Loop;
//Timer 클래스 전방선언 후 cpp 파일에서 include
class CTimer* m_Timer;
class CPlayer* m_Player;
public:
bool Init(HINSTANCE hInst);
//Win32API 기본 코드의 메시지 루프 부분을 작동시킬 함수.
int Run();
private:
//[게임의 흐름을 구성하는 단계]
//1. 게임로직을 작성할 함수.
//이후 인자로 전달해 줄 DeltaTime은 여기서 구한다.
void Logic();
//2. 사용자의 입력
void Input(float DeltaTime);
//3. 입력받은 내용에 대해서 데이터 업데이트
// +인공지능들의 데이터 업데이트
//화면에 표시할 좌표를 만든다던지 하는 로직을 구현할 함수
void Update(float DeltaTime);
//4. 업데이트된 데이터를 토대로 충돌을 수행
//위에서 계산된 로직을 토대로 충돌하는 오브젝트들이 생겼는지 판단
void Collision(float DeltaTime);
//4. 출력이 되어야 하는 물체들을 판단.(화면 밖의 오브젝트들)
//5. 화면에 출력이 되어야 하는 물체들을 출력
void Render(float DeltaTime);
//윈도우 클래스 구조체를 생성하여 반환한다/
void Register();
//윈도우 창을 생성한다.
//WIN32API 기본코드의 Init_Instance 함수와 같은 기능
bool Create();
//전역 함수로 선언하여 함수의 주소가 하나만 존재하도록 한다.
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
};
//Class CGameManager
//GameManager.cpp
#include "GameManager.h"
#include "resource.h"
#include "Timer.h"
#include "GameObject/Player.h"
//클래스 바깥에서 정적 변수 초기화
DEFINITION_SINGLETON(CGameManager)
bool CGameManager::m_Loop = true;
CGameManager::CGameManager()
{
//메모리 릭 체크 함수
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
//_CrtSetBreakAlloc(100);
}
CGameManager::~CGameManager()
{
SAFE_DELETE(m_Timer);
SAFE_DELETE(m_Player);
//프로그램이 종료되면 DC를 제거한다.
//m_hWnd에서 생성된 m_hDC를 삭제하라.
ReleaseDC(m_hWnd, m_hDC);
}
bool CGameManager::Init(HINSTANCE hInst)
{
//인자로 들어오는 hInst를 저장하고
m_hInst = hInst;
//Register 함수를 통해 윈도우 클래스 구조체를 만들어주고 등록한다.
Register();
//윈도우 창을 생성하고 등록한다.
Create();
//타이머 생성, 동적할당이므로 소멸자에서 제거 예약
m_Timer = new CTimer;
m_Timer->Init();
//DC를 할당받는다.
//할당받은 이후에는 반드시 ReleaseDC를 이용해 할당을 해제해 주어야 한다.
m_hDC = GetDC(m_hWnd);
m_Player = new CPlayer;
m_Player->Init();
m_Player->SetPos(100.0f, 100.0f);
m_Player->SetSize(100.0f, 100.0f);
m_Player->SetPivot(0.5f, 0.5f);
return true;
}
int CGameManager::Run()
{
//운영체제가 만들어준 메시지를 얻어오기 위한 구조체이다.
MSG msg;
//메시지 루프 반복 여부를 m_Loop 변수로 판단한다.
while (m_Loop)
{
//PeekMessage - 주석 분리하여 블로그에 업로드함
//메시지 큐가 차있으면 메시지를 가져오고 큐에서 삭제(PM_REMOVE)
//메시지 큐가 비어있으면 false를 반환.
if (PeekMessage(&msg, m_hWnd, 0, 0, PM_REMOVE))
{
//두 함수는 한 쌍
//설명 주석은 분리하여 블로그에 따로 업로드하였음.
TranslateMessage(&msg);
DispatchMessage(&msg);
}
//메시지 큐에 처리할 메시지가 없으면
//(PeekMessage 함수가 false를 반환하면)
//여기로 들어와서 게임 로직을 처리한다.
else
{
Logic();
}
}
return (int)msg.wParam;
}
void CGameManager::Logic()
{
//매번 Logic이 실행될 때마다 DeltaTime 갱신
m_Timer->Update();
float DeltaTime = m_Timer->GetDelatTime();
Input(DeltaTime);
Update(DeltaTime);
Collision(DeltaTime);
Render(DeltaTime);
}
void CGameManager::Input(float DeltaTime)
{
}
void CGameManager::Update(float DeltaTime)
{
m_Player->Update(DeltaTime);
}
void CGameManager::Collision(float DeltaTime)
{
}
void CGameManager::Render(float DeltaTime)
{
//fps 출력
char FPSText[64] = {};
sprintf_s(FPSText, "* FPS: %d", (int)m_Timer->GetFPS());
TextOutA(m_hDC, 1100, 0,FPSText, strlen(FPSText));
//델타타임 출력
char DeltaTimeText[64] = {};
sprintf_s(DeltaTimeText, "* DeltaTime: %.5f", DeltaTime);
TextOutA(m_hDC, 1100, 20, DeltaTimeText, strlen(DeltaTimeText));
//작동시간 출력
static float CurrentTime = 0.f;
CurrentTime += DeltaTime;
char CurrentTimeText[64] = {};
sprintf_s(CurrentTimeText, "* Time: %.5f", CurrentTime);
TextOutA(m_hDC, 1100, 40, CurrentTimeText, strlen(CurrentTimeText));
//원 모양의 플레이어 출력
m_Player->Render(m_hDC, DeltaTime);
}
void CGameManager::Register()
{
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = m_hInst;
wcex.hIcon = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_ICON1));
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_ICON1));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = nullptr;
wcex.lpszClassName = TEXT("GameFramework");
RegisterClassExW(&wcex);
}
bool CGameManager::Create()
{
m_hWnd = CreateWindowW(TEXT("GameFramework"), TEXT("GameFramework"), WS_OVERLAPPEDWINDOW,
100, 0, 0, 0, nullptr, nullptr, m_hInst, nullptr);
if (!m_hWnd)
{
return FALSE;
}
RECT rc = { 0, 0, 1280, 720 };
AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW,FALSE);
MoveWindow(m_hWnd, 50, 50, rc.right - rc.left,
rc.bottom - rc.top, TRUE);
ShowWindow(m_hWnd, SW_SHOW);
UpdateWindow(m_hWnd);
return TRUE;
}
LRESULT CGameManager::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
//WIN32API 기본 코드에서 가져온 내용
//
//message 큐로부터 받은 message에 따라 어떤 동작을 할지 결정.
switch (message)
{
case WM_DESTROY:
m_Loop = false;
PostQuitMessage(0);
break;
//ESC 키 누르면 종료
case WM_KEYDOWN:
if (wParam == VK_ESCAPE)
{
m_Loop = false;
PostQuitMessage(0);
}
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
<작업 결과>