Unity多人游戏开发-Netcode for GameObject-官方文档中文翻译

文章目录

官方文档

首先亮出文档,可以直接去看官方文档。
官方文档
翻译水平有限,如有错误,欢迎在评论区指正。
部分教程链接如下:
教程

文章版本

本文章基于Netcode for GameObject 1.5.2、1.6.0进行翻译。
关于Netcode for GameObject:1.5.2;
发行说明:1.5.2;
API参考:1.5.2;
开始使用\将Unity UNet迁移到Netcode for GameObjects(此部分内容及其之前内容的版本):1.5.2;
开始使用\开始使用NGO(此部分内容及其之后内容的版本):1.6.0;

关于Netcode for GameObject

Netcode for GameObjects(NGO)是一个为Unity构建的高级网络库,用于抽象化网络逻辑。它使你能够将游戏对象和世界数据通过网络会话发送给多个玩家。通过使用NGO,你可以专注于构建游戏,而不必关注低级协议和网络框架。

要了解更多关于Netcode for GameObjects的功能和能力,请查看下面的内容:

开始入门

安装Unity Netcode
从UNet迁移到Netcode
升级到Unity Netcode Package

开始项目

开始使用Netcode

教程和示例

Boss Room(首领房间)
Bite Size Samples(小示例)
Dilmer’s Tutorials(Dilmer的教程)

核心概念

网络
组件
对象
消息系统
序列化
场景

调试

日志记录
故障排除
错误消息

术语和常见问题解答

高级术语
多人游戏架构
常见问题解答

别忘了查看发行说明API文档

开始之前

Netcode支持以下版本:

Netcode支持以下平台:

  • Windows、macOS和Linux
  • iOS和Android
  • 运行在Windows、Android和iOS操作系统上的XR平台
  • 大多数封闭平台,如游戏主机。关于特定封闭平台的更多信息,请联系我们。这些内容通常受到保密协议的保护。
    • 当在游戏主机(如PlayStation、Xbox或任天堂Switch)上运行时,你在测试和正式发布游戏前应了解特定的Netcode政策。请参考游戏主机的内部文档以获取更多信息。此类内容通常受到保密协议的保护。

最后更新日期:2023年8月2日

发行说明

Netcode for GameObjects

Netcode for GameObjects的更新日志

以下内容跟踪了下一个版本的Unity Netcode的功能、更新、错误修复和重构。因为Netcode for GameObjects是开源的,你可以在com.unity.netcode.gameobjects的GitHub存储库中访问完整的发行说明和变更日志。

发布 |日期 | 更新日志
1.5.2 2023-07-24 1.5.2
1.5.1 2023-06-09 1.5.1
1.4.0 2023-04-10 1.4.0
1.3.1 2023-03-27 1.3.1
1.2.0 2022-11-21 1.2.0
1.1.0 2022-10-18 1.1.0
1.0.2 2022-09-08 1.0.2
1.0.1 2022-08-23 1.0.1
1.0.0 2022-06-27 1.0.0

最后更新日期:2023年8月14日

Boss Room(首领房间)

Boss Room更新日志

以下内容跟踪了Boss Room示例项目的下一个版本的功能、更新、错误修复和重构。由于Boss Room是开源的,你可以在com.unity.multiplayer.samples.coop的GitHub存储库中访问完整的发行说明和变更日志。
发布 | 日期 | 更新日志
2.1.0 2023-04-27 2.1.0
2.0.4 2022-12-14 2.0.4
2.0.3 2022-12-09 2.0.3
全部内容在此,我就不一个一个复制了,太累了

最后更新日期:2023年6月13日

Bitesize

多人Bitesize示例

以下内容跟踪了多人Bitesize示例项目的下一个版本的功能、更新、错误修复和重构。由于多人Bitesize是开源的,你可以在com.unity.multiplayer.samples.bitesize的GitHub存储库中访问完整的发行说明和变更日志。
发布 | 日期 | 更新日志
1.1.0 2022-12-13 1.1.0

最后更新日期:2023年8月14日

API参考

API参考

开始使用

安装Netcode for GameObjects

你可以使用本指南来帮助你在Unity项目中设置Netcode for GameObjects (NGO)。

前提条件

在开始安装Netcode之前,请确保你具备以下条件:

  • 一个有效的Unity账户和许可证。
  • 支持的Unity版本。请查看Netcode的要求以获取具体的Unity版本详情。
  • 现有的Unity项目。如果你对Unity还不熟悉,可以参考Get started with NGO部分以获得指南。

兼容性

  • Unity编辑器版本2021.3或更高
  • Mono和IL2CPP脚本后端

支持的平台

  • Windows、MacOS和Linux
  • iOS和Android
  • Windows、Android和iOS上的XR平台
  • 大多数封闭平台,例如游戏主机。关于特定封闭平台的更多信息,请联系NGO开发团队
  • WebGL(需要NGO 1.2.0+和UTP 2.0.0+)

注意
当运行在游戏主机等封闭平台(PlayStation、Xbox、Nintendo Switch)上时,可能会存在特定的政策和注意事项。请参考你所用游戏主机的文档以获取更多信息。

使用包管理器安装NGO

  1. Unity编辑器中选择“Window”>“Package Manager”。
  2. 在包管理器中,点击“+”Add符号>“Add package by name…”。
  3. 将com.unity.netcode.gameobjects输入到包名称字段中,然后选择“Add”。

适用于Unity编辑器版本2020.3 LTS或更早版本

  1. 从Unity编辑器中选择“Window”>“Package Manager”。
  2. 在包管理器中,点击“+”Add符号>“Add package by git URL…”
  3. 在git URL字段中输入或粘贴https://github.com/Unity-Technologies/com.unity.netcode.gameobjects,然后选择“Add”。

