多人游戏会话浏览

使用本主题,用户可以启用多人游戏会话浏览,并查询符合指定条件的打开多人游戏会话列表。

多人游戏会话浏览是 2016 年 11 月引入的。 在会话浏览方案中,游戏玩家可以检索可加入游戏会话的列表。 此列表中的每个会话条目都包含有关游戏的一些其他元数据,玩家可以使用这些元数据帮助他们选择要加入的会话。 玩家还可以根据元数据筛选会话列表。 玩家一旦看见吸引他们的游戏会话,就会加入其中。

玩家还可以创建新的游戏会话,并使用会话浏览招募其他玩家,而不是依赖于匹配。 多人游戏会话浏览与传统的匹配不同。 通过浏览多人游戏会话,玩家选择要加入的游戏会话。 使用匹配时,玩家选择一个“查找游戏”选项,该选项尝试将玩家自动放在合适的游戏会话中。

虽然多人游戏会话浏览是一个手动且速度较慢的过程,可能无法始终为玩家选择最佳游戏体验,但玩家具有更多的控制权,可以认为浏览功能是更好的选择。 游戏中既包括多人游戏会话浏览,又包括匹配方案,这是很常见的。 通常,匹配用于常玩的游戏模式,而会话浏览用于自定义游戏。

示例方案

John 想要打一款竞技类多人游戏,但他想要玩能随机选择其“主力”游戏。 他可以检索打开游戏会话列表,并通过多人会话浏览找到那些在描述中包含“随机主力”的会话。

如果游戏的 UI 具有选项,可选择“随机主力”游戏模式,并仅检索已标记的会话以指示他们是“随机主力”游戏。 当 John 找到一个自己喜欢的游戏会话时,他就可以加入该会话。 当有足够多的玩家加入会话时,该游戏会话的主机将会启动游戏。

角色

多人游戏会话浏览功能中的会话可能会指定正在为特定角色招募玩家。 例如,一个玩家可能想要创建一个游戏会话,指定该会话最多包含 5 个袭击类别,但必须至少包含 2 个治愈者角色和 1 个坦克人角色。

当其他玩家申请会话时,他们可以预先选择其角色。 然后,该服务会根据所选角色有多少空位来决定他们是否可以加入会话。 如果玩家想要保留 2 个空位供他们的好友加入,则游戏可以指定“好友”角色,而且只有会话主机上作为好友的玩家,才能填充专门为“好友”角色预留的 2 个位置。

有关角色的更多信息,请参阅多人游戏角色

多人游戏会话浏览的工作方式

会话浏览的工作原理是使用搜索句柄。 搜索句柄是数据包,其中包含对会话的引用,以及与会话相关的其他元数据,即搜索属性。

在创建符合多人游戏会话浏览条件的新游戏会话后,游戏将会为该会话创建搜索句柄。 搜索句柄存储在 多人游戏服务目录 (MPSD) 中,该目录用于维护游戏的搜索句柄。 游戏需要检索会话列表时,可以将搜索查询发送到 MPSD,以返回符合搜索条件的搜索句柄列表。 然后,游戏可以使用会话列表向玩家显示可加入的游戏列表。 当会话已满,或以其他方式不能加入时,游戏可从 MPSD 中删除搜索句柄,以便该会话将不再显示在多人游戏会话浏览查询中。

注意

向用户显示会话列表时,使用搜索句柄。 请勿对背景匹配项使用搜索柄。 对于背景匹配,建议使用 SmartMatch。 有关详细信息,请参阅使用多人游戏管理器通过 SmartMatch 找到一款多人游戏

为多人游戏会话浏览设置会话

若要使用会话的搜索句柄,会话必须将 searchableuserAuthorizationStyle 设置为 trueuserAuthorizationStyle 功能仅适用于 UWP 游戏,但我们建议将其应用于所有 Xbox 游戏,包括 Microsoft Game Development Kit (GDK) 游戏。 这样可确保将来可移植性访问游戏。

注意

配置为 userAuthorizationStyletrue将会将会话的 readRestrictionjoinRestriction 更改为 local 而不是 none。 这意味着游戏必须使用搜索句柄或转移句柄来加入游戏会话。

配置 Xbox 服务时,可在会话模板中设置这些功能。

注意

对于多人游戏会话浏览,只能在会话中创建要用于实际游戏玩法搜索句柄。 请勿将其用于大厅会话。

游戏会话所有权

