4

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.

FrakyDale
  • 647
  • 8
  • 22

1 Answers1

3

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.

Chairs
  • 109
  • 5
  • (sorry for the unformatted comment, i cant make it look great, Unity Answers markdown SUCKS: http://imgur.com/rQi496t) . I'm sorry, but i don't understand your solution. Having the example scene: - I place the `CmdToggleSwitch` that you've made in LightSwitch.cs since that script is attached in the same GameObject where NetworkIdentity is present. - How do i call that function from any player? As you said, [Command] methods can only be called by the NetworkIdentity owner : s – FrakyDale Dec 13 '16 at 17:32
  • Sorry I wasn't clear. The CmdToggleSwitch would be located in a script that is attached to the player object, not on the light switch. The command will then executed on the server version of your player, who then calls the ToggleMethod() on the server light switch. Then change in the light switch is then sent to the clients via RPC or SyncVar. – Chairs Dec 13 '16 at 17:50
  • The main thing to keep in mind is that anytime you use [Commad] in a script it has to attached to your Network player. – Chairs Dec 13 '16 at 18:06
  • Question updated publishing the ending code. I hate how much code i had to write just to toggle a single light. With Photon Network this was just 2 methods and done... – FrakyDale Dec 15 '16 at 15:00
  • @Chairs I was wondering if you could please put a full example with separate scripts and which object that includes the SyncVar as well, as my understanding of where this has to be located is lacking. Thanks in advance – Alan-Dean Simonds Jan 18 '17 at 05:15
  • @Alan-DeanSimonds I updated the post to include an example script from one of my own projects. Although the SyncVar and [Command] function are on the same object in this example, they could just as well be in separate scripts. – Chairs Jan 20 '17 at 01:31