본문으로 바로가기

 

2020_0601_Framework.zip
0.03MB


[프로그램 설계]

1. vector class를 이용하여 n개의 Enemy* 오브젝트 풀 생성

2. 생성된 오브젝트 풀은 화면 상단에 한 줄로 나열하여 현재 상태 체크

3. 화면 중앙으로부터 540 * 1080 pixel 크기의 게임영역 지정

4. Player는 게임영역내에서 좌, 우로만 이동가능하도록 제한

5. Enemy는 게임영역 상단에서부터 좌,우 범위 내에서 랜덤위치에 출현

6. 게임영역 하단에 도착 시 전체영역 상단 최초 지점으로 위치 초기화

7. delay timer를 이용하여 각각의 Enemy는 랜덤한 타이밍으로 다시 재활성되어 5~6 반복

8. 한번 바닥으로 떨어진 Enemy는 색상을 변경하여 재활용

9. Enemy 색상에 따라 Player와 충돌시 구분된 효과 구현

 

[구현 중점]

1. 오브젝트 풀

  - Debug를 위한 오브젝트 풀 가시화

2. Restart 기능

  - 최적화된 객체 초기화

 

Circle & Rect 충돌처리 프로젝트에서 수정하여 작성함

추가된 코드

CollisionScene
더보기
// 멤버 변수
private:
	RECT rect_PlayArea;
	bool isPlaying;
	HBRUSH brushes[7];

// 멤버 함수
public:
	void InitBrush();
	bool OnGround(Enemy* enemy);
	void RestartGame();
///////////////////////////////////////////////////////////////////////////////////////////////
bool CollisionScene::Init()
{
	// 게임 플레이 영역 설정
	LONG center_x = bufferRT.right * 0.5;
	LONG center_y = bufferRT.bottom * 0.5;
	rect_PlayArea = { center_x - 270, center_y - 420, center_x + 270, center_y + 420 }; // 540 * 960

	// 플레이어 생성
	m_pPlayer = new Player;
	m_pPlayer->SetPos(center_x, rect_PlayArea.bottom - m_pPlayer->Size().y);
	m_pPlayer->rect_PlayArea = rect_PlayArea;

	// Enemy 생성
	enemyCount = 30;
	for (int i = 0; i < enemyCount; i++)
	{
		Enemy* enemy = new Enemy;
		enemy->SetRandomSize(); // Pos 설정전 Size 먼저 재설정(순서 준수)
		POINT pos = { 50 * (i + 1), bufferRT.top + 50 };
		enemy->startPos = pos;
		enemy->SetPos(pos);
		enemy->rect_PlayArea = rect_PlayArea;
		enemys.emplace_back(enemy);
	}

	// Brush 초기화
	InitBrush();

	return true;
}

void CollisionScene::Update()
{
	if (GetAsyncKeyState(VK_F3))
		RestartGame();

	if (isPlaying)
	{
		m_pPlayer->Update(); // 플레이어를 가장 먼저 Update
		if (m_pPlayer->GetHP() == 0)
			isPlaying = false; // 플레이어 HP가 0이되면 게임 종료

		for (int i = 0; i < enemyCount; i++)
		{
			enemys[i]->Update();

			if (enemys[i]->isActive)
			{ // Active 상태의 Object만 충돌검사
				if (Collision(enemys[i]))
				{ // 플레이어와 충돌
					int color = enemys[i]->Color();
					m_pPlayer->TouchEnemy(color); // Enemy객체의 색상에 따라 충돌시 효과 구분
					int colorNext = (color + 1) % 7;
					enemys[i]->Color(colorNext);
					enemys[i]->SetBrush();
					enemys[i]->Reset();
				}
				else if (OnGround(enemys[i]))
				{ // 바닥에 도달
					int color = enemys[i]->Color();
					int colorNext = (color + 1) % 7;
					enemys[i]->Color(colorNext);
					enemys[i]->SetBrush();
					enemys[i]->Reset();
				}
			} // End of if (enemy -> isActive)
		} // End of For Loop (enemys)
	} // End of if (isPlaying)
}

void CollisionScene::Render()
{
	if (isPlaying) // 게임 진행중일 때만 오브젝트 출력
	{
		for (int i = 0; i < enemyCount; i++)
			//if(enemys[i]->isActive) // 디버그를 위해 오브젝트 풀을 화면 상단에 출력
			enemys[i]->Render();

		m_pPlayer->Render(); // 최상단에 표시하기 위해 플레이어를 가장 나중에 출력
	}
	RenderUI(); // UI는 항상 출력
}

bool CollisionScene::OnGround(Enemy* enemy)
{
	if (enemy->GetPos().y >= rect_PlayArea.bottom + 30) // 바닥에 떨어지면 true 반환
		return true;

	return false;
}

void CollisionScene::RestartGame()
{ // 오브젝트 생성 과정 없이 데이터만 초기화
	isPlaying = false;	// 게임 진행중에 호출시 껐다가 켠다

	m_pPlayer->HP = 100;
	m_pPlayer->Speed(10.0);

	LONG center_x = bufferRT.right * 0.5;
	m_pPlayer->SetPos(center_x, rect_PlayArea.bottom - m_pPlayer->Size().y);

	for (int i = 0; i < enemyCount; i++)
	{
		enemys[i]->isActive = false;
		enemys[i]->InitEnemy(); // 생성자에서만 호출되던 Init함수를 별도로 호출해준다
		enemys[i]->SetRandomSize(); // Pos 설정전 Size 먼저 재설정(순서 준수)
		POINT pos = { 50 * (i + 1), bufferRT.top + 50 };
		enemys[i]->startPos = pos;
		enemys[i]->SetPos(pos);
	}

	isPlaying = true;
}

