비고
이 항목은 DirectX 자습서 시리즈를 사용하여 간단한 UWP(유니버설 Windows 플랫폼) 게임 만들기의 일부입니다. 그 링크에 있는 주제가 시리즈의 맥락을 설정합니다.
샘플 게임의 기본 프레임워크를 마련하고 상위 수준 사용자 및 시스템 동작을 처리하는 상태 머신을 구현한 후에는 샘플 게임을 게임으로 바꾸는 규칙과 메커니즘을 검사하려고 합니다. 샘플 게임의 주요 개체에 대한 세부 정보와 게임 규칙을 게임 세계와의 상호 작용으로 변환하는 방법을 살펴보겠습니다.
목표
- UWP DirectX 게임에 대한 게임 규칙 및 메커니즘을 구현하기 위해 기본 개발 기술을 적용하는 방법을 알아봅니다.
주 게임 개체
Simple3DGameDX 샘플 게임에서 Simple3DGame은 기본 게임 개체 클래스입니다. Simple3DGame의 인스턴스는 App::Load 메서드를 통해 간접적으로 생성됩니다.
다음은 Simple3DGame 클래스의 몇 가지 기능입니다.
- 게임 플레이 논리의 구현을 포함합니다.
- 이러한 세부 정보를 전달하는 메서드를 포함합니다.
- 게임 상태를 앱 프레임워크에 정의된 상태 컴퓨터로 변경합니다.
- 앱에서 게임 개체 자체로 게임 상태를 변경합니다.
- 게임의 UI(오버레이 및 헤드업 디스플레이), 애니메이션 및 물리학(역학)을 업데이트하기 위한 세부 정보입니다.
비고
그래픽 업데이트는 게임에서 사용하는 그래픽 장치 리소스를 가져오고 사용하는 메서드를 포함하는 GameRenderer 클래스에서 처리됩니다. 자세한 내용은 렌더링 프레임워크 I: 렌더링 소개를 참조하세요.
- 높은 수준에서 게임을 정의하는 방법에 따라 게임 세션, 수준 또는 수명을 정의하는 데이터의 컨테이너 역할을 합니다. 이 경우 게임 상태 데이터는 게임의 수명 동안이며 사용자가 게임을 시작하면 한 번 초기화됩니다.
이 클래스에서 정의한 메서드 및 데이터를 보려면 아래 의 Simple3DGame 클래스 를 참조하세요.
게임 초기화 및 시작
플레이어가 게임을 시작하면 게임 개체는 상태를 초기화하고, 오버레이를 만들고 추가하고, 플레이어의 성능을 추적하는 변수를 설정하고, 수준을 빌드하는 데 사용할 개체를 인스턴스화해야 합니다. 이 샘플에서는 App::Load에서 GameMain 인스턴스가 생성될 때 수행됩니다.
Simple3DGame
Simple3DGame::Initialize 메서드
샘플 게임은 게임 개체에서 이러한 구성 요소를 설정합니다.
- 새 오디오 재생 개체가 만들어집니다.
- 레벨 기본형, 탄약 및 장애물에 대한 배열을 포함하여 게임의 그래픽 기본 형식에 대한 배열이 만들어집니다.
- 게임 상태 데이터를 저장하기 위한 위치는 Game이라는 이름으로 만들어지고 ApplicationData::Current에서 지정한 앱 데이터 설정 스토리지 위치에 배치됩니다.
- 게임 타이머와 게임 내 초기 오버레이 비트맵이 만들어집니다.
- 특정 뷰 및 프로젝션 매개 변수 집합을 사용하여 새 카메라를 만듭니다.
- 입력 장치(컨트롤러)는 카메라와 동일한 시작 피치 및 요로 설정되므로 플레이어는 시작 컨트롤 위치와 카메라 위치 간에 1 대 1 대응을 가집니다.
- 플레이어 개체가 만들어지고 활성으로 설정됩니다. 구체 개체를 사용하여 플레이어가 벽과 장애물에 근접한 것을 감지하고 카메라가 몰입을 깨뜨릴 수 있는 위치에 배치되지 않도록 합니다.
- 게임 월드 기본 형식이 만들어집니다.
- 실린더 장애물이 생성됩니다.
- 목표(얼굴 개체)이 생성되고 번호가 부여됩니다.
- 탄약 구체가 만들어집니다.
- 레벨이 생성됩니다.
- 높은 점수가 로드됩니다.
- 이전에 저장된 모든 게임 상태가 로드됩니다.
이제 게임에는 전 세계, 플레이어, 장애물, 대상, 탄약 구 등 모든 주요 구성 요소의 인스턴스가 있습니다. 또한 위의 모든 구성 요소의 구성과 각 특정 수준에 대한 해당 동작을 나타내는 수준의 인스턴스가 있습니다. 이제 게임이 수준을 빌드하는 방법을 살펴보겠습니다.
게임 레벨을 빌드하고 로드하기
수준 생성에 대한 대부분의 무거운 작업은 샘플 솔루션의 Level[N].h/.cppGameLevels 폴더에 있는 파일에서 수행됩니다. 매우 구체적인 구현에 중점을 두므로 여기서는 다루지 않습니다. 중요한 점은 각 수준에 대한 코드가 별도의 Level[N] 개체로 실행된다는 것입니다. 게임을 확장하려는 경우 할당된 숫자를 매개 변수로 사용하고 장애물과 대상을 임의로 배치하는 Level[N] 개체를 만들 수 있습니다. 또는 리소스 파일 또는 인터넷에서 수준 구성 데이터를 로드할 수 있습니다.
게임 플레이 정의
이 시점에서 게임을 개발하는 데 필요한 모든 구성 요소가 있습니다. 레벨은 기본 요소로부터 메모리에 구축되어 있으며, 플레이어가 상호 작용을 시작할 준비가 되어 있습니다.
최고의 게임은 플레이어 입력에 즉시 반응하고 즉각적인 피드백을 제공합니다. 이는 트위치 액션, 실시간 1인칭 슈팅 게임부터 사려 깊은 턴 기반 전략 게임에 이르기까지 모든 유형의 게임에 적용됩니다.
Simple3DGame::RunGame 메서드
게임 레벨이 진행 중일 때 게임은 Dynamics 상태에 있습니다.
GameMain::Update 는 아래와 같이 프레임당 한 번씩 애플리케이션 상태를 업데이트하는 기본 업데이트 루프입니다. 업데이트 루프는 Simple3DGame::RunGame 메서드를 호출하여 게임이 Dynamics 상태인 경우 작업을 처리합니다.
// 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::UpdateDynamics 메서드가 Simple3DGame::RunGame 메서드( 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 메서드는 해당 상태의 표현을 렌더링합니다. 이 작업은 이전 섹션에서 설명한 바와 같이 상태를 업데이트하기 위해 GameMain::Update호출 후 즉시 수행되며, 여기에는 Simple3DGame::RunGame 도 포함됩니다.
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. 수준을 초기화한 다음 렌더러에서 다른 코루틴을 호출하여 디바이스별 수준 리소스를 로드하는 코루틴입니다. 이 메서드는 별도의 스레드에서 실행됩니다. 따라서 이 스레드에서 ID3D11DeviceContext 메서드와 달리 ID3D11Device 메서드만 호출할 수 있습니다. 모든 디바이스 컨텍스트 메서드는 FinalizeLoadLevel 메서드에서 호출됩니다. 비동기 프로그래밍이 처음이라면 C++/WinRT의 동시성 및 비동기 작업을 참조하세요 (,).
- FinalizeLoadLevel. 주 스레드에서 수행해야 하는 모든 수준 로드 작업을 완료합니다. 여기에는 Direct3D 11 디바이스 컨텍스트(ID3D11DeviceContext) 메서드에 대한 호출이 포함됩니다.
- startLevel. 새 레벨에 대한 게임 플레이를 시작합니다.
- PauseGame. 게임을 일시 중지합니다.
- RunGame. 게임 루프의 반복을 실행합니다. 게임 상태가 활성 일 때, 게임 루프의 각 반복에서 App::Update가 한 번 호출됩니다.
OnSuspending 및 . 게임의 오디오를 각각 일시 중단/다시 시작합니다.OnResuming
다음은 프라이빗 멤버 함수입니다.
- LoadSavedState 및 SaveState. 게임의 현재 상태를 각각 로드/저장합니다.
- LoadHighScore
SaveHighScore . 게임별로 각각 높은 점수를 로드하고 저장합니다. - InitializeAmmo. 탄약으로 사용되는 각 구체 개체의 상태를 각 라운드의 시작 부분에 대한 원래 상태로 다시 설정합니다.
- UpdateDynamics. 이는 고정된 애니메이션 루틴, 물리, 그리고 컨트롤 입력에 따라 게임의 모든 객체를 업데이트하기 때문에 중요한 방법입니다. 이것이 게임을 정의하는 상호작용의 핵심입니다. 이 내용은 게임 월드 업데이트 섹션에서 다룹니다.
다른 공용 메서드는 표시를 위해 앱 프레임워크에 게임 플레이 및 오버레이 관련 정보를 반환하는 속성 접근자입니다.
데이터 멤버
이러한 개체는 게임 루프가 실행될 때 업데이트됩니다.
- 개체를
MoveLookController로. 플레이어 입력을 나타냅니다. 자세한 내용은 컨트롤 추가참조하세요. - GameRenderer 개체. 모든 디바이스별 개체 및 해당 렌더링을 처리하는 Direct3D 11 렌더러를 나타냅니다. 자세한 내용은 렌더링 프레임워크을 참조하세요.
- 오디오 개체입니다. 게임의 오디오 재생을 제어합니다. 자세한 내용은 소리 추가를 참조하세요.
나머지 게임 변수에는 기본 형식 목록과 해당 게임 내 양, 게임 플레이 관련 데이터 및 제약 조건이 포함됩니다.
다음 단계
업데이트된 기본 형식의 Render 메서드 호출이 화면에서 픽셀로 바뀌는 방식과 같은 실제 렌더링 엔진에 대해서는 아직 설명하지 않았습니다. 두 부분으로 구성된 해당 측면은 렌더링 프레임워크 I: 렌더링 소개 및 렌더링 프레임워크 II: 게임 렌더링 을 포함합니다. 플레이어 컨트롤이 게임 상태를 업데이트하는 방법에 더 관심이 있는 경우 컨트롤 추가을 참조하세요.