Unity Multiplayer (Mirror) - 在所有客户端同步游戏对象的变量时出现问题。 [试图向未经授权的对象发送命令]

Unity Multiplayer (Mirror) - Problem in syncing game object's variables across all clients. [Trying to send command for object without authority]

我正在为我学期的项目在 Unity 中构建一个 3d 虚拟拍卖行,多个用户可以在其中加入主机服务器并与游戏中的动作项目交互并提高他们的出价。更新的出价值应该在所有用户之间同步。通过将我的“角色控制器”添加到网络管理器的“玩家预制件”,我能够同步玩家在网络上的动作。用户还能够与其他游戏对象交互以在本地提高该项目的出价。我在为每个客户同步网络上每个拍卖品的更新出价时遇到问题。

我正在将每个拍卖品添加到网络管理员的“已注册可生成预制件”列表中。

Registered Spawnable Prefabs

这是我遇到的错误


Trying to send command for object without authority. DataSync.CmdIncreaseBidUI
UnityEngine.Debug:LogWarning(Object)
Mirror.NetworkBehaviour:SendCommandInternal(Type, String, NetworkWriter, Int32, Boolean) (at Assets/Mirror/Runtime/NetworkBehaviour.cs:185)
DataSync:CmdIncreaseBidUI(Int32)
DataSync:Update() (at Assets/Scripts/DataSync.cs:38)

这是我放在拍卖品上的脚本。我正在使用 text mash pro 游戏对象来显示拍卖物品的当前出价。

Game Scene

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Mirror;

public class DataSync : NetworkBehaviour
{

    [SyncVar]
    public int num = 100;

    public TMPro.TextMeshPro textObj;

    void Start()
    {
    }

    [Command]
    public void CmdIncreaseBidUI(int num)
    {
        num += 100;
        RpcIncreaseBidUI(num);
    }

    [ClientRpc]
    public void RpcIncreaseBidUI(int num)
    {
        this.num = num;
        GetComponent<TMPro.TextMeshProUGUI>().text = "Current Bid $" + num.ToString();
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown("space"))
        {
            CmdIncreaseBidUI(num);
        }
    }
}

默认情况下,只有 Host/Server 具有 Network Authority 生成的对象。

除非你用某个对对象具有权限的客户端生成它们(使用Networking.NetworkServer.SpawnWithClientAuthority但是如果多个客户端能够与您的情况一样与对象互动。


[SyncVar]

variables will have their values sychronized from the server to clients

..和只有这个方向!


现在可以选择通过 Networking.NetworkIdentity.AssignClientAuthority 获得对对象的权限,所以是的,您可以向 Host/Server 发送 [Command] 并告诉它分配权限给你这样你就可以在那个对象上调用 [Command]

但是,这有一些巨大的缺陷:

  • 由于这可能会经常快速更改,因此您始终必须首先确保您当前拥有权限,并且 Host/Server 必须确保权限可以分配给您
  • 您不知道 Host/Server 何时完成分配权限,因此您必须将 ClientRpc 发送回客户端,以便他知道现在他可以发送 Command 回到主机...你知道这是怎么回事:为什么不简单地告诉 Host/Server 已经在第一个 [Command] 会发生什么 ;)

相反,您必须(应该/我会 ^^)将您的逻辑放入您的播放器脚本(或至少附加到您有权限的播放器对象的脚本),而不是做类似

using System.Linq;

// This script goes on your player object/prefab
public class DataSyncInteractor : NetworkBehaviour
{
    // configure this interaction range via the Inspector in Unity units
    [SerializeField] private float interactionRange = 1;

    // Update is called once per frame
    void Update()
    {
        //Disable this compoennt if this is not your local player
        if (!isLocalPlayer)
        {
            enabled = false;
            return;
        }

        if (Input.GetKeyDown("space"))
        {
            if(FindClosestDataSyncItemInRange(out var dataSync))
            {
                CmdIncreaseBid(dataSync.gameObject, gameObject);
            }
        }
    }

    private bool FindClosestDataSyncItemInRange(out DataSync closestDataSyncInRange)
    {
        // Find all existing DataSync items in the scene that are currently actuve and enabled
        var allActiveAndEnabledDataSyncs = FindObjectsOfType<DataSync>();

        // Use Linq Where to filter out only those that are in range
        var dataSyncsInRange = allActiveAndEnabledDataSyncs.Where(d => Vector3.Distance(d.transform.position, transform.position) <= interactionRange);

        // Use Linq OrderBy to order them by distance (ascending)
        var dataSyncsOrderedByDistance = dataSyncsInRange.OrderBy(d => Vector3.Distance(d.transform.position, transform.position));

        // Take the first item (the closest one) or null if the list is empty
        closestDataSyncInRange = dataSyncsOrderedByDistance.FirstOrDefault();

        // return true if an item was found (meaning closestDataSyncInRange != null)
        return closestDataSyncInRange;
    }

    // As you can see the CMD is now on the player object so here you HAVE the authority
    // You can pass in a reference to GameObject as long as this GameObject has a
    // NetworkIdentity component attached!
    [Command]
    private void CmdIncreaseBid(GameObject dataSyncObject, GameObject biddingPlayer)
    {
        // This is happening on the host
        // Just as a little extra from my side: I thought it would probably be interesting to store the 
        // last bidding player so when it comes to sell the item you know
        // who was the last one to bid on it ;)
        dataSyncObject.GetComponent<DataSync>().IncreaseBid(biddingPlayer);
    }
}

然后稍微改变一下你的DataSync

[RequireComponent(typeof(NetworkIdentity))]
public class DataSync : NetworkBehaviour
{
    // This is automatically synced from HOST to CLIENT
    // (and only in this direction)
    // whenever it does the hook method will be executed on all clients
    [SyncVar(hook = nameof(OnNumChanged))]
    public int num = 100;

    public TMPro.TextMeshPro textObj;

    public GameObject LastBiddingPlayer;

    void Start()
    {
        if(!textObj) textObj = GetComponent<TMPro.TextMeshProUGUI>();
    }

    // This method will only be called on the Host/Server
    [Server]
    public void IncreaseBid(GameObject biddingPlayer)
    {
        // increase the value -> will be synced to all clients
        num += 100;

        // store the last bidding player (probably enough to do this on the host)
        LastBiddingPlayer = biddingPlayer;

        // Since the hook is only called on the clients
        // update the display also on the host
        OnNumChanged(num);
    }

    private void OnNumChanged(int newNum)
    {
        textObj.text = "Current Bid $" + num.ToString();
    }
}