* '리소스 파일' 필터 우클릭 -> 추가 -> 리소스에서 아이콘을 추가하면
GameFramework.rc, icon1.ico, resource.h 생성됨
//main.cpp
/*
[사전 설정]
1. 일단 생성된 프로젝트를 솔루션에서 제거(삭제 X)
2. 폴더를 나누어 정리
* 프로젝트 폴더/Include: 프로젝트 및 소스코드
* 프로젝트 폴더/Bin: 게임 만드는 실행파일, 이미지 등 리소스 파일
* 프로젝트 폴더/BinObj: 소스코드를 컴파일하기 위한 중간 Obj파일이 들어올 폴더
3. 프로젝트 폴더 안에 폴더를 제외한 기존 파일들을 모두 Include 폴더 안으로 이동
4. 솔루션 우클릭 -> 추가 -> 기존 프로젝트 들어가서
Include 폴더로 옮긴 프로젝트 다시 추가
5. 프로젝트 속성 - 상대 경로(프로젝트가 있는 Include 폴더 기준)로 설정
* 구성: 모든 구성
* 플랫폼: 모든 플랫폼
* 출력 디렉터리: ../Bin/
- 이전 폴더로 가서 -> Bin 폴더로 들어가라
* 중간 디렉터리: ../BinObj/
- 이전 폴더로 가서 -> BinObj 폴더로 들어가라
* 대상 이름 - 디버그했을때 나오는 프로그램과 일반 출력을 했을때 나오는 프로그램 명이 다르게
- 구성 - 모든 구성 항목: $(ProjectName)
- 구성 - Debug 항목: $(ProjectName)_Debug
cf)이전 폴더: ../
이전 폴더에 있는 Bin 파일: ../Bin
*/
#include "GameManager.h"
//Window 창의 진입점.
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
//전체 게임을 초기화한다. 초기화에 실패했을 경우 프로그램을 종료한다.
if (!CGameManager::GetInst()->Init(hInstance))
{
CGameManager::DestroyInst();
return 0;
}
//게임 동작
int ReturnValue = CGameManager::GetInst()->Run();
//게임이 종료된다면 모든 메모리를 정리해준다.
CGameManager::DestroyInst();
return ReturnValue;
}
//GameInfo.h
#pragma once
//이 헤더파일은 게임에서 공통으로 사용되는 내용들을 담아두고
//Include해서 사용할 목적으로 제작되었다.
//win32api이므로 windows 헤더는 반드시 필요.
#include <windows.h>
//리스트
#include <list>
//배열
#include <vector>
//정렬안되는 이진트리
#include <unordered_map>
//메모리릭 체크
#include <crtdbg.h>
#pragma once
//싱글턴 패턴으로 만들어주는 매크로 정의
#define DECLARE_SINGLETON(Type) \
private:\
static Type* m_Inst;\
public:\
static Type* GetInst()\
{\
if(!m_Inst)\
m_Inst = new Type;\
return m_Inst;\
}\
static void DestroyInst()\
{\
if(m_Inst)\
{\
delete m_Inst;\
m_Inst = nullptr;\
}\
}\
private:\
Type();\
~Type();
#define DEFINITION_SINGLETON(Type) Type* Type::m_Inst = nullptr;
//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:
HINSTANCE m_hInst;
HWND m_hWnd;
HDC m_DC;
/*루프 지속 여부를 확인할 변수
static 변수로 선언한 이유
계속 루프를 할 지 여부는 메시지를 통해 결정해야 한다.
메시지 루프 처리는 WinProc 함수에서 진행한다.
WinProc는 static 함수이다
-> 일반 선언 변수는 처리가 불가능하고
static 변수만 처리 가능하기 때문에 아래 변수를
static으로 선언한 것*/
static bool m_Loop;
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"
DEFINITION_SINGLETON(CGameManager)
bool CGameManager::m_Loop = true;
CGameManager::CGameManager()
{
//메모리 릭 체크 함수
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
//_CrtSetBreakAlloc(100);
}
CGameManager::~CGameManager()
{
}
bool CGameManager::Init(HINSTANCE hInst)
{
//인자로 들어오는 hInst를 저장하고
m_hInst = hInst;
//Register 함수를 통해 윈도우 클래스 구조체를 만들어주고 등록한다.
Register();
//윈도우 창을 생성하고 등록한다.
Create();
return true;
}
int CGameManager::Run()
{
//운영체제가 만들어준 메시지를 얻어오기 위한 구조체이다.
MSG msg;
/*
GetMessage: 메시지 큐에서 메시지를 꺼내오는 함수이다.
단, 메시지 큐가 비어있을 경우 메시지가 들어올 때까지 빠져나올 수 없다.
이렇게 멈춰서 대기하고 있는 것을 블로킹 모드라고 한다.
큐가 비어서 멈춰있는 시간을 윈도우 데드타임이라고 한다.
게임의 경우에는 잘 쓰이지 않는다.
사람이 아무리 키보드 마우스로 입력을 많이 해 봐야 컴퓨터가 처리하는 데에는
찰나의 시간밖에 안 걸리기 때문에, 데드타임이 매우 길기 때문이다.
그렇기에 게임의 경우에는 PeekMessage라는 함수를 사용한다.
메시지 큐에서 메시지를 꺼낸 뒤의 데드타임동안 프레임을 그리는 것이다.
&msg: 콜 바이 어드레스
*/
//메시지 루프 반복 여부를 m_Loop 변수로 판단한다.
while (m_Loop)
{
//PeekMessage: 이 함수도 메시지 큐에서 메시지를 꺼내오는 함수이다.
//단, 이 함수는 메시지 큐가 비어있을 경우
//false를 반환하며 바로 빠져나오게 된다.
if (PeekMessage(&msg, m_hWnd, 0, 0, PM_REMOVE))
{
/*
TranslateMessage - DispatchMessage: 일종의 한 세트
메시지큐에서 꺼내온 메시지를 TranslateMessage 함수로 넘겨주면
문자 키인지 F1, 방향키 같은 키인지를 판단해준다.
이러한 키들은 WM_KEYDOWN으로 메시지가 인식이 되고
문자 키는 WM_CHAR로 인식이 된다.
키를 누르면 문자키의 경우 WM_CHAR도 만들어져야 하기 떄문에
여기에서 WM_KEYDOWN이 일어나면 문자키의 경우 WM_CHAR메시지를 추가로 만들어서
메시지 큐에 넣어준다.
이 함수가 없을 경우 문자 키를 눌렀을 때 WM_CHAR가 발생하지 않으므로
반드시 써 줘야 한다.
*/
TranslateMessage(&msg);
//DispatchMessage 함수는 메시지큐에서 꺼내온 메시지를 메시지 처리 함수에 보내준다.
//WndProc로 보내주는 것이다.
DispatchMessage(&msg);
}
//메시지 큐에 처리할 메시지가 없으면
//(PeekMessage 함수가 false를 반환하면)
//여기로 들어와서 게임 로직을 처리한다.
else
{
Logic();
}
}
return (int)msg.wParam;
}
void CGameManager::Logic()
{
}
void CGameManager::Input(float DeltaTime)
{
}
void CGameManager::Update(float DeltaTime)
{
}
void CGameManager::Collision(float DeltaTime)
{
}
void CGameManager::Render(float DeltaTime)
{
}
void CGameManager::Register()
{
//레지스터에 등록할 윈도우 클래스 구조체를 만들어준다.
WNDCLASSEXW wcex;
//wcex의 사이즈를 저장. 존재이유는 잘 모름.
wcex.cbSize = sizeof(WNDCLASSEX);
//▼ 아래에 윈도우가 어떤 형태의 윈도우를 생성할지를 설정할 수 있다.
wcex.style = CS_HREDRAW | CS_VREDRAW;
//CS_HREDRAW: Horizontal Redraw | CS_VREDRAW Vertical Redraw
//가로세로 영역 크기가 바뀌었을 때 클라이언트 영역을 전부 다시 그려주는 기능
//WndProc(Window Procedure) 전역함수 -> 함수 포인터
//이벤트 -> 메시지 -> 메시지 큐 -> 메시지를 인자로 집어넣어서 처리해줄 함수를 지정
//메시지 큐에서 꺼내온 메시지를 인자로 전달하며 호출할 함수의 함수 주소를 등록한다.
//이런 식으로 함수 포인터를 등록해놓고 호출하는 방식을 '콜백'이라고 한다.
//WndProc은 전역 함수 포인터이다.
//멤버 함수 포인터와 전역 함수 포인터는 모양새가 다르다.
//전역 함수 포인터를 생성하려면 static 함수를 만들면 된다.
wcex.lpfnWndProc = WndProc;
//예약용(안쓰이지만 값 설정은 해 줘야됨.)
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
//윈도우 인스턴스를 등록한다.
wcex.hInstance = m_hInst;
/* 왼쪽 위에 뜨는 메뉴를 지정. nullptr을 등록하면 메뉴가 뜨지 않음.
게임을 만들 떄는 메뉴를 쓰지 않으므로 nullptr을 많이 씀
일단 사전 생성되어 있는 아이콘을 쓰고 싶으면
리소스 파일 우클릭 -> 추가 -> 리소스 -> icon
위 방법으로 아이콘을 생성하면,
resource.h라는 헤더가 생성되고
해당 파일을 확인하면 아이콘이 번호로
define 되어 있는 것을 확인할 수 있다.
해당 번호를 사용하면 됨(resource.h include 시킬것 */
//실행파일에 뜨는 아이콘
wcex.hIcon = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_ICON1));
//윈도우 창 왼쪽 위에 뜨는 작은 아이콘
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_ICON1));
//커서 변경. ex)IDC_WAIT: 대기
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
//배경화면
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
//메뉴를 사용할지 말지를 결정한다.
//게임에서는 메뉴가 일반적으로 필요 없으므로 nullptr`
wcex.lpszMenuName = nullptr;
/*
* 기본 설정은 프로젝트 '리소스 파일' 안의 StringTable에 들어있는 데이터를
불러와서 이름을 지정해주지만, 굳이 그렇게 할 필요 없이 여바로 지정해주어도 된다.
* 등록할 클래스의 이름을 유니코드 문자열로 만들어서 지정한다.
유니코드 형태의 문자열로 설정하려면 L"(문자열)" 형식으로 하면 된다.
* 하지만 TEXT("문자열")로 하는 것이 더 좋다.
TEXT 매크로는 프로젝트 설정이 유니코드로 되어있을 경우 유니코드 문자열로 만들어지고
멀티바이트로 되어있을 경우 멀티바이트 문자열로 만들어지게 된다.
*/
//wcex.lpszClassName = szWindowClass;
wcex.lpszClassName = TEXT("GameFramework");
//여기까지 했으면 등록은 전부 완료.
RegisterClassExW(&wcex);
}
bool CGameManager::Create()
{
/*
* 1번 인자는 윈도우 클래스에 등록할 이름이다.
* 2번 인자는 타이틀바에 표시할 이름이다.
- 1, 2번 인자의 기본값인 szWIndowClass와 szTitle 대신
그냥 문자열을 넣어 생성해줄수도 있다.
* 3번 인자는 이 윈도우 창이 어떻게 생성될지를 결정하는 선택 인자이다.
- 현재 인자를 WS_OVERLAPPEDWINDOW의 정의로 타고 들어가보면 여러가지 옵션이
조합되어 있는 것을 알 수 있다.
* 4, 5번 인자는 윈도우 창이 생설될 화면에서의 위치를 지정한다.
픽셀로 지정한다.
- 예를 들어 1920, 1080 해상도라면 거기에서 원하는 값을 넣어주면
해당 위치에 나오게 된다.
- 4번은 가로좌표, 5번은 세로좌표.
* 6번, 7번 인자는 윈도우창의 가로, 세로의 크기를 지정한다.
픽셀단위로 지정을 해준다.
- 이 값은 상단바까지 포함한 사이즈이므로
실제 화면이 표시되는 영역은 여기에 인자로 지정해 준 범위보다 좁다.
- 그러므로 여기서는 사이즈를 0으로 설정해 놓고
실제 화면이 표시되는 값의 범위를 조절해 주는 함수인
AdjustWindowRect() 함수를 이용해서 조절하는 것이 좋다.
* 8번 인자는 부모 윈도우가 있다면 부모 윈도우의 핸들을 지정한다.
없으면 nullptr을 지정한다.
* 9번 인자는 메뉴가 있다면 메뉴 핸들을 넣어주고 없으면 nullptr을 지정한다.
* 10번 인자는 윈도우 인스턴스를 지정하여 이 윈도우 인스턴스에 속한
윈도우 창을 만들어지게 된다.
* 11번 인자는 필요 없음.
* 이런 식으로 윈도우 창을 만들어주고 정상적으로 만들어졌다면
생성된 윈도우 창의 핸들을 반환해준다.
* HWND = Handle WiNdoW 윈도우 핸들
*/
//여기서는 클래스에서 잡아놓은 멤버변수에 할당.
m_hWnd = CreateWindowW(TEXT("GameFramework"), TEXT("GameFramework"), WS_OVERLAPPEDWINDOW,
100, 0, 0, 0, nullptr, nullptr, m_hInst, nullptr);
/*
#define WS_OVERLAPPEDWINDOW (WS_OVERLAPPED: 일반적인 윈도우 | \
WS_CAPTION: 창 이름 표시 | \
WS_SYSMENU: 시스템 메뉴(창 닫기 등) | \
WS_THICKFRAME: 테두리 | \
WS_MINIMIZEBOX: 최소화 가능 | \
WS_MAXIMIZEBOX: 최대화 가능)
#define WS_POPUPWINDOW (WS_POPUP: MessageBox와 같은 팝업 창 | \
WS_BORDER | \
WS_SYSMENU)
*/
if (!m_hWnd)
{
return FALSE;
}
//RECT: 사각형을 표현하기 위해서 지원하는 구조체이다.
//left, top, right, bottom 값으로 이루어져 있다.
//left와 top = 사각형의 좌측 상단 좌표
//right와 bottom = 사각형의 우측 하단 좌표
//두 점을 통해 직사각형을 그린다.
//윈도우 크기를 표현하는 Rect 구조체를 하나 만들어준다.
RECT rc = { 0, 0, 1280, 720 };
/*위에서 지정한 크기만큼 클라이언트 영역의 크기로 잡기 위해서
필요한 실제 윈도우의 크기를 얻어온다.
* 1번 인자: 원하는 창의 크기가 저장된 주소(RECT 구조체)
* 2번 인자: 조절할 창의 기본 형태가 무엇인지 전달
* 3번 인자: 메뉴바 있는지 여부 전달
* 해당 인자들을 전달해 주면,
1번 인자에 전달한 주소에 2번 인자, 3번 인자의 특성에 맞게
조절된 사이즈를 반환한다.
* 밑의 코드를 지나서 중단점을 잡고 디버그 모드에 들어가서
rc값을 확인되면 변경된 값이 들어간 것을 확인할 수 있다.*/
AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW,FALSE);
//위에서 얻어온 크기로 윈도우 창의 크기를 변경한다.
//(hWnd, X좌표, Y좌표, 가로길이, 세로길이, 다시 그리기 여부)
//디버그를 해 봤다면 알겠지만
//rc값에 음수가 있을수도 있으므로 뺄셈을 통해 계산해주어야 한다.
MoveWindow(m_hWnd, 50, 50, rc.right - rc.left,
rc.bottom - rc.top, TRUE);
//윈도우 창을 보여준다.
//1번인자에 들어간 핸들의 윈도우 창을 보여줄지 말지를 결정해준다.
//
//2번인자: 기본값인 nCmdShow 대신
//SW_SHOW(창을 보여준다), SW_HIDE(창울 숨긴다 - 백그라운드)를 넣어줄 수도 있다.
//cf)SW_HIDE 외에도 왼도우 서비스를 통해 창을 표시하지 않을 수도 있다.
ShowWindow(m_hWnd, SW_SHOW);
//이 함수를 호출하여 클라이언트 영역이 제대로 갱신되었다면
//0이 아닌 값을 반환하고
//갱신이 실패했을 경우 0을 반환한다.
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;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
'WIN32API FrameWork > 원본' 카테고리의 다른 글
220503_WIN32API_Framework_3-2_입력받은대로 캐릭터 이동 (0) | 2022.05.05 |
---|---|
220503_WIN32API_Framework_3-1_Vector2 구조체와 연산 만들기 (0) | 2022.05.05 |
220502_WIN32API_Framework_3_GameObject 상속 구조 짜기 (0) | 2022.05.04 |
220502_WIN32API_Framework_2_그리기, DeltaTime (0) | 2022.05.04 |
WIN32API 기본 코드 살펴보기 (0) | 2022.05.02 |