Solving the mystery. Warning: Engine Code Details (and modifications) follow:
Calling Delay creates a Latent Action owned by the blueprint that is calling Delay. (1)
If the owner is an Actor, that Actor’s tick function will update its own Latent Actions (2)
All other Latent Actions are updated by the general update - but only if not paused (3)
So, as currently implemented:
- Delays (and other latent actions) in Actor blueprints will work as expected with pause / set tickable when paused.
- Latent actions in any other types of blueprints (Actor components, widgets, etc) will not execute when paused.
** To allow paused ticking for ActorComponents (which fixes a huge majority of the other blueprintables where this might occur), I edited
ActorComponent.cpp ::TickComponent to add
// Update any latent actions we have for this actor
GetWorld()->GetLatentActionManager().ProcessLatentActions(this, DeltaTime);
For UI:
UserWidget in UserWidget.cpp ::TickActionsAndAnimation() also updates its own Latent Actions (and is the only other place where that is already being called)
Because of the way UserWidgets are implemented, however, there’s no place to go to the class settings to set the Tickable when Paused flag. The User Widget itself is not an ActorComponent at all, so doesn’t have access to that variable. It is contained by a WidgetComponent - which is the actual component added to the Actor - and there’s no way that I know of to change the class settings on a member component.
** Instead, the owner actor blueprint needs to get the WidgetComponent object, and use the “Set Tickable when Paused” blueprint node from Event BeginPlay (or whenever)
For Skeletal Animation:
Skeletal Mesh Components actually are set by default to be ticked while paused. There’s a followup issue, for actually animating, though.
SkeletalMeshComponent::ShouldTickPose() checks if the pose has already been ticked this frame. Among other things, it does this by testing LastPoseTickTime == GetWorld()->TimeSeconds via PoseTickedThisFrame()
However, World :: TimeSeconds is not getting updated while paused, so even with the above fixes, ShouldTickPose will always return false!
A fix for this is to replace the usage of LastPoseTickTime - instead of using GetWorld()->TimeSeconds, change to use the global GFrameNumber. This gets incremented regardless of pause timing. I did note that there are several other uses of World::TimeSeconds in the same manner. ie UWidgetComponent has similar comparison logic in ::ShouldDrawWidget()
SkeletalMeshComponent.h
- float LastPoseTickTime;
+ uint32 LastPoseTickFrame;
SkeletalMeshComponent.cpp
::USkeletalMeshComponent()
- LastPoseTickTime = -1.f;
+ LastPoseTickFrame = 0;
::TickPose()
- LastPoseTickTime = GetWorld()->TimeSeconds;
+ LastPoseTickFrame = GFrameNumber;
::PoseTickedThisFrame()
- return LastPoseTickTime == GetWorld()->TimeSeconds;
+ return LastPoseTickFrame == GFrameNumber;
For FX:
Spawn Emitter Attached will have a similar problem.
Quick fix is to get the returned object, and use the Set Tickable when Paused blueprint node.
A probably best fix is to have any spawned emitters inherit the attached object’s tickable when paused. (This would probably be a nice, separate flag to implement for all actors: Tick all children when paused)
(1) - see KismetSystemLibrary.cpp ::Delay(…)
(2) - Actor.cpp :: Tick(…) “…ProcessLatentActions(this, DeltaSeconds);”
(3) - LevelTick.cpp ::Tick(…) " if(!bIsPaused) …ProcessLatentActions(NULL, DeltaSeconds);"