▶️ GameManager.Logic()추가
- Run()에서 윈도우 데트타임일 경우 Logic()을 호출해줍니다.
- Logic()은 게임을 만들기위한 단계들을 분리하여 실행할 함수입니다.
- 프레임별로 게임을 처리할 함수들을 추가합니다.
// GameManager.h
void Logic ();
// 사용자의 입력 받는 기능 - 키 등. deltaTime을 이용하여 계산할 예정.
void Input ( float DeltaTime );
// 데이터의 갱신 담당
void Update ( float DeltaTime );
// 데이터들이 업데이트 되고 나서 처리할 함수.
void PostUpdate ( float DeltaTime );
// 충돌 처리
void Collision ( float DeltaTime );
// 충돌 후 갱신
void PostCollisionUpdate( float DeltaTime );
// 화면 출력 담당
void Render ( float DeltaTime );
▶️ GameManager에서 그림을 그리기 위한 준비
멤버변수로 HDC mhDC의 추가,
소멸자에서 DC의 소멸 (ReleaseDC ( mhWnd, mhDC ); 호출. GetDC()로 호출한 dc는 반드시 소멸시켜줘야 합니다.)
Init()에서 윈도우 생성 후 mhDC에 GetDC()를 이용하여 DC를 받아옵니다.
▶️ 공용으로 사용될 클래스를 생성해봅시다.
1) '02.Share' 필터를 생성
2) Include 폴더 안에 Share폴더를 추가합니다.
3) Timer 클래스를 생성해봅시다.
... 아이콘을 눌러서 include 폴더 안의 Share폴더를 선택하여
Timer 클래스과 헤더 파일의 생성 위치를 지정해주고 클래스를 생성해줍니다.
Share폴더 안에 Timer 클래스가 생성되었습니다.
Shere필터 안에서도 클래스가 많아질 것을 대비하여
01.Timer 필터를 추가하여 정리하였습니다.
이런식으로 미리 용도에 맞춰서 필터와 폴더를 나눠서 미리 파일을 관리하면
파일이 많아졌을 때, 관리하기 용이해집니다.
▶️ 타이머 코드를 작성합니다.
변수 초기화를 위한 Init();
타이머에는 매프레임마다 deltaTime을 계산하고, 계산한 deltaTime을 리턴하는 update()
deltaTime만 리턴하는 GetDeltaTime()을 작성해줍니다.
▶️ Timer.h
class CTimer
{
friend class CGameManager;
private:
static LARGE_INTEGER mSecond;
static LARGE_INTEGER mTime;
static float mDeltaTime;
private:
static void Init ();
static float Update ();
public :
static float GetDeltaTime();
};
▶️Timer.cpp
#include "Timer.h"
LARGE_INTEGER CTimer::mSecond = {};
LARGE_INTEGER CTimer::mTime = {};
float CTimer::mDeltaTime = 0.1f;
void CTimer::Init ()
{
QueryPerformanceFrequency ( &mSecond );
QueryPerformanceCounter ( &mTime );
}
float CTimer::Update ()
{
LARGE_INTEGER Time;
QueryPerformanceCounter ( &Time );
// 현재 흐른 시간 = (현재 타임 - 이전타임) / 초당 고해상도값
mDeltaTime = ( Time.QuadPart - mTime.QuadPart ) / ( float ) mSecond.QuadPart;
return mDeltaTime;
}
float CTimer::GetDeltaTime ()
{
return mDeltaTime;
}
▶️ GameManager.h
#pragma once
#include "GameInfo.h"
class CGameManager
{
private :
static bool mLoop;
// 윈도우 HINSTANCE 저장.
HINSTANCE mhInst = 0;
HWND mhWnd = 0;
HDC mhDC = 0;
// 윈도우 클래스 이름 저장.
TCHAR mClassName[256] = {};
TCHAR mTitleName[256] = {};
public :
// 윈도우창 만들 때 HINSTANCE가 필요. wWinMain에서 인자로 받아서 사용한다
bool Init ( HINSTANCE hInst );
// wWinMain() 반환타입을 int타입으로 받기 때문에
// 윈도우창이 종료되고 반환할 값을 동일하게 반환하는 Run() 함수를 만들어준다.
int Run ();
private:
void RegisterWindowClass ();
bool Create ();
void Logic ();
// 사용자의 입력 받는 기능 - 키 등. deltaTime을 이용하여 계산할 예정.
void Input ( float DeltaTime );
// 데이터의 갱신 담당
void Update ( float DeltaTime );
// 데이터들이 업데이트 되고 나서 처리할 함수.
void PostUpdate ( float DeltaTime );
// 충돌 처리
void Collision ( float DeltaTime );
// 충돌 후 갱신
void PostCollisionUpdate( float DeltaTime );
// 화면 출력 담당
void Render ( float DeltaTime );
static LRESULT CALLBACK WndProc ( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam );
DECLARE_SINGLE ( CGameManager )
};
▶️ GameManager.cpp
#include "GameManager.h"
#include "resource.h"
#include "Share/Timer.h"
DEFINITION_SINGLE ( CGameManager )
bool CGameManager::mLoop = true;
CGameManager::CGameManager ()
{
}
CGameManager::~CGameManager ()
{
ReleaseDC ( mhWnd, mhDC );
}
bool CGameManager::Init ( HINSTANCE hInst )
{
mhInst = hInst;
// lstrcpy() : 유니코드 문자열 복사함수
// Text()를 사용해도 되고 L""로 문자열을 넣어도 된다( 동일함 )
lstrcpy ( mClassName, TEXT( "MyFramework" ) );
lstrcpy ( mTitleName, TEXT( "MyFramework" ) );
RegisterWindowClass ();
if ( !Create () )
return false;
// 인자로 들어간 윈도우에 출력할 수 있는 DC가 만들어진다.
mhDC = GetDC ( mhWnd );
// 타이머 초기화
CTimer::Init ();
return true;
}
int CGameManager::Run ()
{
MSG msg;
while ( mLoop )
{
if ( PeekMessage ( &msg, nullptr, 0, 0, PM_REMOVE ) )
{
// 키보드 입력 메세지가 발생할 경우 동작한다.
// WM_KEYDOWN, WM_KEYUP 등 메세지가 발생하면 문자일 경우 WM_CHAR 메세지를 하나더
// 만들어주는 역할을 한다.
TranslateMessage ( &msg );
// 메세지를 WndProc로 전달해준다.
DispatchMessage ( &msg );
}
// 윈도우 데드타임일 경우 동작한다.(메세지 큐에 메세지가 없는 경우)
else
{
Logic ();
}
}
return ( int ) msg.wParam;
}
void CGameManager::RegisterWindowClass ()
{
WNDCLASSEXW wcex;
// 윈도우클래스 구조체의 크기를 나타낸다. 반드시 지정되어야 한다.
wcex.cbSize = sizeof ( WNDCLASSEX );
// 화면에 출력가능한 영역을 클라이언트 영역이라고 한다.
// 클라이언트 영역의 크기(가로, 세로)가 변경될 시 전부 다시 그려주도록 한다.
wcex.style = CS_HREDRAW | CS_VREDRAW;
// 메세지큐에서 얻어온 메세지를 인자로 넣어서 호출해줄 함수의 주소를 넘겨준다.
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
// 운영체제에서 부여해준 HINSTANCE를 전달한다.
wcex.hInstance = mhInst;
// 실행파일 아이콘을 지정한다.
// MAKEINTRESOURCE() : 인자로 들어간 리소스 아이디를 이용해서 리소스를 로드하는 함수.
// #include "resource.h" 필요
wcex.hIcon = LoadIcon ( mhInst, MAKEINTRESOURCE ( IDI_ICON1 ) );
// 윈도우 창에서의 커서 모양을 나타낸다.
wcex.hCursor = LoadCursor ( nullptr, IDC_ARROW );
// 클라이언트 영역의 색상을 지정한다.
wcex.hbrBackground = ( HBRUSH ) ( COLOR_WINDOW + 1 );
// 윈도우 메뉴를 지정한다.
// 0을 대입하면 메뉴를 없앤다.
wcex.lpszMenuName = 0;
// 등록할 윈도우클래스의 이름을 지정한다.
wcex.lpszClassName = mClassName;
// 윈도우창 좌측 상단의 작은 아이콘을 지정한다.
wcex.hIconSm = LoadIcon ( wcex.hInstance, MAKEINTRESOURCE ( IDI_ICON1 ) );
// 위에서 설정한 윈도우클래스를 등록한다.
RegisterClassExW ( &wcex );
}
bool CGameManager::Create ()
{
// CreateWindow : 윈도우 창을 생성해주는 함수이다.
// WinAPI에서 함수명뒤에 W가 붙으면 유니코드, A가 붙으면 멀티바이트이다.
// 1번인자 : 윈도우 클래스 이름을 지정한다.
// 2번인자 : 윈도우 타이틀바에 출력할 이름을 지정한다.
// 3번인자 : 윈도우 창의 모양을 결정한다.
// 4번인자 : 화면에서 윈도우가 시작할 X지점을 지정한다.
// 5번인자 : 화면에서 윈도우가 시작할 Y지점을 지정한다.
// 6번인자 : 윈도우 창의 가로 크기를 지정한다.
// 7번인자 : 윈도우 창의 세로 크기를 지정한다.
// 8번인자 : 부모윈도우가 있을 경우 부모윈도우의 핸들을 지정한다.
// 9번인자 : 메뉴 핸들을 전달한다.
// 10번인자 : 윈도우 인스턴스를 전달한다. WinMain에서 전달은 값으로 전달해야 한다.
// 11번인자 : 창 생성 데이터를 지정한다. WM_CREATE는 윈도우 생성시 발생하는 메세지인데
// 이 메세지가 발생하면 WndProc 함수의 lParam에 이 값이 전달된다.
// 이렇게 윈도우를 생성하면 윈도우 핸들을 만들어준다.
// 잘못된 생성일 경우 0을 반환한다.
mhWnd = CreateWindowW ( mClassName, mTitleName, WS_OVERLAPPEDWINDOW,
//CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, mhInst, nullptr );
100, 100, 1280, 720, nullptr, nullptr, mhInst, nullptr );
if ( !mhWnd )
return false;
// 위에서 윈도우 창을 만들었다면 ShowWindow 함수를 이용해서 창을 보여줄지 숨길지를
// 결정한다.
ShowWindow ( mhWnd, SW_SHOW );
// 클라이언트 영역을 강제로 다시 그리게 요청해주는 함수이다.
// 처음 생성시나 특정상황에 창을 새로고침 해야 할 경우 사용한다.
UpdateWindow ( mhWnd );
return true;
}
// 게임을 만들기위한 단계들을 분리
void CGameManager::Logic ()
{
float DeltaTime = CTimer::Update ();
Input ( DeltaTime );
Update ( DeltaTime );
PostUpdate ( DeltaTime );
Collision ( DeltaTime );
PostCollisionUpdate ( DeltaTime );
Render ( DeltaTime );
}
void CGameManager::Input ( float DeltaTime )
{
}
void CGameManager::Update ( float DeltaTime )
{
}
void CGameManager::PostUpdate ( float DeltaTime )
{
}
void CGameManager::Collision ( float DeltaTime )
{
}
void CGameManager::PostCollisionUpdate ( float DeltaTime )
{
}
void CGameManager::Render ( float DeltaTime )
{
Rectangle ( mhDC, 500, 500, 200, 200 );
}
LRESULT CGameManager::WndProc ( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
switch ( message )
{
// 윈도우 창 종료메세지
case WM_DESTROY:
mLoop = false;
PostQuitMessage ( 0 );
break;
default:
// 위에서 지정한 메세지 외의 다른 메세지가 발생할 경우 윈도우의 기본 동작으로
// 처리가 되게 만들어준다.
return DefWindowProc ( hWnd, message, wParam, lParam );
}
return 0;
}
GameManager.Render()에서 Rectangle()을 이용하여 네모가 출력된 상태의 코드입니다.
실행하면
위와같이 네모가 출력됩니다.
네모를 출력하는 값을
화면의 크기보다 -1씩 작은 값으로 하면 창보다 조금 작은 네모가 출력될까요?
Rectangle ( mhDC, 1, 1, 1279, 719);
네모를 출력하는 부분을 위와같이수정하고 실행해보았습니다.
네모는 위와 왼쪽의 선만 출력됩니다.
화면을 늘려보면
그제서야 선이 나옵니다.
뭔가 창크기가 안맞는것을 알 수 있습니다.
우리가 1280, 720으로 창을만들면
클라이언트 영역을 1280, 720으로 만드는게 아닙니다.
윈도우 창 전체를 1280, 720의 크기로 만들기 때문에 그 크기안에
타이틀 바라던지 양옆의 크기를 조절하기 위한 티프레임의 크기등이 모두 포함됩니다.
윈도우 클라이언트 영역의 크기를 원하는 크기로 지정해봅시다.
GameManager.Create()에서
CreateWindowW() 호출 후,
ShowWindow() 전에 윈도우 클라이언트 영역의 크기를 원하는 크기로 지정하는 코드를 넣습니다.
// 윈도우 클라이언트 영역의 크기를 원하는 크기로 지정한다.
// 위에서 지정한 윈도우 크기는 타이틀바 등의 크기가 모두 합쳐진 크기로 지정된다.
RECT WindowRC = { 0, 0, 1280, 720 };
AdjustWindowRect ( &WindowRC, WS_OVERLAPPEDWINDOW, FALSE );
위 문장을 삽입 후 ShowWindow에 브레이크 포인트를 걸고 디버그모드로 실행해봅시다
-8, -31부터 1288,728까지 생성되었습니다.
티프레임이 8픽셀 잡아먹는 것을 확인하실 수 있습니다.
// 클라이언트 영역이 1280, 720(WindowRC)이 되기 위해 필요한 윈도우 전체 크기를 설정해준다.
// (ThickFrame, Menu, TitleBar 등이 포함된 전체크기)
AdjustWindowRect ( &WindowRC, WS_OVERLAPPEDWINDOW, FALSE );
SetWindowPos()를 이용해서 윈도우 위치를 다시 설정해줍시다.
// 윈도우 위치 설정.
// 2번째 인자 : 생성위치 HWND_TOPMOST-최상단
// 3,4번째 인자 : 시작위치
// 5번 인자 (cx) : 가로크기
// 6번 인자 (cy) : 세로크기
// 7번 인자 플래그 : SWP_NOMOVE - 이동불가 | SWP_NOZORDER - z의 순서를 변경하지 않음(깊이 변경 금지)
SetWindowPos ( mhWnd, HWND_TOPMOST, 100, 100, WindowRC.right - WindowRC.left, WindowRC.bottom - WindowRC.top, SWP_NOMOVE | SWP_NOZORDER );
이상태로 실행을하면
클라이언트 영역 크기에 맞게 네모가 그려지게 됩니다.
▶️최종 GameManager.cpp
#include "GameManager.h"
#include "resource.h"
#include "Share/Timer.h"
DEFINITION_SINGLE ( CGameManager )
bool CGameManager::mLoop = true;
CGameManager::CGameManager ()
{
}
CGameManager::~CGameManager ()
{
ReleaseDC ( mhWnd, mhDC );
}
bool CGameManager::Init ( HINSTANCE hInst )
{
mhInst = hInst;
// lstrcpy() : 유니코드 문자열 복사함수
// Text()를 사용해도 되고 L""로 문자열을 넣어도 된다( 동일함 )
lstrcpy ( mClassName, TEXT( "MyFramework" ) );
lstrcpy ( mTitleName, TEXT( "MyFramework" ) );
RegisterWindowClass ();
if ( !Create () )
return false;
// 인자로 들어간 윈도우에 출력할 수 있는 DC가 만들어진다.
mhDC = GetDC ( mhWnd );
// 타이머 초기화
CTimer::Init ();
return true;
}
int CGameManager::Run ()
{
MSG msg;
while ( mLoop )
{
if ( PeekMessage ( &msg, nullptr, 0, 0, PM_REMOVE ) )
{
// 키보드 입력 메세지가 발생할 경우 동작한다.
// WM_KEYDOWN, WM_KEYUP 등 메세지가 발생하면 문자일 경우 WM_CHAR 메세지를 하나더
// 만들어주는 역할을 한다.
TranslateMessage ( &msg );
// 메세지를 WndProc로 전달해준다.
DispatchMessage ( &msg );
}
// 윈도우 데드타임일 경우 동작한다.(메세지 큐에 메세지가 없는 경우)
else
{
Logic ();
}
}
return ( int ) msg.wParam;
}
void CGameManager::RegisterWindowClass ()
{
WNDCLASSEXW wcex;
// 윈도우클래스 구조체의 크기를 나타낸다. 반드시 지정되어야 한다.
wcex.cbSize = sizeof ( WNDCLASSEX );
// 화면에 출력가능한 영역을 클라이언트 영역이라고 한다.
// 클라이언트 영역의 크기(가로, 세로)가 변경될 시 전부 다시 그려주도록 한다.
wcex.style = CS_HREDRAW | CS_VREDRAW;
// 메세지큐에서 얻어온 메세지를 인자로 넣어서 호출해줄 함수의 주소를 넘겨준다.
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
// 운영체제에서 부여해준 HINSTANCE를 전달한다.
wcex.hInstance = mhInst;
// 실행파일 아이콘을 지정한다.
// MAKEINTRESOURCE() : 인자로 들어간 리소스 아이디를 이용해서 리소스를 로드하는 함수.
// #include "resource.h" 필요
wcex.hIcon = LoadIcon ( mhInst, MAKEINTRESOURCE ( IDI_ICON1 ) );
// 윈도우 창에서의 커서 모양을 나타낸다.
wcex.hCursor = LoadCursor ( nullptr, IDC_ARROW );
// 클라이언트 영역의 색상을 지정한다.
wcex.hbrBackground = ( HBRUSH ) ( COLOR_WINDOW + 1 );
// 윈도우 메뉴를 지정한다.
// 0을 대입하면 메뉴를 없앤다.
wcex.lpszMenuName = 0;
// 등록할 윈도우클래스의 이름을 지정한다.
wcex.lpszClassName = mClassName;
// 윈도우창 좌측 상단의 작은 아이콘을 지정한다.
wcex.hIconSm = LoadIcon ( wcex.hInstance, MAKEINTRESOURCE ( IDI_ICON1 ) );
// 위에서 설정한 윈도우클래스를 등록한다.
RegisterClassExW ( &wcex );
}
bool CGameManager::Create ()
{
// CreateWindow : 윈도우 창을 생성해주는 함수이다.
// WinAPI에서 함수명뒤에 W가 붙으면 유니코드, A가 붙으면 멀티바이트이다.
// 1번인자 : 윈도우 클래스 이름을 지정한다.
// 2번인자 : 윈도우 타이틀바에 출력할 이름을 지정한다.
// 3번인자 : 윈도우 창의 모양을 결정한다.
// 4번인자 : 화면에서 윈도우가 시작할 X지점을 지정한다.
// 5번인자 : 화면에서 윈도우가 시작할 Y지점을 지정한다.
// 6번인자 : 윈도우 창의 가로 크기를 지정한다.
// 7번인자 : 윈도우 창의 세로 크기를 지정한다.
// 8번인자 : 부모윈도우가 있을 경우 부모윈도우의 핸들을 지정한다.
// 9번인자 : 메뉴 핸들을 전달한다.
// 10번인자 : 윈도우 인스턴스를 전달한다. WinMain에서 전달은 값으로 전달해야 한다.
// 11번인자 : 창 생성 데이터를 지정한다. WM_CREATE는 윈도우 생성시 발생하는 메세지인데
// 이 메세지가 발생하면 WndProc 함수의 lParam에 이 값이 전달된다.
// 이렇게 윈도우를 생성하면 윈도우 핸들을 만들어준다.
// 잘못된 생성일 경우 0을 반환한다.
mhWnd = CreateWindowW ( mClassName, mTitleName, WS_OVERLAPPEDWINDOW,
//CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, mhInst, nullptr );
100, 100, 1280, 720, nullptr, nullptr, mhInst, nullptr );
if ( !mhWnd )
return false;
// 윈도우 클라이언트 영역의 크기를 원하는 크기로 지정한다.
// 위에서 지정한 윈도우 크기는 타이틀바 등의 크기가 모두 합쳐진 크기로 지정된다.
RECT WindowRC = { 0, 0, 1280, 720 };
// 클라이언트 영역이 1280, 720(WindowRC)이 되기 위해 필요한 윈도우 전체 크기를 설정해준다.
// (ThickFrame, Menu, TitleBar 등이 포함된 전체크기
AdjustWindowRect ( &WindowRC, WS_OVERLAPPEDWINDOW, FALSE );
// 윈도우 위치 설정.
// 2번째 인자 : 생성위치 HWND_TOPMOST-최상단
// 3,4번째 인자 : 시작위치
// 5번 인자 (cx) : 가로크기
// 6번 인자 (cy) : 세로크기
// 7번 인자 플래그 : SWP_NOMOVE - 이동불가 | SWP_NOZORDER - z의 순서를 변경하지 않음(깊이 변경 금지)
SetWindowPos ( mhWnd, HWND_TOPMOST, 100, 100, WindowRC.right - WindowRC.left, WindowRC.bottom - WindowRC.top, SWP_NOMOVE | SWP_NOZORDER );
// 위에서 윈도우 창을 만들었다면 ShowWindow 함수를 이용해서 창을 보여줄지 숨길지를
// 결정한다.
ShowWindow ( mhWnd, SW_SHOW );
// 클라이언트 영역을 강제로 다시 그리게 요청해주는 함수이다.
// 처음 생성시나 특정상황에 창을 새로고침 해야 할 경우 사용한다.
UpdateWindow ( mhWnd );
return true;
}
// 게임을 만들기위한 단계들을 분리
void CGameManager::Logic ()
{
float DeltaTime = CTimer::Update ();
Input ( DeltaTime );
Update ( DeltaTime );
PostUpdate ( DeltaTime );
Collision ( DeltaTime );
PostCollisionUpdate ( DeltaTime );
Render ( DeltaTime );
}
void CGameManager::Input ( float DeltaTime )
{
}
void CGameManager::Update ( float DeltaTime )
{
}
void CGameManager::PostUpdate ( float DeltaTime )
{
}
void CGameManager::Collision ( float DeltaTime )
{
}
void CGameManager::PostCollisionUpdate ( float DeltaTime )
{
}
void CGameManager::Render ( float DeltaTime )
{
Rectangle ( mhDC, 1, 1, 1279, 719);
}
LRESULT CGameManager::WndProc ( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
switch ( message )
{
// 윈도우 창 종료메세지
case WM_DESTROY:
mLoop = false;
PostQuitMessage ( 0 );
break;
default:
// 위에서 지정한 메세지 외의 다른 메세지가 발생할 경우 윈도우의 기본 동작으로
// 처리가 되게 만들어준다.
return DefWindowProc ( hWnd, message, wParam, lParam );
}
return 0;
}
'개발개발 > WinAPI & DirectX' 카테고리의 다른 글
연습 - 이동하는 적과 적의 총알 만들기 (0) | 2025.02.12 |
---|---|
키 입력, 총알 생성 및 이동처리 (0) | 2025.02.11 |
기본 구조 설계 : 게임관리자/게임정보/메인 클래스 제작, 폴더 정리를 위한 솔루션 제거와 기존 프로젝트를 솔루션에 추가 (0) | 2025.02.03 |
화면에 도형, 문자, 선 출력하기 (0) | 2025.02.02 |
윈도우 창 생성과 windows.cpp 파일 분석 (0) | 2025.02.01 |
댓글