Multiplayer / Networking

Mirror Integration (Networking):

  1. Import Mirror to the project or GAS to a project already containing Mirror.

  2. Attach an AbilitySystemComponentMirror to any ASC that needs to be networked. They will automatically synchronize.

The networking for the ASC needs: Replication and Prediction.

Video showing Replication and Prediction.

Replication

Means that whatever happens on the server is replicated/mirrored/copied back to the clients.

Prediction

Means that whatever the client does, it will predict changes immediately, and send the action for the server to process.

The server is the source of truth. So if a prediction is incorrect the client must undo its prediction. It will be undone (reconciliated) when the server replicates to the client what really happened.

We need to do replication and prediction at 3 levels: GAs and GEs and Attributes

GA

GA Replication

On the GA, we only replicate the GA's state. Meaning, if it is active or not.

GAs from other clients (non local player) have their GEs stripped away and so they don't apply their GEs on activation replication, but still execute its custom code (e.g. spawning projectile) and trigger its cues and events (OnGamplayAbilityActivated, OnGameplayAbilityDeactivated, OnGameplayAbilityActivationFailed).

GA Prediction

When the client activates an ability, the effects, tags and attributes are modified immediately in its local simulation. An activationGUID (a.k.a Prediction Key) is generated and added to a buffer queue.

The activationGUID is sent to the server.

The server activates the ability on its own simulation, assigns it the same activationGUID and send that to all clients for replication.

The client receives the replication, it checks if the activationGUID is present in the prediction buffer. If present, dont replicate (it was already predicted), else replicate it.

Server Reconciliation (Ability UNDO)

If the server does not activate the ability, after its activation was locally predicted, we need to revert it. The server will send an RPC when OnGameplayAbilityActivationFailed triggers. When the client receives that, it searches the GA that failed on server, disable all GEs associated with that activation and reset attributes to their last replicated value.

GE

GE Replication

We do the same as GA, but this time we strip away unpredictable calculations and edit chanceToApply.

GE Prediction

We do the same as GA, but our prediction buffer uses a different key. The GE EffectBufferKey is a string that contains the ge.applicationGUID and its index on the GA that activated it. That is so we can track which GEs should be removed on GA failed activation.

Calculations cant be predicted if they have a random component to it. (e.g. Min-Max damage). For now we are also not them on local client and stripping them away from local client GEs. That might change in the future and become a canPredict boolean.

Chance To Apply effects can't be predicted. The server must be the source of truth that decides if they were activated. We set chanceToApply to 0 on all local GE's that have chanceToApply != 1.

Attributes

Attributes have a queue prediction buffer, meaning their prediction will add the new value to a sequence of expected values.

When the server replicates an attribute value, we check if that was the next expected value from our prediction queue.

If any value in the predicted sequence is different, it means our prediction was wrong. We clear the prediction queue and assign it to the latest value replicated/received from the server.

NullReferenceException

The most common reason these happen is due to non-empty lists with null objects trying to be serialized. Make sure to check that your list is either empty or has actual objects.

Additional Notes

Late joiners synchronization is supported. Meaning if a player joins a ongoing match/simulation, he gets his state synchronized correctly.

Sometimes, an ASC from a GA activation or GE application is not present on client (e.g. Area of Interest management from Mirror, it culls networked objects that are distant. But the source ASC can be in range, while the target ASC can be out of range). In these cases, we have checks for null ASCs on the network synchronization, and we try to prevent error references.

The bandwidth usage of the ASC Mirror networking is already super low. Even for an action game with dozens of GAs and GEs being activated per second, it stays under 1~2kbps per client. But if you want to go even lower than that, there is a way. You must have all possible GEs in a list, and then make a custom serializer for GameplayEffects that just sends the index of that GE in the list.

Cooldowns

Since the server runs on an specific tickRate, an ability that is out of cooldown on the client can still be on cooldown on the server. Even with the delay from the client activation to the server replication. To workaround this, you can add a server side check on your GA's cooldown to add a tolerance time. For example:

public override float GetCooldownRemaining() {
    if (NetworkServer.active)
        return this.timeActivated + cooldownDuration - Time.time - NetworkServer.tickInterval);
        //or
        return this.timeActivated + cooldownDuration * 0.9 - Time.time );
    else
        return this.timeActivated + cooldownDuration - Time.time);

Testing with multiple open editors (multiplayer):

Make a "Project_Symlink" folder. Then symlink Assets and ProjectSettings from the symlink folder to your original project folder:

mklink /D e.g. mklink /D "D:\UnityProjects\GASify_Symlink\Assets" "D:\UnityProjects\GASify\Assets" mklink /D "D:\UnityProjects\GASify_Symlink\ProjectSettings" "D:\UnityProjects\GASify\ProjectSettings"

Open the "Project_Symlink" in a new editor instance.

Last updated