Marble Maze 示例基础

本主题介绍了 Marble Maze 项目的基本特征。例如,如何在 Windows 运行时环境中使用 Visual C++、如何创建和构造,以及如何生成。 本主题还介绍了代码中使用的几个约定。

注意

与本文档对应的示例代码位于 DirectX Marble Maze 游戏示例中。

以下是本文档所讨论的有关规划和开发通用 Windows 平台 (UWP) 游戏的一些要点。

  • 在 Visual Studio 中使用 DirectX 11 应用(通用 Windows - C++/CX)模板创建你的 DirectX UWP 游戏。
  • Windows 运行时提供类和接口,这样可以采用更现代化的、面向对象的方式开发 UWP 应用。
  • 使用带乘幂号 (^) 的对象引用来管理 Windows 运行时变量的生命周期、使用 Microsoft::WRL::ComPtr 来管理 COM 对象的生命周期,并使用 std::shared_ptrstd::unique_ptr 来管理所有其他堆分配的 C++ 对象的生命周期。
  • 在大多数情况下,使用异常处理而不是结果代码来处理意外错误。
  • 结合使用 SAL 注释和代码分析工具来帮助发现应用中的错误。

创建 Visual Studio 项目

如果已下载并提取了示例,则可以在 Visual Studio 中打开“MarbleMaze_VS2017.sln”文件(在 C++ 文件夹中),然后就可以看到代码了。

为 Marble Maze 创建 Visual Studio 项目时,我们将从现有项目开始。 但是,如果现有的项目没有提供你的 DirectX UWP 游戏所需的基本功能,建议你基于 Visual Studio DirectX 11 应用(通用 Windows - C++/CX)模板创建一个 项目,因为它提供了基本的有效 3D 应用程序。 为此,请按照下列步骤进行操作:

  1. 在 Visual Studio 2019 中,依次选择“文件”>“新建”>“项目...”。

  2. 在“创建新项目”对话框中,选择“DirectX 11 应用(通用 Windows - C++/CX)”。 如果你未看到此选项,则可能未安装所需的组件。有关如何安装其他组件的信息,请参阅通过添加或删除工作负载和组件来修改 Visual Studio 2019

新建项目

  1. 选择“下一步”,然后输入“项目名称”、要存储的文件的“位置”和“解决方案名称”,然后选择“创建”。

DirectX 11 应用(通用 Windows - C++/CX)模板中一个重要的项目设置是 /ZW 选项,它使程序能够使用 Windows 运行时语言扩展。 默认情况下,在使用 Visual Studio 模板时,此选项处于启用状态。 如需如何在 Visual Studio 中设置编译器选项的详细信息,请参阅编译器和链接器选项(C++/CX)

注意 /ZW 选项与 /clr 等选项不兼容。 如果是“/clr”,这意味着不能在同一 Visual C++ 项目中同时设置 .NET Framework 和 Windows 运行时为目标。

 

你从 Microsoft Store 获取的每个 UWP 应用都以应用包的形式提供。 应用包包含一个程序包清单,其中包含有关应用的信息。 例如,可以指定应用的功能(即需要权限才能访问受保护的系统资源或用户数据)。 如果确定应用需要某些功能,请使用程序包清单来声明所需的功能。 该清单还允许指定项目属性(例如支持的设备旋转、磁贴图像和启动屏幕)。 可以通过在项目中打开“Package.appxmanifest”来编辑清单。 有关应用包的详细信息,请参阅打包应用

构建、部署和运行游戏

在 Visual Studio 顶部的下拉菜单中,在绿色播放按钮的左侧,选择部署配置。 建议将其设置为 Debug 模式,针对设备的体系结构(x86 适用于 32 位、x64 适用于 64 位)以及本地计算机。 还可以在“远程计算机”上进行测试,或者对通过 USB 连接的设备进行测试。 然后单击绿色播放按钮以构建并部署到设备。

调试;x64;本地计算机

控制游戏

可以使用触控、加速计、游戏控制器或鼠标来控制 Marble Maze。

  • 使用控制器上的方向键来更改活动菜单项。
  • 使用触控、控制器上的 A 或“开始”按钮或者鼠标来选取菜单项。
  • 使用触控、加速计、左摇杆或鼠标来倾斜迷宫。
  • 使用触控、控制器上的 A 或“开始”按钮或者鼠标来关闭高分表等菜单。
  • 使用控制器上的“开始”按钮或键盘上的 P 键暂停或恢复游戏。
  • 使用控制器上的“后退”按钮或键盘上的“开始”键来重新启动游戏。
  • 当高分表可见时,请使用控制器上的“后退”按钮或键盘上的“开始”键清除所有分数。

代码约定

Windows 运行时是一个编程接口,可用于创建仅在特殊应用程序环境中运行的 UWP 应用。 此类应用使用经授权的函数、数据类型和设备,并且从 Microsoft Store 进行分配。 在最低级别,Windows 运行时由应用程序二进制接口 (ABI) 组成。 ABI 是一种低级二进制协定,使 Windows 运行时 API 可供多种编程语言(如 JavaScript、.NET 语言和 Visual C++)访问。

