게임 개발/[AssortRock] 콘솔 게임(CUI) 설계 및 분석

[PUSH PUSH] 게임 로직 구현 - AssortRock 16일차 오프라인 수업_220928

헛둘이 2022. 9. 29. 16:17
게임 로직 변경
  • C스타일의 2차원 배열을 std::vector로 변경
  • 기존의 경우 메모리 할당하는 것을 Stage 사이즈에 맞게 하드코딩(8, 8)해서 넣었는데
  • 이런 방식으로 갈 경우, 다른 맵을 넣었을 때 애매해지는 문제가 있음
  • 그래서 메모장 맵 파일 맨 위에 맵의 x , y 크기를 넣어줌으로써 그 좌표로 맵을 할당하고,
  • 그 크기로 반복문을 돌며 맵을 로드함

 

Stage::Load 로직 변경
Map* Stage::Load()
{
	FILE* fp = nullptr;
	errno_t error = fopen_s(&fp, "..\\Stages\\Stage02.txt", "rt,ccs=UTF-8");
	if (error != 0)
	{
		std::wcout << L"스테이지 파일이 없습니다.\n";
		std::wcout << L"파일 경로를 확인해주세요.\n";
		exit(0);
	}
	wchar_t buff[64] = L"";
	std::wstring x = fgetws(buff, 63, fp);
	std::wstring y = fgetws(buff, 63, fp);

	int mapX = std::stoi(x);
	int mapY = std::stoi(y);
	mMap = new Map(mapX, mapY);

	for (size_t y = 0; y < mapY; y++)
	{
		fgetws(buff, 63, fp);
		for (size_t x = 0; x < mapX; x++)
		{
			if (buff[x] == L'ㅤ' || buff[x] == L'▩')
				mMap->SetGameObjectInMap(x, y, buff[x]);
			else
				CreateGameObject(buff[x], x, y);
		}
	}

	fclose(fp);
	return mMap;
}
  • int mapX, int mapY 부분이 메모장 맨 위에서 값을 긁어오는 부분
  • CreateGameObject는 메모장에 있는 Ball과 Player를 객체로 매핑하는 함수
  • 벽과 공백이 아니라면 GameObject이므로 map에 있는 GameObject 배열에 할당한다.

 

void Stage::CreateGameObject(wchar_t type, int x, int y)
{
	switch (type)
	{
	case L'★':
	{
		Player* player = new Player(Pos(x,y));
		mMap->AddGameObject(dynamic_cast<GameObject*>(player));
	}
	break;

	case L'●':
	{
		Ball* ball = new Ball(Pos(x, y));
		mMap->AddGameObject(dynamic_cast<GameObject*>(ball));
	}
	break;

	case L'◇':
	{
		House* house = new House(Pos(x, y));
		mMap->AddGameObject(dynamic_cast<GameObject*>(house));
	}
	break;

	default:
		break;
	}

}
  • switch-case 문을 통해 문자에 따라 객체, 좌표를 부여하는 부분

 

 

 


돌을 미는 로직 추가
  • 기존 구조에서 돌을 미는 로직을 추가하는 부분
  • 내가 처음 했던 방식은 어차피 플레이어와 맵이 상호작용하므로 맵에서 Ball의 좌표를 추출해서 상호작용했었음
  • 그런데 어제 쌤이 하신 방식은 플레이어가 list<Ball*> 의 참조를 갖고 각각의 참조에 접근해서 Ball을 움직이는 것
  • Ball이 static 멤버로 list<Ball*>를 갖고 생성자에서 자신의 주소를 이 list에 추가해준 뒤에
  • 이 리스트를 반환하는 함수도 static list<Ball*> GetBalls()와 같이 만들었는데 너무 좋은 방법인 듯 하다.
  • 객체간 상호작용을 가볍게 처리하는 방법에 대해 좀 더 생각해 볼 필요성을 느꼈다. 
enum class DIRECTION
{
	LEFT,
	RIGHT,
	UP,
	DOWN,
	NONE
};
#pragma once
#include "GameObject.h"

