Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Структура приложения Универсальной платформы Windows (UWP) DirectX отличается от структуры традиционного классического приложения. Вместо работы с типами дескрипторов, такими как HWND и функции, такие как CreateWindow, среда выполнения Windows предоставляет такие интерфейсы, как Windows::UI::Core::ICoreWindow , чтобы вы могли разрабатывать приложения UWP более современным, объектно-ориентированным образом. В этом разделе документации показано, как структурирован код приложения Marble Maze.
Замечание
Пример кода, соответствующий этому документу, находится в примере игры DirectX Marble Maze.
Ниже приведены некоторые ключевые моменты, которые обсуждаются в этом документе при структурировании кода игры:
- На этапе инициализации настройте компоненты среды выполнения и библиотеки, которые использует ваша игра, и загрузите ресурсы для конкретной игры.
- Приложения UWP должны начать обработку событий в течение 5 секунд после запуска. Поэтому загружайте только необходимые ресурсы при загрузке приложения. Игры должны загружать большие ресурсы в фоновом режиме и отображать экран прогресса.
- В игровом цикле реагируйте на события Windows, считывайте входные данные пользователя, обновляйте объекты сцены и отрисовывайте сцену.
- Используйте обработчики событий для реагирования на события окна. (Эти сообщения заменяют сообщения окон в настольных приложениях Windows.)
- Используйте компьютер состояния для управления потоком и порядком игровой логики.
Файловая организация
Некоторые компоненты в Marble Maze можно повторно использовать с любой игрой без изменений. Для собственной игры вы можете адаптировать организацию и идеи, предоставляемые этими файлами. В следующей таблице кратко описаны важные файлы исходного кода.
Файлы | Описание |
---|---|
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 | Определяет класс Physics, который реализует физическую симуляцию взаимодействия между мрамором и лабиринтом. |
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 App (Universal Windows) создает основное окно, в которое можно отобразить с помощью Direct3D. Шаблон также включает класс DeviceResources , который создает все ресурсы устройств Direct3D, необходимые для отрисовки трехмерного содержимого в приложении UWP.
Класс App создаёт объект MarbleMazeMain, запускает загрузку ресурсов, выполняет цикл для обновления таймера и вызывает метод MarbleMazeMain::Render в каждом кадре. Методы App::OnWindowSizeChanged, App::OnDpiChanged, и App::OnOrientationChanged каждый вызывают метод MarbleMazeMain::CreateWindowSizeDependentResources, а метод App::Run вызывает методы MarbleMazeMain::Update и MarbleMazeMain::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 загружает ресурсы игры, а затем задает этот флаг. Обновление (MarbleMazeMain::Update) и отрисовка (MarbleMazeMain::Render) проверяют этот флаг. Если этот флаг установлен, игра продолжается как обычная. Если флаг еще не задан, игра отображает экран загрузки.
Дополнительные сведения об асинхронном программировании для приложений UWP см. в статье "Асинхронное программирование" в C++.
Подсказка
Если вы пишете код игры для библиотеки C++ среды выполнения Windows (например, DLL), подумайте о том, чтобы ознакомиться с Создание асинхронных операций в C++ для приложений UWP, чтобы узнать, как создавать асинхронные операции, которые могут использоваться приложениями и другими библиотеками.
Цикл игры
Метод App::Run запускает основной цикл игры (MarbleMazeMain::Update). Этот метод вызывается для каждого кадра.
Чтобы отделить код представления и окна от кода, связанного с конкретной игрой, мы реализовали метод App::Run для пересылки вызовов обновления и отрисовки в объект MarbleMazeMain.
В следующем примере показан метод 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
}
Компьютер состояния
Игры обычно содержат
Цикл игры часто использует компьютер состояния, чтобы он смог выполнить логику, относящуюся к текущему состоянию игры. 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 отвечает на приостановку и возобновление уведомлений по двум причинам:
- Когда приложение приостановлено, игра сохраняет текущее состояние игры и приостанавливает воспроизведение звука. Когда приложение возобновляется, игра возобновляет воспроизведение звука.
- После закрытия и последующего перезапуска приложения игра возобновляется из предыдущего состояния.
Marble Maze выполняет следующие задачи для поддержки приостановки и возобновления работы:
- Он сохраняет свое состояние в постоянном хранилище в ключевых точках игры, например, когда пользователь достигает контрольной точки.
- Он реагирует на приостановку уведомлений, сохраняя его состояние в постоянном хранилище.
- Он реагирует на уведомления о возобновлении, загружая его состояние из постоянного хранилища. Он также загружает предыдущее состояние во время запуска.
Чтобы поддерживать приостановку и возобновление, Marble Maze определяет класс PersistentState. (См. PersistentState.h и PersistentState.cpp). Этот класс использует интерфейс Windows::Foundation::Collections::IPropertySet для чтения и записи свойств. Класс PersistentState
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", чтобы узнать о некоторых ключевых практиках, которые следует учитывать при работе с визуальными ресурсами.
Связанные темы
- Добавление визуального содержимого в пример Marble Maze
- основы примера Marble Maze
- Разработка Marble Maze, игра на платформе UWP на C++ и DirectX