Unity Netcode for GameObjects(多人联机小Demo)

Unity多人游戏开发简易教程

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

Netcode for GameObjects 是 Unity 官方推出的一套用于开发多人网络游戏的框架,旨在简化基于 Unity 引擎的多人游戏开发流程。它允许开发者轻松地在多个客户端之间同步游戏对象(GameObject)的状态、处理网络通信以及管理玩家连接等核心功能,尤其适合中小型多人游戏项目。
链接: 官方地址
提示:官方unityHub 创建项目时 提供的模板 就有。超级好用。

一、安装 Netcode for GameObjects

打开 Unity 项目(建议使用 Unity 2021.3 或更高版本)。
进入 Package Manager(Window → Package Manager)。
点击左上角 “+” 图标 → Add package by name…
输入包名 com.unity.netcode.gameobjects 并安装(确保勾选 “Enable Preview Packages” 以获取最新版本)。

直接搜索下载也可以。
在这里插入图片描述

二、做个小Dome

doem的内容就是:一个客户端随机移动圆球位置,其他客户端会同步显示变化。

1.NetcodeManageNet

创建一个场景,在场景创建一个空物体挂载NetcodeManage组件,并选择UnityTransport,选择后会自动挂载UnityTransport脚本
在这里插入图片描述

2.创建UI

创建4个按钮:,一个text和InputField 。并挂载脚本,如下图
在这里插入图片描述在这里插入图片描述

3.创建预制体

创建一个圆球,挂载NetwrokObject和NetworkTransform
NetworkTransform 的pocition Rotation Scale全部勾选(多人同步:位置、旋转、大小)
在这里插入图片描述
NetworkManage 添加这个预制件,如下图
在这里插入图片描述

4.代码介绍

UI代码

using System.Net;
using System.Net.NetworkInformation;
using Unity.Netcode;
using Unity.Netcode.Samples;
using Unity.Netcode.Transports.UTP;
using UnityEngine;
using UnityEngine.UI;
namespace Twq
{
 public class UI_Init : MonoBehaviour
 {
        public Transform NetworkManager_;
        public Text text_IP;
        public Button HostBtn;

        public Button ServerBtn;

        public InputField inputField;

        public Button ClientBtn;

        public GameObject obj01;
        public GameObject obj02;

        public Button RandomBtn;

        private void Awake()
        {
            text_IP.text = GetIP();
            var networkManager = NetworkManager_.GetComponent<NetworkManager>();
            UnityTransport unityTransport = networkManager.GetComponent<UnityTransport>();
            //服务器地址填写:0.0.0.0   本地可连接,外网也可链接(放在云服务器上才行)
            //其他客户端连接时  输入 服务器IP  即可
            HostBtn.onClick.AddListener(() => {
                unityTransport.SetConnectionData("0.0.0.0", 7777);
                networkManager.StartHost();
                obj01.SetActive(false);
                obj02.SetActive(true);
            });
            ServerBtn.onClick.AddListener(() => {
                unityTransport.SetConnectionData("0.0.0.0", 7777);
                networkManager.StartServer();
                obj01.SetActive(false);
            });
            ClientBtn.onClick.AddListener(() => {
                if (inputField.text != null && inputField.text != "")
                {
                    unityTransport.SetConnectionData(inputField.text, 7777);
                    networkManager.StartClient();
                    obj01.SetActive(false);
                    obj02.SetActive(true);
                }
                else
                {
                    Debug.Log("请输入服务器的IP");
                }

            });

            RandomBtn.onClick.AddListener(() => {
                if (!networkManager.IsClient)
                {
                    Debug.Log("没有 连上");
                }
                if (networkManager.LocalClient != null)
                {
                    // Get `BootstrapPlayer` component from the player's `PlayerObject`
                    if (networkManager.LocalClient.PlayerObject.TryGetComponent(out BootstrapPlayer bootstrapPlayer))
                    {
                        // Invoke a `ServerRpc` from client-side to teleport player to a random position on the server-side
                        bootstrapPlayer.RandomTeleportServerRpc();
                    }
                }
            });
        }