许多游戏会话类型(例如 SmartMatch 或仅朋友的会话)不需要主机或所有者。 但是,设置多人游戏会话浏览游戏时,可能需要一个所有者。 拥有所有者管理的会话对主机是有益的,例如从会话中删除其他成员或更改其他成员的所有权状态。 若要使用会话所有者,会话必须将 hasOwners 设置为 true。 使用多人游戏角色时,可对其进行设置,这样只有所有者才能将角色分配给玩家。

注意

如果会话所有者已阻止 Xbox 成员,则该成员不能加入会话。

如果全部所有者都离开会话,则服务将根据为该会话定义的 ownershipPolicy.migration 策略对该会话执行相应的操作。 如果策略为“oldest”,则加入会话时间最长的玩家将被设置为新的所有者。 如果策略为“endsession”(若未提供,则为默认策略),则服务将结束会话,并从该会话中删除其余所有的玩家。

搜索句柄

搜索句柄以 JSON 结构的形式存储中 MSPD 中。 除了包含对会话的引用,搜索手柄还包含与搜索相关的其他元数据,称为搜索属性。 每次只能为会话创建一个搜索句柄。 要使用 Xbox 服务 API (XSAPI) 为会话创建搜索句柄,请调用 XblMultiplayerCreateSearchHandleAsync 方法。

搜索属性

搜索属性包含以下组成部分:

  • tags是字符串描述符,可用于对游戏会话进行分类,与标签类似。

    • 标记必须以字母开头,不能包含空格,且必须少于 100 个字符。
    • 标记示例:“ProRankOnly”、“norocketlaunchers”、“cityMaps”。
  • strings 是文本变量。

    • 字符串必须以字母开头,不能包含空格,且必须少于 100 个字符。
    • 字符串元数据示例:“Weapons"="knives+pistols+rifles”、“MapName"="UrbanCityAssault”、“description"="Fun casual game, new people welcome”。
  • numbers 是数值变量。 XSAPI 以浮点类型检索数字值。

    • 编号名称必须以字母开头,不能包含空格,且必须少于 100 个字符。
    • 编号元数据示例:MinLevel"= 25"MaxRank"= 10。

注意

标记和字符串值的字母大小写保留在服务中,但在查询标记时必须使用 tolower() 函数。 这表示标记和字符串值目前全作为小写字符处理,即使它们包含大写字符也是如此。

在 XSAPI 中,可通过使用 XblMultiplayerCreateSearchHandleAsync 方法设置标记和元数据等搜索属性。

其他详细信息

在检索搜索句柄时,结果还包含与会话相关的其他有用数据,例如,会话是否关闭,会话是否有任何加入限制。 在 XSAPI 中,这些详细信息,连同搜索属性,都包含在搜索查询后返回的 XblMultiplayerSearchHandle 中。

删除搜索句柄

如果您要从多人游戏会话浏览中删除会话,例如会话已满时,或者会话关闭时,则可以删除搜索句柄。 在 XSAPI 中,可使用 XblMultiplayerDeleteSearchHandleAsync 方法删除搜索句柄。

使用元数据创建搜索句柄

以下代码显示如何使用 Xbox 多人游戏 API 为会话创建搜索句柄。

平面 C

size_t tagsCount = 1;
XblMultiplayerSessionTag tags[1] = {};
tags[0] = XblMultiplayerSessionTag{ "SessionTag" };

size_t numberAttributesCount = 1;
XblMultiplayerSessionNumberAttribute numberAttributes[1] = {};
numberAttributes[0] = XblMultiplayerSessionNumberAttribute{ "numberattributename", 1.1 };

size_t strAttributesCount = 1;
XblMultiplayerSessionStringAttribute strAttributes[1] = {};
strAttributes[0] = XblMultiplayerSessionStringAttribute{ "stringattributename", "string attribute value" };

auto asyncBlock = std::make_unique<XAsyncBlock>();
asyncBlock->queue = queue;
asyncBlock->context = nullptr;
asyncBlock->callback = [](XAsyncBlock* asyncBlock)
{
    std::unique_ptr<XAsyncBlock> asyncBlockPtr{ asyncBlock }; // Take over ownership of the XAsyncBlock*
    XblMultiplayerSearchHandle searchHandle{ nullptr };
    HRESULT hr = XblMultiplayerCreateSearchHandleResult(asyncBlock, &searchHandle);

    if (SUCCEEDED(hr))
    {
        const char* handleId{ nullptr };
        XblMultiplayerSearchHandleGetId(searchHandle, &handleId);
    }
};

