适用于 Unity 的 Multiplay Hosting SDK

注意:本页上的内容适用于 Multiplay Hosting 和 Clanforge。

适用于 Unity 的 Multiplay Hosting SDK 具备全部所需功能,以供您在游戏中使用 Multiplay Hosting 缩放和游戏服务器服务。Unity.Services.Multiplay 是与 Multiplay 服务交互的主命名空间。

注意:继续操作前,请参阅 Multiplay Hosting 入门集成要求

要求和限制

适用于 Unity 的 Multiplay Hosting SDK 兼容 Unity 编辑器 2020.3 及更高版本。

警告:Unity 编辑器 2022 版本存在已知问题,会阻碍 SDK 正常工作。如果您使用 2022 版本,请务必使用 2022.2.3f1 或更高版本。

初始化 SDK

使用实例方法创建 IMultiplayService 单例。您可以使用该单例与 Multiplay Hosting API 交互。

async void Example_InitSDK()
{
	try
	{
		await UnityServices.InitializeAsync();
	}
	catch (Exception e)
	{
		Debug.Log(e);
	}
}

配置游戏服务器

ServerConfig 类表示游戏服务器配置,使您能够为当前游戏会话创建 ServerConfig 实例。

使用 ServerConfig 方法(属于 ServerConfig 类)为当前游戏会话创建 ServerConfig 实例。

参数类型描述
serverIdlong服务器 ID。
allocationId字符串分配 ID。
queryPortushort服务器查询协议端口编号。
portushort游戏会话的连接端口。这是游戏客户端可用以连接游戏会话的端口编号。
serverLogDirectory字符串游戏服务器保存日志文件的目录。

以下代码示例展示如何使用 ServerConfig 方法记录有关游戏服务器的信息,如服务器 ID、分配 ID、端口编号、查询端口编号和服务器日志目录。

using System.Collections;
using System.Collections.Generic;
using Unity.Services.Multiplay;
using UnityEngine;

public class Example_ServerConfiguration
{
	/// <summary>
	/// A simple example of accessing each of the server config's fields and printing them to the debug console.
	/// </summary>
	public static void LogServerConfig()
	{
		var serverConfig = MultiplayService.Instance.ServerConfig;
		Debug.Log($"Server ID[{serverConfig.ServerId}]");
		Debug.Log($"AllocationID[{serverConfig.AllocationId}]");
		Debug.Log($"Port[{serverConfig.Port}]");
		Debug.Log($"QueryPort[{serverConfig.QueryPort}");
		Debug.Log($"LogDirectory[{serverConfig.ServerLogDirectory}]");
	}
}

游戏服务器准备就绪

使用 ReadyServerForPlayersAsync 方法告知 Multiplay Hosting:游戏服务器已准备好接收玩家。请参阅游戏服务器就绪

using System.Threading.Tasks;
using Unity.Services.Multiplay;

/// <summary>
/// Ready the server. This is to indicate that the server is ready to accept players.
/// Readiness is the server's way of saying it's ready for players to join the server.
/// You must wait until you have been Allocated before you can call ReadyServerForPlayersAsync.
/// </summary>
private async void Example_ReadyingServer()
{
	// After the server is back to a blank slate and ready to accept new players
	await MultiplayService.Instance.ReadyServerForPlayersAsync();
}

游戏服务器未就绪

使用 UnreadyServerAsync 方法告知 Multiplay Hosting:游戏服务器不再准备好接收玩家。请参阅游戏服务器就绪

using System.Threading.Tasks;
using Unity.Services.Multiplay;

/// <summary>
/// Unready the server. This is to indicate that the server is in some condition which means it can't accept players.
/// For example, after a game has ended and you need to reset the server to prepare for a new match.
/// </summary>
private async void Example_UnreadyingServer()
{
	// The match has ended and players are disconnected from the server
	await MultiplayService.Instance.UnreadyServerAsync();
}

启动服务器查询处理程序

使用 StartServerQueryHandlerAsync 方法连接具备所提供参数的游戏服务器 SQP 实现。

参数类型描述
maxPlayersushort服务器上可以存在的最大玩家人数。
serverName字符串服务器的名称。
gameType字符串服务器所运行游戏类型的名称或标识符。
buildId字符串游戏的版本。
map字符串服务器正在运行的游戏地图或世界。
portushort供游戏客户端用来连接游戏服务器的端口编号。

Multiplay Hosting 使用 StartServerQueryHandlerAsyncparameters 初始化服务器查询处理程序。

