Поделиться через


Структура приложения Marble Maze

Структура приложения DirectX универсальная платформа Windows (UWP) отличается от структуры традиционного классического приложения. Вместо работы с типами дескрипторов, такими как HWND и функции, такие как CreateWindow, среда выполнения Windows предоставляет такие интерфейсы, как Windows::UI::Core::ICoreWindow, чтобы можно было разрабатывать приложения UWP более современным, объектно-ориентированным образом. В этом разделе документации показано, как структурирован код приложения Marble Maze.

Примечание.

Пример кода, соответствующий этому документу, найден в примере игры DirectX Marble Maze.

Ниже приведены некоторые ключевые моменты, которые обсуждаются в этом документе при структурировании кода игры:

  • На этапе инициализации настройте компоненты среды выполнения и библиотеки, которые использует ваша игра, и загрузите ресурсы для конкретной игры.
  • Приложения UWP должны начать обработку событий в течение 5 секунд после запуска. Поэтому загружайте только необходимые ресурсы при загрузке приложения. Игры должны загружать большие ресурсы в фоновом режиме и отображать экран хода выполнения.
  • В цикле игры реагируйте на события Windows, считывает входные данные пользователя, обновляет объекты сцены и отрисовывает сцену.
  • Используйте обработчики событий для реагирования на события окна. (Эти сообщения заменяют сообщения окна из классических приложений Windows.)
  • Используйте компьютер состояния для управления потоком и порядком игровой логики.

Файловая организация

Некоторые компоненты в Marble Maze можно повторно использовать с любой игрой без изменений. Для собственной игры вы можете адаптировать организацию и идеи, предоставляемые этими файлами. В следующей таблице кратко описаны важные файлы исходного кода.

