탱크 만들기

2025. 3. 10. 22:33원티드_언리얼RPG 2기/WinApi

게임의 기본적인 구조를 배웠다.

게임은 크게 Init(), Release(), Update(), Render() 의 반복이다.

초기화를하며 메모리를 할당하고, 메모리를 해제하고, 변경되는 위치를 업데이트 해주고 화면에 띄운다.

 

실습으로 미사일을 발사하는 탱크를 만들어봤다.

 

1. 탱크의 포신이 방향키 'a' , 'd'키로 움직인다.

2. 스페이스바 키 입력을 받아 미사일 한발 발사한다.

3. 미사일이 화면밖으로 나가야 다음 한발을 발사한다.

 

 

탱크부터 만들자

탱크를 사용하기위해 Tank* 타입의 tank를 만들자

 

#pragma once
#include "GameObject.h"

class Tank;
class MainGame : public GameObject
{
private:
	HDC hdc;   //handle device context, 출력에 필요한 정보를 가지는 데이터 구조체(좌표,색,굵기)등의 정보를 담고있다.
			   //HDC 는 DC의 정보를 저장하는 데이터 구조체의 위치를 알기위함, 하지만 포인터는 아니다.
	PAINTSTRUCT ps;  //그리기위한 정보
	HANDLE hTimer; 
	int mousePosX = 0, mousePosY = 0;
	wchar_t szText[128];
	Tank* tank; 

public:
	void Init();	
	void Release();	
	void Update();	
	void Render(HDC hdc);

	LRESULT MainProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);

	MainGame();
	~MainGame();
};

 

Gameobject.h 는 Init(), Release(), Update(), Render() 를 선언한 곳이다.

//Tank.h
#pragma once
#include "Missile.h"

using namespace std;

class Tank : public GameObject
{
private:
	POINT pos;
	int size;
	float damage;
	string name;
	RECT rcCollision;

	// 포신
	POINT barrelEnd;
	int barrelSize;
	float barrelAngle;	// 단위 : 도(degree)	0 ~ 180		~	360
						//	    : 라디안(radian)0f ~ 3.14f	~	6.28f
	Missile* missile;
	//Missile* LoadedMissile[]; //장전된 미사일을 담은 배열
public:
	void Init();	
	void Release();	
	void Update();	
	void Render(HDC hdc);
	void Move();
	void Fire();
	/*void MachineGun();*/

	void RotateBarrel(float angle);
	void Dead();
	Tank();
	~Tank();
};

 

 탱크의 헤더파일로 탱크의 정보(위치, 크기, 포신의 정보, 미사일 객체)와 탱크의 동작들이 담겨있다.

pos는 POINT타입으로 (x,y) 좌표값을 가지고있는 구조체이다. 탱크는 포신을 움직이거나 미사일의 발사를 알려줄것이다.

//Tank.cpp
#include "Tank.h"
#include "CommonFunction.h"

void Tank::Init()
{
	pos.x = WINSIZE_X / 2;
	pos.y = WINSIZE_Y;
	size = 100;
	damage = 10;
	name = "탱크";
	rcCollision = GetRectAtCenter(pos.x, pos.y, size, size);
	
	// 포신
	barrelSize = 90;
	barrelEnd.x = pos.x;
	barrelEnd.y = pos.y - barrelSize;
	barrelAngle = 3.14f / 4.0f;	// 45도

	//미사일 생성
	missile = new Missile();
	/*LoadedMissile[10] = new Missile();*/
}

void Tank::Release()
{
	/*delete[] LoadedMissile;*/
	delete missile;
}

void Tank::Update()
{
	barrelEnd.x = pos.x + barrelSize * cosf(barrelAngle);
	barrelEnd.y = pos.y - barrelSize * sinf(barrelAngle);
	missile->Update();
}

void Tank::Render(HDC hdc)
{
	// 몸통
	RenderEllipseAtCenter(hdc, pos.x, pos.y, size, size);
	// 포신
	MoveToEx(hdc, pos.x, pos.y, NULL);
	LineTo(hdc, barrelEnd.x, barrelEnd.y);

	missile->Render(hdc);
}

void Tank::Move()
{
}

void Tank::Fire()
{
	bool isFire;        //화면안에 미사일 있으면 true
	isFire = missile->InScreenCheck();
	//미사일 pos, angle에 barrelEnd, barrelAngle넣기
	if (false == isFire)
	{
		missile->ReadyToFire(barrelEnd, barrelAngle);
	}
	
}
void Tank::RotateBarrel(float angle)
{
	barrelAngle += angle;
}

void Tank::Dead()
{
}

Tank::Tank()
{
	Init();
}

Tank::~Tank()
{
}

 

미사일을 만들자

//Missile.h
#pragma once
#include "GameObject.h"

class Missile : public GameObject
{
	POINT pos;		//미사일의 현재 위치
	float angle;	//미사일의 이동방향 (각도)
	int size;		//미사일의 사이즈
	float fSpeed;	//미사일의 속도
	bool InScreen;  //미사일이 화면안에 있는지 확인
public:
	void Init();
	void Release(); 
	void Update();  //미사일 위치 변경 
	void Render(HDC hdc); //미사일 현재 위치 그리기
	bool InScreenCheck(); //미사일이 화면안에 있으면 true 
	void ReadyToFire(POINT pos, float angle); //포신의 각도와 위치를 받기
	Missile();
	~Missile();
};

