Определение основного игрового объекта
Примечание
Этот раздел является частью серии учебников Создание простой игры универсальная платформа Windows (UWP) с помощью DirectX. Раздел по этой ссылке задает контекст для ряда.
После того как вы развернете базовую платформу примера игры и реализовали конечный автомат, который обрабатывает поведение пользователей и системы высокого уровня, вы захотите изучить правила и механизмы, которые превратят пример игры в игру. Давайте рассмотрим подробные сведения об объекте main примера игры и о том, как преобразовать правила игры во взаимодействие с игровым миром.
Задачи
- Узнайте, как применять базовые методы разработки для реализации правил и механики игры UWP с DirectX.
Основной игровой объект
В примере игры Simple3DGameDXSimple3DGame — это класс объектов main игры. Экземпляр Simple3DGame создается косвенно с помощью метода App::Load .
Ниже приведены некоторые функции класса Simple3DGame .
- Содержит реализацию логики игры.
- Содержит методы, сообщающие эти сведения.
- Изменяет состояние игры на конечный автомат, определенный в платформе приложений.
- Изменение состояния игры с приложения на сам игровой объект.
- Сведения об обновлении пользовательского интерфейса игры (наложение и отображение головки), анимации и физики (динамика).
Примечание
Обновление графики осуществляется классом GameRenderer , который содержит методы для получения и использования ресурсов графических устройств, используемых игрой. Дополнительные сведения см. в разделе Инфраструктура отрисовки I. Введение в отрисовку.
- Служит контейнером для данных, определяющих игровой сеанс, уровень или время существования в зависимости от того, как вы определяете игру на высоком уровне. В этом случае данные состояния игры за весь период существования игры и инициализируются один раз, когда пользователь запускает игру.
Чтобы просмотреть методы и данные, определенные этим классом, см. класс Simple3DGame ниже.
Инициализация и запуск игры
При запуске игры пользователем игровой объект должен инициализировать ее состояние, создать и добавить наложенное изображение, задать переменные для отслеживания результатов игрока и создать экземпляры объектов, из которых будут строиться уровни. В этом примере это делается при создании экземпляра GameMain в App::Load.
Игровой объект типа 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] , который принимает назначенное число в качестве параметра и случайным образом размещает препятствия и цели. Вы также можете загрузить данные конфигурации уровня из файла ресурсов или даже из Интернета.
Определение игрового процесса
На этом этапе у нас есть все компоненты, необходимые для разработки игры. Уровни созданы в памяти из примитивов и готовы к взаимодействию с игроком.
Лучшие игры мгновенно реагируют на входные данные игрока и обеспечивают немедленную обратную связь. Это верно для любого типа игры, от twitch-action, в реальном времени от первого лица шутеров до вдумчивых, по очереди стратегических игр.
Метод Simple3DGame::RunGame
Пока выполняется уровень игры, игра находится в состоянии Dynamics .
GameMain::Update — это цикл обновления main, который обновляет состояние приложения один раз для каждого кадра, как показано ниже. Цикл обновления вызывает метод 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.
}
Отрисовка графики игрового мира
Рекомендуется часто обновлять графику в игре, в идеале точно так же часто, как main цикл игры. По мере итерации цикла состояние игрового мира обновляется с вводом или без него. Это позволяет плавно отображать вычисляемые анимации и поведения. Представьте себе, что у нас была простая сцена с водой, которая перемещалась только тогда, когда игрок нажал кнопку. Это было бы нереалистично; хорошая игра выглядит гладкой и плавной все время.
Вспомните цикл примера игры, как показано выше в разделе GameMain::Run. Если окно main игры отображается и не привязывается или не отключается, игра продолжает обновляться и отображать результаты этого обновления. Метод 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, включают перечисленные ниже.
- Инициализация. Задает начальные значения глобальных переменных и инициализирует игровые объекты. Это описано в разделе Инициализация и запуск игры .
- LoadGame. Инициализирует новый уровень и начинает его загрузку.
- LoadLevelAsync. Сопрограмма, которая инициализирует уровень, а затем вызывает другую сопрограмму в отрисовщике для загрузки ресурсов уровня устройства. Данный метод выполняется в отдельном потоке; в результате из данного потока могут вызываться только методы ID3D11Device (в противоположность методам ID3D11DeviceContext). Все контекстные методы устройства вызываются в методе FinalizeLoadLevel. Если вы не знакомы с асинхронным программированием, см. статью Параллелизм и асинхронные операции с C++/WinRT.
- FinalizeLoadLevel. Завершает любую работу, чтобы обеспечить загрузку уровня на основном потоке. Включает любые вызовы контекстных (ID3D11DeviceContext) методов устройства в Direct3D 11.
- StartLevel. Начинает игровой процесс для нового уровня.
- PauseGame. Приостанавливает игру.
- RunGame. Выполняет итерацию игрового цикла. Данный метод вызывается из App::Update один раз при каждой итерации игрового цикла, если игра находится в состоянии Active.
- OnSuspending и OnResuming. Приостановка или возобновление воспроизведения звука игры соответственно.
Ниже приведены частные функции-члены.
- LoadSavedState и SaveState. Загрузите или сохраните текущее состояние игры соответственно.
- LoadHighScore и SaveHighScore. Загрузка и сохранение высокой оценки в играх соответственно.
- InitializeAmmo. Сбрасывает состояние каждого сферического объекта, используемого в качестве боеприпаса, до его исходного состояния в начале каждого раунда.
- UpdateDynamics. Это важный метод, так как он обновляет все игровые объекты на основе консервированных анимаций, физики и контрольных входных данных. Он является основой взаимодействия всех элементов игры. Это рассматривается в разделе Обновление игрового мира .
Другие открытые методы — это метод доступа к свойствам, который возвращает сведения, относящиеся к игровому процессу и наложению, в платформу приложений для отображения.
Элементы данных
Эти объекты обновляются по мере выполнения игрового цикла.
- Объект MoveLookController . Представляет входные данные проигрывателя. Подробнее: Добавление элементов управления.
- Объект GameRenderer. Представляет отрисовщик Direct3D 11, который обрабатывает все объекты, относящиеся к устройству, и их отрисовку. Дополнительные сведения см. в разделе Инфраструктура отрисовки I.
- Объект Audio . Управляет воспроизведением звука в игре. Дополнительные сведения см. в разделе Добавление звука.
Остальные игровые переменные содержат списки примитивов и их соответствующие внутриигровые суммы, а также данные и ограничения для конкретных игр.
Дальнейшие действия
Мы еще не поговорим о фактическом механизме отрисовки — о том, как вызовы методов Render в обновленных примитивах преобразуются в пиксели на экране. Эти аспекты рассматриваются в двух частях: платформа отрисовки I: введение в отрисовку и платформа отрисовки II: отрисовка игры. Если вас больше интересует, как элементы управления игрока обновляют состояние игры, см. статью Добавление элементов управления.
Обратная связь
https://aka.ms/ContentUserFeedback.
Ожидается в ближайшее время: в течение 2024 года мы постепенно откажемся от GitHub Issues как механизма обратной связи для контента и заменим его новой системой обратной связи. Дополнительные сведения см. в разделеОтправить и просмотреть отзыв по