게임 상태 '스택'?


52

게임 상태를 게임에 구현하는 방법에 대해 생각하고있었습니다. 내가 원하는 주요 사항은 다음과 같습니다.

  • 반투명 상위 상태-일시 정지 메뉴를 통해 게임 뒤에서 볼 수 있음

  • 뭔가 OO-나는 이것을 사용하고 이해하기 쉽고 이론을 유지하고 더 많이 추가 할뿐만 아니라 뒤에 이론을 이해합니다.



링크 된 목록을 사용하여 스택으로 취급하려고했습니다. 이것은 반투명도를 위해 아래 상태에 액세스 할 수 있음을 의미합니다.
계획 : 상태 스택을 IGameStates에 대한 포인터 목록으로 연결하십시오. 최상위 상태는 자체 업데이트 및 입력 명령을 처리 한 다음 isTransparent 멤버를 사용하여 아래의 상태를 그릴 지 여부를 결정합니다.
그런 다음 할 수 있습니다 :

states.push_back(new MainMenuState());
states.push_back(new OptionsMenuState());
states.pop_front();

플레이어 로딩을 나타내는 다음 옵션으로 이동 한 다음 주 메뉴로 이동하십시오.
이것이 좋은 생각입니까, 아니면 ...? 다른 것을 봐야할까요?

감사.


OptionsMenuState 뒤에 MainMenuState를 보시겠습니까? 아니면 OptionsMenuState 뒤의 게임 화면입니까?
Skizz

계획은 상태가 불투명도 / 투명도 값 / 플래그를 갖도록하는 것이 었습니다. 나는 최상위 상태가 이것이 사실인지, 그리고 그렇다면 어떤 가치가 있는지 확인하고 보았습니다. 그런 다음 다른 상태보다 많은 불투명도로 렌더링하십시오. 이 경우에는 그렇지 않습니다.
공산주의 오리

나는 늦은 시간이지만 미래의 독자들에게는 new샘플 코드에 표시된 방식으로 사용하지 말고 메모리 누수 또는 기타 더 심각한 오류를 요구합니다.
Pharap

답변:


44

나는 코드 레인저와 같은 엔진에서 일했다. 나는 다른 견해를 가지고 있습니다. :)

먼저, FSM 스택이 없었습니다. 상태 스택이있었습니다. 상태 스택은 단일 FSM을 만듭니다. FSM 스택이 어떻게 생겼는지 모르겠습니다. 아마도 실용적인 것을하기에는 너무 복잡 할 것입니다.

Global State Machine의 가장 큰 문제는 상태 집합이 아니라 상태 스택이라는 것이 었습니다. 예를 들어 로딩 화면 전후에 메인 메뉴가 있는지 여부에 따라 ... / MainMenu / Loading이 ... / Loading / MainMenu와 다릅니다. (게임은 비동기 적이며 로딩은 대부분 서버 구동 ).

이것이 추한 것들의 두 가지 예로써 :

  • 예를 들어 LoadingGameplay 상태로 이어 졌으므로 기본 /로드 및 Base / Gameplay / LoadingGameplay가 Gameplay 상태 내에서로드 할 수 있었으며 정상적인로드 상태에서 많은 코드를 반복해야했습니다 (모두는 아니지만 더 추가하십시오) ).
  • "캐릭터 생성자의 경우 게임 플레이로 이동하는 경우; 게임 플레이의 경우 문자 선택으로 이동하는 경우; 문자 선택의 경우 다시 로그인으로 돌아 가기"와 같은 여러 기능이 있습니다. 다른 상태에서 동일한 인터페이스 창을 표시하고 뒤로 / 앞으로 만들기 버튼은 여전히 ​​작동합니다.

이름에도 불구하고, 그것은 "글로벌"하지 않았습니다. 대부분의 내부 게임 시스템은 다른 시스템과의 상태를 원치 않기 때문에 내부 상태를 추적하는 데 사용하지 않았습니다. UI 시스템과 같은 다른 시스템은이를 사용하여 상태를 자신의 로컬 상태 시스템으로 복사하기 위해서만 사용할 수 있습니다. (특히 UI 상태에 대해서는 시스템에주의해야합니다. UI 상태는 스택이 아니며 실제로 DAG이며 다른 구조를 강요하려고하면 UI가 사용하기에 불편합니다.)