class Player : public GameObject
{
public:
	Player();
	Player(Pos pos);
	~Player();

	virtual void Update(Map* map) override;
	virtual void Render() override;

private:
	DIRECTION InputProcess();

private:

};
  • InputProcess의 로직을 변경해서 반환값으로 방향을 반환해주는 것
  • 방향을 알아야 돌맹이를 어디로 보낼지 알 수 있다.

 

DIRECTION Player::InputProcess()
{
	DIRECTION dir = DIRECTION::NONE;

	if (_kbhit())
	{        //키보드 입력 확인 (true / false)
		char input = _getch();

		switch (input)
		{
		case 'W':
			{
				mPos.y -= 1;
				dir = DIRECTION::UP;
			}
		break;

		case 'A':
			{
				mPos.x -= 1;
				dir = DIRECTION::LEFT;
			}
		break;

		case 'S':
			{
				mPos.y += 1;
				dir = DIRECTION::DOWN;
			}
		break;

		case 'D':
			{
				mPos.x += 1;
				dir = DIRECTION::RIGHT;
			}
		break;

		default:
			break;
		}
	}

	return dir;
}
  • 움직인 방향에 따라 dir에 방향을 넣어주고 반환함

 

void Player::Update(Map* map)
{
	Pos prevPos = mPos;
	DIRECTION dir = InputProcess();

	std::list<Ball*>& balls = Ball::GetBalls();

	for (std::list<Ball*>::iterator iter = balls.begin()
		; iter != balls.end() 
		; iter++)
	{
		Pos ballPos = (*iter)->GetPos();
		Pos prevBallPos = ballPos;
		//내 위치가 볼과 같다면
		if (mPos == ballPos)
		{
			//민 방향에 따라 임시 볼의 위치를 변경한다 
			switch (dir)
			{
			case DIRECTION::LEFT: { ballPos.x -= 1; }
				break;
			case DIRECTION::RIGHT:{ ballPos.x += 1; }
				break;
			case DIRECTION::UP:   { ballPos.y -= 1; }
				break;
			case DIRECTION::DOWN: { ballPos.y += 1; } 
				break;
			case DIRECTION::NONE: {}
				break;
			default:
				break;
			}

			// 숙제
			if (map->IsBarrier(ballPos) || map->IsBall(ballPos))
			{
				mPos = prevPos;
				break;
			}

			else
			{
				(*iter)->SetPos(ballPos);
				map->SetGameObjectInMap((*iter)->GetWChar_t(), ballPos, prevBallPos);
			}
		}
	}

	if (map->IsBarrier(mPos)) 
	{
		mPos = prevPos;
	}

	map->SetGameObjectInMap(mCh, mPos, prevPos);
}
  • 숙제라고 주석 달아놓은 부분은 실제 돌을 움직이고, 벽을 뚫는지 등 비정상적인 동작을 체크해주는 곳
  • map에 IsBarrier라는 함수가 있는데, pos를 넘겨주면 그 pos가 벽인지 확인해주는 함수
  • ball이 움직일 예정인 pos가 벽이거나 볼이라면? 캐릭터도 움직이기 전으로 변경하고 break 해준다.
  • 벽이 아니라면 볼을 해당 좌표로 움직여줌
  • 그러면 그 밑 로직에서 캐릭터의 위치가 벽인지 확인하고 원래 로직대로 진행된다.

 

#pragma once
#include "Common.h"
#include "GameObject.h"

class Ball : public GameObject
{
public:
	static std::list<Ball*>& GetBalls() { return mBalls; }
	Ball();
	Ball(Pos pos);
	~Ball();
	
	void Initialize(Pos pos = Pos(-1, -1));
	virtual void Update(Map* map) override;
	virtual void Render() override;

private:
	static int mBallCount;
	static std::list<Ball*> mBalls;
};
  • static 변수(mBalls)와 함수(GetBalls)를 넣어주어 다른 오브젝트에게 Ball의 원본을 넘길 수 있도록 처리해줌