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

Applies to Windows only

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

Примечание  Этому документу соответствует образец кода игры DirectX Marble Maze.

В этом разделе

В этом документе рассматриваются следующие основные моменты создания структуры программного кода игры.

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

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

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

ФайлыОписание
Audio.h, Audio.cpp Определяют класс Audio, который управляет звуковыми ресурсами.
BasicLoader.h, BasicLoader.cpp Определяют класс BasicLoader, который предоставляет служебные методы, упрощающие загрузку текстур, сеток и шейдеров.
BasicMath.hОпределяет структуры и функции для работы с векторными и матричными данными и вычислениями. Многие из этих функций совместимы с типами шейдеров HLSL.
BasicReaderWriter.h, BasicReaderWriter.cpp Определяют класс BasicReaderWriter, который использует среду выполнения Windows для чтения и записи файловых данных в приложении Магазина Windows.
BasicShapes.h, BasicShapes.cpp Определяют класс BasicShapes, который предоставляет служебные методы для создания основных фигур (кубов, сфер и т. п.). (Эти файлы не используются в реализации Marble Maze.)
BasicTimer.h, BasicTimer.cpp Определяют класс BasicTimer, который позволяет легко получать значения общего и затраченного времени.
Camera.h, Camera.cpp Определяют класс Camera, который предоставляет положение и ориентацию камеры.
Collision.h, Collision.cpp Обрабатывают столкновения шарика с другими объектами (в том числе со стенами лабиринта).
DDSTextureLoader.h, DDSTextureLoader.cpp Определяют функцию CreateDDSTextureFromMemory, которая загружает текстуры в формате DDS из буфера памяти.
DirectXApp.h, DirectXApp.cpp Определяют классы DirectXApp и DirectXAppSource, в которых инкапсулируется представление приложения (окно, поток и события).
DirectXBase.h, DirectXBase.cpp Определяют класс DirectXBase, который предоставляет общую инфраструктуру для многих приложений Магазина Windows на DirectX.
DirectXSample.hОпределяет служебные функции для использования в приложениях Магазина Windows на DirectX.
LoadScreen.h, LoadScreen.cpp Определяют класс LoadScreen, который отображает экран загрузки во время инициализации приложения.
MarbleMaze.h, MarbleMaze.cpp Определяют класс MarbleMaze, который управляет игровыми ресурсами и определяет большую часть игровой логики.
MediaStreamer.h, MediaStreamer.cpp Определяют класс MediaStreamer, который с помощью Media Foundation управляет звуковыми ресурсами в игре.
PersistentState.h, PersistentState.cppОпределяют класс PersistentState, который считывает примитивные типы данных из резервного хранилища и записывает их в резервное хранилище.
Physics.h, Physics.cpp Определяют класс Physics, в котором реализуется физика взаимодействия шарика и лабиринта.
Primitives.h Определяет геометрические типы, используемые в игре.
SampleOverlay.h, SampleOverlay.cpp Определяют класс SampleOverlay, предоставляющий общие данные и операции для двумерной графики и пользовательского интерфейса.
SDKMesh.h, SDKMesh.cpp Определяют класс SDKMesh, который загружает и отрисовывает сетки в формате SDK Mesh (.sdkmesh).
UserInterface.h, UserInterface.cpp Определяют функции пользовательского интерфейса (системы меню, таблицы результатов и т. п.).

 

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

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

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

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

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

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

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

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

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

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

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

Шаблон Приложение DirectX создает основное окно, в котором можно выполнять отрисовку с помощью Direct3D. Этот шаблон включает также класс DeviceResources, создающий все ресурсы устройства Direct3D, которые необходимы для отрисовки трехмерного содержимого в приложении Магазина Windows. Класс AppMain создает объект класса MarbleMaze, запускает загрузку ресурсов, выполняет цикл для обновления таймера и вызывает метод отрисовки MarbleMaze в каждом фрейме. Методы CreateWindowSizeDependentResources, Update и Render для этого класса вызывают соответствующие методы в классе MarbleMaze. В следующем примере показано, где конструктор AppMain создает объект класса MarbleMaze. Класс ресурсов устройства передается этому классу, и он может использовать объекты Direct3D для отрисовки.


    m_marbleMaze = std::unique_ptr<MarbleMaze>(new MarbleMaze(m_deviceResources));
    m_marbleMaze->CreateWindowSizeDependentResources();