HRESULT hr = XblMultiplayerCreateSearchHandleAsync(
    xblContextHandle,
    &xblMultiplayerSessionReference,
    tags,
    tagsCount,
    numberAttributes,
    numberAttributesCount,
    strAttributes,
    strAttributesCount,
    asyncBlock.get()
);

if (SUCCEEDED(hr))
{
    // The call succeeded, so release the std::unique_ptr ownership of XAsyncBlock* since the callback will take over ownership.
    // If the call fails, the std::unique_ptr will keep ownership and delete the XAsyncBlock*
    asyncBlock.release();
}

有关代码示例中使用的函数详细信息,请参阅 multiplayer_c

为会话创建搜索查询

检索搜索句柄列表时,您可以使用搜索查询,将结果限制到符合特定条件的会话。

搜索查询语法是 OData 样式的语法,其中,只有以下运算符受到支持:

运算符 说明
eq 等于
ne 不等于
gt 大于
ge 大于或等于
lt 小于
le 小于或等于
and 逻辑 AND
or 逻辑 OR(请参见下方注释)

注意

您还可以使用 lambda 表达式和 tolower canonical 函数。 目前不支持任何其他 OData 函数。

搜索标记或字符串值时,必须在搜索查询 tolower 函数。 该服务仅支持搜索小写字符串。 Xbox 服务仅返回与搜索查询匹配的前 100 项结果。 如果结果范围太大,则您的游戏应允许玩家优化其搜索查询。

注意

筛选器字符串查询支持逻辑 OR 语句。 但是,只允许 OR 个查询,并且必须是查询的根目录。 您不能在查询中使用多个OR,也不能创建会导致 OR不在查询结构最顶层的查询。

搜索句柄查询示例

在 RESTful 调用中,“Filter”是您要指定 OData Filter 语言字符串的位置,该语言字符串将在您查询所有搜索句柄时返回。 在多人游戏 2015 API 中,可在 XblMultiplayerGetSearchHandlesAsync 方法的 searchFilter 参数中指定搜索筛选器字符串。

目前支持以下筛选器方案。

筛选条件 搜索筛选器字符串
单个成员 xuid“1234566” "session/memberXuids/any(d:d eq '1234566')"
单个所有者 xuid“1234566” "session/ownerXuids/any(d:d eq '1234566')"
字符串“forzacarclass”等于“classb” "tolower(strings/forzacarclass) eq 'classb'"
数字“forzaskill”等于 6 "numbers/forzaskill eq 6"
数字“halokdratio”大于 1.5 "numbers/halokdratio gt 1.5"
标记“coolpeopleonly” "tags/any(d:tolower(d) eq 'coolpeopleonly')"
不包含标记“cursingallowed”的会话 "tags/any(d:tolower(d) ne 'cursingallowed')"
不包含等于 0 的数字“rank”的会话 "numbers/rank ne 0"
不包含等于“classa”的字符串“forzacarclass”的会话 "tolower(strings/forzacarclass) ne 'classa'"
标记“coolpeopleonly”和数字“halokdratio”等于 7.5 "tags/any(d:tolower(d) eq 'coolpeopleonly') eq true and numbers/halokdratio eq 7.5"
数字“halodkratio”大于或等于 1.5,数字“rank”小于 60,数字“customnumbervalue”小于或等于 5 "numbers/halokdratio ge 1.5 and numbers/rank lt 60 and numbers/customnumbervalue le 5"
成就 ID“123456” "achievementIds/any(d:d eq '123456')"
语言代码“en” "language eq 'en'"
计划的时间,返回所有小于或等于指定时间的计划时间 "session/scheduledTime le '2009-06-15T13:45:30.0900000Z'"
发布的时间,返回所有小于指定时间的发布时间 "session/postedTime lt '2009-06-15T13:45:30.0900000Z'"
会话注册状态 "session/registrationState eq 'registered'"
如果会话成员数等于 5 "session/membersCount eq 5"
如果会话成员目标计数大于 1 "session/targetMembersCount gt 1"
如果最大会话成员计数小于 3 "session/maxMembersCount lt 3"
如果会话成员目标计数与会话成员数之间的差小于或等于 5 "session/targetMembersCountRemaining le 5"
如果最大会话成员计数与会话成员数之间的差大于 2 "session/maxMembersCountRemaining gt 2"
如果会话成员目标计数与会话成员数之间的差小于或等于 15。
如果角色未指定目标,则此查询根据最大会话成员计数与会话成员数之间的差进行筛选。
"session/needs le 15"
角色类型为“lfg”的角色“confirmed”(如果具有该角色的成员数等于 5) "session/roles/lfg/confirmed/count eq 5"
角色类型为“lfg”的角色“confirmed”(如果该角色的目标成员数大于 1)。
如果角色未指定目标,则改用最大角色数。
"session/roles/lfg/confirmed/target gt 1"
角色类型为“lfg”的角色“confirmed”(如果角色目标计数与具有该角色的成员数之间的差等于 15)。
如果角色未指定目标,则此查询根据最大角色计数与具有该角色的成员数之间的差进行筛选。
"session/roles/lfg/confirmed/needs le 15"
指向包含特定关键字的会话的所有搜索句柄 "session/keywords/any(d:tolower(d) eq 'level2')"
指向属于特定 SCID 的会话的所有搜索句柄 "session/scid eq '151512315'"
指向使用特定模板名称的会话的所有搜索句柄 "session/templateName eq 'mytemplate1'"
具有标记“elite”或数字“guns”大于 15 且字符串“clan”等于“purple”的所有搜索句柄 "tags/any(a:tolower(a) eq 'elite') or number/guns gt 15 and string/clan eq 'purple'"

