다음을 통해 공유


게임 흐름 관리

참고 항목

이 항목은 DirectX를 사용하여 간단한 UWP(유니버설 Windows 플랫폼) 게임 만들기 자습서 시리즈의 일부입니다. 해당 링크의 항목은 시리즈의 컨텍스트를 설정합니다.

이제 게임에 창이 있고 일부 이벤트 처리기가 등록되었으며 자산을 비동기식으로 로드했습니다. 이 항목은 게임 상태의 사용, 특정 키 게임 상태를 관리하는 방법 및 게임 엔진에 대한 업데이트 루프를 만드는 방법에 대해 설명합니다. 그 다음 사용자 인터페이스 흐름에 대해 알아보고, 마지막으로 UWP 게임에 필요한 이벤트 처리기에 대해 자세히 살펴봅니다.

게임 흐름을 관리하는 데 사용되는 게임 상태

게임 상태를 사용하여 게임의 흐름을 관리할 수 있습니다.

Simple3DGameDX 샘플 게임이 컴퓨터에서 처음 실행될 때 게임은 시작되지 않은 상태입니다. 다음 번에 게임이 실행되면 이러한 상태가 될 수 있습니다.

  • 게임이 시작되지 않았거나 게임이 수준 사이에 있습니다(최고 점수는 0).
  • 게임 루프가 실행되고 있으며 한 수준에 있습니다.
  • 게임이 완료되었기 때문에 게임 루프가 실행되지 않습니다(최고 점수에 0이 아닌 값이 있음).

게임에는 필요한 만큼의 상태가 있을 수 있습니다. 하지만 언제든지 종료할 수 있습니다. 그리고 다시 시작될 때 사용자는 종료된 상태에서 다시 시작될 것으로 예상합니다.

게임 상태 관리

따라서 게임을 초기화하는 동안 게임의 콜드 시작을 지원하고 플라이트를 중지한 후 게임을 다시 시작해야 합니다. Simple3DGameDX 샘플은 중지되지 않는다는 인상을 주기 위해 게임 상태를 항상 저장합니다.

일시 중단 이벤트에 대한 응답으로, 게임 플레이가 일시 중단되었지만 게임 리소스가 여전히 메모리에 있음을 나타냅니다. 마찬가지로 다시 시작 이벤트가 처리되어 샘플 게임이 일시 중단 또는 종료되었을 때의 상태에서 다시 시작됩니다. 상태에 따라 플레이어에게 다양한 옵션이 표시됩니다.

  • 게임이 중간 수준에서 다시 시작되면 게임이 일시 중지된 것처럼 표시되며 오버레이에 계속 옵션이 제공됩니다.
  • 게임을 완료한 상태에서 게임을 다시 시작하면 최고 점수와 새 게임을 플레이하는 옵션이 표시됩니다.
  • 마지막으로, 수준이 시작되기 전에 게임이 다시 시작되면 오버레이에서 사용자에게 시작 옵션을 제공합니다.

샘플 게임은 일시 중단 이벤트 없이 처음 시작하는 콜드 부팅과 일시 중단 상태에서 다시 시작하는 시작을 구분하지 않습니다. 이 디자인은 모든 UWP 앱에 적합합니다.

이 샘플에서 게임 상태의 초기화는 GameMain::InitializeGameState에서 발생합니다(해당 메서드의 개요는 다음 섹션에 표시됨).

흐름을 시각화하는 데 도움이 되는 순서도는 다음과 같습니다. 초기화 및 업데이트 루프를 모두 다룹니다.

  • 초기화는 사용자가 게임의 현재 상태를 확인할 때 시작 노드에서 시작합니다. 게임 코드는 다음 섹션의 GameMain::InitializeGameState를 참조하세요.

게임의 주 상태 컴퓨터

GameMain::InitializeGameState 메서드

GameMain::InitializeGameState 메서드는 GameMain 클래스의 생성자를 통해 간접적으로 호출되며, 이는 App::Load 내에서 GameMain 인스턴스를 만든 결과입니다.

GameMain::GameMain(std::shared_ptr<DX::DeviceResources> const& deviceResources) : ...
{
    m_deviceResources->RegisterDeviceNotify(this);
    ...
    ConstructInBackground();
}

