实现 3D 应用启动器(Win32 应用)

注意

此功能仅适用于运行最新 Windows 预览体验成员外部测试版 (RS5)、版本 17704 及更高版本的电脑。

用户在启动应用程序之前,会先登陆到 Windows Mixed Reality 主页。 默认情况下,你需要从头戴显示设备外部启动沉浸式 Win32 VR 应用和游戏,这些游戏和应用不会出现在 Windows Mixed Reality 的“开始”菜单上的“所有应用”列表中。 如果按照本文中的说明来实现 3D 应用启动器,则可以从Windows Mixed Reality 的“开始”菜单和主页环境中启动沉浸式 Win32 VR 体验。

这仅适用于在 Steam 之外分发的沉浸式 Win32 VR 体验。 对于通过 Steam 分发的 VR 体验,我们已经更新了Windows Mixed Reality for SteamVR Beta 版以及最新的 Windows 预览体验成员 RS5 外部测试版,以便 SteamVR 标题使用默认启动器自动显示在 Windows Mixed Reality 的“开始”菜单中的“所有应用”列表中。 换句话说,本文所述的方法对于 SteamVR 标题是不必要的,将被 Windows Mixed Reality for SteamVR Beta 版功能所取代。

3D 应用启动器创建过程

创建 3D 应用启动器的过程分为三个步骤:

  1. 设计和概念
  2. 建模和导出
  3. 将其集成到你的应用程序中(本文)

在创作要用作应用程序启动器的 3D 资产时,应参考 Windows Mixed Reality 创作指南,以确保兼容性。 不符合此创作规范的资产将不会在 Windows 混合现实主页中呈现。

配置 3D 启动器

