Predicting is a vital and complicated feature in multiplayer games. It helps to create a better gameplay feel since you can see the results of your input immediately and wait for the server confirmation later. Among many cool features in GAS, the prediction is easy to set up for abilities and gameplay effects.
### Prediction in GAS (in a nutshell)
The prediction technique behind the curtains is the following:
1. The ability activates and creates a prediction window
1. The window is "closed" for many reasons, but the naive rule is that the prediction window closes when any latent node/action is executed in the graph.
2. During the active prediction window, anything predictable creates a Prediction Key and binds itself to it
3. When the same prediction window executes on the server, it confirms or rejects all the predictable keys related to the window.
1. If the key is confirmed, it means we don't need to change anything
2. If the key is rejected, we must revert all the related changes to the predicting client.
### Predicting Gameplay Effects
When we talk about predicting gameplay effects, we are interested in how they work with different duration policies: infinite, with duration, and instant.
- Non-tricky part
Infinite mode creates a gameplay effect and doesn't care when the effect is terminated. So, when we receive the prediction key rejection, we need to undo gameplay tag changes and attributes.
The "With duration" mode handles the situation the same way. Of course, we will have some data resyncs in the future, but that's a by-product of the enabled prediction.
- Tricky part
The tricky part starts when we work with instant effects and effects with short durations (less than ping). Let's start with the latter.
#### Short Duration Effects
If we predict an effect on the owning client, and the duration expires before we receive the confirmation, we have two cases:
4. We received confirmation
We predicted correctly. No problems. Move on.
5. We received rejection.
This means we mispredicted and have to undo all the modifications to the state. But since the effect has already ended, the state is already restored. That means we had the wrong state for some period of time, and the correct one will sync from the server as soon as possible. Again, it's not very pretty, but we still have no issues.
#### Instant Effects
The idea for this post emerged when I was thinking about what happens when instant gameplay effects are mispredicted.
When we have a temporary state with timed gameplay effects, the state can be restored since we have an entity to handle the "rejection" process. But Instant gameplay effects are created, applied, and destroyed, so there is no entity to handle rejection.
![[rejected.gif]]
So, how does prediction work with instant gameplay effects? 🤔
To my surprise, it works the same! But with a trick.
What's the problem with instant gameplay effects contrary to timed and infinite ones?
We have no entity to handle server rejection.
What's the easiest way to fix it? What if we create a new entity to handle rejection? No, no, no.
![[nonono.gif]]]
If we introduce the new entity, we need it to support all the functionality of Gameplay Effects—that's a no-go. We don't want such a duplication.
> [!NOTE]
> The following code you can find in ***ApplyGameplayEffectSpecToSelf*** method in
> *Engine\Plugins\Runtime\GameplayAbilities\Source\GameplayAbilities\Private\AbilitySystemComponent.cpp:797*
The easiest way is to prolong the effect lifetime to...\*check notes\* infinity.
```cpp
...
// Clients should treat predicted instant effects as if they have infinite duration. The effects will be cleaned up later.
bool bTreatAsInfiniteDuration = GetOwnerRole() != ROLE_Authority && PredictionKey.IsLocalClientKey() && Spec.Def->DurationPolicy == EGameplayEffectDurationType::Instant;
...
```
When we apply an instant effect, we check whether this effect is in a prediction state.
If yes, we execute the effect...
```cpp
if (Spec.Def->DurationPolicy != EGameplayEffectDurationType::Instant || bTreatAsInfiniteDuration)
{
AppliedEffect = ActiveGameplayEffects.ApplyGameplayEffectSpec(Spec, PredictionKey, bFoundExistingStackableGE);
...
}
```
and change its duration mode to infinite.
```cpp
if (bTreatAsInfiniteDuration)
{
// This should just be a straight set of the duration float now
OurCopyOfSpec->SetDuration(UGameplayEffect::INFINITE_DURATION, true);
}
```
In such a way, we preserve the effect instance until the server response arrives. Easy-peasy.
## Final
A small hack makes the prediction of an instant effect possible without any extra entities to handle the lifetime and predicted state.
#gas #gameplayabilitysystem #unrealEngine #cpp