I’m using Client Network Transform on all of the enemy prefabs which are spawned via a server authoritative enemy spawner.
Whenever a projectile hits an enemy-inheriting object, the enemies onHit event is triggered. In this event two actions are performed: triggering the enemies Hit() function which handles the change of state / health, and second, the impact particle effect.
Now, each client and the server can reduce the health and kill the enemy and the impact is also shown for both players. So, I know that these functions are getting called. However, during the enemy state change I also apply a force/pulse upwards to produce this flinch aggro jump effect. I can’t seem to find the reason why only the server can apply this pulse force. Any help?
Note: the ImpluseForward() function that I call below is not an Rpc. I want the impulse to not be passed through the server and propagated to the clients via a custom Rpc. Instead, I want the Client network transform to handle the physics changes, movement, etc. I had the former approach working, but the flinch appeared to be delayed compared to when the impact occurred.
// Hit function on the Projectile
private void Hit(EnemyBase targetHit, Vector3 positionHit){
StopMoving();
targetHit.Hit();
GameObject impact = Instantiate(impactPrefab, positionHit, Quaternion.identity);
impact.transform.SetParent(targetHit.transform, true);
Destruct();
}
// What happens when targetHit.Hit(); is called from above
void EnemyHit(){
if (currentState.Value == RatState.Idle){
SetRatStateRpc(RatState.Aggro);
}else if (currentState.Value == RatState.Aggro){
SetRatStateRpc(RatState.FirstHit);
if (playersInVicinity.Count != 0) // ImpulseForward Not an RPC
ratMovement.ImpulseForward(playersInVicinity.Last().transform.position);
}
else if (currentState.Value == RatState.FirstHit){
SetRatStateRpc(RatState.SecondHit);
if (playersInVicinity.Count != 0) // ImpulseForward Not an RPC
ratMovement.ImpulseForward(playersInVicinity.Last().transform.position);
}
else if (currentState.Value == RatState.SecondHit) {
Die();
}
}
When you spawn objects their default owner is the server. This means your client authority transform is in fact a server transform. I suppose it’s the server who makes the enemies follow a player?
I wouldn’t use a NetworkTransform, instead you can use a NetworkVariable to synch the position (likely only X/Z so Vector2) and apply these values locally for every client every update. Then you are free to modify the Y value of each enemy on every client without even having to synchronize this across the network. After all, every client already got the info “enemy got hit” so they all apply the same upward force while the server applies a faster forward motion for a short time.
I’m not entirely confident that ignoring the synchronization of the Y would be safe. Consider a level with varying heights and platforms. The XZ are synched but for the server, the enemy could be underneath a platform and on the client the enemy could be above the same platform. I imagine a scenario where the client and server see the same enemy but at different Y heights. I might be wrong in my thinking that the position could be desynchronized along the Y, causing tricky behaviors like this. Let me know what you think on this.
Also, is there any way to allow for the client to modify the position of the enemy prefab? Or is this not possible? I thought that was what the Client Network Transform was created for?
I actually just discovered a small bug in my implementation where the players within the vicinity of the enemy are only tracked locally in a list of GameObject references per client. It seems that the most recent player to enter the vicinity of the enemy will be the target which it follows, then the movement is calculated each frame locally. I suppose since the server is also calculating the movement of the enemy, it is the server who has the control of the movement and the client’s contribution to the movement is ignored? I need to investigate this further, although I haven’t experienced any weird behaviors because of this.
I’m having such a hard time figuring out how to sync these movements so that they are snappy for each client. I don’t care if the positions of the enemies are slightly off and I don’t care if I have to lerp the clients’ enemy positions to match with the server enemies positions, but I really want the knockback effect to be snappy and physics based. The basic idea to solve my problem based on your suggestions:
Spawn enemies via server without network transform.
Sync their movements through the server using Vector2 or Vector3 for the y axis.
Clients will lerp their current enemy positions to the real one represented on the server.
Physics effects like knock back can still occur as long as there is some lerping mentioned above to reconciliate the non-deterministic physics errors.
My only issue with this is the fact that I will be doing some kind of reconciliation which will probably mean that I have to directly modify the transform position of the enemies. This feels like it will cause problems with the desired physics effects like knockback and gravity etc.
If you want snappy responses you have to implement client-side prediction of the effect.
I would split the visualization of the enemy from its transform. You could trigger an animation with root motion but that means creating such an animation in blender. The alternative is to, for the duration of the effect, move the enemy locally by each client and then have it synchronize with its authoritative position again.
You could achieve this with each enemy having an empty object with networktransform, and each client then spawns the visualization of the enemy with a script on it that synchronizes the visual enemy position with the networked object‘s position. This allows you to ignore the network position for a short time. Afterwards or generally the position synch could be lerped rather than applied directly so any offset position doesn‘t just snap after the effect.
Okay I think I understand now, that seems like a really good approach. I do believe I will have to create a priority system to adjust for competing movement; A system where knockback takes priority and can also cancel the reconciliation would allow for both actions to occur without competing with each other.
Thank you so much for you suggestions, this makes things easier
Now that I think more about it, I don’t think I need prediction. My game is a co-op shooter, which means that players wont be competing for projectile accuracy / winning PVP fire fights. If projectiles and enemies are slightly off between players, but each player can at least hit generally the same targets, then I think reconciliation is all I need. As long as the enemies are close enough, then the projectiles will look like they’re hitting near the same location.
If for some reason, the projectile doesn’t line up with the enemy exactly - like in the case the client shoots an enemy and the server sees the projectile hit a foot off, i don’t think people are going to complain and make a riot that the game is janky or desynched or something. I think players will be more concerned with not dying instead of exact accuracy of other-client projectiles.
Just some thoughts for anyone reading that exact accuracy in a game like this is probably no necessary.