        private string GetIP()
        {
            string ipv4 = "";
            foreach (NetworkInterface item in NetworkInterface.GetAllNetworkInterfaces())
            {
                NetworkInterfaceType _type1 = NetworkInterfaceType.Wireless80211;
                NetworkInterfaceType _type2 = NetworkInterfaceType.Ethernet;
                if (item.NetworkInterfaceType == _type1 || item.NetworkInterfaceType == _type2 && item.OperationalStatus == OperationalStatus.Up)
                {
                    foreach (UnicastIPAddressInformation ip in item.GetIPProperties().UnicastAddresses)
                    {
                        if (ip.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
                        {
                            ipv4 = ip.Address.ToString();
                        }
                    }
                }
            }
            return ipv4;
        }
    }
}
 
 
 
 

随机位置代码

using UnityEngine;

namespace Unity.Netcode.Samples
{
    /// <summary>
    /// Component attached to the "Player Prefab" on the `NetworkManager`.
    /// </summary>
    public class BootstrapPlayer : NetworkBehaviour
    {
        /// <summary>
        /// If this method is invoked on the client instance of this player, it will invoke a `ServerRpc` on the server-side.
        /// If this method is invoked on the server instance of this player, it will teleport player to a random position.
        /// </summary>
        /// <remarks>
        /// Since a `NetworkTransform` component is attached to this player, and the authority on that component is set to "Server",
        /// this transform's position modification can only be performed on the server, where it will then be replicated down to all clients through `NetworkTransform`.
        /// </remarks>
        [ServerRpc]
        public void RandomTeleportServerRpc()
        {
            var oldPosition = transform.position;
            transform.position = GetRandomPositionOnXYPlane();
            var newPosition = transform.position;
            print($"{nameof(RandomTeleportServerRpc)}() -> {nameof(OwnerClientId)}: {OwnerClientId} --- {nameof(oldPosition)}: {oldPosition} --- {nameof(newPosition)}: {newPosition}");
        }

        private static Vector3 GetRandomPositionOnXYPlane()
        {
            return new Vector3(Random.Range(-3f, 3f), Random.Range(-3f, 3f), 0f);
        }
    }
}

添加一个服务器和客户端互相通信

using System;
using Twq;
using Unity.Netcode;
using UnityEditor.PackageManager;
using UnityEngine;
using UnityEngine.UI;

public class ConnectionManager : NetworkBehaviour
{
    public GameObject Mask;
    [HideInInspector]
    public static ConnectionManager Singleton_;
    private void Start()
    {
        Singleton_ = this;
        DontDestroyOnLoad(this);
    }
    public override void OnNetworkSpawn()
    {
        // 注册连接回调(只在首次生成时注册一次)
        if (IsServer)
        {
            // 服务器端监听客户端连接
            NetworkManager.Singleton.OnClientConnectedCallback += OnClientConnected;
            // 服务器端监听客户端断开连接
            NetworkManager.Singleton.OnClientDisconnectCallback += OnClientDisconnected;
        }

        if (IsClient)
        {
            // 客户端监听自己是否成功连接到服务器
            NetworkManager.Singleton.OnClientConnectedCallback += OnLocalClientConnected;
            // 客户端监听与服务器的连接是否断开
            NetworkManager.Singleton.OnClientDisconnectCallback += OnLocalClientDisconnected;
        }
    }

    public override void OnNetworkDespawn()
    {
        // 取消注册回调,避免内存泄漏
        if (NetworkManager.Singleton != null)
        {
            NetworkManager.Singleton.OnClientConnectedCallback -= OnClientConnected;
            NetworkManager.Singleton.OnClientDisconnectCallback -= OnClientDisconnected;
            NetworkManager.Singleton.OnClientConnectedCallback -= OnLocalClientConnected;
            NetworkManager.Singleton.OnClientDisconnectCallback -= OnLocalClientDisconnected;
        }
    }

    // 服务器端:当有客户端连接时调用
    private void OnClientConnected(ulong clientId)
    {
        Debug.Log($"服务器: 客户端 {clientId} 已连接");

        // 可以在这里执行:
        // - 生成玩家对象
        // - 发送欢迎消息
        // - 同步初始数据给新连接的客户端
        SendToClient(clientId);
    }