winrt::fire_and_forget GameMain::ConstructInBackground()
{
    ...
    m_renderer->FinalizeCreateGameDeviceResources();

    InitializeGameState();
    ...
}

void GameMain::InitializeGameState()
{
    // Set up the initial state machine for handling Game playing state.
    if (m_game->GameActive() && m_game->LevelActive())
    {
        // The last time the game terminated it was in the middle
        // of a level.
        // We are waiting for the user to continue the game.
        ...
    }
    else if (!m_game->GameActive() && (m_game->HighScore().totalHits > 0))
    {
        // The last time the game terminated the game had been completed.
        // Show the high score.
        // We are waiting for the user to acknowledge the high score and start a new game.
        // The level resources for the first level will be loaded later.
        ...
    }
    else
    {
        // This is either the first time the game has run or
        // the last time the game terminated the level was completed.
        // We are waiting for the user to begin the next level.
        ...
    }
    m_uiControl->ShowGameInfoOverlay();
}

게임 엔진 업데이트

App::Run 메서드에서 GameMain::Run을 호출합니다. GameMain::Run 내에서 사용자가 수행할 수 있는 모든 주요 작업을 처리하기 위한 기본 상태 시스템입니다. 이 상태 시스템의 최상위 수준에서는 게임 로드, 특정 수준 플레이 또는 시스템 또는 사용자에 의해 게임이 일시 중지된 후 수준 진행을 처리합니다.

샘플 게임에는 게임이 있을 수 있는 3가지 주요 상태(UpdateEngineState 열거형으로 표시)가 있습니다.

  1. UpdateEngineState::WaitingForResources. 게임 루프가 순환 중이므로 리소스(특히 그래픽 리소스)를 사용할 수 있게 될 때까지 전환할 수 없습니다. 비동기 리소스 로드 작업이 완료되면 상태를 UpdateEngineState::ResourcesLoaded로 업데이트합니다. 일반적으로 디스크, 게임 서버 또는 클라우드 백 엔드에서 수준이 새 리소스를 로드할 때 발생합니다. 샘플 게임에서 이 동작은 해당 시점에 샘플에 수준별 추가 리소스가 필요 없기 때문에 시뮬레이트됩니다.
  2. UpdateEngineState::WaitingForPress. 게임 루프가 순환하여 특정 사용자 입력을 기다리고 있습니다. 이 입력은 게임을 로드하거나, 수준을 시작하거나, 수준을 계속하는 플레이어 작업입니다. 이 샘플 코드에서는 이러한 하위 상태를 PressResultState 열거형으로 나타냅니다.
  3. UpdateEngineState::Dynamics. 게임 루프가 사용자 재생으로 실행되고 있습니다. 사용자가 플레이하는 동안 게임은 전환할 수 있는 3가지 조건을 확인합니다.
  • GameState::TimeExpired. 수준에 대한 시간 제한이 만료됩니다.
  • GameState::LevelComplete. 플레이어에 의해 수준이 완료됩니다.
  • GameState::GameComplete. 플레이어에 의해 모든 수준이 완료됩니다.

게임은 여러 작은 상태 시스템을 포함하는 상태 시스템입니다. 각 특정 상태는 매우 구체적인 특정 조건으로 정의되어야 합니다. 한 상태에서 다른 상태로 전환할 때는 개별 사용자 입력 또는 시스템 동작(예: 그래픽 리소스 로드)을 기반으로 해야 합니다.

게임을 계획할 때 사용자 또는 시스템이 수행할 수 있는 가능한 모든 작업을 다룰 수 있도록 전체 게임 흐름을 그리는 것을 고려하세요. 게임은 매우 복잡할 수 있으며 그렇기 때문에 상태 시스템은 이러한 복잡성을 시각화하여 보다 관리하기 쉽게 만드는 강력한 도구입니다.

업데이트 루프에 대한 코드를 살펴보겠습니다.

GameMain::Update 메서드

이는 게임 엔진을 업데이트하는 데 사용된 상태 시스템의 구조입니다.