Файлы Description
App.h, App.cpp Определяет классы App и DirectXApplicationSource , которые инкапсулируют представление (окно, поток и события) приложения.
Audio.h, Audio.cpp Определяет класс Audio , который управляет звуковыми ресурсами.
BasicLoader.h, BasicLoader.cpp Определяет класс BasicLoader , который предоставляет служебные методы, которые помогают загружать текстуры, сетки и шейдеры.
BasicMath.h Определяет структуры и функции, которые помогают работать с векторными и матрицными данными и вычислениями. Многие из этих функций совместимы с типами шейдеров HLSL.
BasicReaderWriter.h, BasicReaderWriter.cpp Определяет класс BasicReaderWriter, который использует среда выполнения Windows для чтения и записи данных файла в приложении UWP.
BasicShapes.h, BasicShapes.cpp Определяет класс BasicShapes , который предоставляет служебные методы для создания базовых фигур, таких как куби и сферы. (Эти файлы не используются реализацией Marble Maze.
Camera.h, Camera.cpp Определяет класс Камеры, который предоставляет положение и ориентацию камеры.
Collision.h, Collision.cpp Управляет сведениями о столкновении между мрамором и другими объектами, такими как лабиринт.
DDSTextureLoader.h, DDSTextureLoader.cpp Определяет функцию CreateDDSTextureFromMemory , которая загружает текстуры, которые находятся в .dds формате из буфера памяти.
DirectXHelper.h Определяет вспомогательные функции DirectX, которые полезны для многих приложений UWP DirectX.
LoadScreen.h, LoadScreen.cpp Определяет класс LoadScreen , который отображает экран загрузки во время инициализации приложения.
MarbleMazeMain.h, MarbleMazeMain.cpp Определяет класс MarbleMazeMain , который управляет ресурсами, зависящими от игры, и определяет большую часть игровой логики.
MediaStreamer.h, MediaStreamer.cpp Определяет класс MediaStreamer , который использует Media Foundation для управления звуковыми ресурсами игры.
PersistentState.h, PersistentState.cpp Определяет класс PersistentState , который считывает и записывает примитивные типы данных из и в резервное хранилище.
Physics.h, Physics.cpp Определяет класс физики, который реализует моделирование физики между мрамором и лабиринтом.
Primitives.h Определяет геометрические типы, используемые игрой.
SampleOverlay.h, SampleOverlay.cpp Определяет класс SampleOverlay , который предоставляет общие 2D-данные и операции пользовательского интерфейса.
SDKMesh.h, SDKMesh.cpp Определяет класс SDKMesh , который загружает и отрисовывает сетки, которые находятся в формате сетки SDK (SDKmesh).
StepTimer.h Определяет класс StepTimer , который предоставляет простой способ получить общее и истекшее время.
UserInterface.h, UserInterface.cpp Определяет функциональные возможности, связанные с пользовательским интерфейсом, такими как система меню и таблица высокой оценки.

 

Форматы ресурсов времени разработки и времени выполнения

Если вы можете, используйте форматы времени выполнения вместо форматов времени разработки для более эффективной загрузки игровых ресурсов.

Формат времени разработки — это формат, используемый при разработке ресурса. Как правило, трехмерные конструкторы работают с форматами времени разработки. Некоторые форматы времени разработки также основаны на тексте, чтобы их можно было изменить в любом текстовом редакторе. Форматы времени разработки могут быть подробными и содержать больше информации, чем требуется для вашей игры. Формат времени выполнения — это двоичный формат, считываемый игрой. Форматы времени выполнения обычно более компактны и эффективны для загрузки, чем соответствующие форматы времени разработки. Поэтому большинство игр используют ресурсы во время выполнения.

Хотя ваша игра может напрямую читать формат времени разработки, существует несколько преимуществ для использования отдельного формата времени выполнения. Так как форматы времени выполнения часто более компактны, они требуют меньше места на диске и требуют меньше времени для передачи по сети. Кроме того, форматы времени выполнения часто представляются как структуры данных, сопоставленные с памятью. Поэтому их можно загружать в память гораздо быстрее, чем, например, текстовый файл на основе XML. Наконец, поскольку отдельные форматы времени выполнения обычно кодируются в двоичном коде, они сложнее изменять конечным пользователем.

Шейдеры HLSL являются одним из примеров ресурсов, использующих различные форматы времени разработки и времени выполнения. Marble Maze использует hlsl в качестве формата времени разработки и CSO в качестве формата времени выполнения. HLSL-файл содержит исходный код для шейдера; CSO-файл содержит соответствующий код байтов шейдера. При преобразовании HLSL-файлов в автономном режиме и предоставлении CSO-файлов с игрой не требуется преобразовывать исходные файлы HLSL в код байтов при загрузке игры.

По инструкциям проект Marble Maze включает как формат времени разработки, так и формат времени выполнения для многих ресурсов, но вам нужно поддерживать только форматы времени разработки в исходном проекте для собственной игры, так как их можно преобразовать в форматы времени выполнения, если они нужны. В этой документации показано, как преобразовать форматы времени разработки в форматы времени выполнения.

Жизненный цикл приложения

Marble Maze следует жизненному циклу типичного приложения UWP. Дополнительные сведения о жизненном цикле приложения UWP см. в разделе "Жизненный цикл приложений".

При инициализации игры UWP обычно инициализирует компоненты среды выполнения, такие как Direct3D, Direct2D и любые входные, звуковые или физические библиотеки, которые он использует. Она также загружает ресурсы, необходимые для начала игры. Эта инициализация происходит один раз во время игрового сеанса.

После инициализации игры обычно выполняют цикл игры. В этом цикле игры обычно выполняют четыре действия: обрабатывают события Windows, собирают входные данные, обновляют объекты сцены и отрисовывает сцену. Когда игра обновляет сцену, она может применить текущее состояние ввода к объектам сцены и имитировать физические события, такие как столкновения объектов. Игра также может выполнять другие действия, такие как воспроизведение звуковых эффектов или отправка данных по сети. Когда игра отрисовывает сцену, она захватывает текущее состояние сцены и рисует его на отображаемое устройство. В следующих разделах описаны эти действия более подробно.

Добавление в шаблон

Шаблон приложения DirectX 11 (универсальная версия Windows) создает основное окно, в котором можно отобразить с помощью Direct3D. Шаблон также включает класс DeviceResources , который создает все ресурсы устройств Direct3D, необходимые для отрисовки трехмерного содержимого в приложении UWP.

Класс App создает объект класса MarbleMazeMazeMain , запускает загрузку ресурсов, циклов для обновления таймера и вызывает метод MarbleMazeMazeMain::Render каждого кадра. Методы App::OnWindowSizeChanged, App::OnDpiChanged и App::OnOrientationChanged вызывают метод MarbleMazeMain::CreateWindowSizeDependentResources, а метод App::Run вызывает метод MarbleMazeMazeMain::Update и MarbleMazeMazeMain::Render.

В следующем примере показано, где метод App::SetWindow создает объект класса MarbleMazeMazeMain . Класс DeviceResources передается методу, чтобы он смог использовать объекты Direct3D для отрисовки.

    m_main = std::unique_ptr<MarbleMazeMain>(new MarbleMazeMain(m_deviceResources));

Класс App также начинает загрузку отложенных ресурсов для игры. Дополнительные сведения см. в следующем разделе.

Кроме того, класс App настраивает обработчики событий CoreWindow. Когда обработчики для этих событий вызываются, они передают входные данные в класс MarbleMazeMain .

Загрузка игровых ресурсов в фоновом режиме

Чтобы убедиться, что игра может реагировать на события окна в течение 5 секунд после его запуска, рекомендуется загружать ресурсы игры асинхронно или в фоновом режиме. Как ресурсы загружают в фоновом режиме, игра может реагировать на события окна.

Примечание.

Вы также можете отобразить главное меню, когда оно будет готово, и разрешить остальным ресурсам продолжить загрузку в фоновом режиме. Если пользователь выбирает параметр из меню перед загрузкой всех ресурсов, можно указать, что ресурсы сцены продолжают загружаться, отображая индикатор выполнения, например.

 

Даже если ваша игра содержит относительно мало игровых ресурсов, рекомендуется загружать их асинхронно по двум причинам. Одна из причин заключается в том, что трудно гарантировать, что все ресурсы будут быстро загружаться на всех устройствах и всех конфигурациях. Кроме того, при включении асинхронной загрузки код готов масштабироваться при добавлении функциональных возможностей.

Асинхронная загрузка ресурсов начинается с метода App::Load . Этот метод использует класс задач для загрузки игровых ресурсов в фоновом режиме.

    task<void>([=]()
    {
        m_main->LoadDeferredResources(true, false);
    });

Класс MarbleMazeMain определяет флаг m_deferredResourcesReady, указывающий, что асинхронная загрузка завершена. Метод MarbleMazeMain::LoadDeferredResources загружает ресурсы игры, а затем задает этот флаг. Этапы обновления (MarbleMazeMazeMain::Update) и отрисовки (MarbleMazeMazeMain::Render) приложения проверяют этот флаг. Если этот флаг установлен, игра продолжается как обычная. Если флаг еще не задан, игра отображает экран загрузки.

Дополнительные сведения об асинхронном программировании для приложений UWP см. в статье "Асинхронное программирование" в C++.

Совет

Если вы пишете код игры, который является частью библиотеки C++ среда выполнения Windows C++ (другими словами, dll), рассмотрите, следует ли читать создание асинхронных операций в C++ для приложений UWP, чтобы узнать, как создавать асинхронные операции, которые могут использоваться приложениями и другими библиотеками.

 

Цикл игры

Метод App::Run запускает основной цикл игры (MarbleMazeMain::Update). Этот метод вызывается каждым кадром.

Чтобы отделить код представления и окна от кода для игры, мы реализовали метод App::Run для пересылки вызовов обновления и отрисовки объекта MarbleMazeMazeMain .

В следующем примере показан метод App::Run , включающий основной цикл игры. Цикл игры обновляет общие переменные времени и интервала времени, а затем обновляет и отрисовывает сцену. Это также гарантирует, что содержимое отображается только при отображении окна.

void App::Run()
{
    while (!m_windowClosed)
    {
        if (m_windowVisible)
        {
            CoreWindow::GetForCurrentThread()->Dispatcher->
                ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);

            m_main->Update();

            if (m_main->Render())
            {
                m_deviceResources->Present();
            }
        }
        else
        {
            CoreWindow::GetForCurrentThread()->Dispatcher->
                ProcessEvents(CoreProcessEventsOption::ProcessOneAndAllPending);
        }
    }

    // The app is exiting so do the same thing as if the app were being suspended.
    m_main->OnSuspending();

#ifdef _DEBUG
    // Dump debug info when exiting.
    DumpD3DDebug();
#endif //_DEGBUG
}

