当前位置:   article > 正文

unity netcode for gameobject(NGO)逻辑代码教程_unity ngo

unity ngo

前言

本文均为作者摸索得出的经验,主要介绍制作联机游戏的逻辑代码,比如生成/销毁物体,逻辑同步等。以下内容仅仅是NGO的冰山一角,用于快速开发联机内容,我会在以后的文章里更新NGO类型的详细介绍

作者的信息源:

  1. youtube code monkeyLearn Unity Multiplayer (FREE Complete Course, Netcode for Game Objects Unity Tutorial 2023) (youtube.com)
  2. bilibili:Unity多人游戏学习:从MLAPI到Netcode for GameObjects_哔哩哔哩_bilibili
  3. 官方文档: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封装了基础数值类型,自动地将数值同步给所有客户端。任何时刻加入的客户端都会自动获得最新的数值,非常方便。

实例

举例:后加入的客户端能够同步最新的数值

脚本展示

控制游戏运行时间

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using Unity.Netcode;
  5. public class GameManager : NetworkBehaviour {
  6. public static GameManager Instance { private set; get; }
  7. //标识游戏运行时间
  8. public NetworkVariable<float> GameTimer = new NetworkVariable<float>(0f);
  9. public float gameTimerMax = 10f;
  10. private void Awake() {
  11. Instance = this;
  12. }
  13. //在网络对象生成时调用,可以在这里初始化NetworkVariable,也可以直接声明时初始化
  14. public override void OnNetworkSpawn() {
  15. if (IsServer)
  16. GameTimer.Value = gameTimerMax;
  17. }
  18. private void Update() {
  19. //游戏运行时间应该由服务器控制,并且所有NetworkVariable的默认权限为只能被服务器修改,可以被所有客户端读取
  20. if (!IsServer) {
  21. return;
  22. }
  23. if (GameTimer.Value > 0) {
  24. GameTimer.Value -= Time.deltaTime;
  25. }
  26. }
  27. }

时钟UI,及时展示 

  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using UnityEngine;
  5. using UnityEngine.UI;
  6. public class Clock : MonoBehaviour
  7. {
  8. Image image;
  9. private void Awake() {
  10. image = GetComponent<Image>();
  11. }
  12. private void Start() {
  13. //NetworkVariable具有事件OnValueChanged,允许其他元素对此做出响应
  14. GameManager.Instance.GameTimer.OnValueChanged += GameTimer_OnValueChanged;
  15. }
  16. private void GameTimer_OnValueChanged(float previousValue, float newValue) {
  17. image.fillAmount = GameManager.Instance.GameTimer.Value / GameManager.Instance.gameTimerMax;
  18. }
  19. }

逻辑同步

对于游戏行为的同步,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文字会改变
 

  1. public void DebugCallBack() {
  2. DebugClientRpc();
  3. }
  4. [ClientRpc]
  5. private void DebugClientRpc() {
  6. debugText.text = "button clicked";
  7. }

效果展示: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末尾的代码替换成

  1. public void DebugCallBack() {
  2. DebugServerRpc();
  3. }
  4. [ServerRpc(RequireOwnership =true)]
  5. private void DebugServerRpc() {
  6. debugText.text = "button clicked";
  7. }

DebugServerRpc在GameManager中定义,GameManager属于Server/Host

测试: unity运行客户端,窗口运行主机

可以看到没有拥有者权限的客户端调用ServerRpc报错

RequireOwnership=false

将GameManager末尾的代码替换成

  1. public void DebugCallBack() {
  2. DebugServerRpc();
  3. }
  4. [ServerRpc(RequireOwnership = false)]
  5. private void DebugServerRpc() {
  6. debugText.text = "button clicked";
  7. }

测试: unity运行客户端,窗口运行主机

很显然,在Client中没有运行,而Host中运行了ServerRpc。同样地,Host是充当Sever的Client,Host能够调用ServerRpc,并且能够运行ServerRpc。此外,Host是GameManager的拥有者,RequireOwnership=true,仍然能够调用运行

利用Rpc广播消息

通过ServerRpc调用ClientRpc,实现广播

  1. public void DebugCallBack() {
  2. DebugServerRpc();
  3. }
  4. //在任意客户端按下按钮后,服务器运行ServerRpc,服务器再调用所有客户端的ClientRpc,实现了广播效果
  5. [ServerRpc(RequireOwnership = false)]
  6. private void DebugServerRpc() {
  7. DebugClientRpc();
  8. }
  9. [ClientRpc]
  10. private void DebugClientRpc() {
  11. debugText.text = "button clicked";
  12. }

测试:unity运行主机,其他窗口运行客户端

可以看到任何一个Client按下按钮后,其他所有客户端都会同步

RPC的消息分发逻辑

建议去看官方文档,很多配图ClientRpc | Unity Multiplayer Networking (unity3d.com)

这里只提一下,Host既是Client也是Server,如果Host是RPC的调用者也是执行者,那么该消息不再网络分发,而是直接本地执行。

写在未来

RPC是可以传递参数的,只能是unmanaged类型,并且RPC函数的默认参数提供了调用者的信息,以此我们可以定向地为玩家执行逻辑。下一章将讲述如何用RPC动态创建物体

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/88957
推荐阅读
相关标签
  

闽ICP备14008679号