void GameMain::Update()
{
    // The controller object has its own update loop.
    m_controller->Update(); 

    switch (m_updateState)
    {
    case UpdateEngineState::WaitingForResources:
        ...
        break;

    case UpdateEngineState::ResourcesLoaded:
        ...
        break;

    case UpdateEngineState::WaitingForPress:
        if (m_controller->IsPressComplete())
        {
            ...
        }
        break;

    case UpdateEngineState::Dynamics:
        if (m_controller->IsPauseRequested())
        {
            ...
        }
        else
        {
            // When the player is playing, work is done by Simple3DGame::RunGame.
            GameState runState = m_game->RunGame();
            switch (runState)
            {
            case GameState::TimeExpired:
                ...
                break;

            case GameState::LevelComplete:
                ...
                break;

            case GameState::GameComplete:
                ...
                break;
            }
        }

        if (m_updateState == UpdateEngineState::WaitingForPress)
        {
            // Transitioning state, so enable waiting for the press event.
            m_controller->WaitForPress(
                m_renderer->GameInfoOverlayUpperLeft(),
                m_renderer->GameInfoOverlayLowerRight());
        }
        if (m_updateState == UpdateEngineState::WaitingForResources)
        {
            // Transitioning state, so shut down the input controller
            // until resources are loaded.
            m_controller->Active(false);
        }
        break;
    }
}

사용자 인터페이스 업데이트

플레이어가 시스템 상태에 대한 알림을 계속 받도록 하여 플레이어의 동작 및 게임을 정의하는 규칙에 따라 게임 상태를 변경할 수 있도록 해야 합니다. 이 샘플 게임을 포함하여 게임 대부분은 일반적으로 UI(사용자 인터페이스) 요소를 사용하여 플레이어에게 이 정보를 제공합니다. UI에는 게임 상태 및 점수 또는 탄약, 또는 남은 가능성의 수와 같은 기타 재생 관련 정보가 포함됩니다. UI는 주 그래픽 파이프라인과 별도로 렌더링되고 3D 프로젝션 위에 배치되므로 오버레이라고도 합니다.

일부 UI 정보는 사용자가 주 게임 플레이 영역에서 완전히 벗어나지 않고도 해당 정보를 볼 수 있도록 HUD(헤드업 디스플레이)로도 제공됩니다. 샘플 게임에서 이 오버레이는 Direct2D API를 사용하여 만듭니다. 떠는 XAML을 사용하여 이 오버레이를 만들 수도 있으며, 이는 게임 샘플 확장에서 설명합니다.

사용자 인터페이스에 대한 구성 요소는 다음과 같이 두 가지가 있습니다.

  • 게임 플레이의 현재 상태에 대한 점수와 정보가 포함된 HUD입니다.
  • 일시 중지 비트맵은 게임의 일시 중지/일시 중단 상태 동안 텍스트가 중첩된 검은색 사각형입니다. 이는 게임 오버레이입니다. 사용자 인터페이스 추가에서 자세히 설명합니다.

당연히 오버레이에는 상태 시스템도 있습니다. 오버레이는 수준 시작 또는 게임 종료 메시지를 표시할 수 있습니다. 기본적으로 게임이 일시 중지되면 플레이어에게 표시할 게임 상태에 대한 정보를 출력할 수 있는 캔버스입니다.

렌더링된 오버레이는 게임의 상태에 따라 다음 6개의 화면 중 하나가 될 수 있습니다.

  1. 게임 시작 시 리소스 로드 진행률 화면.
  2. 게임 플레이 통계 화면.
  3. 수준 시작 메시지 화면
  4. 시간이 부족하지 않고 모든 수준이 완료되었을 때 게임 종료 화면.
  5. 시간이 초과될 경우 게임 종료 화면.
  6. 일시 중지 메뉴 화면.

게임의 그래픽 파이프라인에서 사용자 인터페이스를 분리하면 게임의 독립적인 그래픽 렌더링 엔진에서 작업을 수행하여 게임 코드의 복잡성을 현저하게 줄일 수 있습니다.

다음은 샘플 게임에서 오버레이의 상태 시스템을 구조화하는 방법입니다.