Компьютер состояния

Игры обычно содержат компьютер состояния (также известный как конечный автомат состояния или FSM) для управления потоком и порядком игровой логики. Компьютер состояния содержит заданное количество состояний и возможность перехода между ними. Компьютер состояния обычно начинается с начального состояния, переходит к одному или нескольким промежуточным состояниям и, возможно, заканчивается в состоянии терминала.

Цикл игры часто использует компьютер состояния, чтобы он смог выполнить логику, относящуюся к текущему состоянию игры. Marble Maze определяет перечисление GameState , которое определяет каждое возможное состояние игры.

enum class GameState
{
    Initial,
    MainMenu,
    HighScoreDisplay,
    PreGameCountdown,
    InGameActive,
    InGamePaused,
    PostGameResults,
};

Например , состояние MainMenu определяет, что отображается главное меню и что игра не активна. И наоборот, состояние InGameActive определяет, что игра активна, и что меню не отображается. Класс MarbleMazeMain определяет переменную члена m_gameState для хранения активного состояния игры.

Методы MarbleMazeMain::Update и MarbleMazeMain::Render используют инструкции switch для выполнения логики текущего состояния. В следующем примере показано, как выглядит оператор switch для метода MarbleMazeMain::Update (сведения удаляются для иллюстрации структуры).