게임 흐름이 실제로 어떻게 구성되어 있는지 모르는 인프라 프로그래머의 코드를 통합하는 작업을 분리하는 것이 좋았습니다. 패처를 작성하는 사람에게 "코드를 Client_Patch_Update에 입력"하고 그래픽을 작성하는 사람에게 알릴 수 있습니다. "Client_MapTransfer_OnEnter에 코드를 넣습니다"를로드하면 많은 문제없이 특정 논리 흐름을 바꿀 수 있습니다.

부수적 인 프로젝트에서 나는 스택이 아닌 상태 설정으로 더 나은 행운 을 얻었습니다. 비 관련 시스템을 위해 여러 대의 기계를 만드는 것을 두려워하지 않고 자신이 "전역 상태"를 갖는 함정에 빠지는 것을 거부했습니다. 전역 변수를 통해 사물을 동기화하는 복잡한 방법-물론 마감일이 가까워 지더라도 목표 로 디자인하지는 않습니다 . 기본적으로 게임의 상태는 스택이 아니며 게임의 상태는 모두 관련이 없습니다.

또한 GSM은 함수 포인터와 비 로컬 동작이 수행하는 경향이 있기 때문에 디버깅을 더 어렵게 만들었지 만 이러한 큰 상태 전이의 디버깅은 그다지 재미가 없었습니다. 상태 스택 대신 상태 세트가 실제로 도움이되지는 않지만 알고 있어야합니다. 함수 포인터가 아닌 가상 함수는 다소 완화 할 수 있습니다.


좋은 답변, 감사합니다! 나는 당신의 게시물과 과거 경험에서 많은 것을 취할 수 있다고 생각합니다. : D + 1 / 틱
공산주의자 오리

계층 구조의 가장 좋은 점은 맨 위로 밀리고 실행중인 다른 것에 대해 걱정할 필요가없는 유틸리티 상태를 구축 할 수 있다는 것입니다.
coderanger

이것이 세트가 아닌 계층에 대한 인수인지 어떻게 알지 못합니다. 오히려 계층 구조는 모든 상태 간 통신을 더 복잡하게 만듭니다.

UI가 실제로 DAG라는 점은 잘 알려져 있지만 확실히 스택으로 표현할 수 있다는 데 동의하지 않습니다. 연결된 방향성 비순환 그래프 (그리고 연결된 DAG가 아닌 경우를 생각할 수 없음)는 트리로 표시 될 수 있으며 스택은 본질적으로 트리입니다.
Ed Ropple

2
스택은 트리의 하위 집합으로, 모든 그래프의 하위 집합 인 DAG의 하위 집합입니다. 모든 스택은 트리이고 모든 트리는 DAG이지만 대부분의 DAG는 트리가 아니며 대부분의 트리는 스택이 아닙니다. DAG에는 토폴로지 순서가있어 스택에 저장 (예 : 순회 분석 등) 할 수 있지만 스택에 넣은 후에는 중요한 정보가 손실됩니다. 이 경우 이전 형제가있는 경우 화면과 부모 화면을 탐색 할 수 있습니다.

11

다음은 매우 유용한 게임 상태 스택 구현 예입니다. http://creators.xna.com/en-US/samples/gamestatemanagement

C #으로 작성되었으며 컴파일하려면 XNA 프레임 워크가 필요하지만 아이디어를 얻으려면 코드, 설명서 및 비디오를 확인하십시오.

상태 전환, 투명 상태 (예 : 모달 메시지 상자) 및로드 상태 (기존 상태의 언로드 및 다음 상태의로드를 관리하는)를 지원할 수 있습니다.

