학원/경일게임아카데미

26. 열여섯번째 수업과제 [러닝 게임]

셩잇님 2022. 12. 29. 18:35
반응형

경일게임아카데미 프로그래밍반 28기 16일차 수업과제 (2021. 04. 29)

 

 

 


 

 

 

오늘은 WIN32 API을 활용하여 러닝 게임을 제작해봅시다!
과제1 - 러닝 게임을 제작하세요

조건.
1. 주인공은 가만히 있는 상태이며, 땅이 뒤로 가는 상태이다.  

2. 주인공이 땅 사이에 빠지면 게임 오버 된다.

3. 2단 점프를 구현해라.

5. 장애물의 종류는 정사각형, 2단 점프용 박스, 3. 엎드린 상태에서 피할 수 있는 박스 총 3가지 이다.

4. 1번 2번 장애물 위에 점프로 올라가게 되면 장애물 위에 있다가 장애물이 다 지나갈 시 땅으로 떨어진다.
//5. 3번 장애물은 숙여서 피하기

6. 장애물에 부딪치면 주인공은 뒤로 스르륵 밀린다. 후에 다시 앞으로 스르륵 천천히 오며 주인공이 맵 밖으로 다 나가게 되면 죽게된다. 

7. 기초 상태 정의하기 (기본 상태, 걷는 상태, 뛰는 상태, 점프한 상태)

 

 

 


 

 

 

소스코드 (내가 작업한 것)

#include "stdafx.h"
#include "playGround.h"

playGround::playGround()
{

}

playGround::~playGround()
{

}

//초기화는 여기다 하세요 제발
HRESULT playGround::init()
{
	gameNode::init();

	_x = WINSIZEX / 2 - 250;
	_y = WINSIZEY / 2 + 250;

	// 플레이어 초기화(생성)
	_player = RectMakeCenter(_x, _y, 100, 100);	

	_jumpPower = _gravity = _jumpCount = 0;
	_isJump = false;
	_isGameover = false;
	_jumpcheck = true;
	
	// 맵 초기화(생성)
	for (int i = 0; i < LANDMAX; i++)
	{
		_rndX = RND->getFromIntTo(0, 1200);
		_land[i].crush = RectMakeCenter(_rndX, WINSIZEY / 2 + 415 + (i * 350), 50, 50);
		
		_land[i].left = RectMakeCenter
		(_land[i].crush.left - 300, WINSIZEY / 2 + 350, 400, 100);

		_land[i].Middle = RectMakeCenter
		(_land[i].crush.right + 300, WINSIZEY / 2 + 350, 400, 100);

		_land[i].right = RectMakeCenter
		(_land[i].Middle.right + 300, WINSIZEY / 2 + 350, 400, 100);
	}
	return S_OK;
}

//메모리 해제는 여기다 하세요 제발
void playGround::release()
{
	gameNode::release();
}

//여기에다 연산하세요 제에발
void playGround::update()
{
	gameNode::update();

	// 인터벌
	/*
	_count++;

	if (_count % 50 == 0)
	{
		_rc.left += 10;
		_rc.right += 10;

		_count = 0;
	}
	*/

	if (!_isGameover)
	{
		if (KEYMANAGER->isOnceKeyDown(VK_SPACE) && _jumpcheck == true)
		{
			_gravity = 0.15f;
			_jumpPower = 5.0f;
			_jumpCount++;
			_isJump = true;
		}

		if (_jumpCount == 2)
		{
			_jumpcheck = false;
			_jumpCount = 0;
		}

		if (_isJump)
		{
			_y -= _jumpPower;
			_jumpPower -= _gravity;
		}

		for (int i = 0; i < LANDMAX; i++)
		{
			_land->crush.left  -= 1;
			_land->crush.right -= 1;

			_land[i].left = RectMakeCenter
			(_land[i].crush.left - 300, WINSIZEY / 2 + 350, 400, 100);

			_land[i].Middle = RectMakeCenter
			(_land[i].crush.right + 300, WINSIZEY / 2 + 350, 400, 100);

			_land[i].right = RectMakeCenter
			(_land[i].Middle.right + 300, WINSIZEY / 2 + 350, 400, 100);

			if (_land[i].Middle.left < 0)
			{
				_rndX = RND->getFromIntTo(0, 1200);
				_land[i].crush = RectMakeCenter(_rndX, WINSIZEY / 2 + 415 + (i * 350), 50, 50);
			}

			if (_player.bottom > _land[i].left.top   ||
				_player.bottom > _land[i].Middle.top ||
				_player.bottom > _land[i].right.top)
			{
				_jumpcheck = true;
			}

			RECT temp;
			if (IntersectRect(&temp, &_player, &_land->crush))
			{
				_isGameover = true;
			}
		}
	}

	if (KEYMANAGER->isOnceKeyDown(VK_RETURN))
	{
		if (_isGameover) this->init();
	}
	_player = RectMakeCenter(_x, _y, 100, 100);
	
}