下一步

在安装了Netcode for GameObjects (NGO)之后,请参阅以下内容以继续你的旅程:

最后更新时间:2023年8月14日

将 MLAPI 升级为 Netcode for GameObjects

使用本指南将 MLAPI 0.1.0 升级至 Netcode for GameObjects (Netcode) 1.0.0

升级至 Netcode for GameObjects
强烈建议尽快从 MLAPI 升级至 Netcode for GameObjects。MLAPI 不再得到维护,未来将不会再更新。MLAPI 被视为废弃的产品。

备份你的 MLAPI 项目

信息
请将此步骤视为必要步骤:升级到 Netcode 的包版本可能会导致你当前项目出现问题。新版本修改了文件和位置,与之前的 MLAPI 版本有很大的不同。

使用以下推荐的方法备份你的项目:

  • 创建你整个项目文件夹的副本。
  • 使用源代码控制软件,比如Git。

最佳实践
我们建议使用这两种方法来备份你的项目。这将为你提供一个复制的项目,并通过已提交的更改和历史记录进行跟踪。

在你的MLAPI项目上使用升级工具

手动从MLAPI安装的.dll版本升级到新的包版本会导致场景和预制体中所有的MLAPI组件引用中断。Netcode使用不同于.dll的GUID来引用组件。

为了帮助你进行到Netcode的升级过程,我们创建了一个升级工具。

开始升级,通过在Package Manager窗口中使用 “Add package from git URL…” 选项,将升级工具添加到你的项目中。使用以下URL:

https://github.com/Unity-Technologies/multiplayer-community-contributions.git?path=/com.unity.multiplayer.mlapi-patcher#release-0.1.0

安装更新程序包后,你可以继续进行升级过程。

安装Netcode包

按照安装指南安装Netcode。

安装完成后,你会在控制台中看到错误消息,这是预期的行为,因为你的项目现在同时包含了MLAPI和Netcode。这个问题将在本指南结束时得到解决。

安装Netcode包还会安装其他一些包,如Unity Transport、Unity Collections、Unity Burst等。

Burst包需要重新启动编辑器。因此在安装后重新启动Unity。Unity将在下次启动时要求你进入故障安全模式,这是正常行为,因为你的所有网络代码不再编译。

危险
暂时不要删除旧版的MLAPI。在接下来的步骤中,它仍然会被使用到。

更新脚本引用

通过在菜单栏中选择 Window > Netcode Patcher 打开 Netcode Patcher 窗口。该修补程序将会问你是否在使用 MLAPI 的安装版或源码版。

以前在项目中使用 MLAPI 有两种主要方式。你可以通过使用 MLAPI 安装程序下载发布版本的 MLAPI,或者手动将源文件复制到你的项目中。

提示
如果你不确定你正在使用哪种 MLAPI 方式,请检查你的项目中是否有 Assets/MLAPI/Lib/MLAPI.dll 文件。如果是这样的话,你正在使用安装版。

从安装版升级。

  1. 选择Installer。
  2. 选择Update Script References。

从源码版升级。

  1. 选择 Source。
  2. 窗口会提示你链接一个 MLAPI 源码目录。
  3. 获取包含 MLAPI 源码的项目目录,拖放到该字段中。
  4. 选择 Update Script References。

在完成补丁程序的“更新脚本引用(Update Script References)”过程后,你的预制体(Prefabs)和游戏对象(GameObject)上的网络代码组件应该已经被更新为它们的新名称。

Patcher 窗口中还有一个“Replace Type Names(替换类型名称)”按钮。这一步是可选的。它会自动将你的脚本中的旧类型名称重命名为 Netcode 中的 API 更改,节省了手动重命名的时间。它会对一些类型名称进行全局替换。如果你想对更改有更多的控制,你可以手动执行此过程。

移除旧的 MLAPI 版本

从你的项目中移除所有包含现有非包版本 MLAPI 的文件夹。通常意味着从项目中移除 Assets/MLAPI 和 Assets/Editor/MLAPI 文件夹。

将你的代码升级到新的 Netcode APIs

信息
代码升级是一个手动而且漫长的过程。如果在升级过程中遇到困难,请加入我们的 Discord,我们会为你提供支持。

Unity 多人游戏团队尽力保持了大部分 MLAPI 在 Netcode 中的完整性。然而,为了成功编译,仍然需要进行一些更改。

NetworkVariable 变化

NetworkVariable 类型现在只支持泛型,并且泛型中指定的类型必须是值类型。首先,将所有的 NetworVariable* 类型更改为泛型对应类型。例如,NetworkVariableInt 变为 NetworkVariable,NetworkVariableFloat 变为 NetworkVariable,依此类推。现在,一些类型(例如字符串)将不符合 NetworkVariable 的新类型要求。如果你的类型是字符串,你可以使用 FixedString32Bytes。需要注意的是,这种类型不允许你更改字符串的大小。对于只包含值类型的自定义结构体,你可以实现 INetworkSerializable 接口,这样就可以正常工作。最后,对于其他类型,你将需要创建自己的 NetworkVariable。为此,创建一个新的类,继承自 NetworkVariableBase,并实现所有抽象成员。如果你之前已经有了自定义的 NetworkVariable,现在的读取和写入函数会使用我们的 FastBuffer 从流中读取或写入。

场景管理的变化

场景管理有一些变化,统一了用户的使用方式。首先,它现在在 NetworkManager 单例下。因此,你可以直接通过以下方式访问它:

var sceneManager = NetworkManager.Singleton.SceneManager;