나는 (C #이 아닌) 취미 프로젝트에서 동일한 개념을 사용하고 있습니다 (기여 된 프로젝트는 더 큰 프로젝트에는 적합하지 않을 수 있습니다).


5

이것은 우리가 사용하는 FSM 스택과 유사합니다. 기본적으로 각 상태에 enter, exit 및 tick 기능을 제공하고 순서대로 호출하십시오. 로딩과 같은 것들을 다루는 데 아주 잘 작동합니다.


3

"Game Programming Gems"볼륨 중 하나에는 게임 상태를위한 상태 머신 구현이있었습니다. http://emergent.net/Global/Documents/textbook/Chapter1_GameAppFramework.pdf 에는 작은 게임에서이 게임을 사용하는 방법에 대한 예가 있으며 게임을 읽을 수있을만큼 Gamebryo에 한정되어서는 안됩니다.


"DirectX로 롤 플레잉 게임 프로그래밍"의 첫 번째 섹션은 상태 시스템 (및 프로세스 시스템-매우 흥미로운 구별)도 구현합니다.
Ricket

훌륭한 문서이며 예제에서 사용하는 불필요한 객체 계층이 부족하여 과거에 어떻게 구현했는지 거의 설명합니다.
dash-tom-bang

3

토론에 약간의 표준화를 추가하기 위해 이러한 종류의 데이터 구조에 대한 고전적인 CS 용어는 푸시 다운 자동화 입니다.


실제 상태 스택 구현이 푸시 다운 자동 장치와 거의 동일한 지 확실하지 않습니다. 다른 답변에서 언급했듯이 실제 구현은 항상 "pop two states", "swap these states"또는 "이 데이터를 스택 외부의 다음 상태로 전달"과 같은 명령으로 끝납니다. 그리고 오토 마톤은 데이터 구조가 아닌 컴퓨터입니다. 상태 스택과 푸시 다운 오토마타는 모두 스택을 데이터 구조로 사용합니다.

1
"실제로 상태 스택을 구현하는 것이 푸시 다운 오토 마톤과 거의 같은지 확실하지 않습니다." 차이점이 뭐야? 둘 다 유한 한 상태 세트, 상태 히스토리 및 상태 푸시 및 팝을위한 기본 조작이 있습니다. 언급 한 다른 작업은 근본적으로 그와 다릅니다. "Pop two states"는 두 번 터지는 것입니다. "스왑"은 팝과 푸시입니다. 데이터 전달은 핵심 아이디어를 벗어난 것이지만 "FSM"을 사용하는 모든 게임은 더 이상 이름이 적용되지 않는 추가 데이터를 사용합니다.
munificent

푸시 다운 오토 마톤에서 전환에 영향을 줄 수있는 유일한 상태는 맨 위에있는 상태입니다. 중간에 두 상태를 바꾸는 것은 허용되지 않습니다. 중간에있는 주를 보는 것조차 허용되지 않습니다. "FSM"이라는 용어의 의미 론적 확장이 합리적이고 이점이 있다고 생각하지만 (우리는 여전히 가장 제한적인 의미로 "DFA"및 "NFA"라는 용어를 사용하고 있음) "푸시 다운 오토 마톤"은 컴퓨터 과학 용어이며 모든 단일 스택 기반 시스템에 적용하면 혼란이 발생합니다.

어떤 경우에는 상태 입력을 필터링하고 처리를 "낮은"상태로 전달하는 것이 편리하지만 어떤 것에 영향을 미칠 수있는 유일한 상태가 가장 높은 상태 인 구현을 선호합니다. (예를 들어 컨트롤러 입력 처리는이 방법에 매핑되며, 최상위 상태는 관심 비트를 가져 와서 지우고 스택에서 다음 상태로 제어를 전달합니다.)
dash-tom-bang

1
좋은 지적, 고정!
munificent

1

상태 시스템의 기능을 제한 할뿐만 아니라 스택이 완전히 필요한지는 확실하지 않습니다. 스택을 사용하면 여러 가능성 중 하나로 상태를 '종료'할 수 없습니다. "메인 메뉴"에서 시작한 다음 "게임로드"로 이동하고 저장된 게임을 성공적으로로드 한 후 "일시 정지"상태로 가고 사용자가로드를 취소하면 "메인 메뉴"로 돌아갈 수 있습니다.

나는 상태가 종료 할 때 따라야 할 상태를 지정하게합니다.

현재 상태 이전의 상태 (예 : "주 메뉴-> 옵션-> 주 메뉴"및 "일시 정지-> 옵션-> 일시 정지")로 돌아 가려는 경우 시작 매개 변수를 다시 상태.


어쩌면 내가 질문을 오해 했을까?
Skizz

아니, 넌하지 않았어. 다운 투터가 그랬다고 생각합니다.
공산주의 오리

스택을 사용한다고해서 명시적인 상태 전이를 사용할 수있는 것은 아닙니다.
dash-tom-bang

1

전환 및 기타 사항에 대한 또 다른 솔루션은 상태 엔진과 함께 대상 및 소스 상태를 제공하는 것입니다. 진실은 대부분의 국가 기계가 현재 프로젝트에 맞게 조정되어야 할 것입니다. 하나의 솔루션은이 게임이나 그 게임에 도움이 될 수 있고 다른 솔루션은이를 방해 할 수 있습니다.

class StateMachine
{
public:
    StateMachine(Engine *);
    void Push(State *);
    State *Pop();
    void Update();
    Engine *GetEngine();

private:
    std::stack<State *> _states;
    Engine *_engine;
};

상태는 현재 상태 및 기계를 매개 변수로 사용하여 푸시됩니다.

void StateMachine::Push(State *state)
{
    State *from = 0;
    if (!_states.empty()) from = _states.top();
    _states.push(state);
    state->Enter(this, from);
}

상태는 같은 방식으로 나타납니다. 당신 Enter()이 더 낮은 것을 부르는지 여부 State는 구현 질문입니다.

State *StateMachine::Pop()
{
    _ASSERT(!_states.empty());
    State *state = _states.top();
    State *to = 0;
    _states.pop();
    if (!_states.empty()) to = _states.top();
    state->Exit(this, to);
    return state;
}

입력, 업데이트 또는 종료시 State필요한 모든 정보를 얻습니다.

void SomeGameState::Enter(StateMachine *sm, State *from)
{
    Engine *eng = sm->GetEngine();
    eng->GetKeyboard()->KeyDown.Bind(this, &SomeGameState::KeyDown);
    LoadLevelState *state = new LoadLevelState();
    state->SetLevel(eng->GetSaveGame()->GetLevelName());
    state->Load.Bind(this, &SomeGameState::OnLevelLoaded);
    sm->Push(state);
}

void SomeGameState::Update(StateMachine *sm)
{
    Engine *eng = sm->GetEngine();
    float time = eng->GetFrameTime();
    if (shouldExit)
        sm->Pop();
}

void SomeGameState::Exit(StateMachine *sm, State *from)
{
    Engine *eng = sm->GetEngine();
    eng->GetKeyboard()->KeyDown.UnsubscribeAll(this);
}

0

여러 게임에서 매우 유사한 시스템을 사용했는데 몇 가지 예외가 있지만 훌륭한 UI 모델로 사용된다는 것을 알았습니다.

우리가 직면 한 유일한 문제는 특정 상태에서 새 상태를 푸시하기 전에 여러 상태를 다시 표시하고 (일반적으로 잘못된 UI의 표시이므로 요구 사항을 제거하기 위해 UI를 리플 로우했습니다) 마법사 스타일을 만드는 것이 바람직한 경우였습니다. 선형 흐름 (데이터를 다음 상태로 전달하여 쉽게 해결)

실제로 사용 된 구현은 스택을 래핑하고 업데이트 및 렌더링을위한 논리와 스택 작업을 처리했습니다. 스택의 각 작업은 상태에서 이벤트를 트리거하여 발생하는 작업을 알립니다.

스왑 (선형 흐름의 경우 팝앤 푸시) 및 재설정 (기본 메뉴로 돌아가거나 흐름 종료)과 같은 일반적인 작업을 단순화하기 위해 몇 가지 도우미 기능도 추가되었습니다.


UI 모델로서 이것은 의미가 있습니다. "주 메뉴", "옵션 메뉴", "게임 화면"및 "일시 정지 화면"이 더 높은 수준 인 반면, 내 머릿속에서는 메인 게임 엔진의 내부와 연관시킬 것이기 때문에 주 (州)라고 부르겠습니다. 코어 게임의 내부 상태와 상호 작용하지 않는 경우가 많으며 단순히 "일시 중지", "일시 정지 해제", "로드 레벨 1", "시작 레벨", "재시작 레벨", "저장"및 "복원", "세트 볼륨 레벨 57"등. 게임마다 크게 다를 수 있습니다.
케빈 카스 카트

0

이것은 거의 모든 프로젝트에 적용되는 방법입니다. 왜냐하면 그것이 매우 잘 작동하고 매우 간단하기 때문입니다.

가장 최근의 프로젝트 인 Sharplike 는 제어 흐름을 이와 같은 방식으로 처리합니다. 우리의 상태는 모두 상태가 변경 될 때 호출되는 일련의 이벤트 함수와 연결되어 있으며, 동일한 상태 머신 내에 여러 상태의 스택을 가질 수있는 "명명 된 스택"개념이 있습니다. 필요한 도구는 아니지만 편리합니다.

Skizz가 제안한 "컨트롤러가 종료 될 때 어떤 상태를 따라야하는지 컨트롤러에게 알려주십시오"라는 패러다임에 대해주의를 기울여야합니다. 새로운 멤버가있는 상태 하위 클래스를 호출 한 다음 호출 상태로 돌아갈 때 읽어야합니다.)보다 훨씬 어렵습니다.


