找回密码
 立即注册
查看: 245|回复: 0

[简易教程] Unity多人游戏学习(一)初探MLAPI

[复制链接]
发表于 2021-12-14 19:01 | 显示全部楼层 |阅读模式
前言

本人之前一直是客户端开发,最近几个月在给部门做多人游戏框架,现在正在学习Unity的MLAPI作为参考。
MLAPI是Unity的一个网络游戏框架,之前是个开源三方库,现在已经被官方钦定为主流方向。参考了一些文档,据说Unity之前官方方案叫做UNet现在已经弃用,UNet里分为负责底层消息封装的LLAPI和上层逻辑的HLAPI,现在的MLAPI沿用UNet的LLAPI部分,而上层HLAPI被彻底抛弃。
为什么参考MLAPI?本人对Unity和UE4都停留在略懂的阶段,发现UE4那套网络同步机制和引擎耦合比较重,理解起来难度较大;而MLAPI作为三方库,不侵入引擎源码,很好利用了Unity本身的GameObject Prefab机制,代码容易阅读和调试,和我现在工作内容比较切合。
这个专栏准备通过MLAPI学习多人游戏的制作原理,官方文档
Getting Started with MLAPI | Unity Multiplayer Networking (unity3d.com)
第一章记下HelloWorld demo如何运行以及其中一些基本概念。
安装MLAPI

window->package manager里add package from git url,输入https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi.git?path=/com.unity.multiplayer.mlapi#release/0.1.0
下载package,国内网络可能要开vpn
下载到工程的Library\PackageCache目录,想要在vs里调试仓库里的c#代码,需要在preferences的external tools里把git packages加到csproj


HelloWorld Demo

Your First Networked Game "Hello World" | Unity Multiplayer Networking (unity3d.com)
创建NetworkManager

NetworkManager是服务器/客户端的全局单例(补充一下,MLAPI的脚本可以在服务器/客户端双端运行,通过NetworkManager的成员变量IsServer/IsClient/IsHost区分是服务器/客户端还是两者皆是的host),按照Unity常用做法,创建一个全局空GameObject,起名NetworkManager,里面添加一个MLAPI的NetworkManager component。
NetworkManager里需要配置一些选项,必须选择一种transport,这里选择UNetTransport,应该就对应之前的LLAPI:


选择UNetTransport之后会自动添加一个UNetTransport component,如果之前没有的话,在里面可以设置连接选项,本例可以不用设置。
创建Player Prefab

需要网络同步的GameObject都是通过NetworkSpawnManager从Prefab实例化出来的,需要在NetworkManager的NetworkPrefabs里注册所有网络Prefab。
在场景里创建一个GameObject,本例可以创建一个Capsule,然后添加一个NetworkObject component


NetworkObject会根据Prefab名字(如这里是Player)生成PrefabHash,之后就是通过这个hash值找到Prefab并实例化的。将GameObject存成Prefab。每个NetworkObject拥有唯一的NetworkObjectId用于身份识别。
在前面NetworkManager的NetworkPrefabs列表里添加刚才的Player Prefab,并勾选DefaultPlayerPrefab,NetworkManager默认建立连接后会给每个连接生成Player prefab,勾选后生成的就是刚才制作的prefab了。
运行NetworkManager

NetworkManager有3种运行模式:

  • host也叫listen server,本机又是客户端又是服务器,星际魔兽那种局域网游戏房主
  • server也叫dedicated server,纯服务器,线上可以不跑渲染等客户端逻辑
  • client纯客户端
NetworkManager通过StartHost/Server/Client接口启动网络,在editor上有三个按钮,可以在editor里运行游戏并用这些按钮启动网络:


HelloWorld

这个demo中,客户端向服务器申请改变位置,服务器随机生成客户端位置后同步给客户端。
全局添加一个HelloWorldManager脚本用于在OnGUI添加操作按钮。
给Player prefab添加HelloWorldPlayer脚本用于生成和同步位置,HelloWorldPlayer继承自NetworkBehaviour。
RPC和NetworkVariable

前面NetworkObject能够标识每个网络实体,NetworkBehaviour能够实现实体间通信。通信有两种方式:RPC和NetworkVariable同步。
HelloWorldPlayer中有NetworkVariable Position用于同步位置,可以看到其权限配置是只有server能写,所有人都能读,服务器的NetworkBehaviour每个网络tick会自动将dirty的NetworkVariable通过网络消息发送给客户端,因此客户端每个Update只需要拿Position变量应用在transform上即可。
而改变服务器Position是通过客户端发送RPC实现的,下面SubmitPositionRequestServerRpc是个服务器RPC,客户端调用这个函数实际上是发送消息给服务器,在服务器执行修改Position。
public NetworkVariableVector3 Position = new NetworkVariableVector3(new NetworkVariableSettings
        {
            WritePermission = NetworkVariablePermission.ServerOnly,
            ReadPermission = NetworkVariablePermission.Everyone
        });

        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;
        }
RPC函数需要用[ServerRpc] [ClientRpc]修饰,并且函数名必须以ServerRpc或ClientRpc结尾。
运行HelloWorld

Unity editor不像UE能开多个GameInstance,需要Build出exe再执行。
下面开两个实例,左边是host,右边是client,胶囊体对应每个玩家,可以看到玩家位置双端保持同步。


其他笔记

如何区分服务器/客户端

NetworkManager有IsServer/IsClient/IsHost变量,在StartServer/Client/Host时决定,其中如果IsHost,则IsServer和IsClient同时满足。
NetworkBehaviour中也有上述变量(实际上是getter),通过NetworkManager单例获取:
protected bool IsClient => IsRunning && NetworkManager.Singleton.IsClient;如何区分是否本地控制

NetworkObject有OwnerClientId,通过和NetworkManager的LocalClientId比较,可以知道是否归自己控制。
NetworkObject和NetworkBehaviour都有IsOwner和IsLocalPlayer接口获取是否是自己控制以及是否是自己的Player。
public bool IsLocalPlayer => NetworkManager.Singleton != null && IsPlayerObject && OwnerClientId == NetworkManager.Singleton.LocalClientId;

public bool IsOwner => NetworkManager.Singleton != null && OwnerClientId == NetworkManager.Singleton.LocalClientId;
前面例子中,通过LocalClientId找到本地NetworkClient,然后获取PlayerObject找到NetworkObject及HelloWorldPlayer component,从而操作属于自己的player:
if (NetworkManager.Singleton.ConnectedClients.TryGetValue(NetworkManager.Singleton.LocalClientId,
                    out var networkedClient))
{
    var player = networkedClient.PlayerObject.GetComponent<HelloWorldPlayer>();
    if (player)
    {
        player.Move();
    }
}
应该也可以用其他做法,例如添加到全局变量等。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-5-9 08:06 , Processed in 0.146928 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表