프로젝트/[인프런] 게임 프로그래머 입문 올인원

[게임 프로그래머 입문 올인원] 포인터와 배열 : 배열 실습 (19강)

순정법사 2023.09.06

A. 배열 실습에 앞서

1. 먼저 생각해보기

a. 게임의 기초적인 구조

게임은 항상 돌아가야 함

어딘가에서 while문으로 사용자에게 계속된 입력, 로직, 출력을 받는 형태로 진행됨

따라서 대부분의 게임 구조는 아래와 같음

 

 

b. 실습해볼 맵의 기본 구조

  • 5X5의 구조
  • 사용자 ♨ 가 □ 내에서는 자유롭게 움직이고
  • ■로는 이동하지 못하게 제작

 

 

c. 실습해볼 맵의 기능

0) 기본 처리

커서의 깜빡임 제어하기

1) 입력

사용자의 움직임을 감지하기

 

2) 로직
플레이어 움직이기 (범위 체크, 위치 정리, 새 위치 이동)

입력값 처리하기 (값이 유효한지 체크, 유효하다면 플레이어 움직이기 실행)

 

3) 출력
값에 따른 맵 생성하기 (커서의 포지션을 잡아주기를 실행 후 생성)

 

 

 


B. 배열 실습

1. 1차원 맵으로 생성하기

a. 기본 기능 생성하기

  1. Helper.h / Helper.cpp 파일을 만들기
  2. 커서의 위치를 이동하는 기능
  3. 커서를 껐다 켜는 기능과 (코드는 원래 있던 windows.h에 정의되어 있는 함수를 사용)
  4. 플레이어가 움직이는걸 감지할 기능 생성하기

 

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로 맵 생성하기

  1. Map.h / Map.cpp 파일을 만들기
  2. 먼저 1D로 생성되는 맵의 그림을 그려주고
  3. 맵을 생성하는 함수를 생성
    1. 커서 포지션을 0으로 설정
    2. 맵의 인덱스 정보로 맵 이미지를 특수기호로 변경
  4. 추가적으로 맵을 외부에서 사용할 수 있게 extern 선언과
  5. 필요한 맵 사이즈의 상수를 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. 플레이어의 위치에 따른 맵 변경하기

  1. Player.h / Player.cpp 파일을 만들기
  2. 플레이어의 초기 위치 잡기
  3. 맵의 값에 따라서 이동할 수 있는 범위 잡기
  4. 기존 위치를 정리하기
  5. 입력 받은 위치로 새 위치로 이동하기

 

main()함수에서 3.2로 실행시 위와 같음

 

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문에서 계속된 값을 주기 때문에

로직에 사용하기 위해선 값을 처리를 해줘야함

따라서 키보드를 떼고 있을 때 움직일 수 있게 처리!!

 

  1. 키보드를 떼고 있으면, 움직일 수 있는지 판별 후 
  2. 값에 따라 위에서 생성한 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. 완성된 모습

뱀 키우기 게임이 생각난다

 

 

 


출처 : https://www.inflearn.com/course/%EA%B2%8C%EC%9E%84-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-%EC%9E%85%EB%AC%B8-%EC%98%AC%EC%9D%B8%EC%9B%90-rookiss#curriculum

 

[게임 프로그래머 입문 올인원] C++ & 자료구조/알고리즘 & STL & 게임 수학 & Windows API & 게임 서버 -

어디부터 시작할지 막막한 게임 프로그래밍 입문자를 위한 All-In-One 커리큘럼입니다. C++, 자료구조/알고리즘, STL, 게임 수학, Windows API, 게임 서버 입문으로 이어지는 알찬 커리큘럼으로 게임 프

www.inflearn.com