如何在UNet/Unity5中同步非玩家GameObject属性?

6
我正在学习Unity 5、UNET和网络技术的基础知识。我制作了一个简单的3D游戏,你可以在游戏中走动并改变物体的颜色。但是现在我想把它变成多人游戏,我遇到了很多问题,无法弄清如何将更改发送到网络,以便所有玩家都可以看到单个玩家的颜色更改。
问题的一部分在于使用较新的UNET网络引擎时很难找到答案。有时我会遇到关于旧方式的回答。
所以主要问题是,如何进行非玩家GameObject的属性更改网络化?比如颜色、形状、大小等等。
下面是我目前的一些代码 - 我已经尝试了许多不同版本,所以现在只发布当前版本:
 using UnityEngine;
 using System.Collections;
 using UnityEngine.Networking;

 public class Player_Paint : NetworkBehaviour {

     private int range = 200;
     [SerializeField] private Transform camTransform;
     private RaycastHit hit;
     [SyncVar] private Color objectColor;
     [SyncVar] private GameObject objIdentity;

     void Update () {
         CheckIfPainting();
     }

     void CheckIfPainting(){
         if(Input.GetMouseButtonDown(0)) {
             if (Physics.Raycast (camTransform.TransformPoint (0, 0, 0.5f), camTransform.forward, out hit, range)) {
                 string objName = hit.transform.name;
                 CmdPaint(objName);
             }
         }
     }

     [ClientRpc]
     void RpcPaint(){
         objIdentity.GetComponent<Renderer>().material.color = objectColor;
     }

     [Command]
     void CmdPaint(string name) {
         objIdentity = GameObject.Find (name);  //tell us what was hit
         objectColor = new Color(Random.value, Random.value, Random.value, Random.value);
         RpcPaint ();
     }
 }

我已经尝试了很多解决方案,包括编写一个单独的脚本来改变颜色并使用[SyncVar]和钩子函数。我还在期望更新客户端对象的每个功能上尝试了Debug.Log,并且它们都按预期数据执行。
我真的不知道还能做什么。我感觉我想要做的事情非常简单,但我在任何问题、教程或其他资源中都没有遇到同步非玩家游戏对象的方法。有任何想法都会有所帮助,谢谢。

我感觉我想做的事情非常简单,但是我在任何问题、教程或其他资源中都没有找到同步非玩家游戏对象的方法。我的挫败感就是这样。 - Daryl Bennett
@MichaelS 或任何阅读此信息的人,一旦你知道如何做,它就非常简单。这里有一个非常好的解释:https://forum.unity.com/threads/assign-authority-to-local-client-gameobject.371113/#post-3592541 - Fattie
4个回答

12

我找到了答案。但是几乎每个问题、帖子、示例等都是关于玩家对象,而不是非玩家对象,所以很困难。

所以,我需要使用AssignClientAuthority函数。我已经尝试过几次,但是没有正确使用它。这是一个可用的C#脚本应用于玩家:

using UnityEngine;
using System.Collections;
using UnityEngine.Networking;

public class Player_Paint : NetworkBehaviour {

    private int range = 200;
    [SerializeField] private Transform camTransform;
    private RaycastHit hit;
    [SyncVar] private Color objectColor;
    [SyncVar] private GameObject objectID;
    private NetworkIdentity objNetId;

    void Update () {
        // only do something if it is the local player doing it
        // so if player 1 does something, it will only be done on player 1's computer
        // but the networking scripts will make sure everyone else sees it
        if (isLocalPlayer) {
            CheckIfPainting ();
        }
    }

    void CheckIfPainting(){
        // yes, isLocalPlayer is redundant here, because that is already checked before this function is called
        // if it's the local player and their mouse is down, then they are "painting"
        if(isLocalPlayer && Input.GetMouseButtonDown(0)) {
            // here is the actual "painting" code
            // "paint" if the Raycast hits something in it's range
            if (Physics.Raycast (camTransform.TransformPoint (0, 0, 0.5f), camTransform.forward, out hit, range)) {
                objectID = GameObject.Find (hit.transform.name);                                    // this gets the object that is hit
                objectColor = new Color(Random.value, Random.value, Random.value, Random.value);    // I select the color here before doing anything else
                CmdPaint(objectID, objectColor);    // carry out the "painting" command
            }
        }
    }

    [ClientRpc]
    void RpcPaint(GameObject obj, Color col){
        obj.GetComponent<Renderer>().material.color = col;      // this is the line that actually makes the change in color happen
    }

    [Command]
    void CmdPaint(GameObject obj, Color col) {
        objNetId = obj.GetComponent<NetworkIdentity> ();        // get the object's network ID
        objNetId.AssignClientAuthority (connectionToClient);    // assign authority to the player who is changing the color
        RpcPaint (obj, col);                                    // usse a Client RPC function to "paint" the object on all clients
        objNetId.RemoveClientAuthority (connectionToClient);    // remove the authority from the player who changed the color
    }
}

!!!重要提示!!! 想要受到影响的每个对象必须具有NetworkIdentity组件,并且它必须设置为LocalPlayerAuthority。

所以这个脚本只是改变随机颜色,但你应该能够改变实际的东西来将其应用到任何材料或其他你想要与非玩家对象网络化的变化上。"应该"是最佳词语-我还没有尝试过任何其他功能。

编辑-为学习目的添加了更多注释。


