개발개발/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;
}

MyFramework.zip
0.03MB

728x90