Answering my own question, for anyone struggling with this:
- Regarding the automatic spawning of a "player" prefab, which the network does.
In fact, you will get one for each player. (Seems obvious once you say it.) There will be a whole pile of these all over the place.
So here:
-client
server -client
-client
in fact you will see three of the spawned prefabs.
(Aside: in my example I'm using the server as a "server", not a "host" ..

if you were using the "host" concept, there would be yet another spawned player prefab in there.)
So when you launch the app, only one PC, you make it the server, no connections yet ..

then you launch a second PC, the first client, they connect ... it now looks like this

then you launch a third PC, the second client, they connect ... it now looks like this

.. and so on.
So you have one of the cloned player items for each client. (Again, if you use the "host" concept for the server, that just means there will be another client there on the same physical box as the server.)
Each and every "player" has its own auto-spawned player object - and that is on ALL the devices. Five clients, there will be five auto-spawned player objects on EVERY device (on all six devices).
So what caused the specific bug mentioned?
The hidden surprising difficulty in Unet is the .isLocalPlayer
concept.
Say you do have a script like Comms
in the example which is on the player prefab, which seems a natural thing to do. Say some other part of the game has to reach out to Comms (to say send a message to the server).
In fact, you must find your 'own' player prefab ....
You must find your 'own' player prefab ....
You may invisage having code something like this ..
// laser blows up
// tell Comms to send a message to the server about that
// find Comms ..
Comms co = FindObjectOfType<Comms>();
// tell it to send the message
co.TellServerLaserBlewUp();
However this is wrong and debugging will be incredibly erratic.
In fact your code would look like this:
// laser blows up
// tell Comms to send a message to the server about that
// find >> OUR << Comms ..
foreach (Comms co in FindObjectsOfType<Comms>() ) {
string ilp = co.isLocalPlayer ? "YY" : "NN";
Debug.Log("I tried one of the many Comms! .. " + ilp);
if (co.isLocalPlayer) {
Debug.Log("Found it!");
co.TellServerLaserBlewUp();
}
}
(Note - obviously, you wouldn't Find something like this every time in production, you'd use a singleton or whatever your cup of tea is.)
Consider your script that is "on" the player prefab. In our example it is "Comms.cs".
In most unity examples they envisage you doing stuff "in that script".
In that case it is more intuitive: you just break away if you are not .isLocalPlayer
.
However this is very naive, in practice your zillion scripts around the scene will want to / need to contact your "Comms.cs" and have it do things (since it is the only one that connects to the networking.
In fact,
you do have to find "your" .isLocalPlayer
at all times.
Tricky!
To repeat - Unity doco examples tend to emphasize doing things "on that script". In practice on any large real world project it's not like that, you are "contacting" that script as a central communications resource.