주 게임 개체 정의

참고 항목

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

샘플 게임의 기본 프레임워크를 배치하고 높은 수준의 사용자 및 컴퓨터 동작을 처리하는 상태 시스템을 구현했다면 샘플 게임을 게임으로 바꾸는 규칙과 메커니즘을 조사하고 싶을 것입니다. 샘플 게임의 주요 개체에 대한 세부 정보와 게임 규칙을 게임 세계와의 상호 작용으로 변환하는 방법을 살펴보겠습니다.

목표

  • UWP DirectX 게임에 대한 게임 규칙 및 메커니즘을 구현하기 위해 기본 개발 기술을 적용하는 방법을 알아봅니다.

주 게임 개체

Simple3DGameDX 샘플 게임에서 Simple3DGame은 기본 게임 개체 클래스입니다. Simple3DGame의 인스턴스는 App::Load 메서드를 통해 간접적으로 생성됩니다.

다음은 Simple3DGame 클래스의 몇 가지 기능입니다.

  • 게임 플레이 논리의 구현을 포함합니다.
  • 이러한 세부 정보를 전달하는 메서드가 포함되어 있습니다.
    • 앱 프레임워크에 정의된 상태 시스템에 대한 게임 상태 변경 사항
    • 앱에서 게임 개체 자체로의 게임 상태 변경 사항
    • 게임의 UI(오버레이 및 헤드업 디스플레이), 애니메이션, 물리(역학) 업데이트에 대한 세부 정보

    참고 항목

    그래픽 업데이트는 GameRenderer 클래스에 의해 처리되며, 여기에는 게임에서 사용하는 그래픽 디바이스 리소스를 가져오고 사용하는 메서드가 포함됩니다. 자세한 내용은 렌더링 프레임워크 I: 렌더링 소개를 참조하세요.

  • 높은 수준에서 게임을 정의하는 방법에 따라 게임 세션, 수준 또는 수명을 정의하는 데이터의 컨테이너 역할을 합니다. 이 경우 게임 상태 데이터는 게임의 수명에 대한 것이며, 사용자가 게임을 시작할 때 한 번 초기화됩니다.

이 클래스에서 정의한 메서드와 데이터를 보려면 아래의 Simple3DGame 클래스를 참조하세요.

게임 초기화 및 시작

플레이어가 게임을 시작하면 게임 개체는 상태를 초기화하고, 오버레이를 생성해 추가하며, 플레이어의 성능을 추적하는 변수를 설정하고, 수준을 빌드하는 데 사용할 개체를 인스턴스화해야 합니다. 이 샘플에서 이는 GameMain 인스턴스가 App::Load에서 만들어질 때 수행됩니다.

Simple3DGame 유형의 게임 개체는 GameMain::GameMain 생성자에 만들어집니다. 그런 다음 GameMain::GameMain에서 호출되는 GameMain::ConstructInBackground fire-and-forget 코루틴 중에 Simple3DGame::Initialize 메서드를 사용하여 초기화됩니다.

Simple3DGame::Initialize 메서드

샘플 게임은 게임 개체에서 이러한 구성 요소를 설정합니다.

  • 새 오디오 재생 개체가 생성됩니다.
  • 레벨 기본 형식, 탄약, 장애물에 대한 배열을 포함하여 게임의 그래픽 기본 형식에 대한 배열이 생성됩니다.
  • 게임 상태 데이터를 저장하기 위한 위치는 게임이라는 이름으로 생성되고 ApplicationData::Current에서 지정한 앱 데이터 설정 스토리지 위치에 배치됩니다.
  • 게임 타이머와 게임 내 초기 오버레이 비트맵이 생성됩니다.
  • 특정 뷰 및 프로젝션 매개 변수 집합을 사용하여 새 카메라를 생성합니다.
  • 입력 장치(컨트롤러)는 카메라와 동일한 시작 피치 및 요로 설정되므로 플레이어는 시작 컨트롤 위치와 카메라 위치 간에 1대1 대응을 가집니다.
  • 플레이어 개체가 생성되고 활성으로 설정됩니다. 구형 개체를 사용하여 벽과 장애물에 대한 플레이어의 근접 정도를 검색하고 카메라가 몰입을 방해할 수 있는 위치에 배치되지 않도록 합니다.
  • 게임 월드 기본 형식이 생성됩니다.
  • 실린더 장애물이 생성됩니다.
  • 대상(Face 개체)이 생성되고 번호가 매겨집니다.
  • 탄약 구가 생성됩니다.
  • 레벨이 생성됩니다.
  • 높은 점수가 로드됩니다.
  • 이전에 저장된 모든 게임 상태가 로드됩니다.