如果为 Win32 应用程序创建 3D 应用启动器,则这些启动器将出现在 Windows Mixed Reality 的“开始”菜单的“所有应用”列表中。 要创建上述启动器,请按照以下步骤操作来创建一个引用 3D 应用启动器的视觉元素清单 XML 文件:

  1. 创建 3D 应用启动器资产 GLB 文件(请参阅建模和导出)。
  2. 为应用程序创建一个视觉元素清单
    1. 可以从下面的示例开始。 有关更多详细信息,请参阅完整的视觉元素清单文档。
    2. 通过适合应用的 PNG/JPG/GIF 更新 Square150x150Logo 和 Square70x70Logo。
      • 这些项将用于 Windows Mixed Reality 的“所有应用”列表中应用的 2D 徽标以及桌面上的开始菜单。
      • 文件路径基于包含视觉元素清单的文件夹。
      • 你仍然需要通过标准机制为你的应用提供桌面开始菜单图标。 它可以直接放置在可执行文件中,也可以放置在创建的快捷方式中。 例如,通过 IShellLink::SetIconLocation 来这样做。
      • 可选:如果需要,你可以使用 resources.pri 文件,以便让 MRT 针对不同的分辨率缩放和高对比度主题提供多个资产大小。
    3. 更新“MixedRealityModel 路径”,使之指向 3D 应用启动器的 GLB
    4. 使用与可执行文件相同的名称(扩展名为“.VisualElementsManifest.xml”)保存文件,将其保存在同一目录中。 例如,对于可执行文件“contoso.exe”,相应的 XML 文件名为“contoso.visualelementsmanifest.xml”。
  3. 向桌面 Windows 开始菜单中添加你的应用程序的快捷方式。 有关 C++ 实现方法的示例,请参阅下面的示例
    • 在 %ALLUSERSPROFILE%\Microsoft\Windows\Start Menu\Programs (machine) 或 %APPDATA%\Microsoft\Windows\Start Menu\Programs (user) 中创建
    • 如果更新更改了你的视觉元素清单或其引用的资产,那么更新程序或安装程序应更新快捷方式,以便重新解析清单并更新缓存的资产。
  4. 可选:如果桌面快捷方式没有直接指向应用程序的 EXE(例如,如果是调用类似“myapp://”的自定义协议处理程序),则开始菜单将不会自动找到应用的 VisualElementsManifest.xml 文件。 若要解决此问题,快捷方式应使用 System.AppUserModel.VisualElementsManifestHintPath () 指定视觉元素清单的文件路径。 这可以使用与 System.AppUserModel.ID 相同的技术在快捷方式中设置。 不强制要求你使用 System.AppUserModel.ID,但如果你希望快捷方式与应用程序的显式应用程序用户模型 ID(如果已使用)匹配,那么你可以使用它。 有关 C++ 示例,请参阅下面的示例应用启动器快捷方式创建部分。

示例视觉元素清单

<Application xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance">
  <VisualElements
    ShowNameOnSquare150x150Logo="on"
    Square150x150Logo="YOUR_APP_LOGO_150X150.png"
    Square70x70Logo=" YOUR_APP_LOGO_70X70.png"
    ForegroundText="light"
    BackgroundColor="#000000">
    <MixedRealityModel Path="YOUR_3D_APP_LAUNCHER_ASSET.glb">
        <SpatialBoundingBox Center="0,0,0" Extents="Auto" />
    </MixedRealityModel>
  </VisualElements>
</Application>

示例应用启动器快捷方式创建

下面的示例代码演示了如何在 C++ 中创建快捷方式,包括覆盖视觉元素清单 XML 文件的路径。 请注意,仅当你的快捷方式没有直接指向与清单关联的 EXE 的情况下,才需要进行覆盖(例如,你的快捷方式使用自定义协议处理程序,如“myapp://”)。

示例 .LNK 快捷方式创建 (C++)

#include <windows.h>
#include <propkey.h>
#include <shlobj_core.h>
#include <shlwapi.h>
#include <propvarutil.h>
#include <wrl.h>

#include <memory>

using namespace Microsoft::WRL;

#define RETURN_IF_FAILED(x) do { HRESULT hr = x; if (FAILED(hr)) { return hr; } } while(0)
#define RETURN_IF_WIN32_BOOL_FALSE(x) do { DWORD res = x; if (res == 0) { return HRESULT_FROM_WIN32(GetLastError()); } } while(0)

int wmain()
{
    RETURN_IF_FAILED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED));

    ComPtr<IShellLink> shellLink;
    RETURN_IF_FAILED(CoCreateInstance(__uuidof(ShellLink), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink)));
    RETURN_IF_FAILED(shellLink->SetPath(L"MyLauncher://launch/app-identifier"));

    // It is also possible to use an icon file in another location. For example, "C:\Program Files (x86)\MyLauncher\assets\app-identifier.ico".
    RETURN_IF_FAILED(shellLink->SetIconLocation(L"C:\\Program Files (x86)\\MyLauncher\\apps\\app-identifier\\game.exe", 0 /*iIcon*/));

    ComPtr<IPropertyStore> propStore;
    RETURN_IF_FAILED(shellLink.As(&propStore));

    {
        // Optional: If the application has an explict Application User Model ID, then you should usually specify it in the shortcut.
        PROPVARIANT propVar;
        RETURN_IF_FAILED(InitPropVariantFromString(L"ExplicitAppUserModelID", &propVar));
        RETURN_IF_FAILED(propStore->SetValue(PKEY_AppUserModel_ID, propVar));
        PropVariantClear(&propVar);
    }

    {
        // A hint path to the manifest is only necessary if the target path of the shortcut is not a file path to the executable.
        // By convention the manifest is named <executable name>.VisualElementsManifest.xml and is in the same folder as the executable
        // (and resources.pri if applicable). Assets referenced by the manifest are relative to the folder containing the manifest.

        //
        // PropKey.h
        //
        //  Name:     System.AppUserModel.VisualElementsManifestHintPath -- PKEY_AppUserModel_VisualElementsManifestHintPath
        //  Type:     String -- VT_LPWSTR  (For variants: VT_BSTR)
        //  FormatID: {9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}, 31
        //  
        //  Suggests where to look for the VisualElementsManifest for a Win32 app
        //
        // DEFINE_PROPERTYKEY(PKEY_AppUserModel_VisualElementsManifestHintPath, 0x9F4C2855, 0x9F79, 0x4B39, 0xA8, 0xD0, 0xE1, 0xD4, 0x2D, 0xE1, 0xD5, 0xF3, 31);
        // #define INIT_PKEY_AppUserModel_VisualElementsManifestHintPath { { 0x9F4C2855, 0x9F79, 0x4B39, 0xA8, 0xD0, 0xE1, 0xD4, 0x2D, 0xE1, 0xD5, 0xF3 }, 31 }

        PROPVARIANT propVar;
        RETURN_IF_FAILED(InitPropVariantFromString(L"C:\\Program Files (x86)\\MyLauncher\\apps\\app-identifier\\game.visualelementsmanifest.xml", &propVar));
        RETURN_IF_FAILED(propStore->SetValue(PKEY_AppUserModel_VisualElementsManifestHintPath, propVar));
        PropVariantClear(&propVar);
    }

    constexpr PCWSTR shortcutPath = L"%APPDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\game.lnk";
    const DWORD requiredBufferLength = ExpandEnvironmentStrings(shortcutPath, nullptr, 0);
    RETURN_IF_WIN32_BOOL_FALSE(requiredBufferLength);

    const auto expandedShortcutPath = std::make_unique<wchar_t[]>(requiredBufferLength);
    RETURN_IF_WIN32_BOOL_FALSE(ExpandEnvironmentStrings(shortcutPath, expandedShortcutPath.get(), requiredBufferLength));

    ComPtr<IPersistFile> persistFile;
    RETURN_IF_FAILED(shellLink.As(&persistFile));
    RETURN_IF_FAILED(persistFile->Save(expandedShortcutPath.get(), FALSE));

    return 0;
}

示例 .URL 启动器快捷方式

[{9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}]
Prop31=C:\Program Files (x86)\MyLauncher\apps\app-identifier\game.visualelementsmanifest.xml
Prop5=ExplicitAppUserModelID

[{000214A0-0000-0000-C000-000000000046}]
Prop3=19,0

[InternetShortcut]
IDList=
URL=MyLauncher://launch/app-identifier
IconFile=C:\Program Files (x86)\MyLauncher\apps\app-identifier\game.exe
IconIndex=0

另请参阅