A. 배열 실습에 앞서
1. 먼저 생각해보기
a. 게임의 기초적인 구조
게임은 항상 돌아가야 함
어딘가에서 while문으로 사용자에게 계속된 입력, 로직, 출력을 받는 형태로 진행됨
따라서 대부분의 게임 구조는 아래와 같음
b. 실습해볼 맵의 기본 구조
- 5X5의 구조
- 사용자 ♨ 가 □ 내에서는 자유롭게 움직이고
- ■로는 이동하지 못하게 제작
c. 실습해볼 맵의 기능
0) 기본 처리
커서의 깜빡임 제어하기
1) 입력
사용자의 움직임을 감지하기
2) 로직
플레이어 움직이기 (범위 체크, 위치 정리, 새 위치 이동)
입력값 처리하기 (값이 유효한지 체크, 유효하다면 플레이어 움직이기 실행)
3) 출력
값에 따른 맵 생성하기 (커서의 포지션을 잡아주기를 실행 후 생성)
B. 배열 실습
1. 1차원 맵으로 생성하기
a. 기본 기능 생성하기
- Helper.h / Helper.cpp 파일을 만들기
- 커서의 위치를 이동하는 기능
- 커서를 껐다 켜는 기능과 (코드는 원래 있던 windows.h에 정의되어 있는 함수를 사용)
- 플레이어가 움직이는걸 감지할 기능 생성하기
Helper.h
#pragma once
enum MoveDir
{
MD_NONE,
MD_LEFT,
MD_RIGHT,
MD_UP,
MD_DOWN
};
void HandleKeyInput();
void SetCursorPosition(int x, int y);
void SetCursorOnOff(bool visible);
extern MoveDir GMoveDir; //사용자의 입력도 값을 처리 해야해서 외부에서 알아야 함
Helper.cpp
#include "Helper.h"
#include <windows.h>
MoveDir GMoveDir;
//2. 커서의 위치 이동
void SetCursorPosition(int x, int y)
{
HANDLE output = ::GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos = { (SHORT)x, (SHORT)y };
::SetConsoleCursorPosition(output, pos);
}
//3. 커서 깜빡이는 것을 없애는 기능
void SetCursorOnOff(bool visible)
{
HANDLE output = ::GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO cursorInfo;
::GetConsoleCursorInfo(output, &cursorInfo);
cursorInfo.bVisible = visible;
::SetConsoleCursorInfo(output, &cursorInfo);
}
//4. 플레이어가 움직이는걸 감지할 기능 생성하기
void HandleKeyInput()
{
if (::GetAsyncKeyState(VK_LEFT) & 0x8000)
GMoveDir = MD_LEFT;
else if (::GetAsyncKeyState(VK_RIGHT) & 0x8000)
GMoveDir = MD_RIGHT;
else if (::GetAsyncKeyState(VK_UP) & 0x8000)
GMoveDir = MD_UP;
else if (::GetAsyncKeyState(VK_DOWN) & 0x8000)
GMoveDir = MD_DOWN;
else
GMoveDir = MD_NONE;
}
b. 게임의 커서 깜빡임 잡아주기 - 실행
처음 실행했을 때 커서가 깜빡이면 안되므로 false로 실행
c. 1D로 맵 생성하기
- Map.h / Map.cpp 파일을 만들기
- 먼저 1D로 생성되는 맵의 그림을 그려주고
- 맵을 생성하는 함수를 생성
- 커서 포지션을 0으로 설정
- 맵의 인덱스 정보로 맵 이미지를 특수기호로 변경
- 추가적으로 맵을 외부에서 사용할 수 있게 extern 선언과
- 필요한 맵 사이즈의 상수를 const화 하기
Map.h
#pragma once
//4.
const int MAP_SIZE = 5;
void PrintMap1D();
//5,
extern int GMap1D[MAP_SIZE * MAP_SIZE];
Map.cpp
#include "Map.h"
#include "Helper.h"
#include <iostream>
using namespace std;
//2. 1D로 생성되는 맵의 그림
int GMap1D[MAP_SIZE * MAP_SIZE] =
{
1, 1, 0, 1, 1,
1, 0, 0, 0, 1,
1, 0, 2, 0, 1,
1, 0, 0, 0, 1,
1, 1, 1, 1, 1,
};
//3.
void PrintMap1D()
{
SetCursorPosition(0, 0); //커서의 포지션을 0.0으로 잡아줌
//맵의 인덱스 정보로 맵을 변경해줌
for (int i = 0; i < MAP_SIZE * MAP_SIZE; i++)
{
switch (GMap1D[i])
{
case 0:
cout << "□";
break;
case 1:
cout << "■";
break;
case 2:
cout << "♨";
break;
}
if ((i + 1) % MAP_SIZE == 0)
cout << endl;
}
}
d. 생성한 맵을 출력
e. 플레이어의 위치에 따른 맵 변경하기
- Player.h / Player.cpp 파일을 만들기
- 플레이어의 초기 위치 잡기
- 맵의 값에 따라서 이동할 수 있는 범위 잡기
- 기존 위치를 정리하기
- 입력 받은 위치로 새 위치로 이동하기
Player.h
#pragma once
void MovePlayer(int x, int y);
extern int GPlayerX;
extern int GPlayerY;
Player.cpp
#include "Player.h"
#include "Map.h"
#include "Helper.h"
bool canMove = true;
int GPlayerX = 2; //1. 플레이어의 초기 위치
int GPlayerY = 2;
void MovePlayer(int x, int y)
{
// 2. 범위 체크
if (x < 0 || x >= MAP_SIZE)
return;
if (y < 0 || y >= MAP_SIZE)
return;
// 벽 체크
//Y가 늘어나는 만큼 인덱스값이 늘어나기 때문에 아래 공식이 발생
int index = y * MAP_SIZE + x;
if (GMap1D[index] == 1)
return;
// 3. 기존 위치 정리
{
int index = GPlayerY * MAP_SIZE + GPlayerX;
GMap1D[index] = 0;
}
// 4. 새 위치 이동
{
GPlayerX = x; //새 위치로 이동 후 저장
GPlayerY = y;
int index = GPlayerY * MAP_SIZE + GPlayerX;
GMap1D[index] = 2;
}
}
f. 플레이어의 이동을 처리
입력 받을 때 사용하는 GetAsyncKeyState은 while문에서 계속된 값을 주기 때문에
로직에 사용하기 위해선 값을 처리를 해줘야함
따라서 키보드를 떼고 있을 때 움직일 수 있게 처리!!
- 키보드를 떼고 있으면, 움직일 수 있는지 판별 후
- 값에 따라 위에서 생성한 e. 코드를 실행시켜준다
Player.cpp
...
void HandleMove()
{
// 키보드 떼고 있으면, 다음 번엔 움직일 수 있다
if (GMoveDir == MD_NONE)
{
canMove = true;
return;
}
// 움직일 수 있는지?
if (canMove == false)
return;
canMove = false;
switch (GMoveDir)
{
case MD_LEFT:
MovePlayer(GPlayerX - 1, GPlayerY);
break;
case MD_RIGHT:
MovePlayer(GPlayerX + 1, GPlayerY);
break;
case MD_UP:
MovePlayer(GPlayerX, GPlayerY - 1);
break;
case MD_DOWN:
MovePlayer(GPlayerX, GPlayerY + 1);
break;
}
}
물론 헤더파일에도 추가해줘야 함
2. 2차원 맵으로 생성하기
a. 2차원 배열로 맵 변경하기
Map.h
#pragma once
const int MAP_SIZE = 5;
void PrintMap2D();
extern int GMap2D[MAP_SIZE][MAP_SIZE];
Map.cpp
...
int GMap2D[MAP_SIZE][MAP_SIZE] =
{
{ 1, 1, 1, 1, 1 },
{ 1, 0, 0, 0, 1 },
{ 1, 0, 2, 0, 1 },
{ 1, 0, 0, 0, 1 },
{ 1, 1, 1, 1, 1 },
};
void PrintMap2D()
{
SetCursorPosition(0, 0);
for (int y = 0; y < MAP_SIZE; y++)
{
for (int x = 0; x < MAP_SIZE; x++)
{
switch (GMap2D[y][x])
{
case 0:
cout << "□";
break;
case 1:
cout << "■";
break;
case 2:
cout << "♨";
break;
}
}
cout << endl;
}
}
b. 플레이어 위치에 따른 맵 변경 코드 수정하기
Player.cpp
void MovePlayer(int x, int y)
{
// 2. 범위 체크
if (x < 0 || x >= MAP_SIZE)
return;
if (y < 0 || y >= MAP_SIZE)
return;
// 벽 체크
//Y가 늘어나는 만큼 인덱스값이 늘어나기 때문에 아래 공식이 발생
int index = y * MAP_SIZE + x;
if (GMap2D[y][x] == 1)
return;
// 3. 기존 위치 정리
{
int index = GPlayerY * MAP_SIZE + GPlayerX;
GMap2D[GPlayerY][GPlayerX] = 0;
}
// 4. 새 위치 이동
{
GPlayerX = x; //새 위치로 이동 후 저장
GPlayerY = y;
int index = GPlayerY * MAP_SIZE + GPlayerX;
GMap2D[GPlayerY][GPlayerX] = 2;
}
}
3. 완성된 모습
'프로젝트 > [인프런] 게임 프로그래머 입문 올인원' 카테고리의 다른 글
[게임 프로그래머 입문 올인원] 동적할당과 캐스팅 : 아이템 드랍 (36강 + 38강) (0) | 2023.09.14 |
---|---|
[게임 프로그래머 입문 올인원] 포인터와 배열 : 달팽이 문제 (25강) (2) | 2023.09.07 |
[게임 프로그래머 입문 올인원] 포인터와 배열 : 로또 번호 생성기 (24강) (0) | 2023.09.07 |
[게임 프로그래머 입문 올인원] 함수와 디버깅 : TextRPG (15강) (0) | 2023.09.06 |
[인프런] 게임 프로그래머 입문 올인원 목차 (0) | 2023.09.01 |