初始化服务器查询处理程序后,您可以随时使用该调用提供的 IServerCheckManager 实例来更新这些值。您必须调用 UpdateServerCheck(),这些更改才会生效。游戏服务器变量发生更改时,请调用 UpdateServerCheck()。请参阅 SQP,以了解更多信息。

如果查询请求每 60 秒发送一次,那么您调用 UpdateServerCheck() 的频率应该足够高,以确保将最新的数据发送回 Multiplay Hosting。

实现这种操作的最佳途径之一就是在 Update 循环中进行调用。当然,您也可以在发生特定游戏事件(如玩家加入、玩家离开或游戏对战开始)时进行调用。

注意currentPlayers 字段始终从 0 开始。每当有玩家连接游戏服务器或断开连接时,您都必须更新该值。

以下示例展示如何启动服务器查询处理程序。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Services.Multiplay;

/// <summary>
/// An example of how to use SQP from the server using the Multiplay SDK.
/// The ServerQueryHandler reports the given information to the Multiplay Service.
/// </summary>
public class Example_ServerQueryHandler : MonoBehaviour
{
	private const ushort k_DefaultMaxPlayers = 10;
	private const string k_DefaultServerName = "MyServerExample";
	private const string k_DefaultGameType = "MyGameType";
	private const string k_DefaultBuildId = "MyBuildId";
	private const string k_DefaultMap = "MyMap";

	public ushort currentPlayers;

	private IServerQueryHandler m_ServerQueryHandler;

	private async void Start()
	{
		m_ServerQueryHandler = await MultiplayService.Instance.StartServerQueryHandlerAsync(k_DefaultMaxPlayers, k_DefaultServerName, k_DefaultGameType, k_DefaultBuildId, k_DefaultMap);
	}

	private void Update()
	{
		m_ServerQueryHandler.UpdateServerCheck();
	}

	public void ChangeQueryResponseValues(ushort maxPlayers, string serverName, string gameType, string buildId)
	{
		m_ServerQueryHandler.MaxPlayers = maxPlayers;
		m_ServerQueryHandler.ServerName = serverName;
		m_ServerQueryHandler.GameType = gameType;
		m_ServerQueryHandler.BuildId = buildId;
	}

	public void PlayerCountChanged(ushort newPlayerCount)
	{
		m_ServerQueryHandler.CurrentPlayers = newPlayerCount;
	}
}

订阅服务器事件

使用 SubscribeToServerEventsAsync 方法订阅针对游戏服务器的服务器事件回调。

以下示例展示如何订阅和处理游戏服务器事件回调,包括 OnAllocateOnDeallocateOnError

using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Services.Multiplay;
using UnityEngine;

/// <summary>
/// An example of how to access and react to multiplay server events.
/// </summary>
public class Example_ServerEvents : MonoBehaviour
{
	private MultiplayEventCallbacks m_MultiplayEventCallbacks;
	private IServerEvents m_ServerEvents;

	/// <summary>
	/// This should be done early in the server's lifecycle, as you'll want to receive events as soon as possible.
	/// </summary>
	private async void Start()
	{
		// We must first prepare our callbacks like so:
		m_MultiplayEventCallbacks = new MultiplayEventCallbacks();
		m_MultiplayEventCallbacks.Allocate += OnAllocate;
		m_MultiplayEventCallbacks.Deallocate += OnDeallocate;
		m_MultiplayEventCallbacks.Error += OnError;
		m_MultiplayEventCallbacks.SubscriptionStateChanged += OnSubscriptionStateChanged;

		// We must then subscribe.
		m_ServerEvents = await MultiplayService.Instance.SubscribeToServerEventsAsync(m_MultiplayEventCallbacks);
	}

// Handle Multiplay events.

}

处理 Multiplay Hosting 事件

MultiplayEventCallbacks 类提供回调以响应 Multiplay Hosting 事件,如分配、取消分配、错误和游戏服务器状态更改。

以下代码示例展示如何响应事件回调,如 OnAllocateOnDeallocateOnError

using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Services.Multiplay;
using UnityEngine;

/// <summary>
/// An example of how to access and react to multiplay server events.
/// </summary>
public class Example_ServerEvents : MonoBehaviour
{
	private MultiplayEventCallbacks m_MultiplayEventCallbacks;
	private IServerEvents m_ServerEvents;