接下来,现在只有一个场景事件:OnSceneEvent。你可以订阅它,从SceneEvent类中获取场景事件的信息。在该类中,你将找到SceneEventType,它会告诉你来自场景管理器的事件类型的详细信息。最后,用于在场景之间切换的主要函数已更改以匹配Unity场景管理器。现在,你需要使用两个参数调用LoadScene函数:场景名称和LoadSceneMode,这是Unity中加载场景的标准方式。而不是使用SwitchScene函数。

NetworkBehavior 的变化

NetworkBehavior 有两个主要的变化。首先,NetworkStart 方法变成了 OnNetworkSpawn,我们引入了 OnNetworkDespawn 来保持对称。其次,现在需要重写 OnDestroy 方法,因为 NetworkBehavior 已经在使用它。

行为变化

我们尽量将行为变化降到最低,但其中两个变化可能会导致你的脚本出现错误。首先,当应用程序退出时,NetworkManager 现在会自行关闭连接。如果你自己实现了关闭连接的操作,你将会得到一个错误,提示你尝试了两次断开连接。其次,库现在会在 OnNetworkSpawn(之前称为 NetworkStart)方法返回后触发所有 NetworkVariable 的 OnValueChanged 事件。你需要相应地重构依赖于这个顺序的脚本。

升级 RPCs

Netcode 版本的 RPCs 调用方式发生了变化。请阅读我们关于 RPC 的新文档,并使用新系统替换你现有的 RPCs。

序列化

我们用新的 INetworkSerializable 接口替换了旧的 INetworkSerializable 接口。这个接口工作方式有些不同。详见 INetworkSerializable

页面还提供了关于嵌套序列化类型的信息。

SyncVars

在 Netcode 中,SyncVars 已经被移除。将你现有的 SyncVars 转换为 NetworkVariables

移除 Patcher Package

在升级项目完成后,你可以在 Unity 包管理器中移除 Netcode Patcher 包,因为它不再需要。

故障排除

错误:找不到类型或命名空间名为 ‘MLAPI’

如果你的项目使用了程序集定义(.asmdef)文件,在切换到包版本后,你的程序集定义文件需要引用 com.unity.multiplayer.mlapi.runtime。

错误:找不到类型或命名空间名为 ‘NetworkedBehaviour’

如果在控制台中得到这样的错误消息(或者与 NetworkedBehaviour 不同的其他 Netcode 类型),那很可能是因为你的代码使用了过时的 API。打开错误消息中指示的脚本,并将所有 API 更新为新的命名。

错误:SerializedObjectNotCreatableException:索引 0 处的对象为空

如果每次进入游玩模式或保存场景时出现这种情况,关闭 Unity 编辑器,然后重新打开,问题应该就解决了。

下一步

在迁移和更新至 Netcode 包后,我们建议考虑以下事项:

最后更新时间为2023年2月2日

将Unity UNet迁移到Netcode for GameObjects

使用这个逐步指南来将你的项目从Unity UNet迁移到Netcode for GameObjects(Netcode)。如果需要帮助,请在Unity Multiplayer Networking Discord上与我们联系。

UNet已弃用

UNet是一个完全被弃用的产品,你应该尽快升级到Netcode for GameObjects。

当前限制事项

请查看以下关于从先前版本的Unity UNet迁移到Netcode的限制事项:

  • 命名约束可能会引起问题。UNet的方法以CmdRpc为前缀,而Netcode要求后缀。这可能需要复杂的多行正则表达式来查找和替换,或手动更新。例如,CommandAttribute已更名为ServerRpcAttributeClientRPCAttribute已更名为ClientRpcAttribute
  • 在IDE中无法显示RPC后缀命名模式的错误。
  • 客户端和服务器在UNet中有不同的表示形式。UNet包含一些在Netcode中不存在的回调函数。
  • 需要将预制体添加到Netcode的预制体注册列表中。
  • Netcode目前不支持匹配功能。

备份你的项目

在进行迁移之前,建议你备份你的项目。例如:

  • 创建你整个项目文件夹的副本。
  • 使用类似Git的源代码版本控制软件。

最佳实践

建议同时使用这两种方法备份你的项目。这将为你提供一个复制的项目,并通过提交的更改和历史进行跟踪。

安装Netcode并重新启动Unity

请参考Netcode安装指南获取更多信息。

注意
如果你第一次安装Git,你需要重新启动系统。

RPC调用

调用RPC的方式与UNet相同。只需调用函数,它将发送一个RPC。

将NetworkIdentity替换为NetworkObject

在Netcode中,UNet的NetworkIdentity被称为NetworkObject,工作方式类似。

将UNet的NetworkTransform替换为Netcode的NetworkTransform

在Netcode中,UNet的NetworkTransform也被称为NetworkTransform,工作方式类似。

NetworkTransform与UNET的NetworkTransform在功能上不完全对等。它缺少诸如刚体的位置同步等功能。

将UNet的NetworkAnimator替换为Netcode的NetworkAnimator

在项目的各处,将UNet的NetworkAnimator替换为Netcode的NetworkAnimator组件。

更新NetworkBehaviour

在项目的所有地方将UNet的NetworkBehaviour替换为Netcode的NetworkBehaviour

UNet示例

public class MyUnetClass : NetworkBehaviour
{
    
    
    [SyncVar]
    public float MySyncFloat;
    public void Start()
    {
    
    
        if (isClient)
        {
    
    
            CmdExample(10f);
        }
        else if (isServer)
        {
    
    
            RpcExample(10f);
        }
    }
    [Command]
    public void CmdExample(float x)
    {
    
    
        Debug.Log(“Runs on server”);
    }
    [ClientRpc]
    public void RpcExample(float x)
    {
    
    
        Debug.Log(“Runs on clients”);
    }
}