0

기본적으로이 정확한 시스템을 여러 시스템에서 직교 적으로 사용했습니다. 예를 들어, 프론트 엔드 및 게임 내 메뉴 (일명 "일시 정지") 상태에는 자체 상태 스택이 있습니다. 게임 내 UI는 상태 전환이 틴트 될 수있는 "전역"측면 (헬스 바 및 맵 / 레이더와 같은)을 가지고 있지만 상태간에 공통적 인 방식으로 업데이트되었지만 이와 같은 것을 사용했습니다.

게임 내 메뉴는 DAG로 "더 나은"표현 일 수 있지만 암시 적 상태 시스템 (다른 화면으로 이동하는 각 메뉴 옵션은 이동 방법을 알고 있으며 뒤로 버튼을 누르면 항상 맨 위 상태로 나타남)을 사용합니다. 정확히 동일합니다.

이러한 다른 시스템들 중 일부는 "상위 상태 바꾸기"기능을 가지고 있었지만 일반적으로 StatePop()다음 과 같이 구현 되었습니다 StatePush(x);.

메모리 카드 처리는 실제로 많은 "작업"을 작업 대기열에 푸시했기 때문에 비슷했습니다 (LIFO가 아닌 FIFO와 마찬가지로 기능적으로 스택과 동일한 기능을 수행함). 일단 이런 종류의 구조를 사용하기 시작하면 ( "현재 일어나고있는 일이 있고, 그것이 끝나면 스스로 터진다") 코드의 모든 영역을 감염시키기 시작한다. AI조차도 이와 같은 것을 사용하기 시작했습니다. 플레이어가 소리를 냈지만 보이지 않았을 때 인공 지능은 "실마리가없는"다음 "경고"로 바뀌었다가 플레이어를 보았을 때 마지막으로 "활성"으로 올라갔습니다. 골판지 상자에 적을 당신을 잊게하십시오!