	/// <summary>
	/// This should be done early in the server's lifecycle, as you'll want to receive events as soon as possible.
	/// </summary>
	private async void Start()
	{
		// We must first prepare our callbacks like so:
		m_MultiplayEventCallbacks = new MultiplayEventCallbacks();
		m_MultiplayEventCallbacks.Allocate += OnAllocate;
		m_MultiplayEventCallbacks.Deallocate += OnDeallocate;
		m_MultiplayEventCallbacks.Error += OnError;
		m_MultiplayEventCallbacks.SubscriptionStateChanged += OnSubscriptionStateChanged;

		// We must then subscribe.
		m_ServerEvents = await MultiplayService.Instance.SubscribeToServerEventsAsync(m_MultiplayEventCallbacks);
	}

	/// <summary>
	/// Handler for receiving the allocation multiplay server event.
	/// </summary>
	/// <param name="allocation">The allocation received from the event.</param>
	private void OnAllocate(MultiplayAllocation allocation)
	{
		// Here is where you handle the allocation.
		// This is highly dependent on your game, however this would typically be some sort of setup process.
		// Whereby, you spawn NPCs, setup the map, log to a file, or otherwise prepare for players.
		// After you the allocation has been handled, you can then call ReadyServerForPlayersAsync()!
	}

	/// <summary>
	/// Handler for receiving the deallocation multiplay server event.
	/// </summary>
	/// <param name="deallocation">The deallocation received from the event.</param>
	private void OnDeallocate(MultiplayDeallocation deallocation)
	{
		// Here is where you handle the deallocation.
		// This is highly dependent on your game, however this would typically be some sort of teardown process.
		// You might want to deactivate unnecessary NPCs, log to a file, or perform any other cleanup actions.
	}

	/// <summary>
	/// Handler for receiving the error multiplay server event.
	/// </summary>
	/// <param name="error">The error received from the event.</param>
	private void OnError(MultiplayError error)
	{
		// Here is where you handle the error.
		// This is highly dependent on your game. You can inspect the error by accessing the error.Reason and error.Detail fields.
		// You can change on the error.Reason field, log the error, or otherwise handle it as you need to.
	}

	/// <summary>
	///
	/// </summary>
	/// <param name="state"></param>
	private void OnSubscriptionStateChanged(MultiplayServerSubscriptionState state)
	{
		switch (state)
		{
			case MultiplayServerSubscriptionState.Unsubscribed: /* The Server Events subscription has been unsubscribed from. */ break;
			case MultiplayServerSubscriptionState.Synced: /* The Server Events subscription is up to date and active. */ break;
			case MultiplayServerSubscriptionState.Unsynced: /* The Server Events subscription has fallen out of sync, the subscription tries to automatically recover. */ break;
			case MultiplayServerSubscriptionState.Error: /* The Server Events subscription has fallen into an errored state and won't recover automatically. */ break;
			case MultiplayServerSubscriptionState.Subscribing: /* The Server Events subscription is trying to sync. */ break;
		}
	}
}

分配

public event Action<MultiplayAllocation> Allocate;

使用 Allocate 回调来响应 MultiplayAllocation 事件。

取消分配

public event Action<MultiplayDeallocation> Deallocate;

使用 Deallocate 回调来响应 MultiplayDeallocation 事件。

错误

public event Action<MultiplayError> Error;

使用 Error 回调来响应 MultiplayError 事件。

SubscriptionStateChange

public event Action<MultiplayServerSubscriptionState> SubscriptionStateChanged;

使用 SubscriptionStateChanged 回调来响应 MultiplayServerSubscriptionState 事件。

使用 Unity 编辑器部署资源

使用 Multiplay Authoring 模块(与 Multiplay 包一起安装),您可以选择直接在 Unity 编辑器中创作和修改资源。然后,您可以使用 Deployment 包将资源从 Unity 编辑器上传到 Dashboard(后台)。

Unity 编辑器中存在的 Multiplay 配置允许用户将其源代码控制视为单一可信来源(而非云端版本),从而简化回滚、分割和其他常见操作。

设置 Multiplay

在 Unity 编辑器中使用 Multiplay:

  1. 安装 SDK
  2. 将您的项目关联到 Unity 编辑器

安装所需的包

要在编辑器中创建 Multiplay 配置,请安装以下包:

  • Deployment
  • Multiplay:
    • 对于 Unity 6 及更高版本:多人游戏
    • 对于 Unity 2022 LTS 及更低版本:Multiplay

