Commands from Server to Client?

No real easy way to ask this so I’ll explain what I’m trying to accomplish in steps.

  1. Player joins the server as a client and has LocalPlayerAuthority.
  2. Player has minions they can summon…and summons one.
  3. This initiates and animation on the player (and animations are sync’d via NetworkAnimator).
  4. On the server, when the animation hits a certain point, the server creates the minion.

This is where I’m stuck. I can’t figure out how I can send a message back from the server to the (appropriate) player that will tell their client to give LocalPlayerAuthority on that minion so that the player can have control over it, and ONLY that player.

ClientRpc is the the other direction: server to client communication. See here: http://docs.unity3d.com/Manual/UNetActions.html

The way I’m understanding that page @liortal is that the ClientRpc will be called on the GameObject on all connected clients. How would I only call it on a specific client?

You have to create a NetworkMessage and send the message to this specific client.
See here: http://docs.unity3d.com/Manual/UNetMessages.html

2 Likes

You can always have the client request a new player object via ClientScene.AddPlayer(). You only have to make sure, the server picks a different prefab. If done the regular way, the requesting client will have ownership (NetworkBehavior.isLocalPlayer = true) automatically.
I’ll try to script out of my mind what I want to say… hope you get the idea even if the following code may still have errors.
It sort of abuses the playerControllerId to distinguish whether a player or a minion should be spawned. In the example I chose Id >= 1024 to be minions, lower ids to be players.

For player and minion creation override this NetworkManager function:

public override void OnServerAddPlayer(NetworkConnection conn, short playerControllerId)
{
   (...) //get spawn position etc.
   GameObject obj;
   if (playerControllerId >= 1024) //hardcoded for demonstration
   {
      //spawn controllable minion
      obj = (GameObject)Instantiate(minionPrefab, position, rotation);
   }
   else
   {
      //spawn player
      obj = (GameObject)Instantiate(playerPrefab, position, rotation);
   }
   NetworkServer.AddPlayerForConnection(conn, obj, playerControllerId);
}

Minion spawn:

//some NetworkBehaviour attached to player object
[ClientRpc]
RpcRequestMinion()
{
   //remote clients won't request any spawn
   if (isLocalPlayer)
   {
      ClientScene.AddPlayer(1024); //1024+ identifies "player" as minion
   }
}

Now add the logic to invoke RpcRequestMinion on the server.

Important: Make sure to always have unique playerControllerIds for each client (meaning for each connection, different connections may share the same playerControllerIDs).

Alright. I’m getting there using the NetworkServer.SendToClient method. However, I can’t seem to figure out how to decipher (in code) what a players NetworkConnection.connectionId is. I need this in order to know the specific player to send the message to.

EDIT - Never mind I found it. The script needs a reference to the NetworkManager class and it’s found under NetworkManager.client.connection.connectionId.

But I don’t understand how the client knows what to do with the message.

 /*------THIS CODE EXISTS INSIDE THE MINION SCRIPT------*/
    public class MyMsgType
    {
        public static short Activate = MsgType.Highest + 1;
    }
    public class ActivateMessage : MessageBase
    {
        public bool localPlayerAuthority;
    }
    public void Activate(bool _localPlayerAuthority, int _connectionID)
    {
        ActivateMessage msg = new ActivateMessage();

        msg.localPlayerAuthority = _localPlayerAuthority;

        NetworkServer.SendToClient(_connectionID, MyMsgType.Activate, msg);
    }

…and then in the code that summons the minion I have…

minion.GetComponent().Activate(true, connectionId);

Hi !

You have to register the message to receive it.

// somewhere register the networkmessage handler
NetworkManager.client.RegisterHandler(MyMsgType.Activate, OnActivateHandler);

public class ActivateMessage : MessageBase
{
    public uint netId;
    public bool localPlayerAuthority;
}

public void Activate(bool _localPlayerAuthority, int _connectionID)
{
    ActivateMessage msg = new ActivateMessage();
    msg.netId = netId;
    msg.localPlayerAuthority = _localPlayerAuthority;
    NetworkServer.SendToClient(_connectionID, MyMsgType.Activate, msg);
}

static void OnActivateHandler(NetworkMessage netMsg)
{
    ActivateMessage activateMessage = netMsg.ReadMessage<ActivateMessage >();
   
    // you need to get the gameobject to call function
    // to do that you have to add the netId in the network message.
   GameObject go = ClientScene.FindLocalObject(activateMessage.netId);
   if(go != null)
   {
       // put your code here to active/summon a minion
   }
}

@Firoball … I like your method better…less work… :slight_smile: However, I’m having trouble wrapping my head around helping the server to know which prefab to create. There are hundreds of minions, so I can’t just make the minion a public variable. I need a way to make it a parameter of the OnServerAddPlayer function…but I don’t see how, since that function itself is not actually called in the normal sense. Would I just need to use the playerControllerId variable to help decide which minion to Instantiate? For example, in this line…

obj = Resources.Load("Prefabs/Entities/Minions/" + _prefab) as GameObject;

Should I just change it to…

obj = Resources.Load("Prefabs/Entities/Minions/" + _playerControllerId.ToString()) as GameObject;

…and label my minions by numbers instead of their names in the prefabs directory?

EDIT - You also mentioned having the server spawn a different prefab. What did you mean by this?

With loading different prefabs I basically meant this line and following:

if (playerControllerId >= 1024)

If this method works out, you have to decide yourself.
Normally this unique id defines client controlled objects, like multiple players on a single client. It allows to connect different input control schemes and the like.
This means if you have like two minions, both dragons, tying a single id to “dragon” will not work, you will need to reserve a range of ids for dragons.
Like 1024 to 1033 are dragons, 1034 to 1043 are unicorns. But then you are limtied to ten minions of each kind per client. However doing this with regular ranges you can easily calculate some unique minion id (example above: (id-1024)/10 rounded) you can then easily use for spawning from an List containing the prefabs or whatever way you like. There are many possibilites.

As mentioned before, using this controllerId for picking the prefab to spawn is sort of an abuse in order to avoid some more complex system either in lower level or with additional Commands.

As to the spawning itself I’m not sure if Reosurces.Load is enough. Still have to experiment there myself. The Nework Manager might need the prefab reference before instantiating on both server and client - and I don’t know if the Spawnable Prefab list, to be filled in the editor, will do for player objects as well.

Maybe turn “playerPrefab” into a list and fill it with Resources.Load() on Awake on the server and all clients - or do it manually in the editor.

Best is to hope some Network Manager expert is around and dropping in here right now :smile:

Well…still playing around with the best possible way to accomplish this. ClientScene.AddPlayer() is proving to be better than requesting control of the object from the server via messages. However, still running into some issues. One of the smaller issues is that “short playerControllerId” can’t be any higher than 32.

Oh, I didn’t know about that limit, I thought you can use the full range of short. Sorry about that.

I just noticed my “minionPrefab” idea above will not work like shown.
There is a problem with prefab registration on the client.

Internally, NetworkManager is doing this:

        internal void RegisterClientMessages(NetworkClient client)
        {
(...)
            if (this.m_PlayerPrefab != null)
            {
                ClientScene.RegisterPrefab(this.m_PlayerPrefab);
            }
            foreach (GameObject current in this.m_SpawnPrefabs)
            {
                if (current != null)
                {
                    ClientScene.RegisterPrefab(current);
                }
            }
        }

So just creating a “minionPrefab” pointer to drag your prefab on will not work, as the ClientScene will not recognize it as spawnable object.
Maybe there is a chance it works if it was added to the spawnable prefab list instead.