5.5.4 - Unexpected slate animation tick behavior affected by player input

(Apologies for leaving “steps to reproduce” blank as I’m not 100% sure of the steps involved in the repro here)

We recently introduced a system to pool damage number WidgetComponent actors as an optimization to avoid the cost of widget construction every time something takes damage. We use actors with WidgetComponents for the ease of positioning the widgets relative to 3D world space. We attempt to play a little “pop in” animation on the widgets when shown, but after introducing the pooling system, the animations don’t play under certain conditions.

I manage to find several contributing factors here:

  1. In the engine:
    1. Since the actors with WidgetComponents are spawned but hidden, they allow their MyGCWidget to be released assuming that it will be re-constructed next time it’s added to the viewport (which will call TakeWidget_Private where it’s assigned). Seems reasonable.
    2. UUMGSequenceTickManager::TickWidgetAnimations will check to see if `UserWidget->IsConstructed()` and will stop animations from playing if not constructed (which is checking for a valid MyGCWidget). Also seems reasonable.
  2. In our game logic:
    1. When we show a widget from the pool we also tell it to play the animation (on the same frame) since it will be now be painted at the end of the current frame when slate is ticked and we don’t want there to be a one-frame pop of the previous/uninitialized state the widget was in.

Here’s the kicker:

Scenario #1 - When the user initiates an input that fires a shot that deals damage (as in a semi-automatic weapon), it goes through APlayerController::PlayerTick in TG_PrePhysics which calls UAbilitySystemComponent::TryActivateAbility that ultimately performs the GameplayAbility that deals damage and shows the damage number. This DOES play the animation correctly as expected.

Scenario #2 - When the user continues to hold down an input that fires a shot that deals damage (as in a fully automatic weapon), it goes through UAbilityTask_WaitDelay::OnTimeFinish in FTimerManager::Tick which calls UAbilitySystemComponent::TryActivateAbility that ultimately performs the GameplayAbility that deals damage and shows the damage number. This DOES NOT play the animation correctly as expected.

If I’m interpreting this correctly, it seems like the the tick order here affects whether or not the underlying slate widget will be constructed in time for UUMGSequenceTickManager::TickWidgetAnimations to occur (and treat the UserWidget as constructed or not). We can verify this by introducing a one-frame delay between showing the widget on screen and playing the animation (which works… with the caveat that it now draws it’s previous state for one fame). My intuition would be that if you’re starting an animation at any point in the frame prior to UUMGSequenceTickManager::TickWidgetAnimations being called, some mechanism would ensure the necessary data exists for the animation to play. Perhaps that is the case and something about scenario #2 results in TickWidgetAnimations being called earlier in the frame than in scenario #1? (Another possibility that I did not verify, but may alternatively be the case, is that somehow the timer manager tick can occur after UUMGSequenceTickManager::TickWidgetAnimations within the same frame, but I can’t think of a reason why that would be the case so I didn’t pursue investigating that possibility.) That said, it does seem very consistent in our project, so I don’t believe a race condition is at play here (or it’s a very generous one on my hardware).

Given the above, would you have any recommendations as to how we address this problem at the engine level? Technically we can mitigate this at a game level by setting render opacity to 0 during the first frame before playing the animation on the next frame, but that just seems like a band-aid fix for the underlying issue.

Hi,

We’ve made some pretty huge changes to how UMG animations are managed, and the good news is that I suspect those changes may solve the issue you’re seeing here since we addressed some similar issues with needing to wait a tick after construction before being able to play an animation. The bad news is that those changes may be tough to backport, and not all of them made it into 5.6. If you do plan to upgrade, you should get the bulk of the rework with 5.6 (CL#41112585) and be in a better position to cherrypick the additional fixes. Once you’ve upgraded, you’ll want to grab CL#43336467 and CL#43410676. Those changes are *very* recent so I’m not yet sure if they’ll make it into a 5.6 hotfix or be pushed back to 5.7, but if you do end up backporting them then I’d be interested to hear how things go.

Best,

Cody

Hi,

I do think the rework probably makes it not worth diving too deep into the underlying issue, so if you have a good workaround for now (such as setting opacity to 0 and delaying a frame before playing the animation) then that’s probably the best approach for now. Once you’ve rolled out 5.6, backporting those other changes (if they haven’t made it to a hotfix) should be a much lighter lift if you want to see if that addresses the root cause.

Best,

Cody

Well, that’s certainly good to hear that the issues may potentially be addressed, but those are certainly some pretty large changes to cherrypick. 5.6 is on our radar but we haven’t discussed a timeline for that yet.

If you don’t think there’s any information we could contribute to this issue (considering that rework likely makes it moot), we may just go for the band-aid fix and wait until we can upgrade the engine to see if the underlying tick timings behave as expected.

Thanks.