1
太好了!我很高兴能帮忙!我真的很惊讶在网上找不到这个答案。我还尝试了使用替换功能来调整大小而不是“重绘”的相同脚本,结果非常简单。 - Michael S
@MichaelS,能否给我一个详细的介绍,因为在游戏中我有一个物体,玩家可以看着它,另一个物体会在他们看着它的时候移动位置,当他们看开了,它就会回到原来的位置。经过多个小时的尝试,我无法使其同步到两个玩家或使任何一个玩家实际影响该物体。 - Alan-Dean Simonds
@Alan-DeanSimonds 我在这里添加了更多的注释。在我的脚本中,我正在改变对象的颜色,所以我使用了“painting” - 所以任何地方都说painting或paint,对于你来说它将是moving或move。问题是对象是否会移回其原始位置?还是说它应该这样做,只是两个玩家都没有移动? - Michael S
@MichaelS 问题在于,如果服务器计算机与按钮交互,则两个连接的客户端都会更新,但是客户端计算机只会在自己的机器上更新。我只需要更好地理解如何在通过脚本移动到另一个游戏对象上的对象上更新转换。如果您需要更多信息,我将创建一个新问题并添加图像。 - Alan-Dean Simonds
我已经有一段时间没有看过这个(或任何Unity相关的东西)。但是如果我记得正确,客户端(玩家)正在修改对象(在RpcPaint()中),所以他们需要权限。然后我们使用RemoveClientAuthority,这样(我想)客户端就不能再进行更改,服务器可以重新获得权限并将这些更改传输给所有客户端。 - Michael S
显示剩余3条评论

2

Unity 5.3.2p3 非玩家对象分配客户端权限

对于任何有兴趣设置此项的人,这是我的方法:

客户端: 针对LocalPlayer组件 -> 通过传递对象的NetworkInstanceId调用命令来分配和删除对象权限。您可以在此组件上添加任何UI以调用这些方法。

服务器端:

    [Command]
    void CmdAssignObjectAuthority(NetworkInstanceId netInstanceId)
    {
        // Assign authority of this objects network instance id to the client
        NetworkServer.objects[netInstanceId].AssignClientAuthority(connectionToClient);
    }

    [Command]
    void CmdRemoveObjectAuthority(NetworkInstanceId netInstanceId)
    {
        // Removes the  authority of this object network instance id to the client
        NetworkServer.objects[netInstanceId].RemoveClientAuthority(connectionToClient);
    }  

客户端 3.对象组件 ->
OnStartAuthority() - 允许向服务器发送命令 OnStopAuthority() - 不允许向服务器发送命令

就是这样啦!


确实,别忘了只有服务器能够做到 - 因此客户端必须请求服务器来完成它。 这是理解该问题的关键。 - Fattie

1

2018年:

不要使用“Assign Object Authority”,

我非常建议直接使用

.SpawnWithClientAuthority

这真的非常简单。

事实上,就是这么简单!

  [Command]
  void CmdPleaseSpawnSomething() {
 
        GameObject p = Instantiate(some_Prefab);
        NetworkServer.SpawnWithClientAuthority(p, connectionToClient);
    }

{在这段代码中,请注意"connectionToClient"是自动可用的,它表示调用此命令的"客户端"。}

在客户端(您想要"拥有"该物品的客户端)上,只需调用CmdPleaseSpawnSomething()

我的意思是 - 就是这样简单,谢天谢地。

这里有一个长而清晰的解释:

https://forum.unity.com/threads/assign-authority-to-local-client-gameobject.371113/#post-3592541


嗨,Fattie。我之前看到过这种观点,但据我所知,这只是少数人的看法。你的问题比技术写作读者预期的要多得多,当然,我希望你的材料根本不需要编辑。我会让步于标题,但“希望有所帮助”确实非常冗余(104个)。 - halfer
就此而言,社区对串行编辑持有相当分歧的观点 - 一些人认为可以,而另一些人则认为不应该。我的政策是将串行编辑分成小组,这样用户就不会被编辑淹没。不幸的是,迄今唯一的抱怨者(从记忆中可能有四到五个人?)都是极其固执的作者,他们在任何事情上都不肯让步(其中两个人目前因总体难缠而受到长时间禁令)。 - halfer
底线是志愿编辑在这里为了提高质量 - 请随意查看我迄今为止所做的约50K次编辑。我相信它们符合社区的期望。 - halfer

0
我对这段代码进行了小修改,并添加了通过射线投射使玩家能够进行更改的可能性。
using UnityEngine;
using System.Collections;
using UnityEngine.Networking;

public class Raycasting_Object : NetworkBehaviour {

    private int range = 200;
//  [SerializeField] private Transform camTransform;
    private RaycastHit hit;
    [SyncVar] private Color objectColor;
    [SyncVar] private GameObject objectID;
    private NetworkIdentity objNetId;

    void Update () {
        if (isLocalPlayer) {    
            CheckIfPainting ();
        }
    }

    void CheckIfPainting(){

        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        Debug.DrawRay (ray.origin, ray.direction * 100, Color.cyan);

        if(isLocalPlayer && Input.GetMouseButtonDown(0)) {
            if (Physics.Raycast (ray.origin, ray.direction, out hit, range)) {
                objectID = GameObject.Find (hit.transform.name);                                    // this gets the object that is hit
                Debug.Log(hit.transform.name);
                objectColor = new Color(Random.value, Random.value, Random.value, Random.value);    // I select the color here before doing anything else
                CmdPaint(objectID, objectColor);
            }
        }

    }

    [ClientRpc]
    void RpcPaint(GameObject obj, Color col){
        obj.GetComponent<Renderer>().material.color = col;      // this is the line that actually makes the change in color happen
    }

    [Command]
    void CmdPaint(GameObject obj, Color col) {
        objNetId = obj.GetComponent<NetworkIdentity> ();        // get the object's network ID
        objNetId.AssignClientAuthority (connectionToClient);    // assign authority to the player who is changing the color
        RpcPaint (obj, col);                                    // usse a Client RPC function to "paint" the object on all clients
        objNetId.RemoveClientAuthority (connectionToClient);    // remove the authority from the player who changed the color
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接