발전하는 춘배
[원고, C++/OOP] 로그라이크 게임 만들어보기 10 - 상태 머신 본문
지금 Game.run은 이렇다.
void Game::run() {
isRunning = true;
world->populateMap("map1", 50, 50);
player= world->getPlayer();
camera->follow(player->getX(), player->getY(), world->getCurrentMap()->getWidth(), world->getCurrentMap()->getHeight());
while (isRunning) {
render();
handleInput();
if (world->isPlayerDead()) {
std::cout << "You died!\n";
isRunning = false;
break;
}
camera->follow(player->getX(), player->getY(), world->getCurrentMap()->getWidth(), world->getCurrentMap()->getHeight());
}
}
뭔가 마음에 안 든다.
일단 코드가 잘 안 읽힌다.
그리고 전에 테스트 하면서 느낀 건데
Enter command: /inv
-----------Inventory-----------
Inventory (0/32):
-------------------------------
-----------Equipment-----------
-------------------------------
# # # # # . # # # # # # # # # . . . . . . . . # # # # # . .
# # # # # . # # # # # # # # # . . . . . . . . # # # # # # #
# # # # # . # # # # # # # # # . . . . . . . . # # # # # # #
# # # # # . # # # # # # # # # . . . . . . . . # # # # # # #
# # # # # . # # # # # # # # # . . . . . . . . # # # # # # #
# # # # # . # # # # # # # # # . . . . . . . . # # # # # # #
# # # # # . # # # # # # # # # . # # # . # # # # # # # # # #
# # # # # . # # # # # # # # # . # # # . # # # # # # # # # #
# # # # # . # # # # # # # # # . # # # . # # # # # # # # # #
# # # # # . # # # # # # # # # . # # # . # # # # # # # # # #
# # # # # . # # # # # # # # # . # # # . # # # # # # # # # #
# # # . . . . . # # # # # # # . # # # . . . . . . # # # # #
# # # . . . . . # # # # # # # . # # # # # # # # . # # # # #
# # # . . . . M # # # # # # # . # # # # # # # # . # # # # #
# # # . . . . . # # # # # # # . # # # # # # # # . # # # # #
# # # @ . . . . # # # # # # # . # # # # # # # # . # # # # #
# # # M . . . . # # # # # # # . # # # # # # # # . # # # # #
# # # # # . # # # # # # # # # . # # # # # # # # . # # # # #
# # # # # . # # # # # # # # # . # # # # # # # # . # # # # #
# # # # # . # # # # # # # # # . # # # . . . . . . . . . . .
# # # # # . . . . . . # # # # . # # # . . . . . . . . . . .
# # # # # # . . . . . . . . . . # # # . . . . . . . . . . .
# # # # # # . . . . . . . . . . # # # . . . . . . . . . . .
# # # # # # . . . . . . . . . . # # # . . . . . . . . . . .
# # # # # # . . . . . . . . . . # # # . . . . . . . . . . .
# # # # # # . . . . . . . . . # # # # . . . . . . . . . . .
# # # # # # . . . . . . . . . # # # # . . . . . . . . . . .
# # # # # # . . . . . . . . . # # # # . . . . . . . . . . .
# # # # # # # # # # . # # # # # # # # . . . . . . . . . . .
# # # # # # # # # # . # # # # # # # # . . . . . . . . . . .
Enter command: /getTestItem
# # # # # . # # # # # # # # # . . . . . . . . # # # # # . .
# # # # # . # # # # # # # # # . . . . . . . . # # # # # # #
# # # # # . # # # # # # # # # . . . . . . . . # # # # # # #
# # # # # . # # # # # # # # # . . . . . . . . # # # # # # #
# # # # # . # # # # # # # # # . . . . . . . . # # # # # # #
# # # # # . # # # # # # # # # . . . . . . . . # # # # # # #
# # # # # . # # # # # # # # # . # # # . # # # # # # # # # #
# # # # # . # # # # # # # # # . # # # . # # # # # # # # # #
# # # # # . # # # # # # # # # . # # # . # # # # # # # # # #
# # # # # . # # # # # # # # # . # # # . # # # # # # # # # #
# # # # # . # # # # # # # # # . # # # . # # # # # # # # # #
# # # . . . . . # # # # # # # . # # # . . . . . . # # # # #
# # # . . . . . # # # # # # # . # # # # # # # # . # # # # #
# # # . . . . M # # # # # # # . # # # # # # # # . # # # # #
# # # . . . . . # # # # # # # . # # # # # # # # . # # # # #
# # # @ . . . . # # # # # # # . # # # # # # # # . # # # # #
# # # M . . . . # # # # # # # . # # # # # # # # . # # # # #
# # # # # . # # # # # # # # # . # # # # # # # # . # # # # #
# # # # # . # # # # # # # # # . # # # # # # # # . # # # # #
# # # # # . # # # # # # # # # . # # # . . . . . . . . . . .
# # # # # . . . . . . # # # # . # # # . . . . . . . . . . .
# # # # # # . . . . . . . . . . # # # . . . . . . . . . . .
# # # # # # . . . . . . . . . . # # # . . . . . . . . . . .
# # # # # # . . . . . . . . . . # # # . . . . . . . . . . .
# # # # # # . . . . . . . . . . # # # . . . . . . . . . . .
# # # # # # . . . . . . . . . # # # # . . . . . . . . . . .
# # # # # # . . . . . . . . . # # # # . . . . . . . . . . .
# # # # # # . . . . . . . . . # # # # . . . . . . . . . . .
# # # # # # # # # # . # # # # # # # # . . . . . . . . . . .
# # # # # # # # # # . # # # # # # # # . . . . . . . . . . .
Enter command: /inv
-----------Inventory-----------
Inventory (3/32):
1. Small Potion x3
Heals +20 health to the player
2. Iron Sword x1
A basic iron sword that increases attack power
3. Iron Armor x1
Heavy armor that greatly increases defense
-------------------------------
-----------Equipment-----------
-------------------------------
# # # # # . # # # # # # # # # . . . . . . . . # # # # # . .
# # # # # . # # # # # # # # # . . . . . . . . # # # # # # #
# # # # # . # # # # # # # # # . . . . . . . . # # # # # # #
# # # # # . # # # # # # # # # . . . . . . . . # # # # # # #
# # # # # . # # # # # # # # # . . . . . . . . # # # # # # #
# # # # # . # # # # # # # # # . . . . . . . . # # # # # # #
# # # # # . # # # # # # # # # . # # # . # # # # # # # # # #
# # # # # . # # # # # # # # # . # # # . # # # # # # # # # #
# # # # # . # # # # # # # # # . # # # . # # # # # # # # # #
# # # # # . # # # # # # # # # . # # # . # # # # # # # # # #
# # # # # . # # # # # # # # # . # # # . # # # # # # # # # #
# # # . . . . . # # # # # # # . # # # . . . . . . # # # # #
# # # . . . . . # # # # # # # . # # # # # # # # . # # # # #
# # # . . . . M # # # # # # # . # # # # # # # # . # # # # #
# # # . . . . . # # # # # # # . # # # # # # # # . # # # # #
# # # @ . . . . # # # # # # # . # # # # # # # # . # # # # #
# # # M . . . . # # # # # # # . # # # # # # # # . # # # # #
# # # # # . # # # # # # # # # . # # # # # # # # . # # # # #
# # # # # . # # # # # # # # # . # # # # # # # # . # # # # #
# # # # # . # # # # # # # # # . # # # . . . . . . . . . . .
# # # # # . . . . . . # # # # . # # # . . . . . . . . . . .
# # # # # # . . . . . . . . . . # # # . . . . . . . . . . .
# # # # # # . . . . . . . . . . # # # . . . . . . . . . . .
# # # # # # . . . . . . . . . . # # # . . . . . . . . . . .
# # # # # # . . . . . . . . . . # # # . . . . . . . . . . .
# # # # # # . . . . . . . . . # # # # . . . . . . . . . . .
# # # # # # . . . . . . . . . # # # # . . . . . . . . . . .
# # # # # # . . . . . . . . . # # # # . . . . . . . . . . .
# # # # # # # # # # . # # # # # # # # . . . . . . . . . . .
# # # # # # # # # # . # # # # # # # # . . . . . . . . . . .
Enter command: /use
Enter item slot: 2
맵을 그냥 매번 출력해버리니 영 불편하다.
맵을 매번 출력하는 게 아니라, 인벤토리나 아이템 장착, 사용 등 커맨드를 입력했을 때는 맵을 출력하지 않고, 게임을 실제로 '진행' 할 때만 맵을 출력하도록 해 보는 게 목표이다.
이를 위해 매번 if문을 돌리고 싶지는 않으니, 상태 머신 개념을 도입해본다.
1. 게임 상태 머신
그림을 잘 그려보자.

