- Home /
Unet - NetworkServer.Spawn() correct usage
Hello, I am working on an RTS game: I have a gamelord class (attached to its own object) that is supposed to handle the status of the game, then i have a playerController class that handles player actions and is attached on the player prefab, which gets fed to the network manager and spawned correctly when a new client connects to the game. The idea would be to centralize all gameplay actions into the gamelord and separate all the local stuff that needs to happen to other classes.
The gamelord is "unique" in the sense that it's meant to hold the same information on each client, most of its variables are syncvars, and all of its functions are supposed to be things that should happen on the server and then echo on every client, so stuff like creating a unit or building , keeping track of winning/losing conditions, etc.
playerControllers on the other hand handle the local player's input and then translate that input into game actions by calling the appropriate functions on the gamelord. So if the player builds something, the playerController class would do something like "gamelord.Build(something)" and then the gamelord would actually instantiate and spawn that something.
So, in code, here is what i am trying to do
on the playerController :
[Command]public void CmdCreateBuildingHere(Vector3 pos,GameObject what)
{
//some other non-related code
gameLord.SpawnBuilding(playerId, pos, what);
}
then, on the gamelord
[Server]
public void SpawnBuilding(string owner,Vector3 where,GameObject what)
{
GameObject newBuilding = (GameObject) GameObject.Instantiate(what,where,Quaternion.identity);
RTSGamePiece newbuildingCtrl = newBuilding.GetComponent<RTSGamePiece>();
newbuildingCtrl.owner = owner;
NetworkServer.Spawn(newBuilding);
}
This approach does not work, as it behaves correctly only on the host, while if the client builds something, it won't show on the server.
On the other hand, moving the SpawnBuilding() lines of code directly into the first function and forgetting about the gamelord does make it work, but architecturally it's not what i meant to do, and it seems less logical to give more weight to the player object in an RTS where the important objects are not the player objects (like in FPSes).
All the objects that i want to spawn are already registered in the netmanager (i did it manually via the editor, the prefab for the netmanager already contains all the prefabs it needs in the "spawn info" section), so that should not be the cause.
My question is: Considering the constraints of Unet (some of which i am aware, such as commands only working on playerObjs or prefab registering, and many of which i'm not) ,is there a way to do this in the way i wanted it to?
Specifically:in the client, have the playerController class calling a function in the gamelord, and then on the server have the gamelord actually instantiating the object and spawning it across the network.
Should i rethink my approach? Am i misusing Commands and the [Server] attribute? Is there something about NetworkServer.Spawn that i am missing?
Thank you for your time, Dario.
Are your scripts are NetworkBehaviors and not $$anonymous$$onoBehaviors?
Answer by meat5000 · Jul 28, 2015 at 07:32 PM
Here's an example.
@Command
function CmdSpawnOnNetwork()
{
var myPattern = myGun.bulletPattern;
var projectileObject = Instantiate(myGun.bulletType, Vector3(transform.position.x, transform.position.y, transform.position.z), transform.rotation * Quaternion.Euler(0, 180, 0));
NetworkServer.Spawn(projectileObject);
projectileObject.AddComponent(SwerveShot);
}
Notice its @Command
(`[Command]` -C#) not @Server
.
@Server
specifies a function to run on server only.
The Command attribute is to invoke a method on a server from a client.
Spawn your object, hold its reference, use that reference to perform actions on that object. You seem to have done all this.
Just change [Server] to [Command] and make that one your Cmd function. If you want to call a function on a client only use [Client]
@Client
public function ShootButtonPress() :void
{
CmdSpawnOnNetwork();
}
on a function or
if(isClient)
within one.
In the above code, my Button press fires off the Client function which in turn invokes the Cmd Command function, which is the bit that does the networking stuff.
The object you spawn needs to be a registered prefab. If its not you can register it with ClientScene.RegisterPrefab or something like. I've never used it, yet. You can register a SpawnHandler for objects with no prefab
http://docs.unity3d.com/ScriptReference/Networking.ClientScene.RegisterSpawnHandler.html
He did use [Command] in his code. The function that has [Server] was called from the one that already has [Command]. I'm not sure whether that [Server] tag is necessary, but I'm not sure if it's wrong either.
All of the prefabs are registered already, so that's not the problem.
$$anonymous$$y problem is that the second function only gets called from the host's local client, and not from remote clients. If the host builds something, it does work and it does spawn across the network. If a remote client tries to, it won't work because the second function does not get called. Why?
meat5000: i can't change the second function from [server] to a [Command], because as per documentation [Command] is something you can use only within player objects, and my gamelord code (second function) exists within its own object that isn't owned by any client.
nisovin: the [server] tag in the second function apparently does not make any difference, as the problem remains the same with or without it, so i thought since that function actually has to run only on the server, might as well put the tag.
Answer by sharat · Sep 24, 2015 at 02:51 PM
I don't think you can pass a GameObject as an argument to a Command function. You can only pass in simple arguments, NetworkIdentity's, and NetworkBehaviors to Command's/ClientRpc's. Also, I'm assuming this is a prefab you are passing in, so I don't think that NetworkBehavior's or NetworkIdentity's will work on it since they wouldn't have an instance id set by the server.
Instead, I would have some sort of helper function that can change the prefab reference to a index of some sort. Here's an example:
[Client]
public void CreateBuildingHere(Vector3 pos, GameObject prefabObject)
{
int prefabIndex = NetworkManager.singleton.spawnPrefabs.IndexOf(prefabObject);
CmdCreateBuildingHere(pos, prefabIndex);
}
[Command]
private void CmdCreateBuildingHere(Vector3 pos, int prefabIndex)
{
//some other non-related code
GameObject prefabToSpawn = NetworkManager.singleton.spawnPrefabs[prefabIndex];
gameLord.SpawnBuilding(playerId, pos, prefabToSpawn);
}
Keep in mind, that this example may have problems if you are dynamically registering prefabs to your NetworkManager. You may want to have your own prefab indexing system instead of using the list of spawnPrefabs.
Answer by Lnoldori · Nov 27, 2015 at 10:24 AM
To @grandmapuckett I saw that you had already check the documentation but in order to have it as an answer here i give the following.
For anyone still having a similar problem although it has been answered already in a lot of other places.
From Unet Documentation : Remote Actions
Commands
Commands are sent from player objects on the client to player objects on the server. For security, Commands can only be sent from YOUR player object, so you cannot control the objects of other players.
The problem is that this will work for a client which is a host also, but not for a remote client. I think unity should warn about this and not let us think that is permitted.
To @sharat
Arguments to Remote Actions
GameObject with a NetworkIdentity component attached
I am a little bit late to the party but I am not sure if this
GameObject with a NetworkIdentity component attached
is also valid for prefabs or only for already instantiated gameobjects.