Netcode for GameObjects示例项目

public class MyNetcodeExample : NetworkBehaviour
{
    
    
    public NetworkVariable<float> MyNetworkVariable = new NetworkVariable<float>();
    public override void OnNetworkSpawn()
    {
    
    
        ExampleClientRpc(10f);
        ExampleServerRpc(10f);
    }
    [ServerRpc]
    public void ExampleServerRpc(float x)
    {
    
    
        Debug.Log(“Runs on server”);
    }
    [ClientRpc]
    public void ExampleClientRpc(float x)
    {
    
    
        Debug.Log(“Runs on clients”);
    }
}

查看NetworkBehaviour获取更多信息。

将SyncVar替换

SyncVar替换为NetworkVariable,并在你的项目的所有地方使用。

为了实现与SyncVar钩子相等的功能,请将一个函数订阅到NetworkVariableOnValueChanged回调中。UNet钩子和Netcode的OnValueChanged回调之间的一个明显区别是,Netcode会给你旧值和新值,而UNet只给你旧值。在UNet中,你还需要手动分配SyncVar的值。

UNET示例

public class SpaceShip : NetworkBehaviour
{
    
    
    [SyncVar]
    public string PlayerName;


    [SyncVar(hook = "OnChangeHealth"))]
    public int Health = 42;

    void OnChangeHealth(int newHealth){
    
    
        Health = newHealth; //在 Netcode 中不再需要这一行代码。
        Debug.Log($"我的新health是 {
      
      newHealth}.");
    }
}

Netcode for GameObjects示例

// 不要忘记使用一个初始值初始化 NetworkVariable。
public NetworkVariable<string> PlayerName = new NetworkVariable<string>();

public NetworkVariable<int> Health = new NetworkVariable<int>(42);

// 这是如何更新 NetworkVariable 的值的方法,你也可以使用 .Value 来访问 NetworkVariable 的当前值。
void MyUpdate()
{
    
    
    Health.Value += 30;
}


void Awake()
{
    
    
  //  在 Awake 或 Start 中调用此方法来订阅 NetworkVariable 的更改。
    Health.OnValueChanged += OnChangeHealth;
}

void OnChangeHealth(int oldHealth, int newHealth){
    
    
    //现在不再需要手动赋值给变量,Netcode 会自动为你完成。
    Debug.Log($"我的新health是 {
      
      newHealth}. 之前我的health是 {
      
      oldHealth}");
}

在你的项目中替换所有使用SyncVar的后缀递增和递减操作。Netcode的NetworkVariable.Value公开了一个值类型,这就是为什么不支持后缀递增/递减的原因。

UNET示例


public int Health = 42;

public void Update(){
    
    
  Health++;
}


Netcode for GameObjects示例项目


public NetworkVariable<int> Health = new NetworkVariable<int>(42);

public void Update(){
    
    
  Health.Value = Health.Value + 1;
}

请参考NetworkVariable获取更多信息。

将SyncList替换为NetworkList

请在你的项目中的所有地方将SyncList替换为NetworkListNetworkList具有一个OnListChanged事件,它类似于UNet的回调

UNET 示例

public SyncListInt m_ints = new SyncListInt();

private void OnIntChanged(SyncListInt.Operation op, int index)
{
    
    
    Debug.Log("列表更改 " + op);
}


public override void OnStartClient()
{
    
    
    m_ints.Callback = OnIntChanged;
}

Netcode for GameObjects 示例

NetworkList<int> m_ints = new NetworkList<int>();

// 在 Awake 或 Start 中调用此方法来订阅 NetworkList 的更改。
void ListenChanges()
{
    
    
    m_ints.OnListChanged += OnIntChanged;
}

// NetworkListEvent 包含关于操作和更改的索引的信息。
void OnIntChanged(NetworkListEvent<int> changeEvent)
{
    
    

}

替换Command/ClientRPC

UNet的Command/ClientRPC在Netcode中被替换成了Server/ClientRpc,其工作方式类似。

UNET 示例

    [Command]
    public void CmdExample(float x)
    {
    
    
        Debug.Log(“Runs on server”);
    }
    [ClientRpc]
    public void RpcExample(float x)
    {
    
    
        Debug.Log(“Runs on clients”);
    }

Netcode for GameObjects 示例

    [ServerRPC]
    public void ExampleServerRpc(float x)
    {
    
    
        Debug.Log(“Runs on server”);
    }
    [ClientRPC]
    public void ExampleClientRpc(float x)
    {
    
    
        Debug.Log(“Runs on clients”);
    }

注意
在Netcode中,RPC函数的名称必须以ClientRpc/ServerRpc后缀结尾。

请查看消息系统以获取更多信息。

替换OnServerAddPlayer

在你的项目中,将每个地方的OnServerAddPlayer替换为ConnectionApproval

UNET 示例

using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.Networking.NetworkSystem;

class MyManager : NetworkManager
{
    
    
    public override void OnServerAddPlayer(NetworkConnection conn, short playerControllerId, NetworkReader extraMessageReader)
    {
    
    
        if (extraMessageReader != null)
        {
    
    
            var s = extraMessageReader.ReadMessage<StringMessage>();
            Debug.Log("我的名字是" + s.value);
        }
        OnServerAddPlayer(conn, playerControllerId, extraMessageReader);
    }
}

Netcode for GameObjects 示例

仅服务器示例:

using Unity.Netcode;

private void Setup() 
{
    
    
    NetworkManager.Singleton.ConnectionApprovalCallback += ApprovalCheck;
    NetworkManager.Singleton.StartHost();
}

