This project contains a small sample for demonstrating Unreal Engine bug UE-78453 and several workarounds for the bug when using the Gameplay Ability System.
This bug happens when all of the following conditions are true:
- This is a multiplayer game.
- There are multiple character pawns among which the player can switch control at any time; OR the player switches between controlling a floating camera and controlling a character pawn.
- A character has been granted an ability.
- The ability is initiated from the server side.
When these conditions are true, there is a difference in behavior between what a client sees and what the server and other clients see.
For example, consider the following setup:
- There is 1 client and 1 listen server.
Player #1
(Client #1
) can controlCharacter A
andCharacter B
. Initially,Client #1
is controllingA
.Player #2
(theListen Server Client
) can controlCharacter X
andCharacter Y
. Initially, theListen Server Client
is controllingX
.- All characters have a server-initiated "Run in place" gameplay ability that plays a montage.
Player #1
switches to possessing/controllingCharacter B
but crosses a trigger volume that initiates the "Run in place" action on the server onCharacter A
.- The
Listen Server Client
switches to possessing/controllingCharacter Y
but crosses a trigger volume that initiates the "Run in place" action on the server onCharacter X
.
In the setup above:
Player #2
will observe thatCharacter A
is playing the montage, butPlayer #1
won't see any montage play, even thoughCharacter A
"belongs" toPlayer #1
andPlayer #1
had invoked the action that played the montage in the first place.- Both
Player #1
andPlayer #2
will observe thatCharacter X
is playing the montage. This is becausePlayer #2
is the Listen Server Client, andClient #1
knows thatCharacter X
is not local toClient #1
.
- Clone this project locally.
- Check out the with-bug tag.
- Compile the project.
- Open the project in UE 5.1+.
- Launch the project with 2 players in "Listen Server" mode.
- Click into a "Client" window.
- Move the character pawn up the ramp until they fall off the other side and start playing a "running in place" animation.
- Press the
TAB
key to switch which character you are controlling. - Move the second character pawn up the ramp until they fall off.
- Both the character pawn you were previously controlling and the character pawn you are currently controlling animate in the "Listen Server" window.
- Only the character pawn you are controlling animates in the "Client" window.
Both the character pawn you were previously controlling and the character pawn you are currently controlling should animate in both the "Client" and "Server" windows since the server executes a gameplay ability on both pawns when you enter the trigger box at the end of the ramp.
- It appears that
OnRep_Controller
does not get called client-side for clients who are un-possessing a pawn. So,Client #1
will not receive anOnRep_Controller
callback forCharacter A
when switching to controlCharacter B
. However, UE will eventually replicatenullptr
toClient #1
as the controller ofCharacter A
on the next tick, but without any RepNotify. - ASC Ability Actor Info updates tend to happen in the same frame as the change
in controllers, so the updated info will still record that the player
controller for
Client #1
is the "owner" of Character A even thoughClient #1
now possessesCharacter B
instead. When it comes times to play the montage, the ASC thinks that the character is stll locally controlled and disregards the montage playback instruction from the server. - The
Listen Server Client
is not subject to the bug because the controller ofCharacter A
is immediately set tonullptr
during the controller change but before the ASC Ability Actor Info is refreshed in the same frame (there is no replication delay).
Looking back in commit history, this project demonstrates two approaches to work around this issue:
- Blueprint-driven, Timer solution: Whenever a player controller is
switching from controlling
Pawn A
toPawn B
, set a timer to refresh the ASC Ability Actor Info onPawn A
after a brief delay (0.1 to 0.5 secs);
OR - C++-driven, Timer Manager solution in
SetPawn
: In the player controller, override theSetPawn()
method so you can capture the old pawn before it gets replace with the new pawn. Then, use theTimerManager
to schedule an ASC Ability Actor Info update on the next tick.
In both cases, you are ensuring that the controller info has replicated to the client before the ASC has captured it.