Search code examples
c#unity-game-engineunity3d-unet

UNET - Let any player send Commands to the same NetworkIdentity


Unity version: 5.5

Scene example:

  • Light [GameObject with a Light component]
  • LightSwitch - [Contains: BoxCollider|NetworkIdentity|Script inherited from NetworkBehaviour that toggles light on/off when someone clicks over it's BoxCollider]

LightSwitch.cs

public class LightSwitch : NetworkBehaviour
{
    public GameObject roomLight;

    void OnMouseDown () {
        CmdToggleLight(!roomLight.activeSelf);
    }

    [Command]
    void CmdToggleLight (bool status) {
        RpcToggleLight(status);
    }

    [ClientRpc]
    void RpcToggleLight (bool status) {
        roomLight.SetActive(status);
    }
}

¿How can i let any player click that LightSwitch and toggle the lights on/off?

Edit: Following the example, this is the code that i had to build:

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

public class LightSwitch : NetworkBehaviour
{
    public GameObject roomLight;

    [SyncVar(hook="setLight")]
    private bool status;


    void OnMouseDown()
    {
        // my player's singleton
        Player.singleton.CmdToggleSwitch(this.gameObject);
    }

    public void toggleLight()
    {
        status = !status;
    }

    void setLight(bool newStatus)
    {
        roomLight.SetActive(newStatus);
    }

    [ClientRpc]
    public void RpcToggleSwitch(GameObject switchObject) 
    {
        switchObject.GetComponent<LightSwitch>().toggleLight();
    }
}

Player.cs code:

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

public class Player : NetworkBehaviour {

    public static Player singleton;

    void Start () {
        singleton = this;
    }

    //... my player class code ....//

    [Command]
    public void CmdToggleSwitch(GameObject gObject)
    {
        gObject.GetComponent<LightSwitch>().RpcToggleSwitch(gObject);
    }
}

A big piece of shit just to toggle a light, thanks to Unity.


Solution

  • Unfortunately commands can only be sent from the client player object. From the docs:

    [Command] functions are invoked on the player GameObject associated with a connection.

    What you can do those is place a toggle function on the player object that passes the network identity of the object you want to toggle. For instance:

    [Command]
    void CmdToggleSwitch (GameObject switch) //switch must have network identity component
    {
      //Set the switch state on the server
      switch.GetComponent<LightSwitch>().ToggleMethod();
    }
    

    And then on the server you can either call an RPC or have the light switch state as a SyncVar. The later method is probably easier and more reliable (i.e. a new player will have the correct light switch state set automatically).

    Here is an (untested) example:

    using UnityEngine;
    using UnityEngine.Networking;
    
    public class RaycastExample : NetworkBehaviour
    
        void ToggleSwitch()
            {
                RaycastHit hit;
                //Use raycasting to find switch
                if (Physics.Raycast(transform.position, transform.forwards, out hit, 5.0f))
                {
                    if (hit.GetComponent<LightSwitch>())
                    {
                        //Send the command to toggle the switch of the server, passing in the LightSwitch gameobject as the parameter
                        CmdToggleSwitch (hit.transform.gameObject);
                    }
                }
        }
    
        //This will be run on the server version of our player
        [Command]
        void CmdToggleSwitch (GameObject switch) //switch must have network identity component
        {
          //Set the switch state on the server
          //Server light switch will need some way to notify the clients of the change. RPC or SyncVar.
          switch.GetComponent<LightSwitch>().ToggleMethod();
        }
    

    Edit:

    This is a code snippet from one of my projects. This snippet comes from a script attached to the networked player object that I use to control the player's health.

        [SyncVar(hook="OnHealth")]
        public float currentHealth = 100f;
    

    Firstly, I declare a variable to hold the players health and assign it a SyncVar attribute. By assigning this attribute, the server will update this variable across the clients whenever this variable is changed on the server. I also add the hook parameter causing the OnHealth() function to be called on all clients when this variable in updated.

    void OnHealth (float newHealth) {
        currentHealth = newHealth;
    }
    

    Here is the OnHealth function. This function is called every time the server notifies the client that currentHealth has changed. It also passes the new value of currentHealth as an argument, we just need to manually assign this passed value as the new currentHealth.

        public void TakeDamage (float damage) {
            if (!isServer)
                return;
    
            currentHealth -= damage;
            if (currentHealth < 0)
                currentHealth = 0;
        }
    

    I also have a public function that allows the player to take damage. Notice the check in the first line of this function, I only care if the damage occurs on the server. The SyncVar attribute will cause the change in currentHealth to by synced across the clients. In this particular game, the player is only damaged when they get hit by a projectile. By calling the damage function this way, the server becomes the source of true (if the client is lagging the projectile may appear to miss when it should hit or vice versa - in that case, using a [Command] attribute may lead to conflicting results).

        [Command]
        public void CmdHeal(float heal) {
                currentHealth += heal;
        }
    

    In this game there is also a heal function that occurs when the player presses a certain key. Since the server doesn't know when this key is pressed, I can't update the health the way I do with the damage function. Therefore, I use a [Command] attribute. So this is the flow: player presses key -> Client notifies server that CmdHeal was called -> CmdHeal executes on the server -> server notifies all clients that currentHealth has changed.