private void ApprovalCheck(byte[] connectionData, ulong clientId, NetworkManager.ConnectionApprovedDelegate callback)
{
    
    
    // 在这里编写你的逻辑
    bool approve = true;
    bool createPlayerObject = true;

    // 预制体哈希。使用null以使用默认玩家预制体
    // 如果使用这个哈希,请将"MyPrefabHashGenerator"替换为添加到场景中NetworkManager对象的NetworkPrefabs字段的预制体的名称
    ulong? prefabHash = NetworkpawnManager.GetPrefabHashFromGenerator("MyPrefabHashGenerator");
    
    //如果approve为true,则添加连接。如果为false,则断开客户端连接
    callback(createPlayerObject, prefabHash, approve, positionToSpawnAt, rotationToSpawnWith);
}

请查看连接批准以获得更多信息。

将 NetworkServer.Spawn 替换为 NetworkObject.Spawn

将项目中的所有NetworkServer.Spawn替换为NetworkObject.Spawn。

UNET 示例


using UnityEngine;
using UnityEngine.Networking;

public class Example : NetworkBehaviour
{
    
    
    //在Inspector面板中分配预制体
    public GameObject m_MyGameObject;
    GameObject m_MyInstantiated;

    void Start()
    {
    
    
        //实例化预制体
        m_MyInstantiated = Instantiate(m_MyGameObject);
        //生成在Inspector面板中分配的游戏对象
        NetworkServer.Spawn(m_MyInstantiated);
    }
}

Netcode for GameObjects 示例

GameObject go = Instantiate(myPrefab, Vector3.zero, Quaternion.identity);
go.GetComponent<NetworkObject>().Spawn();

请查看对象生成以获取更多信息。

自定义生成处理程序

Netcode具有自定义生成处理程序,用于替代UNet的自定义生成函数。请查看对象池以获取更多信息。

替换 NetworkContextProperties

Netcode拥有 IsLocalPlayerIsClientIsServerIsHost 来替代 UNet 的 isLocalPlayerisClientisServer。在 Netcode 中,每个对象都可以由特定的对等方拥有。可以使用 IsOwner 来检查这一点,它类似于 UNet 的 hasAuthority

网络接近性检查器 / 使用 Netcode 可见性的 OnCheckObserver

Netcode 中没有 NetworkPromimityChecker UNet 组件的直接等效组件。对于客户端的网络可见性与 UNet 类似。Netcode 没有 UNet 中 ObjectHide 消息的等效物。在 Netcode 中,主机上的网络对象始终可见。Netcode 没有 UNet 中 OnSetLocalVisibility 函数的等效函数。通过使用 NetworkObject.CheckObjectVisibility,可以将使用 OnCheckObserver 的手动网络接近性实现移植到 Netcode 中。对于 Netcode 的可见性系统,不需要 OnRebuildObservers

UNET 示例

public override bool OnCheckObserver(NetworkConnection conn)
{
    
    
 return IsvisibleToPlayer(getComponent<NetworkIdentity>(), coon);
}

public bool IsVisibleToPlayer(NetworkIdentity identity, NetworkConnection conn){
    
    
    // 任何接近性函数。
    return true;
}

Netcode for GameObjects 示例

public void Start(){
    
    
    NetworkObject.CheckObjectVisibility = ((clientId) => {
    
    
        return IsVisibleToPlayer(NetworkObject, NetworkManager.Singleton.ConnectedClients[clientId]);
    });
}

public bool IsVisibleToPlayer(NetworkObject networkObject, NetworkClient client){
    
    
    // 任何接近性函数。
    return true;
}

请参阅 Object Visibility 以了解更多关于 Netcode 网络可见性检查的信息。

更新场景管理

在 Netcode 中,场景管理不像 UNet 那样通过 NetworkManager 完成。NetworkSceneManager 提供了相同的功能,用于切换场景。

UNET 示例

public void ChangeScene()
{
    
    
    MyNetworkManager.ServerChangeScene("MyNewScene");
}

Netcode for GameObjects 示例

public void ChangeScene()
{
    
    
    NetworkSceneManager.LoadScene("MyNewScene", LoadSceneMode.Single);
}

更新 ClientAttribute/ClientCallbackAttribute 和 ServerAttribute/ServerCallbackAttribute

目前,Netcode 没有提供使用属性标记函数只在服务器或客户端上运行的替代方案。你可以手动在函数中返回以实现这个功能。

UNET 示例

[Client]
public void MyClientOnlyFunction()
{
    
    
    Debug.Log("我是客户端!");
}

Netcode for GameObjects 示例

public void MyClientOnlyFunction()
{
    
    
    if (!IsClient) {
    
     return; }

    Debug.Log("我是客户端!");
}

用 RPC 事件替代 SyncEvent

Netcode 没有为 SyncEvent 提供等效的功能。要将 SyncEvent 从 UNet 移植到 Netcode,可以发送一个 RPC 来在另一端触发事件。

UNET 示例

public class DamageClass : NetworkBehaviour
{
    
    
    public delegate void TakeDamageDelegate(int amount);

    [SyncEvent]
    public event TakeDamageDelegate EventTakeDamage;

    [Command]
    public void CmdTakeDamage(int val)
    {
    
    
        EventTakeDamage(val);
    }
}

Netcode for GameObjects 示例

public class DamageClass : NetworkBehaviour
{
    
    
    public delegate void TakeDamageDelegate(int amount);

    public event TakeDamageDelegate EventTakeDamage;

    [ServerRpc]
    public void TakeDamageServerRpc(int val)
    {
    
    
        EventTakeDamage(val);
        OnTakeDamageClientRpc(val);
    }

    [ClientRpc]
    public void OnTakeDamageClientRpc(int val){
    
    
        EventTakeDamage(val);
    }
}

网络发现