GameState.h :

enum GameState
{
   k_frontend,
   k_gameplay,
   k_inGameMenu,
   k_moviePlayback,
   k_numStates
};

void GameStatePush(GameState);
void GameStatePop();
void GameStateUpdate();

GameState.cpp :

// k_maxNumStates could be bigger, but we don't need more than
// one of each state on the stack.
static const int k_maxNumStates = k_numStates;
static GameState s_states[k_maxNumStates] = { k_frontEnd };
static int s_numStates = 1;

static void (*s_startupFunctions)()[] =
   { FrontEndStart, GameplayStart, InGameMenuStart, MovieStart };
static void (*s_shutdownFunctions)()[] =
   { FrontEndStop, GameplayStop, InGameMenuStop, MovieStop };
static void (*s_updateFunctions)()[] =
   { FrontEndUpdate, GameplayUpdate, InGameMenuUpdate, MovieUpdate };

static void GameStateStart(GameState);
static void GameStateStop(GameState);

void GameStatePush(GameState gs)
{
   Assert(s_numStates < k_maxNumStates);
   GameStateStop(s_states[s_numStates - 1])
   s_states[s_numStates] = gs;
   s_numStates++;
   GameStateStart(gs);
}

void GameStatePop()
{
   Assert(s_numStates > 1);  // can't pop last state
   s_numStates--;
   GameStateStop(s_states[s_numStates]);
   GameStateStart(s_states[s_numStates - 1]);
}

void GameStateUpdate()
{
   GameState current = s_states[s_numStates - 1];
   s_updateFunctions[current]();
}

void GameStateStart(GameState gs)
{
   s_startupFunctions[gs]();
}

void GameStateStop(GameState gs)
{
   s_shutdownFunctions[gs]();
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.