刷新搜索结果

应避免自动刷新会话列表。 让玩家选择使用 UI 手动刷新列表。 如果玩家优化其搜索条件以更好地筛选结果,这会非常有用。 如果玩家尝试加入会话,但该会话已满或关闭,则您的游戏也应刷新搜索结果。

注意

搜索刷新过多可能会导致服务限制。 您的游戏应限制查询的刷新速率。

为了减少服务呼叫量,搜索句柄包括自定义会话属性,可用于存储和查询快速更改的会话属性。 此类属性不应存储在搜索属性中。

查询搜索句柄

以下代码演示如何查询搜索句柄。 API 返回 XblMultiplayerSearchHandle 对象的集合,这些对象表示与查询匹配的所有搜索句柄。

平面 C

auto asyncBlock = std::make_unique<XAsyncBlock>();
asyncBlock->queue = queue;
asyncBlock->context = nullptr;
asyncBlock->callback = [](XAsyncBlock* asyncBlock)
{
    std::unique_ptr<XAsyncBlock> asyncBlockPtr{ asyncBlock }; // Take over ownership of the XAsyncBlock*
    size_t resultCount{ 0 };
    auto hr = XblMultiplayerGetSearchHandlesResultCount(asyncBlock, &resultCount);
    if (SUCCEEDED(hr) && resultCount > 0)
    {
        auto handles = new XblMultiplayerSearchHandle[resultCount];

        hr = XblMultiplayerGetSearchHandlesResult(asyncBlock, handles, resultCount);

        if (SUCCEEDED(hr))
        {
            // Process handles
            for (auto i = 0u; i < resultCount; ++i)
            {
                const char* handleId{ nullptr };
                XblMultiplayerSearchHandleGetId(handles[i], &handleId);

                XblMultiplayerSearchHandleCloseHandle(handles[i]);
            }
        }
    }

};

const char* sessionName{ "MinGameSession" };
const char* orderByAttribute{ nullptr };
bool orderAscending{ false };
const char* searchFilter{ nullptr };
const char* socialGroup{ nullptr };

HRESULT hr = XblMultiplayerGetSearchHandlesAsync(
    xblContextHandle,
    scid,
    sessionName,
    orderByAttribute,
    orderAscending,
    searchFilter,
    socialGroup,
    asyncBlock.get()
);
if (SUCCEEDED(hr))
{
    // The call succeeded, so release the std::unique_ptr ownership of XAsyncBlock* since the callback will take over ownership.
    // If the call fails, the std::unique_ptr will keep ownership and delete the XAsyncBlock*
    asyncBlock.release();
}

有关代码示例中使用的函数详细信息,请参阅 multiplayer_c

使用搜索句柄加入会话

玩家检索到要加入的会话的搜索句柄后,请确保游戏使用 XblMultiplayerWriteSessionByHandleAsync 将自身添加到会话中。

注意

XblMultiplayerWriteSessionAsync 方法不能用于加入多人游戏会话浏览会话。