Netcode 并没有提供网络发现功能。贡献库提供了一个用于网络发现的示例实现。

下一步

在迁移并更新到 Netcode 包之后,我们建议你参考以下内容:

最后更新于2023年2月1日

开始使用NGO

使用本指南学习如何创建你的第一个NGO项目。它将引导你创建一个简单的Hello World项目,实现Netcode for GameObjects(NGO)的基本功能。

参考命令行测试助手(Testing the command line helper),了解如何使用命令行助手测试你的程序。

先决条件

在开始之前,请确保具备以下先决条件:

  • 一个有效授权的活跃Unity账户。
  • Unity Hub
  • 一个支持NGO的Unity编辑器版本。请参考NGO要求

在继续之前,请使用Unity编辑器版本2021.3或更高版本创建一个新项目。

提示
如果你还没有Assets / Scripts/文件夹,请现在创建一个:

  1. Projects选项卡中右键单击Assets文件夹,然后选择Create > Folder
  2. 将新文件夹命名为Scripts

这里是你将保存所有脚本的地方。

安装Netcode for GameObjects

请参考安装Netcode for GameObjects

添加基本组件

本部分指导你添加网络游戏的基本组件:

创建NetworkManager组件

本部分指导你创建一个NetworkManager组件。

首先,创建NetworkManager组件:

  1. Hierarchy选项卡中右键单击,然后选择Create Empty来创建一个空的GameObject。
    请添加图片描述
  2. 将空的 GameObject 重命名为 NetworkManager。

请添加图片描述
3. 选择 NetworkManager,然后在 Inspector 选项卡中选择Add Component

请添加图片描述
4. 从组件列表中选择 Netcode > NetworkManager
请添加图片描述
5. 在 Inspector 选项卡中,找到 Unity Transport 部分,然后选择 UnityTransport 作为协议类型(Protocol type)。

请添加图片描述
请添加图片描述
6. 按下 Ctrl/Cmd + S 保存场景(或选择File > Save)。

为每个连接的玩家创建一个要生成的角色物体

提示信息
当你将预制体放入 PlayerPrefab槽时,你是在告诉库当客户端连接到游戏时,它会自动将该预制体生成为连接客户端的角色。如果你没有设置任何预制体作为 PlayerPrefab,NGO将不会生成玩家对象。请参考玩家对象

本节指导你创建一个为每个连接的玩家生成的对象。

  1. 在 Unity 编辑器中,在Hierarchy(层级)选项卡中右键单击,然后选择3D Object(3D 对象) > Capsule (胶囊)。
  2. 将胶囊对象命名为 Player。
  3. 选中 Player,通过在 Inspector 选项卡中选择Add Component(添加组件) > Netcode > NetworkObject 添加一个 NetworkObject 组件。
  4. Project(项目)选项卡下的Assets(资产)文件夹中右键单击,然后选择Create(创建) > Folder(文件夹)。
  5. 将文件夹命名为 Prefabs
  6. 通过将其从Hierarchy选项卡拖动到 Prefabs 文件夹中,将之前创建的 Player 对象变成一个预制体。

请添加图片描述

  1. 通过在scene选项卡中选择玩家胶囊体,然后按下Delete键(或 macOS 上的 Cmd + Delete键)来从场景中删除玩家。

提示
你可以从场景中删除玩家游戏对象,因为你在 NetworkManager 组件的 Player prefab属性中分配了这个网络预制体。该库不支持将玩家对象定义为在场景中放置的 NetworkObject。

  1. 选择 NetworkManager
  2. Inspector 选项卡中,找到 PlayerPrefab 字段。
    请添加图片描述
  3. Project选项卡中将Player预制体拖动到你在 Inspector 选项卡中创建的 PlayerPrefab 槽中。
    请添加图片描述
  4. 通过在 Hierarchy 选项卡中右键单击,然后选择 3D Object > Plane,向场景中添加一个3D平面(位于0,0,0处)。

注意

添加平面可以提供一个可视化的参考点来显示玩家预制体的位置,但这并不是必需的。

请添加图片描述

  1. 按下 Ctrl/Cmd + S(选择File > Save)来保存场景。

将你的场景添加到构建(build)中

本节指导你将你的场景添加到构建中。

启用 NetworkManager场景管理设置允许服务器控制哪些场景加载给客户端。然而,你必须将当前场景添加到构建中以进入Play模式。

注意
默认情况下,NetworkManager 的场景管理选项已启用。

  1. 通过选择File > Build Settings打开构建设置窗口。
  2. 选择Add Open Scenes(添加打开的场景)。

Scenes/SampleScene 会列在构建的场景中。你可以关闭构建设置窗口。

添加 RPCs

本节指导你向项目中添加基本的RPCs。

提示
如果你还没有 Assets/Scripts/ 文件夹,现在创建一个:

  1. Projects选项卡中右键单击 Assets 文件夹,然后选择Create > Folder(创建 > 文件夹)。
  2. 将新文件夹命名为 Scripts
    这里将是你保存所有脚本的地方。

创建一个名为 RpcTest.cs 的脚本:

  1. Project选项卡中选择 Assets > Scripts
  2. Scripts 文件夹中右键单击,然后选择Create > C# Script
  3. 将脚本命名为 RpcTest

RpcTest.cs 脚本添加到玩家预制体中:

  1. Assets > Prefabs 中选择Player预制体。
  2. Inspector 选项卡中(选择了玩家预制体),选择Add Component
  3. 选择 Scripts > Rpc Test

编辑 RpcTest.cs 脚本:

  1. Project 选项卡中选择 Assets > Scripts > RpcTest
  2. Inspector 选项卡中(选择了脚本),选择Open。这会在默认的本地文本编辑器中打开此脚本。
  3. 编辑 RpcTest.cs 脚本,使其与以下内容匹配:
using Unity.Netcode;
using UnityEngine;

public class RpcTest : NetworkBehaviour
{
    
    
    public override void OnNetworkSpawn()
    {
    
    
        if (!IsServer && IsOwner) //只在拥有此 NetworkBehaviour 实例的 NetworkObject 的客户端向服务器发送 RPC
        {
    
    
            TestServerRpc(0, NetworkObjectId);
        }
    }

    [ClientRpc]
    void TestClientRpc(int value, ulong sourceNetworkObjectId)
    {
    
    
        Debug.Log($"客户端接收到 RPC #{
      
      value} on NetworkObject #{
      
      sourceNetworkObjectId}");
        if (IsOwner) //只在拥有此 NetworkBehaviour 实例的 NetworkObject 的客户端向服务器发送 RPC
        {
    
    
            TestServerRpc(value + 1, sourceNetworkObjectId);
        }
    }

    [ServerRpc]
    void TestServerRpc(int value, ulong sourceNetworkObjectId)
    {
    
    
        Debug.Log($"服务器接收到 RPC #{
      
      value} on NetworkObject #{
      
      sourceNetworkObjectId}");
        TestClientRpc(value, sourceNetworkObjectId);
    }
}
  1. 按下Ctrl/Cmd + S保存场景(或选择File > Save)。

测试RPCs

本节将指导你测试前一节中添加的RPCs。

  1. 选择File > Build And Run
  2. 关闭运行的游戏。
  3. 在终端中同时启动客户端和服务器,如命令行测试助手中所示。

提示
你可以使用多人游戏模式(Multiplayer Play Mode,MPPM)软件包,而不是使用命令行辅助脚本,它允许你运行多个Unity编辑器实例以测试多人游戏功能。请参阅多人游戏模式以了解更多信息。
注意:MPPM仅支持Unity编辑器版本2023.1及更高版本。

在客户端和服务器生成后,在客户端和服务器的控制台中会显示彼此发送RPC消息的日志。

客户端在其OnNetworkSpawn调用中首次发起交换,计数器值为0。然后它使用下一个值向服务器发起RPC调用。服务器接收到此调用并调用客户端。控制台分别显示服务器和客户端的以下内容。

Server Received the RPC #0 on NetworkObject #1
Server Received the RPC #1 on NetworkObject #1
Server Received the RPC #2 on NetworkObject #1
Server Received the RPC #3 on NetworkObject #1
...
Client Received the RPC #0 on NetworkObject #1
Client Received the RPC #1 on NetworkObject #1
Client Received the RPC #2 on NetworkObject #1
Client Received the RPC #3 on NetworkObject #1
...

只有拥有 RpcTest 脚本的 NetworkObject 的客户端才会在服务器上发送RPCs,但它们都会从服务器接收RPCs。这意味着如果你使用多个客户端进行测试,控制台将记录每个迭代中服务器和所有客户端接收到的每个 NetworkObject 的RPCs。如果使用主机和客户端进行测试,你将在主机的控制台上看到以下内容。这是因为作为服务器,它会接收到其他客户端的服务器RPCs,并且作为客户端,它也会接收到自己的客户端RPCs。

Server Received the RPC #0 on NetworkObject #2
Client Received the RPC #0 on NetworkObject #2
Server Received the RPC #1 on NetworkObject #2
Client Received the RPC #1 on NetworkObject #2
Server Received the RPC #2 on NetworkObject #2
Client Received the RPC #2 on NetworkObject #2
Server Received the RPC #3 on NetworkObject #2
Client Received the RPC #3 on NetworkObject #2
...

注意
这里的 NetworkObjectId2,因为主机也有一个具有 RpcTest 脚本的 NetworkObject 生成,但它不会发送启动链的初始RPC,因为它是服务器。

通过脚本扩展功能

本节将展示如何使用两个脚本:HelloWorldPlayer.csHelloWorldManager.cs扩展Hello World项目的功能。

HelloWorldManager.cs脚本

  1. Scripts文件夹中创建一个名为HelloWorldManager.cs的新脚本。
  2. 在场景中创建一个新的空对象HelloWorldManager,并将脚本附加为其组件。
  3. 将以下代码复制到HelloWorldManager.cs脚本中:
using Unity.Netcode;
using UnityEngine;

namespace HelloWorld
{
    
    
    public class HelloWorldManager : MonoBehaviour
    {
    
    
        void OnGUI()
        {
    
    
            GUILayout.BeginArea(new Rect(10, 10, 300, 300));
            if (!NetworkManager.Singleton.IsClient && !NetworkManager.Singleton.IsServer)
            {
    
    
                StartButtons();
            }
            else
            {
    
    
                StatusLabels();

                SubmitNewPosition();
            }

            GUILayout.EndArea();
        }

        static void StartButtons()
        {
    
    
            if (GUILayout.Button("Host")) NetworkManager.Singleton.StartHost();
            if (GUILayout.Button("Client")) NetworkManager.Singleton.StartClient();
            if (GUILayout.Button("Server")) NetworkManager.Singleton.StartServer();
        }

        static void StatusLabels()
        {
    
    
            var mode = NetworkManager.Singleton.IsHost ?
                "Host" : NetworkManager.Singleton.IsServer ? "Server" : "Client";

            GUILayout.Label("Transport: " +
                NetworkManager.Singleton.NetworkConfig.NetworkTransport.GetType().Name);
            GUILayout.Label("Mode: " + mode);
        }

