赞
踩
本文均为作者摸索得出的经验,主要介绍制作联机游戏的逻辑代码,比如生成/销毁物体,逻辑同步等。以下内容仅仅是NGO的冰山一角,用于快速开发联机内容,我会在以后的文章里更新NGO类型的详细介绍
作者的信息源:
Client,Server是客户端和服务器,没什么好说的。Host就是将一个Client作为Server,而不额外创建Server。Host既是Client,也是Server,进行消息分发。
作为下文的 NetworkVariable和Rpc函数的参数
在我的学习范围内,只有非托管类型才能被网络传输,包括了数值与字符流(不是String,是指形如FixedString64Bytes的固定长度类型),也可以使用自定义类,但是它的字段必须全是unmanaged,并且实现INetworkSerializable接口。
有兴趣可以了解:
NGO官方文档介绍Custom Serialization | Unity Multiplayer Networking (unity3d.com)
C#官方介绍:Unmanaged types - C# reference - C# | Microsoft Learn
每个客户端场景中都会有本地场景物体与网络物体,以下是2个Player物体,每加入一个玩家,每个客户端都会生成一个Player物体。接下来介绍的网络组件属性字段用来标识不同客户端的网络物体
player脚本继承了NetworkBehaviour,所有需要网络同步的组件都要继承NetworkBehaviour
归属于本地的网络物体字段
不归属于本地的网络物体字段:有变化的部分已指出
当unity运行客户端,窗口运行主机时
任何的数值、枚举类型、字符流同步都推荐使用NetworkVariable
NGO文档对NetworkVariable的介绍
NetworkVariable | Unity Multiplayer Networking (unity3d.com)
简单来说NetworkVariable封装了基础数值类型,自动地将数值同步给所有客户端。任何时刻加入的客户端都会自动获得最新的数值,非常方便。
举例:后加入的客户端能够同步最新的数值
脚本展示
控制游戏运行时间
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- using Unity.Netcode;
- public class GameManager : NetworkBehaviour {
- public static GameManager Instance { private set; get; }
- //标识游戏运行时间
- public NetworkVariable<float> GameTimer = new NetworkVariable<float>(0f);
- public float gameTimerMax = 10f;
- private void Awake() {
- Instance = this;
- }
- //在网络对象生成时调用,可以在这里初始化NetworkVariable,也可以直接声明时初始化
- public override void OnNetworkSpawn() {
- if (IsServer)
- GameTimer.Value = gameTimerMax;
- }
- private void Update() {
- //游戏运行时间应该由服务器控制,并且所有NetworkVariable的默认权限为只能被服务器修改,可以被所有客户端读取
- if (!IsServer) {
- return;
- }
- if (GameTimer.Value > 0) {
- GameTimer.Value -= Time.deltaTime;
- }
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
时钟UI,及时展示
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- using UnityEngine.UI;
-
- public class Clock : MonoBehaviour
- {
- Image image;
- private void Awake() {
- image = GetComponent<Image>();
- }
- private void Start() {
- //NetworkVariable具有事件OnValueChanged,允许其他元素对此做出响应
- GameManager.Instance.GameTimer.OnValueChanged += GameTimer_OnValueChanged;
- }
-
- private void GameTimer_OnValueChanged(float previousValue, float newValue) {
- image.fillAmount = GameManager.Instance.GameTimer.Value / GameManager.Instance.gameTimerMax;
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
对于游戏行为的同步,NGO采用RPC(Remote Procedure Call)发送同步信息,除此之外还有Custom Messages,这里主要介绍RPC
个人理解:客户端和服务端使用同一套代码运行,但是执行同步的RPC函数的调用和运行被拆到了不同用户中。
官方文档(提供了运行逻辑图):ClientRpc | Unity Multiplayer Networking (unity3d.com)
分为客户端RPC和服务端RPC,声明RPC函数时要用ServerRpc/ClientRpc结尾,并且函数开头声明[ServerRpc]/[ClientRpc]
特点:在Server/Host中调用,在Client/Host中运行,客户端中不调用ClientRpc
测试案例:在GameManager末尾增加以下代码,添加了一个按钮,按下时UI文字会改变
- public void DebugCallBack() {
- DebugClientRpc();
- }
- [ClientRpc]
- private void DebugClientRpc() {
- debugText.text = "button clicked";
- }
效果展示:unity运行客户端,窗口运行主机
可以看到unity中按下按钮,文字不改变,但是窗口中按下按钮,二者一起改变,因为Host同样也作为客户端。这说明了ClientRpc在Server/Host中调用了,在所有客户端中运行了
特点:与ClientRpc相反,在Client/Host中调用,在Server/Host中运行,Server中不调用ServerRpc
另外,ServerRpc有一个重要参数:RequireOwnership
因为Client可能有多个,这个参数指明了哪些Client可以调用ServerRpc,含义为是否需要拥有者权限。当RequireOwnership=false时,所有客户端都可以调用,为true时,只有定义ServerRpc函数的NetworkObject的拥有者可以调用。
将GameManager末尾的代码替换成
- public void DebugCallBack() {
- DebugServerRpc();
- }
- [ServerRpc(RequireOwnership =true)]
- private void DebugServerRpc() {
- debugText.text = "button clicked";
- }
DebugServerRpc在GameManager中定义,GameManager属于Server/Host
测试: unity运行客户端,窗口运行主机
可以看到没有拥有者权限的客户端调用ServerRpc报错
将GameManager末尾的代码替换成
- public void DebugCallBack() {
- DebugServerRpc();
- }
- [ServerRpc(RequireOwnership = false)]
- private void DebugServerRpc() {
- debugText.text = "button clicked";
- }
测试: unity运行客户端,窗口运行主机
很显然,在Client中没有运行,而Host中运行了ServerRpc。同样地,Host是充当Sever的Client,Host能够调用ServerRpc,并且能够运行ServerRpc。此外,Host是GameManager的拥有者,RequireOwnership=true,仍然能够调用运行
通过ServerRpc调用ClientRpc,实现广播
- public void DebugCallBack() {
- DebugServerRpc();
- }
- //在任意客户端按下按钮后,服务器运行ServerRpc,服务器再调用所有客户端的ClientRpc,实现了广播效果
- [ServerRpc(RequireOwnership = false)]
- private void DebugServerRpc() {
- DebugClientRpc();
- }
-
- [ClientRpc]
- private void DebugClientRpc() {
- debugText.text = "button clicked";
- }
测试:unity运行主机,其他窗口运行客户端
可以看到任何一个Client按下按钮后,其他所有客户端都会同步
建议去看官方文档,很多配图ClientRpc | Unity Multiplayer Networking (unity3d.com)
这里只提一下,Host既是Client也是Server,如果Host是RPC的调用者也是执行者,那么该消息不再网络分发,而是直接本地执行。
RPC是可以传递参数的,只能是unmanaged类型,并且RPC函数的默认参数提供了调用者的信息,以此我们可以定向地为玩家执行逻辑。下一章将讲述如何用RPC动态创建物体
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。