void CollisionScene::RenderUI()
{
	// 전체영역 우측 UI
	wstring str = L"F1 : 원";
	TextOut(hdc, bufferRT.right - 300, bufferRT.top + 200, str.c_str(), str.length());
	str = L"F2 : 사각형";
	TextOut(hdc, bufferRT.right - 300, bufferRT.top + 240, str.c_str(), str.length());
	str = L"F3 : 다시하기";
	TextOut(hdc, bufferRT.right - 300, bufferRT.top + 280, str.c_str(), str.length());
	str = L"이동 : → ← ↑ ↓";
	TextOut(hdc, bufferRT.right - 300, bufferRT.top + 320, str.c_str(), str.length());
	str = L"HP : " + to_wstring(m_pPlayer->GetHP());
	TextOut(hdc, bufferRT.right - 300, bufferRT.top + 360, str.c_str(), str.length());
	str = L"SPEED : " + to_wstring(m_pPlayer->Speed());
	TextOut(hdc, bufferRT.right - 300, bufferRT.top + 400, str.c_str(), str.length());

	// 게임영역 우측 UI
	int left = rect_PlayArea.right + 50;
	int top = rect_PlayArea.top + 50;
	int right = left + 50;
	int bottom = top + 50;
	for (int i = 0; i < 7; i++)
	{
		SelectObject(hdc, brushes[i]);
		Ellipse(hdc, left, top, right, bottom);
		switch (i)
		{
		case 0: str = L"게임오버"; break;
		case 1: str = L"체력+1"; break;
		case 2: str = L"체력-1"; break;
		case 3: str = L"스피드+1"; break;
		case 4: str = L"스피드-1"; break;
		case 5: str = L"미구현"; break;
		case 6: str = L"미구현"; break;
		}
		TextOut(hdc, right + 25, top + 10, str.c_str(), str.length());
		top += 50;
		bottom += 50;
	}

	if (!isPlaying)
		str = L"Game Over!";
	else
		str = L"Game is Running";
	TextOut(hdc, bufferRT.right - 300, bufferRT.top + 440, str.c_str(), str.length());

	// 게임 영역 라인
	MoveToEx(hdc, rect_PlayArea.left, rect_PlayArea.top, nullptr);
	LineTo(hdc, rect_PlayArea.right, rect_PlayArea.top);
	LineTo(hdc, rect_PlayArea.right, rect_PlayArea.bottom);
	LineTo(hdc, rect_PlayArea.left, rect_PlayArea.bottom);
	LineTo(hdc, rect_PlayArea.left, rect_PlayArea.top);
}

void CollisionScene::InitBrush()
{
	brushes[0] = CreateSolidBrush(BLACK);
	brushes[1] = CreateSolidBrush(RED);
	brushes[2] = CreateSolidBrush(GREEN);
	brushes[3] = CreateSolidBrush(BLUE);
	brushes[4] = CreateSolidBrush(CYAN);
	brushes[5] = CreateSolidBrush(MAGENTA);
	brushes[6] = CreateSolidBrush(YELLOW);
}

 

Player
더보기
public:
	RECT rect_PlayArea;
	int HP;

public:
	void TouchEnemy(int color);
	int GetHP() const { return HP; }
	void SetHP(int hp) { HP = hp; }
	void AddHP(int add) { HP += add; }
///////////////////////////////////////////////////////////////////////////////////////////////
void Player::TouchEnemy(int color)
{
	switch (color)
	{
	case 0: HP = 0; break; // BLACK
	case 1: ++HP; break; // RED
	case 2: --HP; break; // GREEN
	case 3: ++speed; break; // BLUE
	case 4: --speed; break; // CYAN
	//case 5: ; break; // MAGENTA, 미구현
	//case 6: ; break; // YELLOW, 미구현
	default: break;
	}
}

 

Enemy
더보기
public:
	bool isActive;		// Object Pool 활용을 위한 활성 스위치
	POINT startPos;		// Obejct Pool 표시를 위한 디버그용 위치값
	int delay;			// 랜덤 낙하를 위한 변수
	RECT rect_PlayArea; // 게임 플레이 영역
    
public:
	void FallDown();
	void CountToActive();
	void Reset();
	void SetDelay();

///////////////////////////////////////////////////////////////////////////////////////////////

void Enemy::Update()
{
	if (isActive)
		FallDown();			// 활성 상태일 때는 낙하 시킨다
	else
		CountToActive();	// 비활성 상태일 때는 delay Counter를 갱신한다
}

void Enemy::InitEnemy()
{
	// 기존 코드는 생략, 추가한 코드만 표시
	SetDelay(); // Random한 낙하를 위해 각 Enemy 마다 delay 랜덤설정
	delay += 240; // 최초 실행시 240프레임 추가 대기(사용자가 게임플레이를 준비할 시간)
}

void Enemy::SetRandomPos()
{
	LONG x, y;
	x = (rand() % (rect_PlayArea.right - rect_PlayArea.left)) + rect_PlayArea.left;
	y = rect_PlayArea.top;
	m_tPos.x = x;
	m_tPos.y = y;
}

void Enemy::FallDown()
{ // 낙하
	m_tPos.y += speed;
}

void Enemy::CountToActive()
{ // delay Counter
	--delay;
	if (delay == 0)
	{
		SetRandomPos();
		Speed(rand() % 12 + 2.0);
		isActive = true;
	}
}

void Enemy::Reset()
{ // 충돌 시 실행되는 코드
	isActive = false;
	m_tPos = startPos;
	SetDelay();
}

void Enemy::SetDelay()
{
	delay = rand() % 120 + 60; // Update 프레임 단위
}