//여기에다 그려라 좀! 쫌!
void playGround::render(HDC hdc)
{
	HDC backDC = this->getBackBuffer()->getMemDC();
	PatBlt(backDC, 0, 0, WINSIZEX, WINSIZEY, WHITENESS); // 흰색 도화지라고 생각하면 된다.
	// 위에 건들지마라
	//================제발 이 사이에 좀 그립시다==========================
	Rectangle(backDC, _player);

	for (int i = 0; i < 1; i++)
	{
		Rectangle(backDC, _land[i].left);
		Rectangle(backDC, _land[i].crush);
		Rectangle(backDC, _land[i].Middle);
		Rectangle(backDC, _land[i].right);
	}

	//==================================================
	//여기도 건들지마라
	this->getBackBuffer()->render(hdc, 0, 0);
}

 

 

 


 

 

 

어떻게 구현해아 할까?
1. 땅을 먼저 구현한다
2. 주인공이 떨어지는 것을 구현한다
3. 2단 점프를 구현한다
4. 장애물 3개를 구현한다
5. 장애물과의 충돌을 구현한다

 

로직은?
1. 땅을 먼저 초기화한후 랜덤값으로 땅의 크기를 설정해준다
2. 주인공의 top이 세로축 설정크기보다 클 떄 죽게 설정한다
3. 2단 점프는 카운트로 구한다.
4. 장애물 생성은 랜덤으로 구현한다
5. 글쎄..

 

 

 


 

 

 

결과화면

 

 

 

문제점이 한둘이 아니다 🤦‍♂️
시작버튼을 누르기도 전에 시작한다
주인공이 땅 위에 서있지 못한다
랜덤으로 생성된 땅에도 문제가 한둘이 아니다.
걷고 있는 맵이 채 끝나기도 전에 새로운 맵이 이상하게 계속 나온다.

해결한 것.
2단 점프는 해결해서 주인공이 땅 위를 인식할 떄 점프 카운트를 초기화 해주었다.
땅과 땅 사이에 떨어지면 죽게하기 위에 크러쉬 박스를 밑에 숨겨두었다.

 

 


 

 

 

아쉬운점
작성하는 당시, 너무 많아 거론할 수가 없다.
지금부터 다시 갈아엎어서 만들어 보자.

 

 

 




 

210502 게임 제작 완료
일요일 오후부터 일찍 나와 카페에 앉아서 구현하기를 시작했다.
제작을 다하니 오후 6시가 넘어 집에 돌아갔다. 오후 내내 구현한 것 같다.

먼저 제작하기에 앞서 사전조건을 다시금 확인하고 차근차근 구현하기로 했다.
구현 순서는 아래와 같다

플레이어 제작, 땅 제작, 땅 이동, 땅 추락시 사망, 플레이어 점프(2단 점프, 중력), 땅 충돌 처리(땅 위, 땅 아래), 장애물 제작, 장애물 이동, 장애물 충돌 처리 (장애물 위, 장애물 왼쪽), 밀렸을 때 캐릭터 원 위치 복귀 순서로 제작하였다.

모든 소스코드에는 추후 알 수 있게 주석을 작성하였다.

소스코드는 아래와 같다

playGround.h 파일

#pragma once
#include "gameNode.h"

class playGround : public gameNode
{
private:
	// 선언부 
	bool _isStart;				// 시작 유무
	int _Speed;					// 땅, 장애물 이동속도

	// 플레이어 관련 변수 초기화 & 선언
	RECT _player;				// 플레이어
	float _gravity;				// 중력
	float _jumpPower;			// 점프
	int _jumpCount;				// 점프 카운트 (2단 점프 체크용)
	bool _isJump;				// 점프 상태 유무
	int _x;					// 플레이어의 X축
	int _y;					// 플레이어의 Y축

	// 땅 관련 변수 초기화 & 선언
	RECT _land[2];				// 땅
	int _width;				// 땅 가로크기 추출 변수

	// 장애물 관련 변수 초기화 & 선언
	RECT _obstacle[3];			// 장애물

public:
	playGround();
	~playGround();

	virtual HRESULT init();
	virtual void release();
	virtual void update();
	virtual void render(HDC hdc);
};

 

 

 

playGround.cpp 파일

#include "stdafx.h"
#include "playGround.h"

playGround::playGround()
{

}

playGround::~playGround()
{

}