Класс AppMain также запускает загрузку отложенных ресурсов для игры. Более подробные сведения см. в следующем разделе. Конструктор DirectXPage настраивает обработчики событий, а также создает класс DeviceResources и класс AppMain.

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

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

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

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

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

Асинхронная загрузка ресурсов начинается с метода AppMain::Load. Этот метод использует класс task Class (Concurrency Runtime) для загрузки ресурсов игры в фоновом режиме.



    task<void>([=]()
    {
        m_marbleMaze->LoadDeferredResources();
    });



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

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

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

Игровой цикл

Метод DirectPage::OnRendering запускает главный игровой цикл. Этот метод вызывается в каждом фрейме.

Чтобы отделить код представлений и окон от игрового кода, мы реализовали метод DirectXApp::Run для перенаправления вызовов обновления и отрисовки в объект MarbleMaze. В методе DirectPage::OnRendering также определяется игровой таймер, который используется для анимации и моделирования физики.

В следующем примере показан метод DirectPage::OnRendering, в который входит главный игровой цикл. Игровой цикл обновляет переменные общего времени и времени кадра, а затем обновляет и отрисовывает сцену. Тем самым можно также гарантировать отрисовку содержимого только в случае видимости окна.




void DirectXPage::OnRendering(Object^ sender, Object^ args)
{
    if (m_windowVisible)
    {
        m_main->Update();

        if (m_main->Render())
        {
            m_deviceResources->Present();
        }
    }
}


Конечный автомат

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

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


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


Например, состояние MainMenu определяет, что отображается главное меню, а игра неактивна. Состояние InGameActive, напротив, определяет, что игра активна, а главное меню не отображается. В классе MarbleMaze определяется переменная-член m_gameState для хранения активного состояния игры.

В методах MarbleMaze::Update и MarbleMaze::Render с помощью оператора switch выполняется логика для текущего состояния. В следующем примере показано, как выглядит этот оператор switch для метода MarbleMaze::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 уведомляет его перед приостановкой, должно иметь возможность сохранить все данные, которые могут потребоваться для восстановления текущего состояния пользователя после перезапуска приложения. Если для сохранения важного состояния в приложении необходимо много ресурсов, может понадобиться регулярно сохранять состояние даже до получения уведомления о приостановке. 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
{
public:
    void Initialize(
        _In_ Windows::Foundation::Collections::IPropertySet^ m_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;
};


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


m_persistentState = ref new PersistentState();
m_persistentState->Initialize(ApplicationData::Current->LocalSettings->Values, "MarbleMaze");

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

В ответ на уведомления о приостановке и возобновлении класс DirectXPage определяет методы SaveInternalState и LoadInternalState, вызываемые при приостановке и возобновлении. Метод MarbleMaze::OnSuspending обрабатывает событие приостановки, а метод MarbleMaze::OnResuming — событие возобновления.

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


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


Метод MarbleMaze::SaveState сохраняет значения состояния игры (текущее положение и скорость шарика, последнюю контрольную точку и таблицу результатов).


void MarbleMaze::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.

Для поддержки перезапуска метод MarbleMaze::Initialize, который вызывается во время запуска, вызывает метод MarbleMaze::LoadState. Метод MarbleMaze::LoadState считывает и применяет состояние к игровым объектам. Этот метод также задает для игры состояние паузы независимо от состояния на момент приостановки (пауза или активная игра). Пауза включается, чтобы пользователя не застала врасплох игровая ситуация. Если на момент приостановки не был активен игровой процесс, то игра переходит в главное меню.


void MarbleMaze::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 нет различия между начальным запуском (без предшествующего события приостановки) и возобновлением после приостановки. Так рекомендуется делать во всех приложениях Магазина Windows.

Другие примеры, демонстрирующие сохранение параметров и файлов в локальном хранилище данных приложений и загрузку параметров и файлов из хранилища см. в разделе Краткое руководство: локальные данные приложения. Дополнительные сведения о данных приложений см. в разделе Доступ к данным приложения с помощью среды выполнения Windows.

Следующие действия

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

Связанные разделы

Добавление визуального содержимого в образец Marble Maze
Основные особенности образца Marble Maze
Разработка Marble Maze, игры Магазина Windows на C++ и DirectX

 

 

Показ:
© 2014 Microsoft