Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Замечание
Этот раздел является частью серии руководств по созданию простой игры на универсальной платформе Windows (UWP) с использованием DirectX. Тема на той ссылке определяет контекст для серии.
После того как вы изложили базовую платформу примера игры и реализовали машину состояния, которая обрабатывает поведение пользователя высокого уровня и системы, вы захотите проверить правила и механики, которые превратят образец игры в игру. Рассмотрим подробные сведения о главном объекте образцовой игры и о том, как преобразовать правила игры во взаимодействие с игровым миром.
Цели
- Узнайте, как применять основные методы разработки для реализации правил игры и механики для игры UWP DirectX.
Основной игровой объект
В примере игры Simple3DGameDXSimple3DGame является основным классом объектов игры. Экземпляр Simple3DGame создается косвенно с помощью метода App::Load.
Ниже приведены некоторые функции класса Simple3DGame.
- Содержит реализацию логики игрового процесса.
- Содержит методы, которые передают эти сведения.
- Изменения в состоянии игры в машину состояний, определенной в платформе приложений.
- Изменения в состоянии игры из приложения на сам объект игры.
- Сведения об обновлении пользовательского интерфейса игры (наложение и индикаторы состояния), анимации и физики (динамики).
Замечание
Обновление графики обрабатывается классом GameRenderer, который содержит методы для получения и использования ресурсов графического устройства, используемых игрой. Дополнительную информацию см. в разделе Фреймворк рендеринга I: Введение в рендеринг.
- Служит контейнером для данных, определяющих игровой сеанс, уровень или время существования, в зависимости от того, как вы определяете игру на высоком уровне. В этом случае данные о состоянии игры в течение всего времени существования игры инициализированы один раз, когда пользователь запускает игру.
Чтобы просмотреть методы и данные, определенные этим классом, см. класс Simple3DGame ниже.
Инициализация и запуск игры
Когда игрок запускает игру, объект игры должен инициализировать состояние, создать и добавить наложение, задать переменные, отслеживающие производительность игрока, и создать экземпляры объектов, которые будут использоваться для создания уровней. В этом примере это делается при создании экземпляра GameMain
Объект игры Simple3DGameсоздается в конструкторе GameMain::GameMain. Затем объект инициализируется с помощью метода Simple3DGame::Initialize в процессе выполнения fire-and-forget корутины GameMain::ConstructInBackground, которая вызывается из GameMain::GameMain.
Метод Simple3DGame::Initialize
Пример игры настраивает эти компоненты в объекте игры.
- Создается новый объект воспроизведения звука.
- Создаются массивы графических примитивов игры, включая массивы для примитивов уровня, боеприпасов и препятствий.
- Создается расположение для сохранения данных о состоянии игры с именем Gameи помещается в место, указанное в настройках данных приложения как ApplicationData::Current.
- Создается таймер игры и начальное наложенное изображение в игре.
- Новая камера создается с определенным набором параметров представления и проекции.
- Устройство ввода (контроллер) устанавливается на тот же начальный угол наклона и поворота, что и камера, поэтому у игрока есть соответствие 1 к 1 между начальным положением контроллера и положением камеры.
- Объект игрока создается и активируется. Мы используем объект сфера для обнаружения близости игрока к стенам и препятствиям и чтобы не позволять камере оказаться в положении, которое может испортить погружение.
- Создается примитив мира игры.
- Создаются цилиндрические препятствия.
- Целевые объекты (объекты face) создаются и нумеруются.
- Создаются сферы аммо.
- Создаются уровни.
- Высокая оценка загружается.
- Загружается любое предыдущее сохраненное состояние игры.
Игра теперь имеет экземпляры всех ключевых компонентов — мира, игрока, препятствий, целей и сфер с боеприпасами. Он также имеет экземпляры уровней, которые представляют конфигурации всех перечисленных выше компонентов и их поведения для каждого конкретного уровня. Теперь давайте посмотрим, как игра создает уровни.
Создание и загрузка уровней игры
Большая часть работы по строительству уровня выполняется в файлах Level[N].h/.cpp, находящихся в папке GameLevels образца решения. Поскольку он фокусируется на очень конкретной реализации, мы не будем охватывать их здесь. Важно, чтобы код для каждого уровня выполнялся как отдельный объект Level[N]. Если вы хотите расширить игру, можно создать объект Level[N], который принимает назначенное число в качестве параметра и случайным образом помещает препятствия и цели. Вы также можете загружать данные конфигурации уровня из файла ресурсов или даже из Интернета.
Определение игрового процесса
На этом этапе у нас есть все компоненты, необходимые для разработки игры. Уровни были созданы в памяти из примитивов и готовы к взаимодействию с игроком.
Лучшие игры мгновенно реагируют на входные данные игрока и предоставляют немедленную обратную связь. Это верно для любого типа игры: от экшенов в реальном времени, шутеров от первого лица, до вдумчивых, пошаговых стратегических игр.
Метод 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 рисует проекцию трехмерного мира, а затем рисует наложение 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 включают следующие функции.
- инициализировать. Задает начальные значения глобальных переменных и инициализирует объекты игры. Это рассматривается в разделе Инициализация и запуск игры.
- Загрузить игру. Инициализирует новый уровень и начинает загрузку.
- LoadLevelAsync. Корутина, которая инициализирует уровень, а затем вызывает другую корутину, используя отрисовщик, для загрузки ресурсов уровня, специфичных для устройства. Этот метод выполняется в отдельном потоке; в результате из этого потока можно вызывать только методы ID3D11Device, в отличие от методов ID3D11DeviceContext. Все методы контекста устройства вызываются в методе FinalizeLoadLevel. Если вы не знакомы с асинхронным программированием, ознакомьтесь с параллелизмом и асинхронными операциями сC++/WinRT.
- Завершениеуровнязагрузки. Завершает любую работу, связанную с загрузкой уровня, которую нужно выполнить в основном потоке. Сюда входят любые вызовы методов в контексте устройства Direct3D 11 (ID3D11DeviceContext).
- Начальный уровень. Запускает игровой процесс для нового уровня.
- PauseGame. Приостанавливает игру.
- RunGame. Выполняет итерацию цикла игры. Он вызывается из App::Update один раз при каждой итерации цикла игры, если состояние игры Активное.
- OnSuspending и OnResuming. Приостановка и возобновление звука игры соответственно.
Ниже приведены частные функции-члены.
- LoadSavedState и SaveState. Загрузка и сохранение текущего состояния игры соответственно.
- LoadHighScore и SaveHighScore. Загрузка и сохранение высокой оценки в играх соответственно.
- ИнициализацияAmmo. Сбрасывает состояние каждого сферического объекта, используемого в качестве боеприпасов, в его первоначальное состояние в начале каждого раунда.
- UpdateDynamics. Это важный метод, так как он обновляет все игровые объекты на основе консервированных процедур анимации, физики и управления входными данными. Это сердце интерактивности, определяющей игру. Это рассматривается в разделе Обновление игрового мира.
Другие общедоступные методы — это методы доступа к свойствам, которые возвращают информацию, специфичную для игрового процесса и наложений, в инфраструктуру приложения для отображения.
Элементы данных
Эти объекты обновляются при выполнении цикла игры.
- объект MoveLookController. Представляет входные данные проигрывателя. Дополнительные сведения см. в разделе Добавление элементов управления.
- объект GameRenderer. Представляет рендерер Direct3D 11, который управляет всеми объектами, специфичными для устройства, и их рендерингом. Дополнительные сведения см. в структуре рендеринга.
- аудио объект. Управляет воспроизведением звука для игры. Дополнительные сведения см. в разделе Добавление звука.
Остальные переменные игры содержат списки примитивов и их соответствующих объемов в игре, а также данные и ограничения, специфичные для игрового процесса.
Дальнейшие шаги
Мы еще не говорили о фактическом движке отрисовки — о том, как вызовы методов Render на обновленных примитивах превращаются в пиксели на вашем экране. Эти аспекты рассматриваются в двух частях:фреймворк отрисовки I: введение в отрисовку и фреймворк отрисовки II: отрисовка игр. Если вы больше заинтересованы в том, как игрок управляет обновлением состояния игры, см. статью Добавление элементов управления.