InScreenCheck()를 통해 미사일이 화면안에 있는지 확인하고 render, update여부를 결정한다.

ReadyToFire()를 통해 미사일을 발사할 초기 위치( 포신 끝) 을 설정해준다.

//Missile.cpp

#include "Missile.h"
#include "CommonFunction.h"
#include "config.h"

void Missile::Init() //미사일의 출발위치, 방향
{
	pos.x = 0;
	pos.y = 0;
	size = 20;
	angle = 3.14f / 4.0f;
	fSpeed = 10.0;
	InScreen = false;
	//여러개 생성
}

void Missile::Release()
{
}

void Missile::Update() //미사일의 현재 위치 변경
{
	if (InScreen == true)
	{
		pos.x += cos(angle) * fSpeed;  //코사인 90은 0
		pos.y -= sin(angle) * fSpeed;
		InScreenCheck();
	}
}

void Missile::Render(HDC hdc)
{
	if (InScreen)
	{
		RenderEllipseAtCenter(hdc, pos.x, pos.y, size, size);
	}	
}

bool Missile::InScreenCheck()
{
	if (pos.x < 0 || pos.x > WINSIZE_X || pos.y < 0 || pos.y > WINSIZE_Y)
	{
		InScreen = false;
		return InScreen;
	}
	else
	{
		InScreen = true;
		return InScreen;
	}
}

void Missile::ReadyToFire(POINT pos, float angle)
{
	this->pos = pos;
	this->angle = angle;
	InScreen = true;
}

Missile::Missile() 
{
	Init();
}

Missile::~Missile()
{
}

 

이제 실제 윈도우창에 실행시키려고한다.

#include "MainGame.h"
#include "Tank.h"

/*
	실습1. 미사일 한발 쏘기
	실습2. 미사일 여러발 쏘기
	실습3. 스킬샷1 (360도 미사일) 쏘기
	실습4. 스킬샷2 (자체 기획) 쏘기
*/

void MainGame::Init()
{
	tank = new Tank;
	tank->Init();
}

void MainGame::Release()
{
	if (tank)
	{
		tank->Release();
		delete tank;
	}
}

void MainGame::Update()
{
	if (tank)	tank->Update();  
	
}

void MainGame::Render(HDC hdc)
{
	wsprintf(szText, TEXT("Mouse X : %d, Y : %d"), mousePosX, mousePosY);
	TextOut(hdc, 20, 60, szText, wcslen(szText));

	if (tank)	tank->Render(hdc);
}

LRESULT MainGame::MainProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
	switch (iMessage)
	{
	case WM_CREATE:
		hTimer = (HANDLE)SetTimer(hWnd, 0, 10, NULL); // 100ms(0.1초)마다 타이머가 호출 
		break;
	case WM_TIMER:
		this->Update(); //게임 업데이트 
		InvalidateRect(g_hWnd, NULL, true); //화면 다시 그리기 요청
		break;
	case WM_KEYDOWN:
		switch (wParam)
		{
		case 'a': case 'A':
			tank->RotateBarrel(DEG_TO_RAD(5));
			break;
		case 'd': case 'D':
			tank->RotateBarrel(DEG_TO_RAD(-5));
			break;
		case 'q': case 'Q':
			/*tank->MachineGun();*/
			break;
		case VK_SPACE:
			tank->Fire();
			break;
		}
		break;
	case WM_LBUTTONDOWN:
		mousePosX = LOWORD(lParam);
		mousePosY = HIWORD(lParam);
		break;
	case WM_LBUTTONUP:
		break;
	case WM_MOUSEMOVE:
		mousePosX = LOWORD(lParam);
		mousePosY = HIWORD(lParam);

		InvalidateRect(g_hWnd, NULL, true);
		break;
	case WM_PAINT:
		hdc = BeginPaint(g_hWnd, &ps);

		this->Render(hdc);

		EndPaint(g_hWnd, &ps);
		break;
	case WM_DESTROY:
		KillTimer(hWnd, 0);
		PostQuitMessage(0);
		break;
	}

	return DefWindowProc(hWnd, iMessage, wParam, lParam);
}

MainGame::MainGame()
{
}

MainGame::~MainGame()
{
}

MainProc 를 수행하는동안  다양한 입력을 받아서 명령을 수행하고  PostQuitMessage(0)이되면  윈도우 큐에  쓰레드가 종료를 요청한다.  

 

 

한발만 발사하는 탱크를 만들었다.

이제 여러발 발사하는 기능, 발사체가 360도로 퍼지는 기능, 탱크를 움직이는 기능, 적을 생성하고 똥피하기 게임이 가능하도록 만들 예정이다. 아직 WinApi의 동작원리와 기능들이 익숙하지 않지만 핵심기능들은 이해했다. 탱크를 만들면서 응용할 수 있도록 하겠다.