        static void SubmitNewPosition()
        {
    
    
            if (GUILayout.Button(NetworkManager.Singleton.IsServer ? "Move" : "Request Position Change"))
            {
    
    
                if (NetworkManager.Singleton.IsServer && !NetworkManager.Singleton.IsClient )
                {
    
    
                    foreach (ulong uid in NetworkManager.Singleton.ConnectedClientsIds)
                        NetworkManager.Singleton.SpawnManager.GetPlayerNetworkObject(uid).GetComponent<HelloWorldPlayer>().Move();
                }
                else
                {
    
    
                    var playerObject = NetworkManager.Singleton.SpawnManager.GetLocalPlayerObject();
                    var player = playerObject.GetComponent<HelloWorldPlayer>();
                    player.Move();
                }
            }
        }
    }
}
  1. 继续阅读以理解示例代码的含义:

在之前的 Hello World 项目中,你通过添加预先创建的 NetworkManager 组件来创建了一个 NetworkManager。该组件允许你在 Play 模式下通过检查组件来启动 Host、Client 或 Server。HelloWorldManager.cs 脚本在进入 Play 模式时创建了一个在屏幕上显示的按钮菜单,这可以简化操作。

提示

  • Host 启动服务器并以客户端的形式加入。
  • Client 以客户端玩家的形式加入服务器。
  • Server 以服务器的形式启动游戏,不会实例化玩家角色。

HelloWorldManager.cs 脚本通过 StartButtons() 方法实现了这个菜单功能。在选择按钮后,StatusLabels() 方法会在屏幕上添加一个标签,显示选择的模式。当测试你的多人游戏时,这有助于区分不同的游戏视图窗口。

static void StartButtons()
 {
    
    
     if (GUILayout.Button("Host")) NetworkManager.Singleton.StartHost();
     if (GUILayout.Button("Client")) NetworkManager.Singleton.StartClient();
     if (GUILayout.Button("Server")) NetworkManager.Singleton.StartServer();
 }

 static void StatusLabels()
 {
    
    
     var mode = NetworkManager.Singleton.IsHost ?
         "Host" : NetworkManager.Singleton.IsServer ? "Server" : "Client";

     GUILayout.Label("Transport: " +
         NetworkManager.Singleton.NetworkConfig.NetworkTransport.GetType().Name);
     GUILayout.Label("Mode: " + mode);
 }

如前面的代码片段中所示,HelloWorldManager.cs 脚本还通过其单例使用 NetworkManager 的实例来获取属性,例如 IsClientIsServerIsLocalClientIsClientIsServer 属性指示了已建立的连接状态。

HelloWorldManager.cs 脚本引入了一个名为 SubmitNewPosition() 的新方法,HelloWorldPlayer 脚本使用该方法创建一个简单的 RPC 调用

:::

HelloWorldPlayer.cs 脚本

提示:
如果你还没有 Assets/Scripts/ 文件夹,请现在创建一个:

  1. Projects 选项卡中,右键单击 Assets 文件夹,然后选择 Create > Folder
  2. 将新文件夹命名为 Scripts

这是你将保存所有脚本的位置。

  1. Scripts 文件夹中创建一个名为 HelloWorldPlayer.cs 的新脚本。
  2. 将该脚本作为组件添加到你的 Player 预制体中。
  3. 将下面的代码复制到 HelloWorldPlayer.cs 脚本中:
using Unity.Netcode;
using UnityEngine;

namespace HelloWorld
{
    
    
    public class HelloWorldPlayer : NetworkBehaviour
    {
    
    
        public NetworkVariable<Vector3> Position = new NetworkVariable<Vector3>();

        public override void OnNetworkSpawn()
        {
    
    
            if (IsOwner)
            {
    
    
                Move();
            }
        }

        public void Move()
        {
    
    
            if (NetworkManager.Singleton.IsServer)
            {
    
    
                var randomPosition = GetRandomPositionOnPlane();
                transform.position = randomPosition;
                Position.Value = randomPosition;
            }
            else
            {
    
    
                SubmitPositionRequestServerRpc();
            }
        }

        [ServerRpc]
        void SubmitPositionRequestServerRpc(ServerRpcParams rpcParams = default)
        {
    
    
            Position.Value = GetRandomPositionOnPlane();
        }

        static Vector3 GetRandomPositionOnPlane()
        {
    
    
            return new Vector3(Random.Range(-3f, 3f), 1f, Random.Range(-3f, 3f));
        }

        void Update()
        {
    
    
            transform.position = Position.Value;
        }
    }
}
  1. 继续阅读以理解代码的含义:

HelloWorldPlayer.cs 脚本为 Hello World 项目的玩家角色添加了一些基本的移动功能。服务器玩家和客户端玩家都可以开始移动。然而,移动是通过服务器的 position NetworkVariable 进行的,这意味着服务器玩家可以立即移动,但客户端玩家必须向服务器请求移动,等待服务器更新 position NetworkVariable,然后在本地复制变化。

HelloWorldPlayer 类继承自 Unity.NetcodeNetworkBehaviour,而不是 MonoBehaviour。这允许你自定义网络代码,并在玩家生成时对发生的事件进行覆盖。

public class HelloWorldPlayer : NetworkBehaviour

对于多人游戏,每个对象都在至少两台机器上运行:玩家一和玩家二。因此,你需要确保两台机器具有相同的行为,并且对对象的信息是正确的。其中一个要考虑的情况是理解玩家如何移动。只有一个玩家能控制玩家对象的移动。以下代码通过验证正在运行代码的机器是否是玩家的所有者来强制执行这一点。

public override void OnNetworkSpawn()
 {
    
    
     if (IsOwner)
     {
    
    
         Move();
     }
 }

持续更新中……

猜你喜欢

转载自blog.csdn.net/weixin_44499065/article/details/132225657