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;
}

 

 

 

<작업 결과>