Creating interactable enviornment items that are usable by all players in Multiplayer

I am just wondering what the best approach is to handling world items that all other players on a server can interact with. A good example would be switches and doors, that exist as part of the scene, but would need to handled by each players client as they interact with it. I’ve tried to wrap my head around it, but I am coming up blank. I have seen people say that the prefabs should be spawned in on launch, but that doesn’t seem like it would help if the objects are static parts of the environment. Whenever I do attempt something, I get the old “Trying to send command for object without authority.”

Any help would be greatly appreciated.

I guess you use UNet!?

Any object that is not owned by a single player is owned by the server and therefore only the server can control it. If you want to change the state of such an object from a client, the client has to send a command through his own player object and on the serverside the server can verify the request and forward it to the object in question.

Any communication from the client goes through his player object. In most multiplayer games you only have a few physical inputs / keys that a player can actually use. (like pressing “e” to “use” something, mouse0 to fire, … ). There are generally two approaches:

  • Either you just send the actual input events to the server and do everything else on the serverside. On the server you can do everything you want.
  • Another option is to do some pre determining on the client and send more explicit commands to the server (again, everything through the player object). For example you can do the raycasting which determines which object a player is looking at on the client and just pass a gameobject reference as parameter to the command you send to the server. However the more you do already on the client, the more difficult it is for the server to verify the validity of the command. A similar issue you see in some games that work this way. For example minecraft. The client essentially tells the server that he interacted with a certain block. However a cheater could simply tell the server he ineracted with a block that isn’t reachable by the player.

So the best security is reached by only sending input events and do the rest on the server. Best precision and user feedback is reached when you do some preprocessing on the client.

So either you just send an interact() message to the server and let the server do the rest, or you send commands like interact(GameObject) where you pass along the object you want to interact with.

Ok! After much trial and error, I figured out a system that works fine for me that solves my initial problem. Part of my solution is handled inside the character controller, and part inside the object itself.

if (Input.GetKeyDown(KeyCode.E))
            {
                CmdActivateObject();
            }

//A trigger volume in front of the player looks for objects with an 'interactable' tag, and when it finds them it will pass it into AssignInteractables!

    private void OnTriggerStay(Collider other)
    {
        if (other.gameObject.tag == "Interactable")
        {
            AssignInteractables(other.gameObject);
            Debug.Log(other.gameObject.name);
        }
    }
    private void OnTriggerExit(Collider other)
    {
        AssignInteractables(null);
    }

//I use this to assign the object that will be interacted with

public void AssignInteractables(GameObject interactable)
    {
        ObjectToAssign = interactable;
        if (ObjectToAssign != null) {
            Debug.Log(ObjectToAssign.name + " ready to assign!");
        }
    }


//This next part handles the interaction and the replication of the interaction on the server and across all other clients. It will reach into the interactable object and look for the Network Interaction script and then change its 'active' state.

[Command]
    void CmdActivateObject()
    {
        bool activStat = ObjectToAssign.GetComponent<NetworkInteraction>().PlayerActivated;
        activStat = !activStat;
        if(isServer)
        {
            RpcActivateObject(activStat);
        }
    }
    [ClientRpc]
    void RpcActivateObject(bool state)
    {
        ObjectToAssign.GetComponent<NetworkInteraction>().PlayerActivated = state;
    }

On each object that I want the player to be able to interact with, I just assign my ‘Network Interaction’ script:

public class NetworkInteraction : MonoBehaviour
{
    [Header ("Activation State")]
    public bool PlayerActivated;
}

Because it is the same across all systems, I can then write any script I want, and reference this PlayerActivated boolean using a locally running script to do all the complex things I need an object to do…

It might not be the most elegant way to handle this problem, but like I said it deffo works for me!