경일게임아카데미 프로그래밍반 28기 15일차 수업과제 (2021. 04. 28)
오늘은 WIN32 API을 활용하여 플래피버드 게임을 제작해봅시다!
과제1 - 플래피버드 게임을 제작하세요
조건.
1. 파이프 위아래 크기는 랜덤으로 생성한다
2. 파이프 사이를 지나가게 되면 점수를 오르게 한다.
3. 파이프가 맵밖으로 다 지나가기전에 새로운 파이프를 만들어준다
소스코드 (내가 작업한 것)
프로젝트 실수로 삭제해버린 것 같다.. 어디에다가 작업했는지 기억두 안나고..
수업자료를 매번 받고 지우고 하는과정에서 내가 실수로 지워버린 것 같다.. 😢
소스코드 (선생님)
#include "stdafx.h"
#include "playGround.h"
playGround::playGround()
{
}
playGround::~playGround()
{
}
//초기화는 여기다 하세요 제발
HRESULT playGround::init()
{
gameNode::init();
_x = WINSIZEX / 2 - 100;
_y = WINSIZEY / 2;
_player = RectMakeCenter(_x, _y, 50, 50);
_jumpPower = _gravity = _score = 0;
_isJump = false;
_isGameOver = false;
for (int i = 0; i < PIPEMAX; i++)
{
_rndY = RND->getFromIntTo(300, 500);
_pipe[i].score = RectMakeCenter(WINSIZEX / 2 + 200 + (i * 350), _rndY, 10, 100);
_pipe[i].up = RectMakeCenter((_pipe[i].score.left + _pipe[i].score.right) / 2,
(_pipe[i].score.top + _pipe[i].score.bottom) / 2 - 300, 100, 400);
_pipe[i].down = RectMakeCenter((_pipe[i].score.left + _pipe[i].score.right) / 2,
(_pipe[i].score.top + _pipe[i].score.bottom) / 2 + 300, 100, 400);
_pipe[i].isCheck = false;
}
return S_OK;
}
//메모리 해제는 여기다 하세요 제발
void playGround::release()
{
gameNode::release();
}
//여기에다 연산하세요 제에발
void playGround::update()
{
gameNode::update();
if (!_isGameOver)
{
if (KEYMANAGER->isOnceKeyDown(VK_SPACE))
{
_gravity = 0.15f;
_jumpPower = 3.0f;
_isJump = true;
}
if (_isJump)
{
_y -= _jumpPower;
_jumpPower -= _gravity;
}
for (int i = 0; i < PIPEMAX; i++)
{
_pipe[i].score.left -= 5;
_pipe[i].score.right -= 5;
_pipe[i].up = RectMakeCenter((_pipe[i].score.left + _pipe[i].score.right) / 2,
(_pipe[i].score.top + _pipe[i].score.bottom) / 2 - 300, 100, 400);
_pipe[i].down = RectMakeCenter((_pipe[i].score.left + _pipe[i].score.right) / 2,
(_pipe[i].score.top + _pipe[i].score.bottom) / 2 + 300, 100, 400);
if (_pipe[i].up.right < 0)
{
_rndY = RND->getFromIntTo(300, 500);
_pipe[i].score = RectMakeCenter(WINSIZEX + 350, _rndY, 10, 100);
_pipe[i].isCheck = false;
}
RECT temp;
if (IntersectRect(&temp, &_player, &_pipe[i].score) && !_pipe[i].isCheck)
{
_score++;
_pipe[i].isCheck = true;
}
if (IntersectRect(&temp, &_player, &_pipe[i].up) ||
IntersectRect(&temp, &_player, &_pipe[i].down))
{
_isGameOver = true;
}
}
}
if (KEYMANAGER->isOnceKeyDown(VK_RETURN))
{
if (_isGameOver) this->init();
}
_player = RectMakeCenter(_x, _y, 50, 50);
}
//여기에다 그려라 좀! 쫌!
void playGround::render(HDC hdc)
{
char str[128];
sprintf_s(str, "점수 : %d", _score);
TextOut(hdc, 10, 10, str, strlen(str));
Rectangle(hdc, _player);
for (int i = 0; i < PIPEMAX; i++)
{
Rectangle(hdc, _pipe[i].up);
Rectangle(hdc, _pipe[i].down);
Rectangle(hdc, _pipe[i].score);
}
}
어떻게 구현해야할까?
1. 파이프를 먼저 생성하자
> 생성할 때 크기는 랜덤하게 만들자
>> 매크로 해더에 있는 랜덤기능을 이용하어 생성하자
> 그러면 파이프 사이를 지나갔다는걸 어떻게 구분하지?
>> 어떻게 해야할까..?
2. 파이프 사이를 지나가게 되면 점수를 오르게 한다
> 1번에 연장선 질문.. 지나갔다는걸 구분하고 난 이후에 점수를 후위 증감연산자를 이용해 하면 될것 같다.
3. 파이프가 맵 밖으로 다 지나가기 전에 새로운 파이프를 만들어준다
모르겠다!
구현함에 있어서 문제점
프로젝트가 날아가긴 했지만 나도 작업을 안했던건 아니다.
문제점 1.
파이프(전체)를 배열로 선언안하고 각자 하나(1, 2, 3, 4)로 만든것이다.
여기서 드는 나의 의문점. 그럼 파이프(전체)로 만들떄 파이프의 범위를 어떻게 설정 해야하나? 🤔
먼저 나는 파이프를 배열로 선언하지 않고 각각의 1, 2, 3, 4의 파이프를 만들었다.
그래서 1, 2파이프가 쌍을 이루고 1.right가 < 0일 경우 3, 4 파이프를 만들어 주려고 했다.
내가 상상한 게임의 구현 모습은 위와 같다
왼쪽 큰 박스는 맵이고 주인공이 있으며 1/2파이프가 쌍을 이뤄 나타났다가 파이프가 1.right가 < 0이 될 경우 3/4 파이프가 나오게 하려고 했다. 또한 3/4파이의 3.right가 < 0이 될 경우 1/2파이프를 다시 호출하여 1/2 호출 3/4를 호출을 왔다갔다 하게 했다.
문제점은 여기서 시작되었다.
1. 1/2 파이프가 < 0 이 될떄까지 3/4번 파이프가 나오지 않아 기존 게임과 다르게 파이프가 계속 생성되지 않고 다 지나가야 생성되어 어색했다.
- 파이프 사이를 들어갈 수 있는 체크점이 없다는 것이였다.
도저히 모르겠어서 선생님에게 여쭤보았다.
답은 의외로 간단하였다
보이지 않는 충돌처리 박스를 만들어주어 그 위로 위파이프와 아래 파이프를 만들어 주고 충돌 처리 박스를 닿을 때 점수가 오르게 하면 되었다.
원래는 파이프 사이에 박스는 사용자에게 보이지 않게 하면 되지만 영상을 찍기 위해 임의적으로 노출되게 시켰다.
해당 파이프 사이에 있는 박스는 충돌 처리를 함이였고, 저 부분을 충돌할때 점수를 추가하면 된다.
그리고 저 파이프 사이에 있는 박스를 기준으로 위, 아래 파이프를 만들어주면 된다.
위 파이프의 right가 < 0이 될경우 파이프를 다시 호출하여 반복적으로 나오게끔 하였다.
개인적으로 해보고 싶은 심화학습
맵 위, 아래로 못나가게 설정해보자.
결과
답을 알고나니 너무나도 쉬웠다.
그렇지만 매번 왜 이렇게 생각하지 못했을까 자책하게 된다. 🤦♂️
수업하면서 설명하시는 코드를 이해하고 해석하는데 문제가 없지만,
문제를 풀이하는 능력이 너무 떨어진다고 느껴져 내 자신이 속상해 참 힘든 하루였다.
선생님이 짜신 코드를 다시한번 작성하여 내 것으로 온전히 만들어 학습해야겠다고 다짐했다.
21.05.11 제작
21.05.12 작성
5월 11일 밤, 스티커 메모에 그동안 작성했던 메모 내용 중 플래피버드를 다시 제작을 하기로 하였다. 애초에 프로젝트를 날려먹었기 때문에 다시 제작하기 위해 이전에 미리 메모를 남겨놓았다. 아울러 5.12일(오늘) 플래피버드에 이미지를 입혀 프로젝트를 다시 만든다는 소식을 들어 어제 밤 미리 만들어 놓고 예습을 했다. 😎
다시 제작한 소스코드 (헤더파일)
#pragma once
#include "gameNode.h"
#define PIPEMAX 3
struct tagPipe
{
RECT top;
RECT bottom;
RECT score;
bool _isCrash;
};
class playGround : public gameNode
{
private:
// 플레이어 초기화
RECT _player;
float _x, _y;
float _jumpPower;
float _gravity;
bool _isJump;
// 파이프 초기화
tagPipe _pipe[PIPEMAX];
int _rndY;
// 게임 내 시스템 초기화
int _score;
bool _isGameStart;
bool _isGameOver;
public:
playGround();
~playGround();
virtual HRESULT init();
virtual void release();
virtual void update();
virtual void render(HDC hdc);
};
다시 제작한 소스코드 (cpp파일)
#include "stdafx.h"
#include "playGround.h"
playGround::playGround()
{
}
playGround::~playGround()
{
}
//초기화는 여기다 하세요 제발
HRESULT playGround::init()
{
gameNode::init();
// 플레이어 초기화
_x = WINSIZEX / 2 - 100; // 플레이어의 X 값 설정
_y = WINSIZEY / 2; // 플레이어의 Y 값 설정
_player = RectMakeCenter(_x, _y, 50, 50); // 설정한 X, Y값을 이용한 플레이어 생성
_jumpPower = _gravity = _score = 0; // Jumppower, gravity, socre 초기화
_isJump = false; // 점프 상태
_isGameStart = false; // 게임 스타트 상태
_isGameOver = true; // 게임 오버 상태
for (int i = 0; i < PIPEMAX; i++)
{
_rndY = RND->getFromIntTo(300, 500); // Y축 랜덤 설정
// socre 초기화
_pipe[i].score = RectMakeCenter(WINSIZEX / 2 + 200 + (i * 350), _rndY, 10, 100);
// top 초기화
_pipe[i].top = RectMakeCenter
(
(_pipe[i].score.left + _pipe[i].score.right) / 2,
(_pipe[i].score.top + _pipe[i].score.bottom) / 2 - 300,
100, 400
);
// bottom 초기화
_pipe[i].bottom = RectMakeCenter
(
(_pipe[i].score.left + _pipe[i].score.right) / 2,
(_pipe[i].score.top + _pipe[i].score.bottom) / 2 + 300,
100, 400
);
// 충돌여부 초기화
_pipe[i]._isCrash = false;
}
return S_OK;
}
//메모리 해제는 여기다 하세요 제발
void playGround::release()
{
gameNode::release();
}
//여기에다 연산하세요 제에발
void playGround::update()
{
gameNode::update();
// 엔터키 누를 시 게임시작.
if (KEYMANAGER->isOnceKeyDown(VK_RETURN))
{
_isGameStart = true;
_isGameOver = false;
}
if (!_isGameOver)
{
// Space(점프)를 할 떄 중력값, 파워, 점프 여부 초기화
if (KEYMANAGER->isOnceKeyDown(VK_SPACE))
{
_gravity = 0.15f;
_jumpPower = 3.0f;
_isJump = true;
}
// 점프 상태일 때 _y 값 설정
if (_isJump)
{
_y -= _jumpPower;
_jumpPower -= _gravity;
}
for (int i = 0; i < PIPEMAX; i++)
{
// 스코어 움직임
_pipe[i].score.left -= 5;
_pipe[i].score.right -= 5;
// TOP, BOTTOM 연산
_pipe[i].top = RectMakeCenter
(
(_pipe[i].score.left + _pipe[i].score.right) / 2,
(_pipe[i].score.top + _pipe[i].score.bottom) / 2 - 300,
100, 400
);
_pipe[i].bottom = RectMakeCenter
(
(_pipe[i].score.left + _pipe[i].score.right) / 2,
(_pipe[i].score.top + _pipe[i].score.bottom) / 2 + 300,
100, 400
);
// TOP의 오른쪽이 0보다 작을때 (맵 밖으로 나갈 경우)
if (_pipe[i].top.right < 0)
{
_rndY = RND->getFromIntTo(300, 500); // Y축 랜덤 설정
_pipe[i].score = RectMakeCenter(WINSIZEX + 350, _rndY, 10, 100); // 스코어 재생성
_pipe[i]._isCrash = false; // 충돌여부 끔
}
// IntersectRect를 이용한 충돌 여부 (점수 상승)
RECT temp;
if (IntersectRect(&temp, &_player, &_pipe[i].score) && !_pipe[i]._isCrash)
{
_score++; // 점수추가
_pipe[i]._isCrash = true; // 충돌여부 킴
}
// IntersectRect를 이용한 충돌 여부 (게임 패배)
if (IntersectRect(&temp, &_player, &_pipe[i].top) ||
IntersectRect(&temp, &_player, &_pipe[i].bottom))
{
_isGameOver = true; // 게임오버 킴
}
}
}
// 'A'키를 입력할 떄 재시작
if (KEYMANAGER->isOnceKeyDown(0x41))
{
if (_isGameOver) this->init();
}
// 플레이어 재 연산
_player = RectMakeCenter(_x, _y, 50, 50);
}
//여기에다 그려라 좀! 쫌!
void playGround::render(HDC hdc)
{
// 초기 시작 안내 문구 출력
if (!_isGameStart)
{
char str2[128];
sprintf_s(str2, "시작하려면 Enter를 누르세요.");
TextOut(hdc, WINSIZEX/2, WINSIZEY/2, str2, strlen(str2));
}
// 플레이어 출력
Rectangle(hdc, _player);
// top, bottom, score 출력
for (int i = 0; i < PIPEMAX; i++)
{
Rectangle(hdc, _pipe[i].top);
Rectangle(hdc, _pipe[i].bottom);
Rectangle(hdc, _pipe[i].score);
}
// 좌측 상단 점수 출력
char str[128];
sprintf_s(str, "점수 : %d", _score);
TextOut(hdc, 10, 10, str, strlen(str));
// 패배 했을 때 재시작 안내 문구 출력
if (_isGameOver == true && _isGameStart)
{
char str3[128];
sprintf_s(str3, "다시 시작하려면 'A'키를 누르세요.");
TextOut(hdc, WINSIZEX / 2, WINSIZEY / 2, str3, strlen(str3));
}
}
어떻게 구현해야 할까, 로직은 이미 한번 블로그에 정리를 해놓았기 때문에 보다 편리하게 코드를 짤 수 있었다.
개인적으로 해보고 싶은 심화학습에 작성한 맵 위, 아래로 나가는 것은 아래로 못나가게 설정해보자는 돌이켜 생각해보니 애초에 플레이어의 파워가 약해 바깥으로 나갈 수 있는 상태가 되지 않아 구현할 수 없다는걸 알았다. 물론, 플레이어의 점프 파워를 변경하면 설정할 수 있겠지만 지금은 할 일이 산더미이니 다음에 해보자! 구현하는 방법은 간단하니 🤓
결과화면
짜란~ 😎
선생님이 했던 게임에 게임 시작, 게임 오버를 추가로 작성하여 안내 문구가 나오도록 업그레이드 까지 하였다. 물론 bool값을 이용하는 간단한 작업이지만 예전에 나는 차마 미처 생각도 못했으니 지금이라도 구현하니 참 기분좋다.
(찐_최종) 결과..
예전에 내 주셨던 과제들을 다시금 풀어보면 그 때 당시에는 굉장히 어려웠는데 돌이켜보니 너무나도 쉬운 과제였다. 아마 오늘의 과제를 끙끙대며 하는 나도, 며칠 뒤에 내가 봤을 땐 정말 쉬운 과제라고 생각할 수 있었으면 좋겠다. 😣
'학원 > 경일게임아카데미' 카테고리의 다른 글
26. 열여섯번째 수업과제 [러닝 게임] (0) | 2022.12.29 |
---|---|
25. 열여섯번째 수업 (0) | 2022.12.26 |
23. 열다섯번째 수업 (0) | 2022.12.26 |
22. 열네번째 수업과제 [윈도우 탐색기 구현] (1) | 2022.12.23 |
21. 열네번째 수업과제 [드래그 앤 드롭] (0) | 2022.12.22 |