- Home /
[UNet] How to initialize non-local player?
I'd like to initialize differently local and other clients' players. And apparently there is no easy way to do it.
For local player I just override OnStartLocalPlayer and everything is cool. But what about players from network? For example if I want a different instantiate animation for local players and network players? Where should I put "StartAwesomeNetworkBzzzztAnimation()"?
I cannot do it in OnStartClient because the isLocalPlayer flag is not set yet.
Answer by thegreatzebadiah · Nov 16, 2015 at 01:49 AM
I think I more or less solved this by using the low level api to send a message to the server any time OnStartClient is called. The server listens for the message and sends a ClientRpc back that calls OnStartNonLocalPlayer:
In my Player script:
public override void OnStartClient()
{
networkManager.client.Send(MsgType.RequestNonLocalPlayerInit, new NetIDMessage(netId));
}
[ClientRpc]
public void RpcStartNonOwner()
{
if (!isLocalPlayer)
{
OnStartNonOwner();
}
}
public void OnStartNonOwner()
{
// Do the thing
}
And then in my NetworkManager
public override void OnMatchCreate(CreateMatchResponse matchResponse)
{
base.OnMatchCreate(matchResponse);
if (matchResponse.success)
{
NetworkServer.RegisterHandler(MsgType.RequestNonLocalPlayerInit, OnRequestNonLocalPlayerInit);
}
}
public void OnRequestNonLocalPlayerInit(NetworkMessage msg)
{
NetIDMessage idMsg = msg.ReadMessage<NetIDMessage>();
GameObject player = ClientScene.FindLocalObject(idMsg.netId);
player.GetComponent<PlayerNetworked>().RpcStartNonOwner();
}
I think this may be a better solution than the one @seanr posted because it handles cases with more than 2 players. @seanr's solution won't work since ClientRpc's aren't buffered. When the first client connects to the server, the player spawns and the rpc is sent, but when a second client connects they will never receive the rpc to initialize the first non-local player since it was sent before they connected.
That's nice solution and pretty straightforward. The main problem with it is that OnStartNonOwner is not guaranteed to run on the first frame Player is enabled (and problably usually doesn't). On laggy network (mobile for example) it can result in spawn animation playing long (even a second?) after new players shows up.
If you're worried about the delay you could alternatively do something similar to what @seanr suggested, as long as you make sure to re-send the rpc every time a new client connects. That way the message gets sent out immediately after the client connects rather than waiting for the client to request it. Of course all the already connected clients will receive the rpc every time a new client connects but that's easy enough to check for and ignore. Unfortuntely I can't test it at the moment to make sure it actually works or I'd post it as another answer.
Answer by seanr · Oct 28, 2015 at 03:32 PM
There is no built-in hook for that. The best solution is probable to invoke a ClientRpc after the player object is spawned, and run the instantiate animation in that handler - but NOT if isLocalPlayer is set.
I was thinking about just waiting one frame after OnStartClient (with coroutine) and then checking isLocalPlayer. But it still looks like an ugly workaround.
that would not be guaranteed to work - for ti$$anonymous$$g reasons.
How so? As far as I know OnStartClient and OnStartLocalPlayer launches in the same frame one after another, so after one frame I'm sure that isLocalPlayer is set. Or is it possible that those methods do not run in the same frame?
Doesn't this not work since ClientRpc's aren't buffered? When the first client connects to the server, the player spawns and the rpc is sent, but then when a second client connects they will never receive the rpc to initialize the first non-local player.
Answer by francesco_dente · Oct 29, 2015 at 04:44 PM
I solved the problem by initializing a generic network player inside of OnStartClient() and then overriding the local player settings inside of onStartLocalplayer(), but I think this is also not a great workaround.
Answer by BlackManta · Oct 14, 2017 at 08:42 PM
I think you can make the assumption OnStartLocalPlayer will happen fairly quickly after OnStartClient. If you do that you can use coroutines.
private bool isAllSet = false
public override void OnStartLocalPlayer()
{
//reset parameters for local player
}
public override void OnStartClient()
{
//Assume that this is going to be a remote machine
//Set parameters for remote
StartCoroutine(AllSetup());
}
private IEnumerator AllSetup()
{
//Need to wait to see if the local player function triggers
yield return new WaitForSecodns(1); //or some other time.
//Now determine if you are local player or not.
if (!isLocalPlayer)
{
//Local Player Animation
}
else
{
//Remote Player Animation
}
}
Also I think you could use async if you are using C# 5. However, I would have to play around with that one for a bit.
Answer by hexagonius · Jan 28, 2018 at 08:12 PM
These are my solutions:
In case you just want a call to non local players without receiving info from the actual localplayer:
void Start() { if (!isLocalPlayer) InitNonLocalPlayer(); }
void InitNonLocalPlayer(){}
In case you need information about the actual LocalPlayer on the nonLocalPlayers:
void OnStartLocalPlayer() { CmdInfoToNonLocalPlayers(); }
[Command] void CmdInfoToNonLocalPlayers() { RpcInfoToNonLocalPlayers() }
[ClientRpc] void RpcInfoToNonLocalPlayers() { if (isLocalPlayer) return;
// Do what you need to do on nonLocalPlayers with info }