switch (m_gameState)
{
case GameState::MainMenu:
    // Do something with the main menu. 
    break;

case GameState::HighScoreDisplay:
    // Do something with the high-score table. 
    break;

case GameState::PostGameResults:
    // Do something with the game results. 
    break;

case GameState::InGamePaused:
    // Handle the paused state. 
    break;
}

Если логика игры или отрисовка зависят от определенного состояния игры, мы подчеркиваем его в этой документации.

Обработка событий приложения и окна

Среда выполнения Windows предоставляет объектно-ориентированную систему обработки событий, чтобы упростить управление сообщениями Windows. Чтобы использовать событие в приложении, необходимо предоставить обработчик событий или метод обработки событий, который отвечает на событие. Кроме того, необходимо зарегистрировать обработчик событий в источнике событий. Этот процесс часто называется проводкой событий.

Поддержка приостановки, возобновления и перезапуска

Marble Maze приостанавливается, когда пользователь переключается с него или когда Windows входит в состояние низкой мощности. Игра возобновляется, когда пользователь перемещает его на передний план или когда Windows выходит из состояния низкой мощности. Как правило, вы не закрываете приложения. Windows может завершить работу приложения, если оно находится в приостановленном состоянии, а Windows требует ресурсов, таких как память, используемое приложением. Windows уведомляет приложение о том, когда оно будет приостановлено или возобновлено, но оно не уведомляет приложение о завершении работы. Таким образом, приложение должно иметь возможность сохранять (в момент, когда Windows уведомляет ваше приложение о том, что оно будет приостановлено), любые данные, необходимые для восстановления текущего пользовательского состояния при перезапуске приложения. Если ваше приложение имеет значительное состояние пользователя, которое дорого сохранить, возможно, вам также потребуется регулярно сохранять состояние, даже прежде чем ваше приложение получит уведомление о приостановке. Marble Maze отвечает на приостановку и возобновление уведомлений по двум причинам:

  1. Когда приложение приостановлено, игра сохраняет текущее состояние игры и приостанавливает воспроизведение звука. Когда приложение возобновляется, игра возобновляет воспроизведение звука.
  2. После закрытия и последующего перезапуска приложения игра возобновляется из предыдущего состояния.