// 초기화
HRESULT playGround::init()
{
	gameNode::init();

	// 초기화 & 선언
	_isStart = false; 							// 시작 유무 체크	

	// 플레이어 관련 변수 초기화 & 선언
	_player = RectMakeCenter
	(
		WINSIZEX / 2 - 400,
		WINSIZEY / 2 + 150, 
		100, 100
	);
	_x = WINSIZEX / 2 - 400;						// PLAYER X축
	_y = WINSIZEY / 2 + 150;						// PLAYER Y축
	_gravity = 0.0f;							// 중력
	_jumpPower = 0.0f;							// 점프
	_jumpCount = 0;								// 점프 카운트
	
	// 땅 관련 변수 초기화 & 선언
	_Speed = 3.0f;								// 속도
	_width = _land[0].right - _land[0].left; 				// 가로
	_land[0] = RectMake							// 땅 1, 2
	(
		WINSIZEX / 2 - 475,
		WINSIZEY / 2 + 200, 
		RND->getFromIntTo(300, 800), 100
	);
	_land[1] = RectMake
	(
		RND->getFromIntTo(_land[0].right, WINSIZEX - _land[0].right) + 200, 
		WINSIZEY / 2 + 200, 
		RND->getFromIntTo(200, WINSIZEX - _width),	100
	);

	// 장애물 관련 변수 초기화 & 선언 (정사각형, 세로로 긴거, 가로로 긴거 순)
	_obstacle[0] = RectMake(WINSIZEX + RND->getFromIntTo(0, 300), WINSIZEY / 2, 200, 200);
	_obstacle[1] = RectMake(WINSIZEX + RND->getFromIntTo(100, 400), WINSIZEY / 2 - 100, 75, 200);
	_obstacle[2] = RectMake(WINSIZEX + RND->getFromIntTo(200, 500), WINSIZEY / 2 + RND->getFromIntTo(0, 50), 200, 75);

	return S_OK;
}

// 메모리 해제
void playGround::release()
{
	gameNode::release();
}

// 연산
void playGround::update()
{
	gameNode::update();

	if (_isStart) 	// isStart가 true일 경우
	{
		// 점프 (SPACE 키를 입력하고, 점프 카운트가 1보다 크거나 같을 떄)
		if (KEYMANAGER->isOnceKeyDown(VK_SPACE) && _jumpCount <= 1 )
		{
			_jumpCount++;				// 2단 점프 카운트용
			_gravity = 0.1f;			// 중력 설정
			_jumpPower = 6.0f;			// 점프 파워 설정
			_isJump = true;				// 플레이어가 점프 상태일 때
		}

		// 점프 상태
		if (_isJump)
		{
			_y -= _jumpPower;			// PLAYER의 Y축 설정을 점프 힘만큼 설정
			_jumpPower -= _gravity;			// JUMP POWER를 GRAVITY 만큼 감소
		}

		// 플레이어에 점프 상태를 연산
		_player = RectMakeCenter(_x, _y, 100, 100);

		// 땅
		for (int i = 0; i < 2; i++)
		{
			// 땅 움직이는 부분
			_land[i].left  -= _Speed;
			_land[i].right -= _Speed;

			// 땅 재생성 부분
			if (_land[i].right <= 0)
			{
				_land[i] = RectMake
				(
					WINSIZEX, WINSIZEY / 2 + 200,
					RND->getFromIntTo(300, 800), 100
				);
			}

			// 땅 충돌 판정
			RECT temp;
			if (IntersectRect(&temp, &_land[i], &_player))
			{
				// 땅 위. LAND TOP이 떨어지는 PLAYER의 TOP 보다 클 때 (PLAYER가 위에 있는 상태)
				if (_player.top < _land[i].top)
				{
					_jumpCount = 0;						// 점프 카운트 초기화
					_jumpPower = 0.0f;					// 점프 힘 초기화 (떨어지지 않게)
					_y = _land[i].top - 50;					// 플레이어의 Y축을 RAND.TOP에서 -50 뺸만큼 설정 
					_player = RectMakeCenter(_x, _y, 100, 100);		// 플레이어 연산
				}
				// 땅 아래에서 땅 바닥과 부딪쳤을때 판정
				if (_player.bottom > _land[i].bottom)
				{
					_y = _land[i].bottom + 50;				// 플레이어의 Y축을 RAND.BOTTOM에서 +50 더한만큼 설정 
					_player = RectMakeCenter(_x, _y, 100, 100);		// 플레이어 연산
				}
			}
		}
		
		// 장애물
		for (int i = 0; i < 3; i++)
		{
			// 장애물 움직이는 부분
			_obstacle[i].left  -= _Speed;
			_obstacle[i].right -= _Speed;

			// 장애물 재생성 부분
			if (_obstacle[i].right <= 0)
			{
				// 장애물의 왼쪽을 맵 X 축 끝에서 랜덤 범위안으로 다시 잡아준다.
				_obstacle[i].left = WINSIZEX + RND->getFromIntTo(200, 400);

				// 장애물이 2개일 때
				if (i == 2)
				{
					// 장애물의 오른쪽을 장애물에 왼쪽 + RND(범위)만큼 더해준다.
					_obstacle[i].right = _obstacle[i].left + RND->getFromIntTo(0, 100);
				}
				else
				{
					_obstacle[i].right = _obstacle[i].left + RND->getFromIntTo(100, 200);
				}
			}

			// 장애물 충돌 판정
			RECT temp;
			if (IntersectRect(&temp, &_obstacle[i], &_player))
			{
				if (temp.bottom - temp.top > temp.right - temp.left)
				{
					// 1. 장애물에 왼쪽으로 밀리는 처리
					_x = _obstacle[i].left - 50;					// 플레이어의 X축 좌표를 장애물 LEFT 에서 50만큼 뺴기 (사용자가 보기엔 왼쪽으로 밀리게 된다.)
					_player = RectMakeCenter(_x, _y, 100, 100);			// 플레이어 연산
				}
				if (temp.bottom - temp.top < temp.right - temp.left)  
				{
					// 2. 장애물 위에 있는 처리 먼저 (땅 위랑 조건이 같다.)
					if (_player.top < _obstacle[i].top)
					{
						_jumpCount = 0;						// 점프 카운트 초기화
						_jumpPower = 0.0f;					// 점프 힘 초기화 (떨어지지 않게)
						_y = _obstacle[i].top - 50;				// 플레이어의 Y축을 obstacle.top에서 -50 뺸만큼 설정
						_player = RectMakeCenter(_x, _y, 100, 100);		// 플레이어 연산
					}
					// 3. 떨어질 떄 장애물 바닥(bottom)에 부딪치면 못올라가는 처리
					else
					{
						_y = _obstacle[i].bottom + 50;
						_player = RectMakeCenter(_x, _y, 100, 100);	
					}
				}
			}

			// 장애물에 충돌하여 맵 바깥으로 나갈경우
			else
			{
				// 원위치
				if (_player.left <= 46)
				{
					// 속도의 절반만큼 (천천히) 앞으로 나오게 한다.
					_x += _Speed / 2;
					_player = RectMakeCenter(_x, _y, 100, 100);
				}
			}
		}

		// 패배 (땅에서 떨어졌을 떄)
		if (_player.right <= 0 || _player.top >= WINSIZEY)
		{
			this->init();
		}
		
	}
	else
	{
		// Enter키를 입력할 경우 true 값으로 변경하여 게임을 시작한다.
		if (KEYMANAGER->isOnceKeyDown(VK_RETURN))
		{
			_isStart = true;
		}
	}
}

