Having some trouble making parts of game authorative (physics)

Hello everyone,

I’ve been designing my game as a non-authorative network game, but ran into some problems that I think can only be resolved by making the host semi-authorative.

The situation in a nutshell:
Each player is responsible for their own spawning. Players can walk around and shoot each other and buildings. When a player hits a building an RPC call is made to check that building’s HP. When that reaches 0, the building is Network.Destroy()'d and replaced by a Prefab containing chunks. These chunks are given a Convex Mesh Collider, RigidBody and NetworkView Component at runtime. After 10 seconds, 2/3 of these chunks are Network.Destroy()'d.

The problem:
While the mentioned Components are indeed added to each chunk, their location and rotation don’t get updated (even though the NetworkView is targeting the Transform Component). Testing over LAN, someone could move a chunk into the river, and that same chunk would lie 100m further away for me. Also, some chunks start moving vigorously, as if they don’t know where to go.
After 10 seconds, 2/3 of the chunks are destroyed for each player, resulting in 0 chunks left to play with.

I’ve tried some messing around with if(Network.isServer)and trying to only get the server to do the chunk-destroying, but I’m afraid I’m in over my head with this for now. As the deadline for this project is fast approaching, I’d love any and all help I could get.
What would be the best way to solve this issue?

Thanks in advance,
Patrick Boelens

Are the debris chunks basically just for show, or are they integral to the gameplay?

Hey Legend,

The chunks are definitely required to be in sync, as they will provide shelter and hiding.

I see. When the building hits zero hp and you replace them with the chunks, are you Network.Instantiating them? And if so, you are only running that code on the server, right?

Also, is it necessary you add the components at runtime? That seems a little silly, why not have the components right on the prefab?

Anyway, I’ve always had trouble observing the transform component of a rigidbody over a network, it seems like the rigidbody physics and the transform disagree on something when getting sync’d over the network. I would take the “NetworkRigidbody.cs” script from the Networking Example project in the resources section of Unity3d.com, put it on the chunk, and have the networkview observe that. Not only does it actually work, but it has some pimp interpolation and is waaay smoother than observing the transform component.

Who is owner of the building gameobjects?

Legend, thanks for that script; I’ll check it out ASAP. The components need to be added at runtime because each chunk Prefab (i.e. BuildingBroken01) contains about 30 chunk-meshes. Multiply that by 6 buildings, and you’d have ±180 chunks you’d need to prep with 3 components each. I figured doing a single for-loop to iterate through all the children and adding the components like that would be more (time-)efficient.

Stash, that’s a good question… As it stands now, I’m guessing it’s the player who shot the building? (player shoots building, building checks own HP, if less than or equal to 0 → Network.Instantiate())

Ewww, you don’t want to do that, I would handle that authoritatively.

Are the buildings already there in the scene off the bat, or are they instantiated at some point? If they already exist, then they are owned by the server, which is a good start. If not, when you do the network.instantiation of the buildings, make sure only the server calls that code, so he becomes the owner of the instantiated objects.

Once you know the server owns all the building objects, making it authoritative is pretty easy. I would do something like this (I’ll call this script BuildingStatus):

maxHP : float = 100.0;
currentHP : float;

Start(){
	if(Network.IsServer){
		//initialize hp on server, then tell the clients
		currentHP = maxHP;
		networkView.RPC("SetHP", RPCMode.Others, currentHP);
	}
}

//only ever call TakeDamage on the server
@RPC
function TakeDamage(damage : float){
	currentHP -= damage;
	networkView.RPC("SetHP", RPCMode.Others, currentHP);

	if(currentHP <= 0)
		DestroySequence();
}

//tells connected players how much hp we have
function SetHP(hp : float){ 
	currentHP = hp;
}

function DestroySequence(){
	//Network.Instantiate all your chunks here
	Network.Destroy(gameObject);
}

Probably similar to what you have, but you want to make sure whenever a player shoots a building, he sends the TakeDamage RPC only to the server, something like this:

function OnCollisionEnter(collision : Collision){
	if(collision.gameObject.GetComponent(BuildingStatus)){
		collision.gameObject.networkView.RPC("TakeDamage", RPCMode.Server, damage);
	}
}

This way, the server is the only person that is ever running the TakeDamage function, and hes just updating the resulting hp for all the clients after he subtracts it for himself. Also, this way, he is the only person that runs “DestroySequence()”, in which you want to Network.Instantiate all the chunks. That way, he is the owner of the chunks in all instances of the game, because he’s the person that instantiated them.

Once you do something like this, if you attach NetworkRigidbody.cs and a networkView observing it when you instantiate each chunk in DestroySequence(), the physics should sync pretty well I think.

Thanks again for your help. =) I think it’d work really well, but I’m running into a strange little problem. The @RPC-call is made to the Server when a building is hit, but it doesn’t execute. If I enter an incorrect number of arguments it will give me an error as expected, so it does recognize the function. I replaced the TakeDamage function to only do a Debug.Log for now, but that fails as well. In the buildings Start() function I decided to throw in the following line: if(Network.IsServer) Debug.Log("Buildingscript acknowledges me as Server");
This Logs as expected. Am I missing the obvious here? =/

Hmm, I was afraid of something like that, I wrote this stuff off the cuff at work, hehe. I think the problem might be that in the collision code I sent you (I assumed you were using projectiles or something), I was trying to send an RPC to a different object and a different script, and although the docs seem to imply you can do that, I don’t think you can. I think you can only make RPC calls to RPC functions defined in that same script.

In that case, you probably want to overload the TakeDamage RPC in a regular function… so you can call the RPC from a different script. So it would be something like this:

function OnCollisionEnter(collision : Collision){
	if(collision.gameObject.GetComponent(BuildingStatus)){
		collision.gameObject.GetComponent(BuildStatus).TakeDamage(damage);
	}
}

And then in BuildStatus, you would do something like this to overload it:

maxHP : float = 100.0;
currentHP : float;

Start(){
	if(Network.IsServer){
		//initialize hp on server, then tell the clients
		currentHP = maxHP;
		networkView.RPC("SetHP", RPCMode.Others, currentHP);
	}
}

function TakeDamage(damage : float){
	if(Network.IsServer)
		NetworkTakeDamage(damage);
	else
		networkView.RPC("NetworkTakeDamage", RPCMode.Server, damage);
}

//only ever call TakeDamage on the server
@RPC
function NetworkTakeDamage(damage : float){
	currentHP -= damage;
	networkView.RPC("SetHP", RPCMode.Others, currentHP);

	if(currentHP <= 0)
		DestroySequence();
}

//tells connected players how much hp we have
function SetHP(hp : float){ 
	currentHP = hp;
}

function DestroySequence(){
	//Network.Instantiate all your chunks here
	Network.Destroy(gameObject);
}

Ah, thanks for the solution! =) Too bad you can’t RPC external scripts though. I won’t be able to try out the syncing until tomorrow, but so far it works and looks clean. Thanks!