오 수업 때 FSM 배웠던 기억이 새록새록 난다.
이렇게 써먹는구나
그림으로 다 나타냈으니 구현은 어렵지 않다.
enum class GameState {
MAIN_MENU,
PLAYING,
PAUSED,
INVENTORY,
GAME_OVER
};
상태들을 선언해주고
void Game::update() {
switch(state) {
case GameState::MAIN_MENU:
std::cout << "Main Menu\n";
std::cout << "1. /start\n";
std::cout << "2. /quit\n";
updateMainMenu();
break;
case GameState::PLAYING:
render();
std::cout << "Playing\n";
std::cout << "1. /menu\n";
std::cout << "2. /quit\n";
std::cout << "3. wasd to move\n";
updatePlaying();
break;
case GameState::PAUSED:
std::cout << "Paused\n";
std::cout << "1. /resume\n";
std::cout << "2. /inv\n";
std::cout << "3. /stat\n";
std::cout << "4. /quit\n";
updatePaused();
break;
case GameState::INVENTORY:
std::cout << "Inventory\n";
player->printInventory();
std::cout << "1. /use\n";
std::cout << "2. /equip\n";
std::cout << "3. /unequip\n";
std::cout << "4. /back\n";
updateInventory();
break;
case GameState::GAME_OVER:
std::cout << "Game Over\n";
std::cout << "1. /restart\n";
std::cout << "2. /quit\n";
updateGameOver();
break;
}
}
각 상태별로 가능한 커맨드들을 플레이어한테 출력해준다.
그리고 각 update상태()에서는 커맨드별로 로직을 수행하고 상태를 전이시킨다.
예를 들자면 이런 느낌
void Game::updatePlaying() {
std::string command;
std::cout << "Enter command: ";
std::cin >> command;
if (command.length() > 1) {
if (command == "/menu") {
state = GameState::PAUSED;
return;
}
if (command == "/quit") {
exit(0);
}
std::cout << "Invalid command.\n";
return;
}
switch (command[0]) {
case 's':
world->tryMoveEntity(player, 0, 1);
break;
case 'd':
world->tryMoveEntity(player, 1, 0);
break;
case 'w':
world->tryMoveEntity(player, 0, -1);
break;
case 'a':
world->tryMoveEntity(player, -1, 0);
break;
default:
std::cout << "Invalid command.\n";
break;
}
if (world->isPlayerDead()) {
std::cout << "You died!\n";
state = GameState::GAME_OVER;
return;
}
camera->follow(player->getX(), player->getY(), world->getCurrentMap()->getWidth(), world->getCurrentMap()->getHeight());
}
그러면 지저분했던 Game.run()은 이렇게 바뀐다.
void Game::run() {
while (true) {
update();
}
}
테스트하니 의도대로 잘 동작한다.
2. 느낀점
느낀 점이 있다면 상태 머신은 그림만 잘 그려놓으면 구현은 직관적이고 쉽다는 것.
반응형
