Определение основного игрового объекта

Примечание

Этот раздел является частью серии учебников Создание простой игры универсальная платформа 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: отрисовка игры. Если вас больше интересует, как элементы управления игрока обновляют состояние игры, см. статью Добавление элементов управления.