为了从 JavaScript 和 .NET 调用 Windows 运行时 API,这些语言需要特定于每种语言环境的投影。 当从 JavaScript 或 .NET 调用 Windows 运行时 API 时,你调用的是投影,而投影又调用了底层 ABI 函数。 虽然可以直接在 C++ 中调用 ABI 函数,但 Microsoft 也为 C++ 提供了投影,因为利用投影,可以更简单地使用 Windows 运行时 API,同时仍保持高性能。 Microsoft 还为 Visual C++ 提供了专门支持 Windows 运行时投影的语言扩展。 其中许多语言扩展类似于 C++/CLI 语言的语法。 但是,本机应用使用此语法来针对 Windows 运行时,而不是针对公共语言运行时 (CLR)。 对象引用或者 hat (^) 修饰符是此新语法的重要部分,因为它通过引用计数启用了运行时对象的自动删除。 运行时不是调用 AddRefRelease 等方法来管理 Windows 运行时对象的生存期,而是在没有其他组件引用对象时(例如,在它离开作用域或将所有引用设置为“nullptr”时)删除该对象。 使用 Visual C++ 创建 UWP 应用的另一个重要部分是“ref new”关键字。 使用“ref new”而不是“new”来创建进行引用计数的 Windows 运行时对象。 有关详细信息,请参阅类型系统 (C++/CX)

重要

仅当创建 Windows 运行时对象或创建 Windows 运行时组件时,才须使用 ^ 和“ref new”。 在编写不使用 Windows 运行时的核心应用程序代码时,可以使用标准 C++ 语法。

Marble Maze 将 ^ 和“Microsoft::WRL::ComPtr”结合使用来管理堆分配的对象并且最大程度减少内存泄露。 我们建议你使用 ^ 来管理 Windows 运行时变量的生命周期、使用 ComPtr 来管理 COM 变量的生命周期(如使用 DirectX 时),以及使用 std::shared_ptr 或 std::unique_ptr 管理所有其他堆分配的 C++ 对象的生命周期。

 

有关可供 C++ UWP 应用使用的语言扩展的详细信息,请参阅 Visual C++ 语言参考 (C++/CX)

错误处理。

Marble Maze 使用异常处理作为处理意外错误的主要方式。 虽然游戏代码通常使用日志记录或错误代码(例如“HRESULT”值)来指示错误,但异常处理有两个主要优点。 首先,它使得代码更易于读取和维护。 从代码角度看,异常处理是将错误传播到可处理该错误的例程的更高效方法。 使用错误代码通常要求每个函数都显式传播错误。 第二个优点是可以将 Visual Studio 调试器配置为在发生异常时中断,以便可以立即在发生错误的位置和上下文停止。 Windows 运行时也广泛使用异常处理。 因此,通过在代码中使用异常处理,可以将所有错误处理合并到一个模型中。

建议在错误处理模型中使用以下约定:

  • 使用异常来传递意外错误。

  • 不要使用异常来控制代码流。

  • 仅捕获可以安全处理和从中恢复的异常。 否则,不要捕获异常并允许应用终止。

  • 调用可返回“HRESULT”的 DirectX 例程时,请使用“DX::ThrowIfFailed”函数。 此函数在 DirectXHelper.h 中定义。 如果提供的“HRESULT”是错误代码,则“ThrowIfFailed”会抛出一个异常。 例如,E_POINTER 会导致 ThrowIfFailed 引发 Platform::NullReferenceException。

    当使用“ThrowIfFailed”时,请将 DirectX 调用放在单独一行上以帮助提高代码可读性,如下面的示例所示。

    // Identify the physical adapter (GPU or card) this device is running on.
    ComPtr<IDXGIAdapter> dxgiAdapter;
    DX::ThrowIfFailed(
        dxgiDevice->GetAdapter(&dxgiAdapter)
        );
    
  • 尽管我们建议避免使用“HRESULT”来处理意外错误,但避免使用异常处理来控制代码流更为重要。 因此,最好在必要时使用“HRESULT”返回值来控制代码流。

SAL 注释

结合使用 SAL 注释和代码分析工具来帮助发现应用中的错误。

通过使用 Microsoft 源代码注释语言 (SAL),可以注释或描述函数如何使用其参数。 SAL 注释还描述了返回值。 SAL 注释使用 C/C++ 代码分析工具来发现 C 和 C++ 源代码中可能存在的缺陷。 工具报告的常见编码错误包括缓冲区溢出、内存未初始化、null 指针取消引用以及内存和资源泄漏。

请考虑 BasicLoader::LoadMesh 方法,该方法在 BasicLoader.h 中声明。 此方法使用 _In_ 指定“filename”是一个输入参数(因此仅用于读取),使用 _Out_ 指定“vertexBuffer”和“indexBuffer”是输出参数(因此仅用于写入),还使用 _Out_opt_ 指定“vertexCount”和“indexCount”是可选的输出参数(可写入)。 由于“vertexCount”和“indexCount”是可选的输出参数,因此允许为“nullptr”。 C/C++ 代码分析工具将检查对此方法的调用以确保其传递的参数满足这些条件。

void LoadMesh(
    _In_ Platform::String^ filename,
    _Out_ ID3D11Buffer** vertexBuffer,
    _Out_ ID3D11Buffer** indexBuffer,
    _Out_opt_ uint32* vertexCount,
    _Out_opt_ uint32* indexCount
    );

若要在应用上执行代码分析,在菜单栏上选择“生成”>“对解决方案运行代码分析”。 有关代码分析的详细信息,请参阅使用代码分析来分析 C/C++ 代码质量

可用注释的完整列表在 sal.h 中定义。 有关详细信息,请参阅 SAL 注释

后续步骤

阅读Marble Maze 应用程序结构,了解有关如何构造 Marble Maze 应用程序代码以及 DirectX UWP 应用的结构与传统桌面应用程序的结构有何不同的信息。