问题描述
我正在 Unity 中为我学期的项目构建一个 3d 虚拟拍卖行,多个用户可以加入主机服务器并与游戏中的动作项目进行交互并提高他们的出价。更新后的出价值应在所有用户之间同步。通过将我的“角色控制器”添加到网络管理器的“玩家预制件”,我能够在网络上同步玩家的动作。用户还可以与其他游戏对象进行交互以在本地提高项目的出价。我在跨网络为每个客户同步每个拍卖项目的更新出价时遇到问题。
我正在将每个拍卖物品添加到网络管理员的“已注册可生成预制件”列表中。
这是我得到的错误
dev_parameter
这是我放在拍卖物品上的脚本。我正在使用 text mash pro 游戏对象来显示拍卖物品的当前出价。
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)
解决方法
正如默认情况下所说,只有主机/服务器在生成的对象上有 Network Authority。
除非您使用对对象具有权限的某个客户端(使用 Networking.NetworkServer.SpawnWithClientAuthority
)来生成它们但是如果多个客户端能够与对象进行交互,这也毫无意义就你而言。
变量的值将从服务器同步到客户端
..并且只在这个方向!
现在可以选择通过 Networking.NetworkIdentity.AssignClientAuthority
获得对对象的权限,所以是的,您可以向主机/服务器发送 [Command]
并告诉它为您分配权限,以便您可以对该对象调用 [Command]
。
然而,这有一些巨大的缺陷:
- 由于这可能会经常且快速地发生变化,因此您始终必须首先确保您当前拥有权限,并且主机/服务器还必须确保可以将权限分配给您
- 您不知道主机/服务器何时完成分配权限,因此您必须将
ClientRpc
发送回客户端,以便他知道现在他可以发送一个Command
到主机......你会看到这是怎么回事:为什么不简单地告诉主机/服务器已经有了第一个[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();
}
}