请参阅 Unity - 手册:Package Manager(包管理器)窗口,熟悉 Unity Package Manager(包管理器)界面。

安装这些包并将它们添加到可用包列表中:

  1. 在 Unity 编辑器中,选择 Window(窗口)> Package Manager(包管理器)
  2. 选择 + > Install package by name(按名称安装包)
  3. 输入 com.unity.services.deployment
  4. 选择 Install(安装)
  5. 对 Multiplay 重复以上步骤:
    • 对于 Unity 6 及更高版本:com.unity.services.multiplayer
    • 对于 Unity 2022 LTS 及更低版本:com.unity.services.multiplay

关联项目

Unity Gaming Services(Unity 游戏服务)项目与 Unity 编辑器关联。您可以在 Unity Dashboard(Unity 后台)中找到您的 UGS Project ID。

  1. 在 Unity 编辑器中,选择 Edit(编辑)> Project Settings(项目设置)> Services(服务)

  2. 关联您的项目。

  • 如果项目没有 Unity Project ID:
1.  选择 **Create a Unity Project ID(创建 Unity Project ID)**> **Organizations(组织)**,然后从下拉菜单中选择一个组织。
2.  选择 **Create project ID(创建 Project ID)**。
  • 如果已有 Unity Project ID:
1.  选择 **Use an existing Unity project ID(使用现有 Unity Project ID)**。
2.  从下拉菜单中选择组织和项目。
3.  选择 **Link project ID(关联 Project ID)**。

此时将显示您的 Unity Project ID,并且项目现在已关联到 Unity 服务。此外,还可以使用 UnityEditor.CloudProjectSettings.projectId 在 Unity 编辑器脚本中访问您的 Project ID。

在 Unity 编辑器中创作

借助 Multiplay Authoring 模块,您可以直接在 Unity 编辑器中创建、编辑和部署 Multiplay 配置。

创建配置

按照以下步骤使用 Multiplay Authoring 模块创建 Multiplay 配置:

  1. 在 Unity 编辑器的 Project(项目)窗口中右键单击,然后选择 Create(创建)> Services(服务)> Multiplay Config(Multiplay 配置)
  2. 为资源文件命名。
  3. Enter(输入)

现在,新配置会显示在 Project(项目)窗口和 Deployment(部署)窗口中;可通过选择 Services(服务)> **Deployment(部署)**来访问 Deployment(部署)窗口。

编辑配置

目前有一种方法可以编辑现有配置:

  • 在 Project(项目)选项卡中,双击现有资源,然后选择任何文本编辑器来编辑配置。

资源文件内容

在 Deployment(部署)窗口查找资源并根据其文件扩展名分配类型。

该配置使用 yaml 格式描述其内容,并采用 .gsh 文件扩展名。

new_multiplay_config.gsh 示例:

version: 1.0
builds:
  my build: # replace with the name for your build
    executableName: Build.x86_64 # the name of your build executable
    buildPath: Builds/Multiplay # the location of the build files
buildConfigurations:
  my build configuration: # replace with the name for your build configuration
    build: my build # replace with the name for your build
    queryType: sqp # sqp or a2s, delete if you do not have logs to query
    binaryPath: Build.x86_64 # the name of your build executable
    commandLine: -port $$port$$ -queryport $$query_port$$ -log $$log_dir$$/Engine.log # launch parameters for your server
    variables: {}
    cores: 1 # number of cores per server
    speedMhz: 750 # launch parameters for your server
    memoryMiB: 800 # launch parameters for your server
fleets:
  my fleet: # replace with the name for your fleet
    buildConfigurations:
      - my build configuration # replace with the names of your build configuration
    regions:
      North America: # North America, Europe, Asia, South America, Australia
        minAvailable: 0 # minimum number of servers running in the region
        maxServers: 1 # maximum number of servers running in the region

该配置在文件中描述了 3 个组成部分:

部署资源

您可以通过 Deployment(部署)窗口部署 Multiplay 配置。有关详细信息,请参阅 Deployment 包手册

一些配置依赖于其他资源,例如版本配置依赖于版本本身。

部署时,首先部署版本,然后部署版本配置,再然后部署机群。该链条中某一点发生故障将导致依赖于这一点的配置停止部署。

Deployment(部署)窗口

Deployment(部署)窗口是 Deployment 包的一项核心功能。此窗口旨在让所有服务使用一个统一的界面来满足部署需求,并可以将云资源上传到其各自的云服务。

有关详细信息,请参阅 Deployment 包手册