이제 게임에 월드, 플레이어, 장애물, 타겟 및 탄환 같은 모든 주요 구성 요소의 인스턴스가 있습니다. 또한 인스턴스 수준이 있으며, 이는 위의 모든 구성 요소의 구성과 각 특정 수준에 대한 해당 동작을 나타납니다. 이제 게임에서 레벨을 구성하는 방법을 살펴보겠습니다.

게임 수준 빌드 및 로드

수준 구성을 위한 대부분의 번거로운 작업은 샘플 솔루션의 GameLevels 폴더에 있는 Level[N].h/.cpp 파일에서 수행됩니다. 매우 구체적인 구현에 초점을 맞추기 때문에 여기서는 다루지 않을 것입니다. 중요한 점은 각 레벨에 대한 코드가 별도의 Level[N] 개체로 실행된다는 점입니다. 게임을 확장하려면 할당된 숫자를 매개 변수로 사용하고 임의로 장애물과 타겟을 배치하는 Level[N] 개체를 만들 수 있습니다. 또는 리소스 파일이나 인터넷을 통해 레벨 구성 데이터를 로드할 수 있습니다.

게임 플레이 정의

이 시점에서 게임 개발에 필요한 모든 구성 요소가 있습니다. 수준은 기본 요소의 메모리에 구성되었으며 플레이어가 상호 작용할 준비가 되었습니다.

가장 뛰어난 게임은 플레이어 입력에 바로 반응하고 즉각적인 피드백을 제공합니다. 이는 트위치 작업, 실시간 1인칭 슈팅 게임부터 사려 깊은 턴 기반 전략 게임에 이르기까지 모든 유형의 게임에 해당됩니다.

Simple3DGame::RunGame 메서드

게임 수준이 진행되는 동안 게임은 역학 상태에 있습니다.

GameMain::Update는 아래와 같이 프레임당 한 번씩 애플리케이션 상태를 업데이트하는 기본 업데이트 루프입니다. 업데이트 루프는 게임이 역학 상태인 경우 작업을 처리하기 위해 Simple3DGame::RunGame 메서드를 호출합니다.