    // 服务器端:当有客户端断开连接时调用
    private void OnClientDisconnected(ulong clientId)
    {
        Debug.Log($"服务器: 客户端 {clientId} 已断开连接");

        // 可以在这里执行:
        // - 清理玩家数据
        // - 通知其他玩家该玩家已离开
        // - 保存玩家进度
    }

    // 客户端:当本地客户端成功连接到服务器时调用
    private void OnLocalClientConnected(ulong clientId)
    {
        // 验证是否是本地客户端的连接事件
        if (clientId == NetworkManager.Singleton.LocalClientId)
        {
            Debug.Log("客户端: 已成功连接到服务器");

            Mask.SetActive(false);
            UIManager.In_.Mask.SetActive(true);
            // 可以在这里执行:
            // - 显示连接成功UI
            // - 请求初始数据
            // - 发送玩家信息到服务器
        }
    }

    // 客户端:当本地客户端与服务器断开连接时调用
    private void OnLocalClientDisconnected(ulong clientId)
    {
        // 验证是否是本地客户端的断开事件
        if (clientId == NetworkManager.Singleton.LocalClientId)
        {
            Debug.Log("客户端: 与服务器断开连接");

            // 可以在这里执行:
            // - 显示断开连接提示
            // - 返回到主菜单
            // - 清理本地玩家数据
        }
    }



    
    public void SendToClient(ulong clientId)
    {
        SendToSpecificClient( clientId, "------服务器:客户端你好呀!");
       
    }



    //服务器调用,在所有客户端上执行 
    [ClientRpc]
    public void ServerToAllClientsMessageClientRpc(string message, ClientRpcParams clientRpcParams = default)
    {
        Debug.Log($"客户端收到服务器的广播消息: {message}");
    }



    //服务器向特定客户端发送消息 
    public void SendToSpecificClient(ulong clientId, string message)
    {
        if (!IsServer) return; // 确保只有服务器可以调用

        var clientRpcParams = new ClientRpcParams
        {
            Send = new ClientRpcSendParams
            {
                TargetClientIds = new ulong[] { clientId }
            }
        };
        ServerToSpecificClientMessageClientRpc(message, clientRpcParams);
    }
    //客户端收到服务器的 私有消息
    [ClientRpc]
    private void ServerToSpecificClientMessageClientRpc(string message, ClientRpcParams clientRpcParams)
    {
        Debug.Log($"客户端收到服务器的私有消息: {message}");
    }



    // 客户端调用此方法,消息会发送到服务器 并在 服务器执行
    [ServerRpc(RequireOwnership = false)]//ServerRpc 是客户端向服务器发送请求的特殊方法。默认情况下,ServerRpc 具有 RequireOwnership = true 属性,这意味着:只有拥有该 NetworkObject 所有权的客户端,才能调用这个 ServerRpc。
    private void SendMessageToServerRpc(string message, ServerRpcParams serverRpcParams = default)
    {
        // 此逻辑在服务器端执行
        Debug.Log($"服务器收到来自客户端 {serverRpcParams.Receive.SenderClientId} 的消息: {message}");

        // 服务器可以在这里处理消息(如广播给所有客户端等)
      //  ServerToAllClientsMessageClientRpc($"客户端 {serverRpcParams.Receive.SenderClientId} 的消息: {message}");
    }


    /// <summary>
    /// 发送消息
    /// </summary>
    /// <param name="message">创建一个InputField (Legacy)和Button (Legacy)  来调用这个函数</param>    
    public void SendMessage(string message)
    {
        if (!string.IsNullOrEmpty(message))
        {
            if (IsServer)
            {
                Debug.Log($"[你(服务器):] {message}");
                ServerToAllClientsMessageClientRpc($"[服务器] {message}");
            }
            else 
            {
                Debug.Log($"[你(客户端):] {message}");
                SendMessageToServerRpc(message);
            }
        }
    }

    //public static UIManager In_;
    //public GameObject Mask;
    //public InputField messageInput;
    //public Button button;
    //void Awake()
    //{
    //    In_ = this;
    //    button.onClick.AddListener(() => {

    //        ConnectionManager.Singleton_.SendMessage(messageInput.text);
    //        messageInput.text = "";

    //    });
    //}



}

总结

项目源码: 链接

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

野区捕龙为宠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值