// 출력
void playGround::render(HDC hdc)
{
	HDC backDC = this->getBackBuffer()->getMemDC();
	PatBlt(backDC, 0, 0, WINSIZEX, WINSIZEY, WHITENESS);
	// 위에 건들지마라
	//================ 제발 이 사이에 좀 그립시다==========================
	
	// 시작 안내 문구
	if (_isStart == false)
	{
		char str[128];
		sprintf_s(str, "시작하려면 Enter를 누르세요.");
		TextOut(backDC, WINSIZEX / 2 - 125, WINSIZEY / 2, str, strlen(str));
	}

	// 주인공 출력
	Rectangle(backDC, _player);		

	// 땅 출력
	for (int i = 0; i < 2; i++)
	{
		Rectangle(backDC, _land[i]);
	}

	// 장애물 출력
	for (int i = 0; i < 3; i++)
	{
		Rectangle(backDC, _obstacle[i]);
	}

	//==================================================
	//여기도 건들지마라
	this->getBackBuffer()->render(hdc, 0, 0);
}

 

 

 


 

 

 

결과화면
1. 떨어져서 죽는 화면

 

 

 

2. 벽에 밀려 끝나는 화면

 

 

 

3. 벽 위에 있는 조건

 

 

 

4. 제자리로 돌아오는 조건

 

 

 


 

 

 

그 외에도 땅, 장애물 밑에 부딪칠때 못올라오는 조건도 모두 구현했다.
주말 내에 시간을 이렇게 내서 제작할 수 있었던 사실에 너무 뿌듯하다. 😎

 

반응형

'학원 > 경일게임아카데미' 카테고리의 다른 글

28. 열일곱번째 수업  (0) 2022.12.29
27. 열여섯번째 수업과제 [악어 게임]  (0) 2022.12.29
25. 열여섯번째 수업  (0) 2022.12.26
24. 열다섯번째 수업과제  (0) 2022.12.26
23. 열다섯번째 수업  (0) 2022.12.26