前言
本文均为作者摸索得出的经验,主要介绍制作联机游戏的逻辑代码,比如生成/销毁物体,逻辑同步等。以下内容仅仅是NGO的冰山一角,用于快速开发联机内容,我会在以后的文章里更新NGO类型的详细介绍
作者的信息源:
- youtube code monkeyLearn Unity Multiplayer (FREE Complete Course, Netcode for Game Objects Unity Tutorial 2023) (youtube.com)
- bilibili:Unity多人游戏学习:从MLAPI到Netcode for GameObjects_哔哩哔哩_bilibili
- 官方文档:About Netcode for GameObjects | Unity Multiplayer Networking (unity3d.com)
基础知识
Host/Client/Server
Client,Server是客户端和服务器,没什么好说的。Host就是将一个Client作为Server,而不额外创建Server。Host既是Client,也是Server,进行消息分发。
unmanaged value(非托管类型)
作为下文的 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
任何的数值、枚举类型、字符流同步都推荐使用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;
}
}
}
时钟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;
}
}
逻辑同步
对于游戏行为的同步,NGO采用RPC(Remote Procedure Call)发送同步信息,除此之外还有Custom Messages,这里主要介绍RPC
个人理解:客户端和服务端使用同一套代码运行,但是执行同步的RPC函数的调用和运行被拆到了不同用户中。
官方文档(提供了运行逻辑图):ClientRpc | Unity Multiplayer Networking (unity3d.com)
Rpc
分为客户端RPC和服务端RPC,声明RPC函数时要用ServerRpc/ClientRpc结尾,并且函数开头声明[ServerRpc]/[ClientRpc]
Client Rpc
特点:在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中调用了,在所有客户端中运行了
Server Rpc
特点:与ClientRpc相反,在Client/Host中调用,在Server/Host中运行,Server中不调用ServerRpc
另外,ServerRpc有一个重要参数:RequireOwnership
因为Client可能有多个,这个参数指明了哪些Client可以调用ServerRpc,含义为是否需要拥有者权限。当RequireOwnership=false时,所有客户端都可以调用,为true时,只有定义ServerRpc函数的NetworkObject的拥有者可以调用。
举例:RequireOwnership=true
将GameManager末尾的代码替换成
public void DebugCallBack() {
DebugServerRpc();
}
[ServerRpc(RequireOwnership =true)]
private void DebugServerRpc() {
debugText.text = "button clicked";
}
DebugServerRpc在GameManager中定义,GameManager属于Server/Host
测试: unity运行客户端,窗口运行主机
可以看到没有拥有者权限的客户端调用ServerRpc报错
RequireOwnership=false
将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,仍然能够调用运行
利用Rpc广播消息
通过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按下按钮后,其他所有客户端都会同步
RPC的消息分发逻辑
建议去看官方文档,很多配图ClientRpc | Unity Multiplayer Networking (unity3d.com)
这里只提一下,Host既是Client也是Server,如果Host是RPC的调用者也是执行者,那么该消息不再网络分发,而是直接本地执行。
文章来源:https://www.toymoban.com/news/detail-761502.html
写在未来
RPC是可以传递参数的,只能是unmanaged类型,并且RPC函数的默认参数提供了调用者的信息,以此我们可以定向地为玩家执行逻辑。下一章将讲述如何用RPC动态创建物体文章来源地址https://www.toymoban.com/news/detail-761502.html
到了这里,关于unity netcode for gameobject(NGO)逻辑代码教程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!