Marble Maze выполняет следующие задачи для поддержки приостановки и возобновления работы:

  • Он сохраняет свое состояние в постоянном хранилище в ключевых точках игры, например, когда пользователь достигает контрольной точки.
  • Он реагирует на приостановку уведомлений, сохраняя его состояние в постоянном хранилище.
  • Он реагирует на уведомления о возобновлении, загружая его состояние из постоянного хранилища. Он также загружает предыдущее состояние во время запуска.

Чтобы поддерживать приостановку и возобновление, Marble Maze определяет класс PersistentState . (См. раздел PersistentState.h и PersistentState.cpp). Этот класс использует интерфейс Windows::Foundation::Collections::IPropertySet для чтения и записи свойств. Класс PersistentState предоставляет методы, которые считывают и записывают примитивные типы данных (например, bool, int, float, XMFLOAT3 и Platform::String), из и в резервное хранилище.

ref class PersistentState
{
internal:
    void Initialize(
        _In_ Windows::Foundation::Collections::IPropertySet^ settingsValues,
        _In_ Platform::String^ key
        );

    void SaveBool(Platform::String^ key, bool value);
    void SaveInt32(Platform::String^ key, int value);
    void SaveSingle(Platform::String^ key, float value);
    void SaveXMFLOAT3(Platform::String^ key, DirectX::XMFLOAT3 value);
    void SaveString(Platform::String^ key, Platform::String^ string);

    bool LoadBool(Platform::String^ key, bool defaultValue);
    int  LoadInt32(Platform::String^ key, int defaultValue);
    float LoadSingle(Platform::String^ key, float defaultValue);

    DirectX::XMFLOAT3 LoadXMFLOAT3(
        Platform::String^ key, 
        DirectX::XMFLOAT3 defaultValue);

    Platform::String^ LoadString(
        Platform::String^ key, 
        Platform::String^ defaultValue);

private:
    Platform::String^ m_keyName;
    Windows::Foundation::Collections::IPropertySet^ m_settingsValues;
};

Класс MarbleMazeMain содержит объект PersistentState . Конструктор MarbleMazeMain инициализирует этот объект и предоставляет локальное хранилище данных приложения в качестве резервного хранилища данных.

m_persistentState = ref new PersistentState();

m_persistentState->Initialize(
    Windows::Storage::ApplicationData::Current->LocalSettings->Values,
    "MarbleMaze");

Marble Maze сохраняет свое состояние, когда мрамор проходит через контрольную точку или цель (в методе MarbleMazeMain::Update), а также когда окно теряет фокус (в методе MarbleMazeMain::OnFocusChange). Если в игре хранится большое количество данных о состоянии, рекомендуется иногда сохранять состояние в постоянном хранилище таким же образом, так как для ответа на уведомление о приостановке требуется только несколько секунд. Таким образом, когда приложение получает уведомление о приостановке, он должен сохранять только измененные данные состояния.

Чтобы реагировать на уведомления о приостановке и возобновлении, класс MarbleMazeMain определяет методы SaveState и LoadState, вызываемые при приостановке и возобновлении. Метод MarbleMazeMain::OnSuspending обрабатывает событие приостановки, а метод MarbleMazeMain::OnResuming обрабатывает событие резюме.

Метод MarbleMazeMain::OnSuspending сохраняет состояние игры и приостанавливает звук.

void MarbleMazeMain::OnSuspending()
{
    SaveState();
    m_audio.SuspendAudio();
}

Метод MarbleMazeMain::SaveState сохраняет значения состояния игры, такие как текущая позиция и скорость мрамора, самая последняя контрольная точка и таблица высокой оценки.

