1, 게임 전반적 흐름을 관리할 관리자 객체 만들기
- 새 필터 -> HighLevel_Interface를 만들고 클래스 Application 추가
//Application.h
#pragma once
class Application
{
public:
Application();
~Application();
bool Initialize();
void Update();
void Rendering();
void Destroy();
private:
};
//Application.cpp
#include "Application.h"
Application::Application()
{
}
Application::~Application()
{
}
bool Application::Initialize()
{
return true;
}
void Application::Update()
{
}
void Application::Rendering()
{
}
void Application::Destroy()
{
}
2. 게임에서 사용할 객체들의 부모 클래스 GameObject 만들기
- 새 필터 -> GameObject를 만들고 클래스 GameObject 추가
- 모든 게임 오브젝트들이 가져야 할 것이 무엇인지 생각해보기 -> 좌표가 있어야 함
- 좌표와 그 외 다른 공통적으로 쓰일 것들을 정의할 뭔가가 필요함 (재사용성 고려)
- 수학 관련 라이브러리 yaMath.h와 그것을 포함한 범용적인 헤더 Common.h를 만듦
// yaMath.h
#pragma once
struct Vector2
{
int x;
int y;
Vector2()
: x(0), y(0)
{
}
Vector2(int x, int y)
: x(x), y(y)
{
}
};
typedef Vector2 Pos;
// Common.h
#pragma once
#include <iostream>
#include <Windows.h>
#include <vector>
#include "yaMath.h"
3. Application 클래스에서 개략적인 게임 로직 틀 만들기
// Application.h
#pragma once
class Application
{
public:
Application();
~Application();
bool Initialize();
void Update();
void Rendering();
void Destroy();
bool GetIsRunning() { return isRunning; }
private:
bool isRunning;
};
// Application.cpp
#include "Application.h"
Application::Application()
: isRunning(true)
{
}
Application::~Application()
{
}
bool Application::Initialize()
{
return true;
}
void Application::Update()
{
}
void Application::Rendering()
{
}
void Application::Destroy()
{
}
// main.cpp
#include <iostream>
#include "Application.h"
Application application;
int main()
{
application.Initialize();
while (application.GetIsRunning())
{
application.Update();
application.Rendering();
}
application.Destroy();
}
- 전역 변수로 만들어진 Application 객체를 사용해서
- Initialize 함수로 초기화 (이후에 게임 오브젝트들의 초기값을 세팅해주는 역할)
- while 루프를 통해 Update(게임의 내부 상태값 변화) 하고, Rendering(변화된 값을 그려줌)을 무한히 반복
- 아마 Update 함수를 통해 Application의 내부 변수인 isRunning을 false로 만들어서 게임을 종료시킬 것 같다.
- while 루프는 GetIsRunning이라는 상태값이 true인지 체크하고 false일 경우 루프를 빠져나간다.
- Destroy는 게임이 종료된 후 리소스를 해제해주는 역할
4. 맵을 그려줄 Map 클래스 설계하기
// Map.h
#pragma once
#define SIZE_MAP_Y 30
#define SIZE_MAP_X 24
class Map
{
public:
Map();
~Map();
void Initialize();
void Render();
private:
wchar_t mMap[SIZE_MAP_Y][SIZE_MAP_X];
// 상수를 넣으면 맵을 탐색하려면 상수를 집어넣어야 하는 불편함이 있음
// 그리고 사이즈 변경 시 매우 불편함
};
// Map.cpp
#include "Common.h"
#include "Map.h"
Map::Map()
{
}
Map::~Map()
{
}
void Map::Initialize()
{
for (size_t y = 0; y < SIZE_MAP_Y; y++)
{
for (size_t x = 0; x < SIZE_MAP_X; x++)
{
mMap[y][x] = L'□';
}
}
}
void Map::Render()
{
for (size_t y = 0; y < SIZE_MAP_Y; y++)
{
for (size_t x = 0; x < SIZE_MAP_X; x++)
{
std::wcout << mMap[y][x];
}
std::cout << std::endl;
}
}
- Map 클래스는 게임이 진행될 맵의 정보를 가진 클래스
- GameObject가 가진 좌표랑은 별 상관이 없으므로 상속받지 않는 별개의 객체
- Initialize 함수에서 Map의 좌표에 초기값 세팅해주기 (Application의 Initialize가 호출해서 초기화)
- Render 함수에서 Map 이중 for문을 돌며 출력 (이 Render 함수는 Application의 Render에서 작동함)
// Application.cpp
#include "Common.h"
#include "Application.h"
Application::Application()
: isRunning(true)
{
}
Application::~Application()
{
}
bool Application::Initialize()
{
mMap.Initialize();
return true;
}
void Application::Update()
{
}
void Application::Rendering()
{
system("cls");
mMap.Render();
Sleep(500);
}
void Application::Destroy()
{
}
- Rendering 함수에 system("cls");는 그리기 전 콘솔 창을 한 번 지워달라는 의미
- Sleep(500)은 출력 속도가 너무 빨라서 깜빡거리는 문제가 있기 때문에,
- 0.5초에 한번씩 출력해달라는 의미, (ms 단위라서 500)
5. 콘솔의 커서 위치와 색상 조정하는 함수 추가
- Common.h에서 언제 어디서나 쓸 수 있는 함수 2개 추가
- 출력 시 커서가 항상 맨 아래 위치하므로 변경이 필요함
- 게임을 만들 때 색상도 시각적으로 중요한 역할을 하므로 세팅 필요
// Common.h
#pragma once
#include <iostream>
#include <Windows.h>
#include <vector>
#include "yaMath.h"
enum Color
{
BLACK, /* 0 : 까망 */
DARK_BLUE, /* 1 : 어두운 파랑 */
DARK_GREEN, /* 2 : 어두운 초록 */
DARK_SKY_BLUE, /* 3 : 어두운 하늘 */
DARK_RED, /* 4 : 어두운 빨강 */
DARK_VIOLET, /* 5 : 어두운 보라 */
DARK_YELLOW, /* 6 : 어두운 노랑 */
GRAY, /* 7 : 회색 */
DARK_GRAY, /* 8 : 어두운 회색 */
BLUE, /* 9 : 파랑 */
GREEN, /* 10 : 초록 */
SKY_BLUE, /* 11 : 하늘 */
RED, /* 12 : 빨강 */
VIOLET, /* 13 : 보라 */
YELLOW, /* 14 : 노랑 */
WHITE, /* 15 : 하양 */
};
inline void GoToXY(int x, int y) {
COORD xy = { static_cast<SHORT>(x), static_cast<SHORT>(y) };
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), xy);
}
inline void SetColor(int color)
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE) , color);
}
// Map.cpp
#include "Common.h"
#include "Map.h"
Map::Map()
{
}
Map::~Map()
{
}
void Map::Initialize()
{
for (size_t y = 0; y < SIZE_MAP_Y; y++)
{
for (size_t x = 0; x < SIZE_MAP_X; x++)
{
mMap[y][x] = L'□';
}
}
}
void Map::Render()
{
SetColor(Color::DARK_VIOLET);
for (size_t y = 0; y < SIZE_MAP_Y; y++)
{
for (size_t x = 0; x < SIZE_MAP_X; x++)
{
std::wcout << mMap[y][x];
}
std::cout << std::endl;
}
}
- 컬러 세팅
6. 씬 (Scene) 도입하기
- Scene은 각 장면을 보여주는 클래스
- 이후 TitleScene과 PlayScene, DeathScene을 만들기 위한 일종의 추상 클래스
- Application은 Scene의 포인터를 가지고 가상함수를 통해 각 장면마다 다르게 표현할 수 있다.
- -> Map 객체는 Application에서 Scene으로 넘어가고, Map의 자리를 Scene이 대신하는 것
// Scene.h
#pragma once
#include "Map.h"
class Scene
{
public:
void Initialize();
void Update();
void Render();
void Destroy();
private:
Map mMap;
};
// Scene.cpp
#include "Common.h"
#include "Scene.h"
void Scene::Initialize()
{
mMap.Initialize();
}
void Scene::Update()
{
}
void Scene::Render()
{
mMap.Render();
}
void Scene::Destroy()
{
}
// Application.h
#pragma once
#include "Scene.h"
enum SCENETYPE
{
TITLE,
PLAY,
DEATH,
END
};
class Application
{
public:
Application();
~Application();
bool Initialize();
void Update();
void Rendering();
void Destroy();
bool GetIsRunning() { return mbRunning; }
private:
bool mbRunning;
Scene* mScenes[SCENETYPE::END] = {nullptr};
int mPlaySceneNumber;
};
// Application.cpp
#include "Common.h"
#include "Application.h"
Application::Application()
: mbRunning(true)
, mPlaySceneNumber((int)SCENETYPE::TITLE)
{
}
Application::~Application()
{
}
bool Application::Initialize()
{
for (int i = 0; i < SCENETYPE::END; i++)
{
mScenes[i]->Initialize();
}
return true;
}
void Application::Update()
{
mScenes[mPlaySceneNumber]->Update();
}
void Application::Rendering()
{
system("cls");
mScenes[mPlaySceneNumber]->Render();
Sleep(500);
}
void Application::Destroy()
{
for (int i = 0; i < SCENETYPE::END; i++)
{
mScenes[i]->Destroy();
}
}
7. Ttitle Scene 작성하기
- 우선 Scene은 기타 씬들의 설계도 역할을 하기 때문에, 객체가 만들어질 필요가 없으므로 추상클래스 처리
// Scene.h
#pragma once
#include "Map.h"
class Scene
{
public:
Scene();
virtual ~Scene();
virtual void Initialize() = 0;
virtual void Update() = 0;
virtual void Render() = 0;
virtual void Destroy() = 0;
protected:
Map mMap;
};
- 순수 가상 함수가 1개라도 있으면 추상 클래스가 된다.
// Scene.cpp
#pragma once
#include "Scene.h"
class TitleScene : public Scene
{
public:
TitleScene();
~TitleScene();
virtual void Initialize() override;
virtual void Update() override;
virtual void Render() override;
virtual void Destroy() override;
};
// TitleScene.cpp
#include "Common.h"
#include "TitleScene.h"
TitleScene::TitleScene()
{
}
TitleScene::~TitleScene()
{
}
void TitleScene::Initialize()
{
}
void TitleScene::Update()
{
}
void TitleScene::Render()
{
short x = SIZE_MAP_X * 2;
short y = 0;
SET_COLOR(Color::DARK_YELLOW);
GOTO_XY(x, y + 2); printf("┏━━━━━━━━━━━━━━━━━━━━┓");
GOTO_XY(x, y + 3); printf("┃ P U S H P U S H ┃");
GOTO_XY(x, y + 4); printf("┃ made by KHM ┃");
GOTO_XY(x, y + 5); printf("┗━━━━━━━━━━━━━━━━━━━━┛");
SET_COLOR(Color::DARK_YELLOW);
GOTO_XY(x + 2, y + 1);
}
void TitleScene::Destroy()
{
}
- inline 함수로 만들어도 되지만, 매크로 함수의 사용법을 알기 위함과, 수업의 내용을 100% 이해하기 위해서
- GOTO_XY, SET_COLOR 함수를 매크로 함수로 변경
- Render 함수는 Application에서 가지고 있는 mScenes의 멤버로써 Rendering에서 불리게 됨
// Common.h
#pragma once
#include <iostream>
#include <Windows.h>
#include <vector>
#include <conio.h>
#include "yaMath.h"
enum Color
{
BLACK, /* 0 : 까망 */
DARK_BLUE, /* 1 : 어두운 파랑 */
DARK_GREEN, /* 2 : 어두운 초록 */
DARK_SKY_BLUE, /* 3 : 어두운 하늘 */
DARK_RED, /* 4 : 어두운 빨강 */
DARK_VIOLET, /* 5 : 어두운 보라 */
DARK_YELLOW, /* 6 : 어두운 노랑 */
GRAY, /* 7 : 회색 */
DARK_GRAY, /* 8 : 어두운 회색 */
BLUE, /* 9 : 파랑 */
GREEN, /* 10 : 초록 */
SKY_BLUE, /* 11 : 하늘 */
RED, /* 12 : 빨강 */
VIOLET, /* 13 : 보라 */
YELLOW, /* 14 : 노랑 */
WHITE, /* 15 : 하양 */
};
#define GOTO_XY(x, y) SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), {static_cast<short>(x), static_cast<short>(y)})
#define SET_COLOR(color) SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE) , color)
//inline void GoToXY(int x, int y) {
// COORD xy = { static_cast<SHORT>(x), static_cast<SHORT>(y) };
// SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), xy);
//}
//
//inline void SetColor(int color)
//{
// SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE) , color);
//}
#include "Common.h"
#include "Application.h"
#include "TitleScene.h"
#include "PlayScene.h"
#include "DeathScene.h"
Application::Application()
: mbRunning(true)
, mPlaySceneNumber((int)SCENETYPE::TITLE)
{
}
Application::~Application()
{
}
bool Application::Initialize()
{
mScenes[SCENETYPE::TITLE] = new TitleScene;
mScenes[SCENETYPE::PLAY] = new PlayScene;
mScenes[SCENETYPE::DEATH] = new DeathScene;
for (int i = 0; i < SCENETYPE::END; i++)
{
mScenes[i]->Initialize();
}
return true;
}
void Application::Update()
{
mScenes[mPlaySceneNumber]->Update();
}
void Application::Rendering()
{
system("cls");
mScenes[mPlaySceneNumber]->Render();
Sleep(500);
}
void Application::Destroy()
{
for (int i = 0; i < SCENETYPE::END; i++)
{
mScenes[i]->Destroy();
}
}
- mScenes[mPlaySceneNumber]->Render()에서 가상함수 테이블에 덮어써진 TitleScene의 Render를 호출
8. 키 입력 받는 로직 만들기 + 나머지 씬 작성하기
- 추상 클래스의 Initialize 로직 변경 (Application을 인자로 받음)
// Scene.h
#pragma once
#include "Map.h"
class Application;
class Scene
{
public:
Scene();
virtual ~Scene();
virtual void Initialize(Application* app) = 0;
virtual void Update() = 0;
virtual void Render() = 0;
virtual void Destroy() = 0;
protected:
Map mMap;
Application* application;
};
- 이후 씬을 넘길 때 Application이 가진 mPlaySceneNumber 멤버의 값을 수정해야 하므로
- Initialize 함수에서 자신의 포인터인 this를 넘김
- Update에 씬 넘기는 로직 추가
// TitleScene.cpp
#include "Common.h"
#include "TitleScene.h"
#include "Application.h"
TitleScene::TitleScene()
{
}
TitleScene::~TitleScene()
{
}
void TitleScene::Initialize(Application* app)
{
application = app;
}
void TitleScene::Update()
{
//char key = 0;
//key = _getch(); // 입력이 있을 때까지 멈춰있음
if (_kbhit()) // 키 눌림여부를 확인하는 함수
{
char input = _getch();
if (input == '\r')
{
application->SetPlaySceneNumber(SCENETYPE::PLAY);
}
}
}
void TitleScene::Render()
{
short x = SIZE_MAP_X * 2;
short y = 10;
SET_COLOR(Color::DARK_YELLOW);
GOTO_XY(x, y + 2); printf("┏━━━━━━━━━━━━━━━━━━━━┓");
GOTO_XY(x, y + 3); printf("┃ P U S H P U S H ┃");
GOTO_XY(x, y + 4); printf("┃ made by KHM ┃");
GOTO_XY(x, y + 5); printf("┗━━━━━━━━━━━━━━━━━━━━┛");
SET_COLOR(Color::DARK_GREEN);
GOTO_XY(x, y + 8); printf(" PRESS ENTER ");
GOTO_XY(x + 2, y + 1);
}
void TitleScene::Destroy()
{
}
// PlayScene.cpp
#include "Common.h"
#include "PlayScene.h"
#include "Application.h"
PlayScene::PlayScene()
{
}
PlayScene::~PlayScene()
{
}
void PlayScene::Initialize(Application* app)
{
application = app;
}
void PlayScene::Update()
{
if (_kbhit()) // 키 눌림여부를 확인하는 함수
{
char input = _getch();
if (input == '\r')
{
application->SetPlaySceneNumber(SCENETYPE::DEATH);
}
}
}
void PlayScene::Render()
{
short x = SIZE_MAP_X * 2;
short y = 10;
SET_COLOR(Color::DARK_RED);
GOTO_XY(x, y + 2); printf("┏━━━━━━━━━━━━━━━━━━━━━┓");
GOTO_XY(x, y + 3); printf("┃ P L A Y S C E N E ┃");
GOTO_XY(x, y + 4); printf("┗━━━━━━━━━━━━━━━━━━━━━┛");
GOTO_XY(x + 2, y + 1);
}
void PlayScene::Destroy()
{
}
- _kbhit() 함수는 키 입력이 있는지 유무를 검사해주는 함수이며, 키를 받기 위해 멈추거나 하지 않음
- 키가 눌리면 _getch() 함수를 호출해서 어떤 키인지 파악하고, 그게 엔터('\r')면 씬을 넘겨준다.
// DeathScene.cpp
#include "Common.h"
#include "DeathScene.h"
#include "Application.h"
DeathScene::DeathScene()
: changeColor((int)Color::BLACK)
{
}
DeathScene::~DeathScene()
{
}
void DeathScene::Initialize(Application* app)
{
application = app;
}
void DeathScene::Update()
{
if (_kbhit()) // 키 눌림여부를 확인하는 함수
{
char input = _getch();
if (input == '\r')
{
application->SetIsRunning(false);
}
}
}
void DeathScene::Render()
{
changeColor = (changeColor + 1) % 10;
short x = SIZE_MAP_X * 2;
short y = 10;
SET_COLOR(changeColor);
GOTO_XY(x, y + 2); printf("┏━━━━━━━━━━━━━━━━━━━━━━━━━┓");
GOTO_XY(x, y + 3); printf("┃ Your character is dead..┃");
GOTO_XY(x, y + 4); printf("┗━━━━━━━━━━━━━━━━━━━━━━━━━┛");
GOTO_XY(x + 2, y + 1);
}
void DeathScene::Destroy()
{
}
- 숙제로 나온 DeathScene.
- enum 값으로 되어 있는 Color를 순환시켜서 매 Render마다 색이 바뀌게 함
- Update문에서 한 번 더 엔터를 누르면 종료되게 함
'게임 개발 > [AssortRock] 콘솔 게임(CUI) 설계 및 분석' 카테고리의 다른 글
[VampireSurvivor] 개인 프로젝트 - 뱀파이어 서바이벌 콘솔버전 - 2 (0) | 2022.10.10 |
---|---|
[VampireSurvivor] 개인 프로젝트 - 뱀파이어 서바이벌 콘솔버전 - 1 (0) | 2022.10.09 |
[PUSH PUSH] 게임 로직 구현 - AssortRock 17일차 오프라인 수업_220929 (1) | 2022.09.30 |
[PUSH PUSH] 게임 로직 구현 - AssortRock 16일차 오프라인 수업_220928 (1) | 2022.09.29 |
[PUSH PUSH] 게임 로직 구현 - AssortRock 11~12일차 오프라인 수업_220921-22 (1) | 2022.09.23 |
댓글