// Updates the application state once per frame.
void GameMain::Update()
{
    // The controller object has its own update loop.
    m_controller->Update();

    switch (m_updateState)
    {
    ...
    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)
            {
                ...

Simple3DGame::RunGame은 게임 루프의 현재 반복에 대해 게임 플레이의 현재 상태를 정의하는 데이터 집합을 처리합니다.

다음은 Simple3DGame::RunGame의 게임 흐름 논리입니다.

  • 해당 수준이 완료될 때까지 메서드에서 시간(초)을 계산하는 타이머를 업데이트하고 해당 레벨의 시간이 만료되었는지 테스트하여 확인합니다. 이는 게임의 규칙 중 하나입니다.시간이 다되면 모든 타겟이 총에 맞지 않으면 게임이 종료됩니다.
  • 시간이 만료되면 메서드에서 TimeExpired 게임 상태를 설정하고 이전 코드의 Update 메서드로 반환합니다.
  • 시간이 남으면 카메라 위치 업데이트, 특히 카메라 평면(플레이어가 보고 있는 곳)에서 투영되는 보기 법선의 각도와 컨트롤러가 마지막으로 폴링된 이후 각도가 이동한 거리에 대한 업데이트를 위해 이동-보기 컨트롤러가 폴링됩니다.
  • 카메라는 이동 모양 컨트롤러의 새 데이터를 기반으로 업데이트됩니다.
  • 플레이어 컨트롤과 관계없이 게임 월드에서 개체의 애니메이션 및 동작 또는 역학이 업데이트됩니다. 이 샘플 게임에서 Simple3DGame::UpdateDynamics 메서드가 호출되어 발사된 탄약 구체의 움직임, 기둥 장애물의 애니메이션 및 타겟의 움직임을 업데이트합니다. 자세한 내용은 게임 세계 업데이트를 참조하세요.
  • 이 메서드는 수준을 성공적으로 완료하기 위한 기준이 충족되었는지 여부를 확인합니다. 그렇다면 수준에 대한 점수를 확정하고 이것이 마지막 수준(6)인지 여부를 확인합니다. 마지막 수준인 경우 메서드는 GameState::GameComplete 게임 상태를 반환합니다. 그렇지 않으면 GameState::LevelComplete 게임 상태를 반환합니다.
  • 수준이 완료되지 않은 경우 메서드는 게임 상태를 GameState::Active로 설정하고 반환합니다.

게임 세계 업데이트

이 샘플에서 게임이 실행 중일 때 Simple3DGame::RunGame 메서드에서 Simple3DGame::UpdateDynamics 메서드가 호출(GameMain::Update에서 호출됨)되어 게임 장면에서 렌더링되는 개체를 업데이트합니다.

UpdateDynamics와 같은 루프는 플레이어 입력과 상관없이 게임 세계를 움직이는 데 사용되는 모든 메서드를 호출하여 몰입형 게임 환경을 만들고 수준을 생생하게 만듭니다. 여기에는 렌더링해야 하는 그래픽과 플레이어 입력이 없는 경우에도 동적 세계를 구현하기 위한 애니메이션 루프 실행이 포함됩니다. 게임에서는 바람에 흔들리는 나무, 해안선을 따라 솟구치는 파도, 컴퓨터 연기, 기지개를 켜고 움직이는 외계인 괴물이 포함될 수 있습니다. 또한 플레이어 구와 월드 간의 충돌 또는 탄약과 장애물 및 대상 간의 충돌을 포함하여 개체 간의 상호 작용을 포함합니다.

게임이 특별히 일시 중지된 경우를 제외하고 게임 루프는 게임 논리, 물리적 알고리즘에 기반을 두고 있는지, 아니면 그냥 단순한 무작위인지에 상관없이 게임 세계를 계속 업데이트해야 합니다.

샘플 게임에서 이 원칙을 역학이라고 하며 기둥 장애물의 상승 및 하강, 발사되고 움직일 때 탄약 구체의 움직임 및 물리적 동작을 포함합니다.

Simple3DGame::UpdateDynamics 메서드

이 방법은 이 네 가지 컴퓨팅 세트를 다룹니다.

  • 월드에서 발사된 탄약 구체의 위치.
  • 기둥 장애물의 애니메이션.
  • 플레이어와 세계 경계의 교집합.
  • 장애물, 표적, 다른 탄약 구체, 월드와 탄약 구체의 충돌.

장애물의 애니메이션은 Animate.h/.cpp 소스 코드 파일에 정의된 루프에서 발생합니다. 탄환과 모든 충돌의 동작은 코드에서 제공한 간단한 물리학 알고리즘으로 정의되며 중력 및 재료 속성을 비롯한 게임 월드에 대한 전역 상수 집합으로 매개 변수화됩니다. 이는 모두 게임 월드 좌표 공간에서 계산됩니다.

흐름 검토

장면의 모든 개체를 업데이트하고 모든 충돌을 계산했으므로 해당 정보를 사용하여 변경된 화면 효과를 그려야 합니다.

GameMain::Update가 게임 루프의 현재 반복을 완료한 후 샘플은 즉시 GameRenderer::Render를 호출하여 업데이트된 개체 데이터를 가져오고 새 장면을 생성하여 플레이어는 아래와 같이 표시됩니다.

void GameMain::Run()
{
    while (!m_windowClosed)
    {
        if (m_visible)
        {
            switch (m_updateState)
            {
            case UpdateEngineState::Deactivated:
            case UpdateEngineState::TooSmall:
                ...
                // Otherwise, fall through and do normal processing to perform rendering.
            default:
                CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(
                    CoreProcessEventsOption::ProcessAllIfPresent);
                // GameMain::Update calls Simple3DGame::RunGame. If game is in Dynamics
                // state, uses Simple3DGame::UpdateDynamics to update game world.
                Update();
                // Render is called immediately after the Update loop.
                m_renderer->Render();
                m_deviceResources->Present();
                m_renderNeeded = false;
            }
        }
        else
        {
            CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(
                CoreProcessEventsOption::ProcessOneAndAllPending);
        }
    }
    m_game->OnSuspending();  // Exiting due to window close, so save state.
}

게임 세계의 그래픽 렌더링

게임의 그래픽은 이상적으로는 메인 게임 루프가 반복되는 만큼 자주 업데이트하는 것이 좋습니다. 루프가 반복되면 플레이어 입력이 있든 없든 게임 세계의 상태가 업데이트됩니다. 이렇게 하면 계산된 애니메이션과 동작이 원활하게 표시됩니다. 플레이어가 버튼을 눌렀을 때만 움직이는 물의 간단한 장면이 있다고 상상해보세요. 이는 현실적이지 않습니다. 뛰어난 게임은 원활하고 유동적으로 보입니다.

위의 GameMain::Run에서와 같이 샘플 게임의 루프를 호출합니다. 게임의 주 창이 표시되고 스냅되거나 비활성화되지 않은 경우 게임이 계속 업데이트되어 해당 업데이트 결과를 렌더링합니다. 다음에 검토할 GameRenderer::Render 메서드는 해당 상태의 표현을 렌더링합니다. 이는 이전 섹션에서 설명한 대로 상태를 업데이트하기 위한 Simple3DGame::RunGame을 포함하는 GameMain::Update를 호출한 직후에 수행됩니다.

GameRenderer::Render는 3D 세계의 투영을 그린 다음 그 위에 Direct2D 오버레이를 그립니다. 완료되면 최종 스왑 체인에 표시할 결합된 버퍼가 표시됩니다.

참고 항목

샘플 게임의 Direct2D 오버레이에 대한 상태는 두 가지가 있습니다. 하나는 게임에서 일시 중지 메뉴에 대한 비트맵을 포함하는 게임 정보 오버레이를 표시하는 상태이고 하나는 게임에서 터치 스크린 이동-보기 컨트롤러에 대한 사각형과 함께 십자형을 표시하는 상태입니다. 점수 텍스트는 두 상태에서 모두 그려집니다. 자세한 내용은 렌더링 프레임워크 I: 렌더링 소개를 참조하세요.

GameRenderer::Render 메서드

void GameRenderer::Render()
{
    bool stereoEnabled{ m_deviceResources->GetStereoState() };

    auto d3dContext{ m_deviceResources->GetD3DDeviceContext() };
    auto d2dContext{ m_deviceResources->GetD2DDeviceContext() };

    ...
        if (m_game != nullptr && m_gameResourcesLoaded && m_levelResourcesLoaded)
        {
            // This section is only used after the game state has been initialized and all device
            // resources needed for the game have been created and associated with the game objects.
            ...
            for (auto&& object : m_game->RenderObjects())
            {
                object->Render(d3dContext, m_constantBufferChangesEveryPrim.get());
            }
        }

        d3dContext->BeginEventInt(L"D2D BeginDraw", 1);
        d2dContext->BeginDraw();

        // To handle the swapchain being pre-rotated, set the D2D transformation to include it.
        d2dContext->SetTransform(m_deviceResources->GetOrientationTransform2D());

        if (m_game != nullptr && m_gameResourcesLoaded)
        {
            // This is only used after the game state has been initialized.
            m_gameHud.Render(m_game);
        }

        if (m_gameInfoOverlay.Visible())
        {
            d2dContext->DrawBitmap(
                m_gameInfoOverlay.Bitmap(),
                m_gameInfoOverlayRect
                );
        }
        ...
    }
}

Simple3DGame 클래스

이는 Simple3DGame 클래스에 의해 정의된 메서드 및 데이터 멤버입니다.

멤버 함수

Simple3DGame에서 정의한 공개 멤버 함수는 다음과 같습니다.

  • 초기화. 전역 변수의 시작 값을 설정하고 게임 개체를 초기화합니다. 이 내용은 게임 초기화 및 시작 섹션에서 다룹니다.
  • LoadGame. 새 레벨을 초기화하고 로드를 시작합니다.
  • LoadLevelAsync. 수준을 초기화한 다음 렌더러에서 다른 코루틴을 호출하여 디바이스별 수준 리소스를 로드하는 코루틴입니다. 이 메서드는 별도의 스레드에서 실행됩니다. 따라서 이 스레드에서 ID3D11Device 메서드와 달리 ID3D11DeviceContext 메서드만 호출할 수 있습니다. 모든 디바이스 컨텍스트 메서드는 FinalizeLoadLevel 메서드에서 호출됩니다. 비동기 프로그래밍이 처음이라면 C++/WinRT를 사용한 동시성 및 비동기 작업을 참조하세요.
  • FinalizeLoadLevel. 기본 스레드에서 수행해야 하는 수준의 로드 작업을 완료합니다. 여기에는 Direct3D 11 디바이스 컨텍스트(ID3D11DeviceContext) 메서드에 대한 호출이 포함됩니다.
  • StartLevel. 새로운 수준의 게임 플레이를 시작합니다.
  • PauseGame. 게임을 일시 중지합니다.
  • RunGame. 게임 루프의 반복을 실행합니다. 게임 상태가 활성 상태인 경우 게임 루프를 반복할 때마다 App::Update에서 호출됩니다.
  • OnSuspendingOnResuming. 게임의 오디오를 각각 일시 중단/다시 시작합니다.

다음은 프라이빗 멤버 함수입니다.

  • LoadSavedStateSaveState. 게임의 현재 상태를 각각 로드/저장합니다.
  • LoadHighScoreSaveHighScore. 게임 전체에서 각각 최고 점수를 로드/저장합니다.
  • InitializeAmmo. 탄약으로 사용되는 각 구체 개체의 상태를 각 라운드의 시작 부분에 대한 원래 상태로 재설정합니다.
  • UpdateDynamics. 만든 애니메이션 루틴, 물리학 및 컨트롤 입력을 기준으로 모든 게임 개체를 업데이트하기 때문에 이것은 중요한 메서드입니다. 이는 게임을 정의하는 대화형 작업의 핵심입니다. 이 내용은 게임 세계 업데이트 섹션에서 다룹니다.

다른 공개 메서드는 표시를 위해 앱 프레임워크에 게임 플레이 및 오버레이 관련 정보를 반환하는 속성 접근자입니다.

데이터 멤버

이러한 개체는 게임 루프가 실행될 때 업데이트됩니다.

  • MoveLookController 개체. 플레이어 입력을 나타냅니다. 자세한 내용은 컨트롤 추가를 참조하세요.
  • GameRenderer 개체. 모든 디바이스별 개체 및 해당 렌더링을 처리하는 Direct3D 11 렌더러를 나타냅니다. 자세한 내용은 렌더링 프레임워크 I을 참조하세요.
  • Audio 개체. 게임의 오디오 재생을 제어합니다. 자세한 내용은 사운드 추가를 참조하세요.

나머지 게임 변수에는 원형 목록과 해당 원형의 게임 내 양, 게임 플레이 관련 데이터, 제약 조건 등이 포함됩니다.

다음 단계

업데이트된 기본 요소의 Render 메서드 호출이 화면의 픽셀로 변환되는 실제 렌더링 엔진에 대해서는 아직 이야기하지 않았습니다. 이러한 측면은 렌더링 프레임워크 I: 렌더링 소개렌더링 프레임워크 II: 게임 렌더링의 두 부분에서 다룹니다. 플레이어 컨트롤이 게임 상태를 업데이트하는 방법에 대해 더 관심이 있으면 컨트롤 추가를 참조하세요.