void MarbleMazeMain::SaveState()
{
    m_persistentState->SaveXMFLOAT3(":Position", m_physics.GetPosition());
    m_persistentState->SaveXMFLOAT3(":Velocity", m_physics.GetVelocity());

    m_persistentState->SaveSingle(
        ":ElapsedTime", 
        m_inGameStopwatchTimer.GetElapsedTime());

    m_persistentState->SaveInt32(":GameState", static_cast<int>(m_gameState));
    m_persistentState->SaveInt32(":Checkpoint", static_cast<int>(m_currentCheckpoint));

    int i = 0;
    HighScoreEntries entries = m_highScoreTable.GetEntries();
    const int bufferLength = 16;
    char16 str[bufferLength];

    m_persistentState->SaveInt32(":ScoreCount", static_cast<int>(entries.size()));

    for (auto iter = entries.begin(); iter != entries.end(); ++iter)
    {
        int len = swprintf_s(str, bufferLength, L"%d", i++);
        Platform::String^ string = ref new Platform::String(str, len);

        m_persistentState->SaveSingle(
            Platform::String::Concat(":ScoreTime", string), 
            iter->elapsedTime);

        m_persistentState->SaveString(
            Platform::String::Concat(":ScoreTag", string), 
            iter->tag);
    }
}

Когда игра возобновляется, она должна возобновить звук только. Не нужно загружать состояние из постоянного хранилища, так как состояние уже загружено в память.

Как игра приостанавливает и возобновляет звук, объясняется в документе Добавление звука в образец Marble Maze.

Для поддержки перезапуска конструктор MarbleMazeMazeMain , который вызывается во время запуска, вызывает метод MarbleMazeMain::LoadState . Метод MarbleMazeMain::LoadState считывает и применяет состояние к игровым объектам. Этот метод также задает текущее состояние игры для приостановки, если игра была приостановлена или активна при приостановке. Мы приостанавливаем игру, чтобы пользователь не был удивлен непредвиденным действием. Он также перемещается в главное меню, если игра не была в состоянии игрового процесса, когда она была приостановлена.

void MarbleMazeMain::LoadState()
{
    XMFLOAT3 position = m_persistentState->LoadXMFLOAT3(
        ":Position", 
        m_physics.GetPosition());

    XMFLOAT3 velocity = m_persistentState->LoadXMFLOAT3(
        ":Velocity", 
        m_physics.GetVelocity());

    float elapsedTime = m_persistentState->LoadSingle(":ElapsedTime", 0.0f);

    int gameState = m_persistentState->LoadInt32(
        ":GameState", 
        static_cast<int>(m_gameState));

    int currentCheckpoint = m_persistentState->LoadInt32(
        ":Checkpoint", 
        static_cast<int>(m_currentCheckpoint));

    switch (static_cast<GameState>(gameState))
    {
    case GameState::Initial:
        break;

    case GameState::MainMenu:
    case GameState::HighScoreDisplay:
    case GameState::PreGameCountdown:
    case GameState::PostGameResults:
        SetGameState(GameState::MainMenu);
        break;

    case GameState::InGameActive:
    case GameState::InGamePaused:
        m_inGameStopwatchTimer.SetVisible(true);
        m_inGameStopwatchTimer.SetElapsedTime(elapsedTime);
        m_physics.SetPosition(position);
        m_physics.SetVelocity(velocity);
        m_currentCheckpoint = currentCheckpoint;
        SetGameState(GameState::InGamePaused);
        break;
    }

    int count = m_persistentState->LoadInt32(":ScoreCount", 0);

    const int bufferLength = 16;
    char16 str[bufferLength];

    for (int i = 0; i < count; i++)
    {
        HighScoreEntry entry;
        int len = swprintf_s(str, bufferLength, L"%d", i);
        Platform::String^ string = ref new Platform::String(str, len);

        entry.elapsedTime = m_persistentState->LoadSingle(
            Platform::String::Concat(":ScoreTime", string), 
            0.0f);

        entry.tag = m_persistentState->LoadString(
            Platform::String::Concat(":ScoreTag", string), 
            L"");

        m_highScoreTable.AddScoreToTable(entry);
    }
}

Внимание

Marble Maze не отличается от холодного запуска ( то есть, начиная в первый раз без предварительного события приостановки) и возобновления из приостановленного состояния. Это рекомендуется для всех приложений UWP.

Дополнительные сведения о данных приложения см. в разделе "Магазин" и получение параметров и других данных приложения.

Следующие шаги

Ознакомьтесь со статьей " Добавление визуального содержимого в пример Marble Maze", чтобы узнать о некоторых ключевых методиках, которые следует учитывать при работе с визуальными ресурсами.