Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Замечание
Этот раздел является частью серии руководств по созданию простой игры на универсальной платформе Windows (UWP) с использованием DirectX. Тема на той ссылке определяет контекст для серии.
Теперь у игры есть окно, зарегистрированы некоторые обработчики событий и асинхронно загружены ресурсы. В этом разделе объясняется использование игровых состояний, управление определенными ключевыми игровыми состояниями и создание цикла обновления для игрового двигателя. Затем мы узнаем о потоке пользовательского интерфейса и, наконец, понять больше о обработчиках событий, необходимых для игры UWP.
Игровые состояния, используемые для управления потоком игр
Мы используем игровые состояния для управления потоком игры.
Когда пример игрового приложения Simple3DGameDX запускается на компьютере впервые, он находится в состоянии, в котором игра не была начата. В последующих запусках игра может находиться в любом из этих состояний.
- Игра не была запущена, или игра находится между уровнями (высокий показатель равен нулю).
- Игровой цикл работает, и находится в середине уровня.
- Цикл игры не выполняется из-за завершения игры (высокая оценка имеет ненулевое значение).
Ваша игра может иметь столько состояний, сколько ей нужно. Но помните, что его можно завершить в любое время. Когда работа возобновляется, пользователь ожидает, что она продолжится с того состояния, в котором была завершена.
Управление состоянием игры
Таким образом, во время инициализации игры вам потребуется поддерживать холодный запуск игры, а также возобновление игры после остановки его в полете. Пример Simple3DGameDX всегда сохраняет состояние игры, чтобы создать впечатление, что она никогда не останавливалась.
В ответ на событие приостановки игровой процесс приостановлен, но ресурсы игры по-прежнему находятся в памяти. Аналогичным образом, событие возобновления обрабатывается, чтобы убедиться, что пример игры возобновляется в том состоянии, в котором она находилась, когда была приостановлена или завершена. В зависимости от состояния для игрока отображаются различные параметры.
- Если игра возобновляется в середине уровня, она отображается как приостановленная, и всплывающее окно предлагает возможность продолжить.
- Если игра возобновляется в состоянии завершения игры, она отображает высокие оценки и возможность играть в новую игру.
- Наконец, если игра возобновляется до начала уровня, то наложение предоставляет пользователю возможность запуска.
Пример игры не различает, происходит ли холодный запуск, первый запуск без события приостановки или возобновление из приостановленного состояния. Это правильный дизайн для любого приложения UWP.
В этом примере инициализация состояний игры происходит в GameMain::InitializeGameState (структура этого метода показана в следующем разделе).
Ниже приведен блок-схема, помогающий визуализировать поток. Он охватывает как инициализацию, так и цикл обновления.
- Инициализация начинается на узле Start, когда вы проверяете текущее состояние игры. Код игры см. в разделе GameMain::InitializeGameState в следующем разделе.
- Дополнительные сведения о цикле обновления см. в разделе "Обновление игрового механизма". Для кода игры перейдите в GameMain::Update.
Метод GameMain::InitializeGameState
метод GameMain::InitializeGameState вызывается косвенно через конструктор класса GameMain, что является результатом создания экземпляра GameMain в App::Load.
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, в которых может находиться игра.
- UpdateEngineState::ОжиданиеРесурсов. Игровой цикл зациклен, не может переходить, пока не будут доступны ресурсы (в частности, графические ресурсы). По завершении асинхронных задач загрузки ресурсов мы обновим состояние updateEngineState::ResourcesLoaded. Обычно это происходит между уровнями, когда уровень загружает новые ресурсы с диска, с игрового сервера или из облачной серверной части. В образце игры мы имитируем такое поведение, так как в этом примере не требуется дополнительных ресурсов на каждом уровне.
- UpdateEngineState::ОжиданиеНажатия. Игровой цикл повторяется, ожидая определенных пользовательских входных данных. Это действие игрока для загрузки игры, запуска уровня или продолжения уровня. Пример кода ссылается на эти под-состояния с помощью перечисления PressResultState.
- UpdateEngineState::D ynamics. Цикл игры выполняется, когда пользователь играет. В то время как пользователь играет, игра проверяет наличие 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;
}
}
Обновление пользовательского интерфейса
Нам нужно держать игрока в курсе состояния системы, и разрешить игровому состоянию изменяться в зависимости от действий игрока и правил, определяющих игру. Многие игры, включая этот пример игры, обычно используют элементы пользовательского интерфейса для представления этой информации игроку. Пользовательский интерфейс содержит отображения состояния игры и другие сведения об игре, такие как счёт, боеприпасы или количество оставшихся шансов. Пользовательский интерфейс также называется наложением, так как он отрисовывается отдельно от основного графического конвейера и помещается на вершину трехмерной проекции.
Некоторая информация о пользовательском интерфейсе также отображается в виде индикаторного дисплея (HUD), чтобы позволить пользователю видеть эту информацию, не отрывая взгляд полностью от основной зоны игры. В примере игры мы создадим это наложение с помощью API Direct2D. Кроме того, мы можем создать это наложение, используя XAML, что обсуждается в Расширение примера игры.
В пользовательском интерфейсе есть два компонента.
- HUD, содержащий оценку и сведения о текущем состоянии игрового процесса.
- Изображение паузы, представляющее собой черный прямоугольник с наложенным текстом во время состояния паузы игры. Это игровой оверлей. Далее мы подробнее обсудим этот вопрос в пункте Добавление пользовательского интерфейса.
Неудивительно, что наложение также имеет государственный компьютер. Наложение может отображать сообщение о начале уровня или окончании игры. Это, по сути, холст, на котором мы можем выводить любую информацию о состоянии игры, которую хотим отобразить игроку, когда игра приостановлена.
Наложение может быть одним из шести экранов в зависимости от состояния игры.
- Экран хода загрузки ресурсов в начале игры.
- Экран статистики игрового процесса.
- Экран сообщения о начале уровня.
- Экран окончания игры, когда все уровни пройдены до истечения времени.
- Экран окончания игры при окончании времени.
- Экран меню приостановки.
Разделяя пользовательский интерфейс от графического конвейера игры, вы можете работать независимо от графического механизма отрисовки игры и значительно снизить сложность кода игры.
Вот как пример игры структурирует состояние машины с наложениями.
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 может в любое время быть активировано, деактивировано, изменено в размере, прикреплено, откреплено, приостановлено или возобновлено, игра должна регистрировать эти события, как только это возможно, и обрабатывать их для обеспечения плавности и предсказуемости взаимодействия для игрока.
Это обработчики событий, используемые в этом примере, и события, которые они обрабатывают.
Обработчик событий | Описание |
---|---|
При активации | Обрабатывает CoreApplicationView::Activated. Приложение игры выведено на передний план, поэтому главное окно активировано. |
OnDpiChanged | Обрабатывает Graphics::Display::DisplayInformation::DpiChanged. DPI дисплея изменился, и игра корректирует свои ресурсы соответствующим образом.
ПримечаниеКоординаты CoreWindow находятся в пикселях, не зависящих от устройства (DIPs) для Direct2D. В результате необходимо уведомить Direct2D об изменении DPI, чтобы отобразить все 2D-ресурсы или примитивы правильно.
|
OnOrientationChanged | Обрабатывает Graphics::Display::DisplayInformation::OrientationChanged. Ориентация дисплея изменяется, и отрисовка должна быть обновлена. |
При отображении содержимое аннулируется | Обрабатывает graphics::Display::DisplayInformation::DisplayContentsInvalidated. Экран требует перерисовки, и игра должна быть снова визуализирована. |
OnResuming | Обрабатывает CoreApplication::Resuming. Приложение игры восстанавливает игру из приостановленного состояния. |
OnSuspending | Обрабатывает CoreApplication::Suspending. Игровое приложение сохраняет состояние на диск. У него есть 5 секунд для сохранения состояния в хранилище. |
ПриИзмененииВидимости | Обрабатывает CoreWindow::VisibilityChanged. Приложение игры изменило свою видимость: оно стало видимым или невидимым в результате того, что другое приложение изменило свою видимость. |
OnWindowActivationChanged | Обрабатывает CoreWindow::Activated. Главное окно игрового приложения было деактивировано или активировано, поэтому необходимо убрать фокус и приостановить игру или восстановить фокус. В обоих случаях экранное сообщение указывает, что игра приостановлена. |
При закрытии окна | Обрабатывает CoreWindow::Closed. Игровое приложение закрывает главное окно и приостанавливает игру. |
OnWindowSizeChanged | Обрабатывает CoreWindow::SizeChanged. Приложение игры переопределяет графические ресурсы и наложение для изменения размера, а затем обновляет целевой объект отрисовки. |
Дальнейшие шаги
В этой статье мы узнали, как общий поток игры управляется с помощью игровых состояний, и что игра состоит из нескольких различных машин состояния. Мы также узнали, как обновить пользовательский интерфейс и управлять обработчиками событий ключевых приложений. Теперь мы готовы изучить цикл отрисовки, игру и ее механику.
Вы можете ознакомиться с остальными темами, которые документируют эту игру, в любом порядке.