- Home /
Help issuing a Command to a NetworkBehaviour spawned on the NetworkServer
I am building a simulation that will be playable on multiple systems on a local network. One of the main objects is a simulation clock.
I am instantiating the object and spawning it in OnServerStart with NetworkServer.Spawn.
The Network Identity has neither Server Only nor Local Authority checked.
The clock shows up on the server and all clients. It can be started from the local client on the server. It runs and keeps time on all clients. It can be stopped from the local client on the server.
My problem comes when I want to stop the clock from any one of the clients. I get an error message of:
Trying to send command for object without authority.
I feel like I am missing something basic.
Do I need to change something on the Network Identity? Do I need to Spawn it differently? Do I need to message the server in some other way then using the [Command] attribute?
Class Attached:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
/*
Clockwork is used to regulate the simulation time.
This class is derived from NetworkBehaviour and should
be spawned by the server on the network with:
NetworkServer.Spawn
It aasumes that the system clocks are in sync via NTP.
DateTime Structs can't be used as SyncVars. So, DateTime is
converted to formatted strings and passed to clients where Hook
methods convert them back to DateTime.
Four events are exposed:
DayTick; - clock face can subscribe to this event to update CurrDate
MonthTick;
CalendarYearTick;
FiscalYearTick;
*/
[NetworkSettings(sendInterval = 0)]
public class Clockwork : NetworkBehaviour {
//Last start in simulation time as string
[SyncVar(hook="PassLastSimulationStartDate")]
string LastSimulationStartDateString;
//Last start in system time as string
[SyncVar(hook="PassLastSystemStartTime")]
string LastSystemStartTimeString;
//Simulation time per System time
[SyncVar]
public int TimeScale; //days per minute
//Is the Clockwork Running
[SyncVar(hook="PassRunning")]
public bool Running;
public DateTime LastSimulationStartDate;
public DateTime CurrentSimulationDate;
public DateTime LastSystemStartTime;
// string version of the current clock date to use in the clock face
public string CurrDate;
//Hook for converting LastSimulationStartDateString to DateTime
void PassLastSimulationStartDate (string LastSimulationStartDateStr) {
LastSimulationStartDate = Convert.ToDateTime (LastSimulationStartDateStr);
}
//Hook for converting PassLastSystemStartTime to DateTime
void PassLastSystemStartTime (string LastSystemStartTimeStr) {
LastSystemStartTime = Convert.ToDateTime (LastSystemStartTimeStr);
}
//Hook for Running to start and stop Clockwork on client
void PassRunning (bool RunningBool) {
if (!isServer) {
if (RunningBool) {
Running = true;
StartCoroutine ("Escapement");
} else {
Running = false;
}
}
}
//Delegate for Clock Events
public delegate void PeriodTick () ;
//Clock Events to allow classes to conserve their Update Method
public static event PeriodTick DayTick;
public static event PeriodTick MonthTick;
public static event PeriodTick CalendarYearTick;
public static event PeriodTick FiscalYearTick;
//Stored values to test for period changes
int PreviousDay;
int PreviousMonth;
int PreviousCalendarYear;
int PreviousFiscalYear;
//coroutine run Clockwork
IEnumerator Escapement () {
while (Running) {
double ElapsedDays = (DateTime.Now - LastSystemStartTime).TotalSeconds * TimeScale / 60;
CurrentSimulationDate = LastSimulationStartDate.AddDays (ElapsedDays);
CurrDate = CurrentSimulationDate.ToString("MM/dd/yyyy");
if (CurrentSimulationDate.Day != PreviousDay) {
if (DayTick != null) {
DayTick ();
}
PreviousDay = CurrentSimulationDate.Day;
if (CurrentSimulationDate.Month != PreviousMonth) {
if (MonthTick != null) {
MonthTick ();
}
PreviousMonth = CurrentSimulationDate.Month;
if (CurrentSimulationDate.Year != PreviousCalendarYear) {
if (CalendarYearTick != null) {
CalendarYearTick ();
}
PreviousCalendarYear = CurrentSimulationDate.Year;
}
if (CurrentSimulationDate.AddDays(92).Year != PreviousFiscalYear) {
if (FiscalYearTick != null) {
FiscalYearTick ();
}
PreviousFiscalYear = CurrentSimulationDate.AddDays(92).Year;
}
}
}
yield return null;
}
StopCoroutine("Escapement");
}
// Use this for initialization
public override void OnStartServer () {
CurrentSimulationDate = DateTime.Today;
Running = false;
}
//Start the Server Clockwork
public void StartClock () {
LastSimulationStartDate = CurrentSimulationDate;
LastSimulationStartDateString = LastSimulationStartDate.ToShortDateString ();
LastSystemStartTime = DateTime.Now;
LastSystemStartTimeString = LastSystemStartTime.ToString ("yyyy-MM-ddTHH:mm:ss.ffff");
Running = true;
StartCoroutine ("Escapement");
}
//Stop the Clockwork
public void StopClock () {
if (hasAuthority) {
Running = false;
} else {
CmdOff ();
}
}
//When the new instance is created on a client the SyncVars will already be synced
//so the Hooks are rerun
public override void OnStartClient () {
if (Running) {
PassLastSimulationStartDate (LastSimulationStartDateString);
PassLastSystemStartTime (LastSystemStartTimeString);
PassRunning (Running);
}
}
[Command]
public void CmdOff () {
Running = false;
}
}
Answer by premium123 · Jul 14, 2017 at 10:21 PM
Normally you can only send commands from a client to the server from the object with authority. This is the player object which get spawned when you connect,
So you have to execute the [Command] function in a script with is attached to the player object. Or in better words, execute a [Command] function from a Networkbehaviour which uses the network identity of the main player on the local computer.
If you call a function with [Command] , this function will be executed on the server computer.
So you must call the CmdOff function from a script with is attached to the player on the server and attached to the player on the client. In my project every player has a script called "PlayerNetworkController" which handles all command functions from all clients.
I like the strategy you laid out.
I created a class derived from NetworkBehaviour as follows:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
public class PlayerClockworkControl : NetworkBehaviour {
public Clockwork CW;
void Start () {
CW = FindObjectOfType<Clockwork> ();
}
public void RequestStop () {
CmdStopClock ();
}
[Command]
public void CmdStopClock () {
CW.StopClock ();
}
}
The RequestStop function can be called from a UI event. I learned the hard way that if you call CmdStopClock directly from the UI, it will run on the local system and not the server.
Thank you for your help.
Your answer
Follow this Question
Related Questions
Rotating character on server using camera angle on client 1 Answer
Independent countdown on server-side 1 Answer
Authoritative Melee Combat System 1 Answer
Authoritative Server with Offline Single-Player 3 Answers
NetworkIdentity.clientAuthorityOwner no set on NetworkLobbyManager's playerPrefab 1 Answer