void GameMain::SetGameInfoOverlay(GameInfoOverlayState state)
{
    m_gameInfoOverlayState = state;
    switch (state)
    {
    case GameInfoOverlayState::Loading:
        m_uiControl->SetGameLoading(m_loadingCount);
        break;

    case GameInfoOverlayState::GameStats:
        ...
        break;

    case GameInfoOverlayState::LevelStart:
        ...
        break;

    case GameInfoOverlayState::GameOverCompleted:
        ...
        break;

    case GameInfoOverlayState::GameOverExpired:
        ...
        break;

    case GameInfoOverlayState::Pause:
        ...
        break;
    }
}

이벤트 처리

게임의 UWP 앱 프레임워크 정의 항목에서 보았듯이 클래스의 많은 보기 공급자 메서드는 이벤트 처리기를 등록합니다. 이러한 메서드는 게임 메커니즘을 추가하거나 그래픽 개발을 시작하기 전에 이러한 중요한 이벤트를 올바르게 처리해야 합니다.

해당 이벤트의 적절한 처리는 UWP 앱 환경의 기본 사항입니다. UWP 앱은 활성화, 비활성화, 크기 조정, 스냅, 스냅 취소, 일시 중단 또는 다시 시작할 수 있으므로 게임에서 최대한 빨리 해당 이벤트를 등록하고 환경을 원활하며 플레이어가 예측할 수 있게 유지하는 방식으로 처리해야 합니다.

다음은 샘플에서 사용되는 이벤트 처리기와 해당 처리기에서 처리하는 이벤트입니다.

이벤트 처리기 설명
OnActivated CoreApplicationView::Activated를 처리합니다. 게임 앱이 포그라운드로 이동되었으므로 기본 창이 활성화됩니다.
OnDpiChanged Graphics::Display::DisplayInformation::DpiChanged를 처리합니다. 디스플레이의 DPI가 변경되었으며 게임이 해당 리소스를 적절하게 조정합니다.
참고CoreWindow 좌표는 Direct2D에서 DIP(장치독립적 픽셀)에 있습니다. 따라서 2D 자산 또는 기본 형식을 올바로 표시하려면 DPI의 변경 내용을 Direct2D에 알려야 합니다.
OnOrientationChanged Graphics::Display::DisplayInformation::OrientationChanged를 처리합니다. 디스플레이 방향이 변경되며 렌더링을 업데이트해야 합니다.
OnDisplayContentsInvalidated Graphics::Display::DisplayInformation::DisplayContentsInvalidated를 처리합니다. 디스플레이 다시 그리기가 필요하며 사용자의 게임을 다시 렌더링해야 합니다.
OnResuming CoreApplication::Resuming을 처리합니다. 게임 앱은 일시 중단된 상태에서 게임을 복원합니다.
OnSuspending CoreApplication::Suspending을 처리합니다. 게임 앱은 해당 상태를 디스크에 저장합니다. 스토리지에 상태를 저장하는 데 5초가 소요됩니다.
OnVisibilityChanged CoreWindow::VisibilityChanged를 처리합니다. 게임 앱의 가시성이 변경되었으며, 다른 앱이 표시되거나 보이지 않게 되었습니다.
OnWindowActivationChanged CoreWindow::Activated를 처리합니다. 게임 앱의 기본 창이 비활성화되거나 활성화되었으므로, 포커스를 제거하고 게임을 일시 중지하거나 포커스를 되찾아야 합니다. 두 경우 모두 오버레이는 게임이 일시 중지되었음을 나타냅니다.
OnWindowClosed CoreWindow::Closed를 처리합니다. 게임 앱은 기본 창을 닫고 게임을 일시 중단합니다.
OnWindowSizeChanged CoreWindow::SizeChanged를 처리합니다. 게임 앱은 크기 변경을 수용하기 위해 그래픽 리소스와 오버레이를 다시 할당한 다음, 렌더링 대상을 업데이트합니다.

다음 단계

이 항목에서는 게임 상태를 사용하여 전체 게임 흐름을 관리하는 방법과 여러 다른 상태 시스템으로 구성된 게임에 대해 알아보았습니다. 또한 UI를 업데이트하고 주요 앱 이벤트 처리기를 관리하는 방법을 확인했습니다. 이제 루프, 게임 및 방법을 렌더링하는 것에 자세히 알아볼 준비가 되었습니다.

이 게임을 설명하는 나머지 항목을 순서에 관계없이 살펴볼 수 있습니다.