bdfgdfg
배달맨 게임코딩 본문
책을 보지않고 우선 만들어보기.
문제없이 작동.
메인 소스코드
#include "Game.h"
#include "Player.h"
#include <iostream>
Game::Game() : m_mapData(nullptr),m_gamePlayer(new Player()), m_clear(false), m_count(0)
{
m_mapData = new int*[MAP_SIZEY];
for (int x = 0; x < MAP_SIZEY; ++x)
m_mapData[x] = new int[MAP_SIZEX];
}
Game::~Game()
{
for (int y = 0; y < MAP_SIZEY; ++y)
delete[] m_mapData[y];
delete[] m_mapData;
delete m_gamePlayer;
}
bool Game::GameInit()
{
CreateMap();
return true;
}
bool Game::GameRun()
{
char input;
int y, x;
while (1)
{
if (m_count == CLEAR_BOX)
{
std::cout << "게임 클리어!" << std::endl;
break;
}
Render();
std::cout << "w(상),s(하),a(좌),d(우)" << std::endl;
std::cin >> input;
switch (input)
{
case UP:
PlayerMove(-1, 0);
break;
case DOWN:
PlayerMove(1, 0);
break;
case LEFT:
PlayerMove(0, -1);
break;
case RIGHT:
PlayerMove(0, 1);
break;
}
system("cls");
}
return true;
}
void Game::PlayerMove(int y, int x)
{
int moveX = m_gamePlayer->playerGetX() + x;
int moveY = m_gamePlayer->playerGetY() + y;
if (m_mapData[moveY][moveX] == OBJ_WALL)
return;
else if (m_mapData[moveY][moveX] == OBJ_SPACE || m_mapData[moveY][moveX] == OBJ_DEST)
{
m_mapData[m_gamePlayer->playerGetY()][m_gamePlayer->playerGetX()] = OBJ_SPACE;
m_gamePlayer->playerSetPos(moveY, moveX);
m_mapData[moveY][moveX] = OBJ_PLAYER;
}
else if (m_mapData[moveY][moveX] == OBJ_BOX)
{
if (m_mapData[moveY + y][moveX + x] != OBJ_WALL && m_mapData[moveY + y][moveX + x] != OBJ_BOX)
{
if (m_mapData[moveY + y][moveX + x] == OBJ_DEST)
m_count++;
m_mapData[moveY + y][moveX + x] = OBJ_BOX;
m_mapData[m_gamePlayer->playerGetY()][m_gamePlayer->playerGetX()] = OBJ_SPACE;
m_gamePlayer->playerSetPos(moveY, moveX);
m_mapData[moveY][moveX] = OBJ_PLAYER;
}
}
}
void Game::Render()
{
int y, x;
for (y = 0; y < MAP_SIZEY; ++y)
{
for (x = 0; x < MAP_SIZEX; ++x)
{
switch (m_mapData[y][x])
{
case OBJ_SPACE:
if (((y == 1 && x == 2) || (y == 1 && x == 3)) && m_mapData[y][x] == OBJ_SPACE) // 1,2 1,3
{
m_mapData[y][x] = OBJ_DEST;
std::cout << '.';
break;
}
std::cout << ' ';
break;
case OBJ_PLAYER:
std::cout << 'p';
break;
case OBJ_WALL:
std::cout << '#';
break;
case OBJ_BOX:
std::cout << 'o';
break;
case OBJ_DEST:
std::cout << '.';
break;
}
}
std::cout << std::endl;
}
}
void Game::CreateMap()
{
int y, x;
for (y = 0; y < MAP_SIZEY; ++y)
{
for (x = 0; x < MAP_SIZEX; ++x)
{
if (y == 0 || y == MAP_SIZEY - 1)
m_mapData[y][x] = OBJ_WALL;
else if (y == 1)
{
if (x == 0 || x == MAP_SIZEX - 1)
m_mapData[y][x] = OBJ_WALL;
else if (x == 2 || x == 3)
m_mapData[y][x] = OBJ_DEST;
else
m_mapData[y][x] = OBJ_SPACE;
}
else if (y == 2)
{
if (x == 0 || x == MAP_SIZEX - 1)
m_mapData[y][x] = OBJ_WALL;
else if (x == 2 || x == 3)
m_mapData[y][x] = OBJ_BOX;
else
m_mapData[y][x] = OBJ_SPACE;
}
else if (y == 3)
{
if (x == 0 || x == MAP_SIZEX - 1)
m_mapData[y][x] = OBJ_WALL;
else
m_mapData[y][x] = OBJ_SPACE;
}
}
}
m_gamePlayer->SetPlayerPoly(OBJ_PLAYER);
m_mapData[m_gamePlayer->playerGetY()][m_gamePlayer->playerGetX()] = m_gamePlayer->playerPoly();
}
플레이어가 목적지를 지날 때 플레이어가 덮어버릴 경우 목적지가 사라지므로 Render부분에서 해당 목적지가 빈공간인지 체크 후 다시 목적지로 넣어줌.
박스를 옮길 때 옮기는 위치가 박스거나 벽이면 움직이지 못하게 조건문을 달아놓음.
또한 플레이어가 목적지 위로 지나가 빈공간을 만들 때 다시 목적지로 만들어놔야한다.
-----------------------------------------------------------------------------------------------------------------------------
맵 데이터를 파일로 읽어들이기
맵 데이터를 코드상으로 하드코딩하는것은 별로 좋지못하다. (물론 프로그램이 돌아가는데는 이상이없음)
매번 맵이 바뀔때마다 다시 컴파일 하는 것은 힘든일이기에.
그렇기에 매우 간단하게 작성된 파일의 데이터를 읽어보도록 하자. (참고로 파일 읽기는 fstream헤더가 필요)
위와 같이 작성된 파일을 코드상에서 파일을 불러와 읽어본다.
ifstream inputFile("stage.txt", ifstream::binary);
inputFile.seekg(0, ifstream::end);//맨끝으로이동
// 위치 = 파일 크기
int fileSize = static_cast<int>(inputFile.tellg());
inputFile.seekg(0, ifstream::beg);//맨처음으로 이동
char* fileImage = new char[fileSize]; //영역확보
inputFile.read(fileImage, fileSize); // 읽기
seekg함수는 파일속 임의의 위치에 접근하는 함수다.
seekg는 매개변수 하나인 버전과 두개인 버전이 있는데
하나인 버전은 처음부터 인자로 넣은 값의 위치까지 읽어들이고
두개인 버전은 첫번째 인자로부터 두번째로 지정한 위치까지 읽어들인다.
즉 둘 다 첫번째 인자로 offset을 건넨다고 생각하면 된다.
두번째 인자로 정수값을 건네도 되지만, 보통
ios::cur(현재의 위치를 기준으로 오프셋 만큼이동)
ios::beg(begin)
ios::end(end)
tellg는 현재 위치를 반환한다.
실행해보면
잘 출력이 된다.
참고로 파일을 읽어들일 때 기준이 되는 경로는 .vcproj(프로젝트파일)파일이 있는 곳이 기준이 된다.
즉 저 파일이 있는 폴더에 맵 데이터를 넣거나 혹은 다른 폴더에 따로 관리를 해야한다면 상대경로를 이용해 넣어줄 수 있다. (물론 현재 inputFile("stage.txt")도 프로젝트 파일이 있는 기준. -> 상대경로임)
즉 따로 Map이란 폴더를 만들고 그 안에 맵 데이터 파일을 집어넣는다면
ifstream inputFile("Map\\stage.txt", ifstream::binary);
이런식으로. 만약 프로젝트 폴더 밖에있다면?
ifstream inputFile("..\\Map\\stage.txt", ifstream::binary);
이렇게 편리하게 상대경로를 통해서 접근하면 된다.
이제 이 맵 데이터를 Game클래스의 멤버 mapData에 저장해보도록 하자.
우선 나는 행과 열의 길이를 알고 싶었기에 따로 계산하는 로직을 추가했다.
(따로 이게 가능한지는 잘 모르겠다)
std::ifstream fin;
fin.open("..\\Map\\stage.txt");
if (!fin.is_open())
return;
int X = 0, Y = 0;
bool colCheck = false;
while (true) // 행과 열의 길이를 계산
{
char c = fin.get();
if (fin.eof())
{
++Y;
fin.clear(); // eof를 만나고 seekg를 사용하기 위해 clear를 해줘야함. (eof비트 셋되므로)
fin.seekg(0, std::ifstream::beg);
break;
}
if (c == '\n')
{
++Y;
colCheck = true;
continue;
}
if(!colCheck) // 열의 길이는 한번 계산하면 끝.
++X;
}
m_mapSizeY = Y;
m_mapSizeX = X;
m_mapData = new int* [m_mapSizeY]; // 계산한 행과 열의 크기만큼 2차원배열 동적할당.
for (int y = 0; y < m_mapSizeY; ++y)
m_mapData[y] = new int[m_mapSizeX];
그리고 나서
for (int y = 0; y < m_mapSizeY; ++y)
{
for (int x = 0; x < m_mapSizeX; ++x)
{
char c = fin.get();
if (c >= '0' && c <= '9')
m_mapData[y][x] = c - '0';
else
m_mapData[y][x] = 0;
}
char c = fin.get(); // \n버리기 위함.
}
fin.close(); // 닫기 필수.
이렇게 동적할당한 배열에 다시 집어넣어주면 된다.
전체코드
#include "Game.h"
#include "Player.h"
#include <iostream>
#include <fstream>
Game::Game() : m_mapData(nullptr),m_gamePlayer(new Player()), m_clear(false), m_count(0),m_mapSizeY(0),m_mapSizeX(0)
{
/*m_mapData = new int*[MAP_SIZEY];
for (int x = 0; x < MAP_SIZEY; ++x)
m_mapData[x] = new int[MAP_SIZEX];*/
}
Game::~Game()
{
for (int y = 0; y < m_mapSizeY; ++y)
delete[] m_mapData[y];
delete[] m_mapData;
delete m_gamePlayer;
}
bool Game::GameInit()
{
CreateMap();
return true;
}
bool Game::GameRun()
{
if (m_mapData == nullptr)
return false;
char input;
int y, x;
while (1)
{
if (m_count == CLEAR_BOX)
{
std::cout << "게임 클리어!" << std::endl << std::endl << std::endl << std::endl << std::endl << std::endl << std::endl << std::endl << std::endl << std::endl << std::endl << std::endl << std::endl << std::endl << std::endl;
break;
}
Render();
std::cout << "w(상),s(하),a(좌),d(우)" << std::endl;
std::cin >> input;
switch (input)
{
case UP:
PlayerMove(-1, 0);
break;
case DOWN:
PlayerMove(1, 0);
break;
case LEFT:
PlayerMove(0, -1);
break;
case RIGHT:
PlayerMove(0, 1);
break;
}
system("cls");
}
return true;
}
void Game::PlayerMove(int y, int x)
{
int moveX = m_gamePlayer->playerGetX() + x;
int moveY = m_gamePlayer->playerGetY() + y;
if (m_mapData[moveY][moveX] == OBJ_WALL)
return;
else if (m_mapData[moveY][moveX] == OBJ_SPACE || m_mapData[moveY][moveX] == OBJ_DEST)
{
m_mapData[m_gamePlayer->playerGetY()][m_gamePlayer->playerGetX()] = OBJ_SPACE;
m_gamePlayer->playerSetPos(moveY, moveX);
m_mapData[moveY][moveX] = OBJ_PLAYER;
}
else if (m_mapData[moveY][moveX] == OBJ_BOX)
{
if (m_mapData[moveY + y][moveX + x] != OBJ_WALL && m_mapData[moveY + y][moveX + x] != OBJ_BOX)
{
if (m_mapData[moveY + y][moveX + x] == OBJ_DEST)
m_count++;
m_mapData[moveY + y][moveX + x] = OBJ_BOX;
m_mapData[m_gamePlayer->playerGetY()][m_gamePlayer->playerGetX()] = OBJ_SPACE;
m_gamePlayer->playerSetPos(moveY, moveX);
m_mapData[moveY][moveX] = OBJ_PLAYER;
}
}
}
void Game::Render()
{
int y, x;
for (y = 0; y < m_mapSizeY; ++y)
{
for (x = 0; x < m_mapSizeX; ++x)
{
switch (m_mapData[y][x])
{
case OBJ_SPACE:
if (((y == 1 && x == 2) || (y == 1 && x == 3)) && m_mapData[y][x] == OBJ_SPACE) // 1,2 1,3
{
m_mapData[y][x] = OBJ_DEST;
std::cout << '.';
break;
}
std::cout << ' ';
break;
case OBJ_PLAYER:
std::cout << 'p';
break;
case OBJ_WALL:
std::cout << '#';
break;
case OBJ_BOX:
std::cout << 'o';
break;
case OBJ_DEST:
std::cout << '.';
break;
}
}
std::cout << std::endl;
}
}
void Game::CreateMap()
{
int y, x;
/*for (y = 0; y < MAP_SIZEY; ++y)
{
for (x = 0; x < MAP_SIZEX; ++x)
{
if (y == 0 || y == MAP_SIZEY - 1)
m_mapData[y][x] = OBJ_WALL;
else if (y == 1)
{
if (x == 0 || x == MAP_SIZEX - 1)
m_mapData[y][x] = OBJ_WALL;
else if (x == 2 || x == 3)
m_mapData[y][x] = OBJ_DEST;
else
m_mapData[y][x] = OBJ_SPACE;
}
else if (y == 2)
{
if (x == 0 || x == MAP_SIZEX - 1)
m_mapData[y][x] = OBJ_WALL;
else if (x == 2 || x == 3)
m_mapData[y][x] = OBJ_BOX;
else
m_mapData[y][x] = OBJ_SPACE;
}
else if (y == 3)
{
if (x == 0 || x == MAP_SIZEX - 1)
m_mapData[y][x] = OBJ_WALL;
else
m_mapData[y][x] = OBJ_SPACE;
}
}
}*/
std::ifstream fin;
fin.open("..\\Map\\stage.txt");
if (!fin.is_open())
return;
int X = 0, Y = 0;
bool colCheck = false;
while (true)
{
char c = fin.get();
if (fin.eof())
{
++Y;
fin.clear(); // eof를 만나고 seekg를 사용하기 위해 clear를 해줘야함. (eof비트 셋되므로)
fin.seekg(0, std::ifstream::beg);
break;
}
if (c == '\n')
{
++Y;
colCheck = true;
continue;
}
if(!colCheck)
++X;
}
m_mapSizeY = Y;
m_mapSizeX = X;
m_mapData = new int* [m_mapSizeY];
for (int y = 0; y < m_mapSizeY; ++y)
m_mapData[y] = new int[m_mapSizeX];
for (int y = 0; y < m_mapSizeY; ++y)
{
for (int x = 0; x < m_mapSizeX; ++x)
{
char c = fin.get();
if (c >= '0' && c <= '9')
m_mapData[y][x] = c - '0';
else
m_mapData[y][x] = 0;
}
char c = fin.get(); // \n버리기 위함.
}
fin.close(); // 닫기 필수.
m_gamePlayer->SetPlayerPoly(OBJ_PLAYER);
m_mapData[m_gamePlayer->playerGetY()][m_gamePlayer->playerGetX()] = m_gamePlayer->playerPoly();
}
CreateMap이외엔 변한부분은 거의 없다. (멤버변수 추가라던지 생성자에서 동적할당한 부분을 없앴다던지.. -> 여기서 생성자에서 동적할당을 하지 않았기에 Run함수에서 맵이 nullptr이라면 종료시키게 한다.)
세가의 신입사원 교육에서 배우는 게임 프로그래밍 정석을 참고하였습니다.
'게임프로그래밍' 카테고리의 다른 글
델타타임-deltaTime (0) | 2021.09.22 |
---|---|
비트 플래그와 비트 연산 (0) | 2021.09.20 |