- Home /
Send Commands from an Object in Multiplayer
Basically I have this:
public class ShipDoor : UsableObject // UsuableObject inherits from NetworkBehavior
{
[SyncVar]
bool isOpen = false;
[SyncVar]
float currentTransition = 0;
public override void Use()
{
CmdUseDoor();
Debug.Log("Opening " + m_name);
}
[Command]
void CmdUseDoor()
{
Debug.Log("Opening(Server) " + m_name);
isOpen = !isOpen;
currentTransition = 0;
}
}
When a player clicks an object he gets a small dialog with a list of commands, when he clicks the command it is called on the object and the object will do whatever it needs to in the [Command].
However, since my client has no authority over this object he can't call the [Command]. How can I allow this? There will be many objects with various commands all doing different things. It would feel a bit messy to cram them all into a single script on the Player object.
The example here is just the client saying "server, I want to use this door" and letting the server do whatever is necessary for that. But without authority I can't interact with the object at all.
My guess is I'd have to call something on the player like:
void Use(GameObject obj)
{
CmdUse(obj);
}
[Command]
void CmdUse(GameObject obj)
{
obj.GetComponent<UsableObject>.Use();
}
And make sure they have a method for every interaction type. I'm hoping I can do it from the object itself, but if not, would this be the least messy alternative?
Further explanation of how it is called:
Essentially it will create buttons for a tooltip with delgate commands to send when the buttons are clicked. The below is in the door script:
public override void CreateUIInterface(Text titleText, GameObject buttonMenu, GameObject toolTipButton, PlayerInteraction playerInteraction)
{
titleText.text = GetName();
GameObject obj = (GameObject)Instantiate(toolTipButton, buttonMenu.transform);
obj.transform.SetParent(buttonMenu.transform);
obj.transform.localScale = new Vector3(1.0f, 1.0f, 1.0f);
if (isOpen)
obj.GetComponent<Button>().GetComponentInChildren<Text>().text = "Close";
else
obj.GetComponent<Button>().GetComponentInChildren<Text>().text = "Open";
obj.GetComponent<Button>().onClick.AddListener(delegate { Use(); });
obj.GetComponent<Button>().onClick.AddListener(delegate { playerInteraction.ClearTooltip(); });
Debug.Log("Instantiated Open: " + toolTipButton.name + ", " + buttonMenu.transform.name);
obj = (GameObject)Instantiate(toolTipButton, buttonMenu.transform);
obj.transform.localScale = new Vector3(1.0f, 1.0f, 1.0f);
obj.GetComponent<Button>().GetComponentInChildren<Text>().text = "Destroy";
obj.GetComponent<Button>().onClick.AddListener(delegate { Destroy(); });
obj.GetComponent<Button>().onClick.AddListener(delegate { playerInteraction.ClearTooltip(); });
obj = (GameObject)Instantiate(toolTipButton, buttonMenu.transform);
obj.transform.localScale = new Vector3(1.0f, 1.0f, 1.0f);
obj.GetComponent<Button>().GetComponentInChildren<Text>().text = "Switch";
obj.GetComponent<Button>().onClick.AddListener(delegate { Switch(); });
obj.GetComponent<Button>().onClick.AddListener(delegate { playerInteraction.ClearTooltip(); });
}
So with this it doesn't have an initial method that is called, it just goes straight to the door with the method. I have tried using the Command as the delegate but it's the same as calling it straight from the door itself (no authority).
Answer by Fikule · Jan 10, 2017 at 01:40 PM
I ended up going with something close to @fafase answer by re-writing a few bits. Essentially I now have:
PlayerInteractions.c
//Interactions//
public void Use(GameObject obj)
{
CmdUse(obj);
}
[Command]
public void CmdUse(GameObject obj)
{
obj.GetComponent<UsableObject>().Use(1);
}
PlayerInteractions will need a function and [Command] for each possible interaction within UseableObject. This script is located on the player, so this is the Use() (or other method) that will be called by the client.
ShipDoor.c
public class ShipDoor : UsableObject
{
[SyncVar]
bool isOpen = false;
public override void CreateUIInterface(Text titleText, GameObject buttonMenu, GameObject toolTipButton, PlayerInteraction playerInteraction)
{
if (isOpen)
buttonCommandList.Add(new UseableButton("Close", delegate { playerInteraction.Use(this.gameObject); }));
else
buttonCommandList.Add(new UseableButton("Open", delegate { playerInteraction.Use(this.gameObject); }));
buttonCommandList.Add(new UseableButton("Destroy", this.Destroy));
buttonCommandList.Add(new UseableButton("Switch", this.Switch));
base.CreateUIInterface(titleText, buttonMenu, toolTipButton, playerInteraction);
buttonCommandList.Clear();
}
public override void Use(int i)
{
isOpen = !isOpen;
currentTransition = 0;
if (isOpen)
RemoveTag("Obstruction");
else
AddTag("Obstruction");
Debug.Log("Opening " + m_name);
}
public override void Destroy()
{
Debug.Log("Destroying " + m_name);
}
public override void Switch()
{
Debug.Log("Switching " + m_name);
}
...
}
ShipDoor (inheriting from UseableObject) will have Use() called from PlayerInteractions by the server. This will let it be updated properly. CreateUIInterface() sets up a list of buttons for the object to display with the PlayerInteraction methods as it's calls, taking the object as a parameter.
UseableObject.c
public class UsableObject : BaseObject
{
public string m_name;
public class UseableButton
{
public UnityAction action;
public string name;
public UseableButton(string p_name, UnityAction p_action)
{
name = p_name;
action = p_action;
}
}
public List<UseableButton> buttonCommandList = new List<UseableButton>();
public virtual void CreateUIInterface(Text titleText, GameObject buttonMenu, GameObject toolTipButton, PlayerInteraction playerInteraction)
{
Debug.Log("Creating Interface");
titleText.text = GetName();
foreach (UseableButton ubtn in buttonCommandList)
{
GameObject obj = (GameObject)Instantiate(toolTipButton, buttonMenu.transform);
obj.transform.localScale = new Vector3(1.0f, 1.0f, 1.0f);
obj.GetComponent<Button>().GetComponentInChildren<Text>().text = ubtn.name;
obj.GetComponent<Button>().onClick.AddListener(ubtn.action);
obj.GetComponent<Button>().onClick.AddListener(delegate { playerInteraction.ClearTooltip(); });
}
}
public virtual void Use(int i)
{
Debug.Log("Not Usable");
}
public virtual void Destroy()
{
Debug.Log("Not Usable");
}
public virtual void Switch()
{
Debug.Log("Not Usable");
}
public virtual string GetName()
{
return m_name;
}
}
UseableObject creates the buttons from the list and contains the virtual functions.
So now when a player clicks an object it creates a dialog with buttons having methods for PlayerInteraction calls with the object in question. Clicking the button will then have the server call back to the object originally clicked. This lets me keep the functionality on the object itself.
Answer by fafase · Jan 08, 2017 at 10:18 AM
Try to get the Command method on the calling object. In your example you have the public Use method on Object A which I assume is called from another object B. That Use method calls the Command method on A.
Try with B object script as such:
void Method(){
if(condition){ CmdCall()}
}
[Command]
private void CmdCall(){
FindObjectOfType<ShipDoor>().Use();
}
And this is ship door:
public class ShipDoor : UsableObject // UsuableObject inherits from NetworkBehavior { [SyncVar] bool isOpen = false; [SyncVar] float currentTransition = 0;
public override void Use()
{
UseDoor();
Debug.Log("Opening " + m_name);
}
void UseDoor()
{
Debug.Log("Opening(Server) " + m_name);
isOpen = !isOpen;
currentTransition = 0;
}
}
Unfortunately this won't work due to how the call to Use() is set up, sorry for omitting that. I've updated my original post with the setup for the call.