Removing the apperance of lag in a coop game using "Dynamically Controlled AI"

NOTE: This technique only works in a Non-Authoritative networking situation

While working on a current project which is an online coop game I was running into significant lag issues which made the gameplay not very fun. Basically a master client or server controlled the AI for all AI components in the game, but when these AI controlled characters would attack a remote client, the remote client would get hit at times when on their screen it seemed like the AI controlled character was much too far away to actually hit them. In shooting games this isn’t as apparent as it is in a game like I am doing right now, where the AI controlled characters needs to physically be close enough to hit you.

The Problem

Here is an example of the problem:

The remote player sees themself at T0 which is now. The server receives the remote players position at T1 which is 100ms behind because of latency. The server could just display the character at T1 but that would look jerky as these updates could come in at less than 10 times per second, so the server interpolates the character from T2 to T1 over 100ms, making the player position on the server 200ms behind in time from where the remote player sees himself.

Remote Character: T2------100ms------>T1----100ms------->T0 (Now)

Now in a coop game this is pretty detrimental because in 200ms the remote character could be well out of range of any attacks that might be done by a server AI controlled character, thus making it look very strange if they are hit.

Using Dynamically Controlled AI

The way I was able to solve this issue was by using what I’m calling “Dynamically Controlled AI” ( I have no idea if other people use this technique or what it should actually be called). With dynamically controlled AI, whenever an AI character determines who his target should be, the AI player checks if the target is owned locally or by a remote player. If the target is owned locally, we continue to let the AI do its thing, HOWEVER if the AI determines the target is a remote player, we then send an RPC to all players in the game to tell them that AI is now being controlled by the target player, instead of by the owner of the AI character.

Now what this does is makes whoever the AI should be attacking, the controller of the AI, which in turns makes the latency 0ms between the AI controlled character and the remote character, as the remote character now controls him. Now the all other players besides the controlling remote character see the AI controlled character with lag, but this is inconsequential as they are not currently being attacked.

By doing this whoever the AI is attacking will notice no lag, which is important as they are the ones who will be receiving hits from the AI.

I hope this idea can help some other people struggling with AI controlled characters over a networked game, as it really improved the gameplay of my current project!

What if I want to attack an enemy that is attacking another player? Won’t it appear to be within range to me in my simulation, but not take the hit?

Hm, this feels very fragile I have to say. But it’s harder to comment on how it could be solved without more details about the game. (FPS, RTS, etc.) ?

I would guess its like a third person melee combat game, judging from the proximity requirements.

In the case of the game I’m working on, when a local player shoots or attacks another character, the checks are done locally and results sent over an RPC, so this is unaffected by switching who controls AI players.

I should also mention that when I make the switch over who controls the AI player I had to create a proxy component, which then takes over state synchronization for that AI player and turns off the normal component which deals with state synchronization, as you can’t switch who owns a NetworkView in Unity or in PhotonNetwork plugin.

The game type im working on right now is a zombie shooter.

Instead of trying to solve the problem when it appears I guess it would be more effective trying to solve the problem before it appears.

AI movement is alot predictable.
Deterministic algorithm - Wikipedia for a game that means each client moves the units on his own and the server just says “hey you shouldn’t be there” if he informs the server on a wrong position.

As addition you could add +1/2 range units when he selects his target so you have a small buffer to inform other players of his target change witch makes him predictable again.

But you should spoiler more information about the game for precise awnsers.

This is what I would propose, the reason you need to “replay” player movement with some interpolation offset is because it’s not possible to predict (in most cases). But for AI movement you can just give them a tiny buffer or let them move a tiny bit in the future, and you wont have this problem.

I actually considered this at first, but AI in many cases has randomness to it, and variables which may not be entirely possibly to synchronize and predict between clients. I also have been testing this alot, and it works really well, the only hiccup is the transition when the player controlling the AI changes. In cases of lag over 200ms sometimes the positions of where the AI player were on the previous controller and the next controller vary pretty heavily so the movement during the transition seems strange, however with some cleaver interpolation during the transition this may not be an issue at all.

Also there is a major issue with trying to roll back or move forward in the physics system at will. I tried at one point to make a fully authoritative server with my previous game, but realized after I had finished most of it, that the physics system does not update the positions of colliders as you change their transform positions. This appears to be done only once every physics step. Making it impossible to roll back time or predict into the future using any of the physics system.

So after working with this for about the whole day. I can safely say this has improved multiplayer playablility and feel 100% in my game. Although it was fairly complicated to get this working.

What I ended up having to do was have a Proxy NetworkView and component instantiate when any AI controlled object spawns. Then if the controlling player changes to someone who doesn’t own this NetworkView the proxy object is told to take over, and a fairly complex system of synchronizing to the last known sent state from the previous owner starts. However once the synchronization and transition to the new owner is complete, the results are really great with the AI controlled objects not appearing to have network lag if they are attacking you, as they really are being controlled by the local client. This really isn’t necessary on games where the AI shoots primarily as its way of attack, but with the AI using melee attacks this really does change the feel of the game greatly.

Good you found a solution, as there is never a perfect solution vor every game. Guess thats what it makes interesting.

May you tell me for the performance how many units you have wandering arround? As my game is “similiar” (mostly melee, ai, multiplayer) but full authoritative. And I got alot of units running without problems but its just a prototyp yet so there’s going to come more network data.

I can spawn as many as I want, and while testing using Photons Cloud servers that are in the netherlands, it worked very well with upwards of 20 ai characters.

I have found that fully authoritative games with AI can’t use this solution at all. Case in point would be when I tested Valve’s “Alien Swarm” on higher ping servers. I would get killed by random attacks from enemies which did not appear to be in range at all, making playing with anything about around 180+ ping very frustrating. So unfortunately I believe the only solution to good coop multiplayer using a fully authoritative server would be having good ping between the server and players.

An easier solution that lets you keep the authority on the server would be to have the server send an RPC to the client to start the enemy attack routine, then have the server wait for however many ms that target NetworkPlayer’s ping is (within reason, you don’t want to wait 500ms for ridiculously laggy clients), then run the attack routine on the server. That way they should happen at roughly same time.

This seems by far easier and more reliable to do.