개발개발/WinAPI & DirectX
연습 - 이동하는 적과 적의 총알 만들기
유잉유잉유잉
2025. 2. 12. 00:20
728x90
✨ 구현목표
조건1. 위, 아래로 이동하는 적 만들기. 플레이어와 동일하게 네모로 표현.
조건2. 적이 위로 이동 중 최상단에 닿으면 아래로 이동한다.
적이 아래로 이동 중 최하단에 닿으면 위로 이동한다.
조건3. 1초에 한번씩 플레이어 방향으로 향하는 총알을 발사한다.
✨ 구현 결과
▶️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 };
std::list<FRect> mEnemyBulletList;
FRect mEnemyRC = { 1100, 100, 1200, 200 };
int mEnemyDir = 1;
float mEnemyFireTime = 1.f;
float mEnemyFireTimer = 0.f;
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 );
// 적 관련 update 처리
void UpdateEnemy ( float DeltaTime );
// 총알 render
void RenderBullet ( std::list<FRect>::iterator iterBegin, std::list<FRect>::iterator iterEd, 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 )
{
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;
Bullet.Right = Bullet.Left + 50.f;
Bullet.Bottom = Bullet.Top + 50.f;
mBulletList.push_back ( Bullet );
}
}
void UpdateBullet ( std::list<FRect>::iterator iterBegin, std::list<FRect>::iterator iterEd, float moveSpeed, float DeltaTime )
{
std::list<FRect>::iterator iter = iterBegin;
std::list<FRect>::iterator iterEnd = iterEd;
for ( ; iter != iterEnd; ++iter )
{
( *iter ).Left += moveSpeed * DeltaTime;
( *iter ).Right += moveSpeed * DeltaTime;
}
}
void CGameManager::Update ( float DeltaTime )
{
UpdateBullet ( mBulletList.begin (), mBulletList.end (), 500.f, DeltaTime );
UpdateEnemy ( DeltaTime );
}
void CGameManager::PostUpdate ( float DeltaTime )
{
}
void CGameManager::Collision ( float DeltaTime )
{
}
void CGameManager::PostCollisionUpdate ( float DeltaTime )
{
}
void CGameManager::RenderBullet ( std::list<FRect>::iterator iterBegin, std::list<FRect>::iterator iterEd, float DeltaTime )
{
std::list<FRect>::iterator iter = iterBegin;
std::list<FRect>::iterator iterEnd = iterEd;
for ( ; iter != iterEnd; ++iter )
{
Ellipse ( mhDC, ( int ) ( *iter ).Left, ( int ) ( *iter ).Top,
( int ) ( *iter ).Right, ( int ) ( *iter ).Bottom );
}
}
void CGameManager::Render ( float DeltaTime )
{
//Rectangle(mhDC, 1, 1, 1279, 719);
Rectangle ( mhDC, ( int ) mRC.Left, ( int ) mRC.Top, ( int ) mRC.Right, ( int ) mRC.Bottom );
RenderBullet ( mBulletList.begin (), mBulletList.end (), DeltaTime );
RenderBullet ( mEnemyBulletList.begin (), mEnemyBulletList.end (), DeltaTime );
}
void CGameManager::UpdateEnemy ( float DeltaTime )
{
// 적 이동
mEnemyRC.Top += 300 * mEnemyDir * DeltaTime;
mEnemyRC.Bottom += 300 * mEnemyDir * DeltaTime;
if ( mEnemyRC.Top <= 0 )
{
mEnemyRC.Top = 0;
mEnemyRC.Bottom = 100;
mEnemyDir = 1;
}
else if ( mEnemyRC.Bottom >= 720 )
{
mEnemyRC.Top = 620;
mEnemyRC.Bottom = 720;
mEnemyDir = -1;
}
Rectangle ( mhDC, ( int ) mEnemyRC.Left, ( int ) mEnemyRC.Top, ( int ) mEnemyRC.Right, ( int ) mEnemyRC.Bottom );
// 총알
mEnemyFireTimer += DeltaTime;
if ( mEnemyFireTimer >= mEnemyFireTime )
{
mEnemyFireTimer -= mEnemyFireTime;
FRect Bullet;
Bullet.Right = mEnemyRC.Left;
Bullet.Left = Bullet.Right - 50.f ;
Bullet.Top = mEnemyRC.Top + 25.f;
Bullet.Bottom = Bullet.Top + 50.f;
mEnemyBulletList.push_back ( Bullet );
}
UpdateBullet ( mEnemyBulletList.begin (), mEnemyBulletList.end (), -500.f, DeltaTime );
}
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;
}
728x90