728x90
▶️ 키입력 처리하기
- GetAsyncKeyState() : 키 입력을 바로 처리할 수 있는 함수입니다.
- DX Input보다는 성능이 좋진 않습니다. 거의 비슷하긴 합니다.
void CGameManager::Input ( float DeltaTime )
{
// GetAsyncKeyState() : 키입력을 바로 처리할 수 있다.
// 한개의 문자키는 ' '로 감싸서 입력한다.
// &연산으로 0x8000을 하여 true가 나올 경우 해당키를 누르고 있는 상태이다.
// 특수키의 경우 이미 등록되어있다
// space의 경우 VK_SPACE를 사용하면 된다.
if ( GetAsyncKeyState ( 'W' ) & 0x8000 )
{
mRC.Top -= 400 * DeltaTime;
mRC.Bottom -= 400 * DeltaTime;
}
if ( GetAsyncKeyState ( 'S' ) & 0x8000 )
{
mRC.Top += 400 * DeltaTime;
mRC.Bottom += 400 * DeltaTime;
}
if ( GetAsyncKeyState ( 'A' ) & 0x8000 )
{
mRC.Left -= 400 * DeltaTime;
mRC.Right -= 400 * DeltaTime;
}
if ( GetAsyncKeyState ( 'D' ) & 0x8000 )
{
mRC.Left += 400 * DeltaTime;
mRC.Right += 400 * DeltaTime;
}
}
- 대각선의 경우 속도가 더 빠릅니다.
피타고라스의 정리를 통해 본다면 대각선이동이 40%가량 더 속도가 빠를거란 계산이 나옵니다.
a가 1이고 b가 1이라면 c는 루트2가 되고, 1.4즈음이 나오게 되기 때문입니다.
대각선이동을 했을 경우 바로 이동시키지 않고
방향만 구한 후,
방향에 대한 정규화를 통해 속도 벡터를 만들어서 처리합니다.
벡터 개념이 잡힌후에 대각선 이동 처리를 해야하기 때문에 일단 넘어갑니다
▶️ 총알 생성
space를 눌렀을 때, 총알 발사를 해봅시다.
- Ellipse를 이용하여 총알(원)을 만듭니다.
- 위치는 위쪽 빨간점이 시작점, 아래쪽 점이 끝점 입니다.
- x는 시작위치에서 길이만큼, y는 높이의 1/4만큼 잡으면 됩니다.
// GameManager.h
private :
...
std::list<FRect> mBulletList;
void CGameManager::Input ( float DeltaTime )
{
...
// space 누를 때, 플레이어 앞에 총알 생성 위치 저장
if ( GetAsyncKeyState ( VK_SPACE ) & 0x8000 )
{
FRect Bullet;
Bullet.Left = mRC.Left + 100.f; // 길이만큼
Bullet.Top = mRC.Top + 25.f; // 높이 100의 1/4만큼
Bullet.Right = Bullet.Left + 50.f;
Bullet.Bottom = Bullet.Top + 50.f;
mBulletList.push_back ( Bullet );
}
}
// GameManager.Render()
void CGameManager::Render ( float DeltaTime )
{
// player draw
Rectangle ( mhDC, (int)mRC.Left, ( int ) mRC.Top, ( int ) mRC.Right, ( int ) mRC.Bottom );
// 총알 그리기
std::list<FRect>::iterator iter = mBulletList.begin ();
std::list<FRect>::iterator iterEnd = mBulletList.end ();
for ( ; iter != iterEnd; iter++ )
{
Ellipse ( mhDC, ( int ) ( *iter ).Left, ( int ) ( *iter ).Top, ( int ) ( *iter ).Right, ( int ) ( *iter ).Bottom );
}
}
space키를 누를 경우 플레이어 오른쪽에 총알이 생성됩니다.
▶️ 총알 앞으로 이동시키기
void CGameManager::Update ( float DeltaTime )
{
// 총알의 오른쪽 이동
std::list<FRect>::iterator iter = mBulletList.begin ();
std::list<FRect>::iterator iterEnd = mBulletList.end ();
for ( ; iter != iterEnd; iter++ )
{
( *iter ).Left += 500.f * DeltaTime;
( *iter ).Right += 500.f * DeltaTime;
}
}
수정이 있는 전체코드
▶️ GameInfo.h
#pragma once
#include <Windows.h>
#include <list>
// 삭제 매크로
#define SAFE_DELETE(p) if(p) { delete p; p = nullptr; }
// 싱글톤 선언 매크로
#define DECLARE_SINGLE(Type)\
private : \
Type();\
~Type();\
private:\
static Type* mInst;\
public:\
static Type* GetInst()\
{\
if( !mInst )\
mInst = new Type;\
return mInst;\
}\
static void DestroyInst()\
{\
SAFE_DELETE(mInst);\
}
// 싱글톤 cpp 선언 매크로
#define DEFINITION_SINGLE(Type) Type* Type::mInst = nullptr;
struct FRect
{
float Left = 0.f;
float Top = 0.f;
float Right = 0.f;
float Bottom = 0.f;
};
▶️ 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] = {};
std::list<FRect> mBulletList;
FRect mRC = { 100, 100, 200, 200 };
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;
// 윈도우 클라이언트 영역의 크기를 원하는 크기로 지정한다.
// 위에서 지정한 윈도우 크기는 타이틀바 등의 크기가 모두 합쳐진 크기로 지정된다.
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 )
{
// GetAsyncKeyState() : 키입력을 바로 처리할 수 있다.
// 한개의 문자키는 ' '로 감싸서 입력한다.
// &연산으로 0x8000을 하여 true가 나올 경우 해당키를 누르고 있는 상태이다.
// 특수키의 경우 이미 등록되어있다
// space의 경우 VK_SPACE를 사용하면 된다.
if ( GetAsyncKeyState ( 'W' ) & 0x8000 )
{
mRC.Top -= 400 * DeltaTime;
mRC.Bottom -= 400 * DeltaTime;
}
if ( GetAsyncKeyState ( 'S' ) & 0x8000 )
{
mRC.Top += 400 * DeltaTime;
mRC.Bottom += 400 * DeltaTime;
}
if ( GetAsyncKeyState ( 'A' ) & 0x8000 )
{
mRC.Left -= 400 * DeltaTime;
mRC.Right -= 400 * DeltaTime;
}
if ( GetAsyncKeyState ( 'D' ) & 0x8000 )
{
mRC.Left += 400 * DeltaTime;
mRC.Right += 400 * DeltaTime;
}
if ( GetAsyncKeyState ( VK_SPACE ) & 0x8000 )
{
FRect Bullet;
Bullet.Left = mRC.Left + 100.f; // 길이만큼
Bullet.Top = mRC.Top + 25.f; // 높이 100의 1/4만큼
Bullet.Right = Bullet.Left + 50.f;
Bullet.Bottom = Bullet.Top + 50.f;
mBulletList.push_back ( Bullet );
}
}
void CGameManager::Update ( float DeltaTime )
{
// 총알의 오른쪽 이동
std::list<FRect>::iterator iter = mBulletList.begin ();
std::list<FRect>::iterator iterEnd = mBulletList.end ();
for ( ; iter != iterEnd; iter++ )
{
( *iter ).Left += 500.f * DeltaTime;
( *iter ).Right += 500.f * DeltaTime;
}
}
void CGameManager::PostUpdate ( float DeltaTime )
{
}
void CGameManager::Collision ( float DeltaTime )
{
}
void CGameManager::PostCollisionUpdate ( float DeltaTime )
{
}
void CGameManager::Render ( float DeltaTime )
{
// player draw
Rectangle ( mhDC, (int)mRC.Left, ( int ) mRC.Top, ( int ) mRC.Right, ( int ) mRC.Bottom );
// 총알 그리기
std::list<FRect>::iterator iter = mBulletList.begin ();
std::list<FRect>::iterator iterEnd = mBulletList.end ();
for ( ; iter != iterEnd; iter++ )
{
Ellipse ( mhDC, ( int ) ( *iter ).Left, ( int ) ( *iter ).Top, ( int ) ( *iter ).Right, ( int ) ( *iter ).Bottom );
}
}
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;
}
▶️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;
mTime = Time;
return mDeltaTime;
}
float CTimer::GetDeltaTime ()
{
return mDeltaTime;
}
728x90
'개발개발 > WinAPI & DirectX' 카테고리의 다른 글
연습 - 이동하는 적과 적의 총알 만들기 (0) | 2025.02.12 |
---|---|
게임실행 단계 분리, 타이머 클래스, 클라이언트 영역 조절 (0) | 2025.02.10 |
기본 구조 설계 : 게임관리자/게임정보/메인 클래스 제작, 폴더 정리를 위한 솔루션 제거와 기존 프로젝트를 솔루션에 추가 (0) | 2025.02.03 |
화면에 도형, 문자, 선 출력하기 (0) | 2025.02.02 |
윈도우 창 생성과 windows.cpp 파일 분석 (0) | 2025.02.01 |
댓글