So you have created a physics based game in Unreal Engine 4, you have put some spring models here and there and you are testing the game at 60fps in your development machine. Now you are wondering what your game physics would look like if the game run at 30fps, maybe because of a frame rate drop or a vsync clamping on a low-end machine. Then you try to make a test; you open the Unreal Engine console and type t.MaxFPS 30 . Chances are that your game will run fine for some time, then it suddenly explodes. What’s happening?

Fixing Your Timestep

If you are a bit familiar with physics engines architecture and the underlying math (numerical analysis[ ]), you know that the evolution of a numerical physics simulation is very tied to the timestep of each iteration; the smaller the time step, the lesser the error and vice versa. Moreover, whatever integration method your physics engine is using, there will always be a region of instability[ ] for your physics model that depends on the timestep. The larger the time step, the more unstable is the system. That’s why your springs explode at 30 fps if you tune their proportional constants at 60 fps[ ]. Here I’m taking spring models as an example for their wide use in game physics, but you can also experience instability when you are dealing with aerodynamic models, or in general if your system is described by the so-called stiff equations[ ].

It is clear that if you want your physics to behave consistently at different frame rates, you should find a way to separate the scene rendering and the physics simulation so that the physics integration time step is fixed or at least confined below a maximum value[ ]. The maximum timestep allowed for your simulation can be calculated using math (interval arithmetic[ ]) or by tuning your simulation at some ideal frame rate.

The best you can achieve on Unreal Engine is a variable time step below an upper bound (semi-fixed timestep[ ]); it is called substepping. But first let’s have a look at all the physics settings available in Unreal Engine 4. If you want to jump directly to the details about Unreal Engine substepping, you can skip the following sections and read here.

Fixed Frame Rate

In Unreal Engine the physics time step has always some relationship with the rendering frame rate, so the first place to look at is: Project Settings > Engine > General Settings > Framerate.

Here you can choose to set a fixed frame rate for your game. This means that the engine will always pass a fixed delta time to physics and tick functions. At the same time the engine will try to reach the desired frame rate. But what happens when there is a frame rate drop? In this case the actual frame rate will be slower than the fixed value but the delta time passed to physics will always be the same fixed value. This means that your simulation will run at a slower non-real-time speed. For example, if you set a fixed frame rate of 60fps but your game is running at 30fps because the machine is not powerful enough, then the simulation will run at half speed because in 33ms of real-time the game simulation will go only 16ms of fixed time forward.

Smooth Frame Rate

Frame rate smoothing is a way to cap the frame rate between a min and max value. It can be found here: Project Settings > Engine > General Settings > Framerate. It can be used to define the min/max acceptable frame rates. To better understand how Smooth Frame Rate works, an understanding of Vertical Sync is also needed as the two have a lot in common. More info about it here.

Max Physics Delta Time

The engine settings about physics can be found here: Project Settings > Engine > Physics.

The Max Physics Delta Time property sets a maximum value for delta time during each physics iteration. This means that the physics time step is tied to the frame delta time until it doesn’t exceed the max physics delta time. When this happens, the physics time step remains fixed to this max value and the simulation starts slowing down thus becoming a non real-time simulation.

This can be useful if, for example, your game runs pretty stable at 60 fps and you know that your physics will work well above 50 fps. Then you can set a Max Physics Delta Time of 1/50sec and if you are ok with some occasional slowdowns in the simulation, you have done. But if your physics have to run at 60 fps and you want your game to run well at 30 fps, then you must use substepping.

Unreal Engine Substepping

The engine settings about substepping can be found here: Project Settings > Engine > Physics.

Substepping is a technique used by Unreal Engine to ensure a certain degree of frame rate independency to the physics simulation. When the frame rate drops down, Unreal will put some extra physics iterations to ensure that the physics timestep will never exceed a maximum value. You can specify this upper bound (Max Substep Delta Time) and a maximum number of physics substeps between two rendering frames (Max Substeps). Let’s say that you have set 16 ms as Max Substep Delta Time. This means that you don’t want the physics timestep to be greater than 16ms or, likewise, the physics frame rate to be smaller than 60fps. Now, if the rendering frame rate is 60 fps or above, there will be no substepping because the physics time step is lower than 16ms already. What happens when your frame rate drops below 60 fps? In this case the engine will put some extra physics substeps to ensure that the physics delta time doesn’t exceed 16 ms. So Unreal Engine will put 2 substeps of duration deltatime/2 when the fps are in the range from 30 to 60. When the rate drops below 30fps, 3 substeps of deltatime/3 will be executed. When the rate drops below 20fps, 4 substeps will be executed and so on.

The number of substeps encreases with the frame delta time until it reaches the Max Substeps value. Given the constraints of the Unreal Engine substepping, the max frame delta time allowed for a real-time simulation will be maxdt * maxsubsteps. Above this delta time the simulation will start slowing down becoming non real-time.

Let’s do some math. Let maxdt be the Max Substep Delta Time, maxsubsteps be the Max Substeps, currfps the current rendering fps, currdt=1/currfps the current frame delta time:

Target FPS = 1 / maxdt

This is the frame rate above wich there will be no substepping. It is the framerate that you should use to tune the game physics.

Limit FPS = 1 / (maxdt * maxsubsteps)

Below this frame rate there will be no more substeps and the simulation will start slowing down.

if currfps <= Limit FPS:

NSubsteps = maxsubsteps

Substep DeltaTime = maxdt

if currfps > Limit FPS:

NSubsteps = ceil(currdt / maxdt)

Substep DeltaTime = currdt / NSubsteps

Here is some pseudo-code for the semi-fixed timestep:

float remainder = 0; double currentTime = time(); while(true) { double newTime = time(); double frameTime = newTime - currentTime; currentTime = newTime; int nSubsteps = Math.ceil(frameTime / MaxDt); if(nSubsteps > MaxSubsteps) nSubsteps = MaxSubsteps; double substepDt = frameTime / nSubsteps; if(substepDt > MaxDt) substepDt = MaxDt; for(int i=0; i<nSubsteps; i++) DoPhysics(substepDt); RenderScene(); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 float remainder = 0 ; double currentTime = time ( ) ; while ( true ) { double newTime = time ( ) ; double frameTime = newTime - currentTime ; currentTime = newTime ; int nSubsteps = Math . ceil ( frameTime / MaxDt ) ; if ( nSubsteps > ; MaxSubsteps ) nSubsteps = MaxSubsteps ; double substepDt = frameTime / nSubsteps ; if ( substepDt > ; MaxDt ) substepDt = MaxDt ; for ( int i = 0 ; i < ; nSubsteps ; i ++ ) DoPhysics ( substepDt ) ; RenderScene ( ) ; }

The image below shows what happens when the frame deltatime is greater than maxdt * maxsubsteps (case B). In this case the sum of all the substep times is smaller than the actual deltatime and the simulation slows down.

Substepping timeline subdivision

Substepping Graph

Although the logic and the math behind the Unreal Engine substepping method are pretty simple, it can be still confusing to figure out the behaviour of the number and duration of substeps when the frame rate changes, given a certain configuration. I’ve come up with an interactive graph where you can play with different values and see how the substepping behaviour changes.





Unreal Engine 4 Substepping

As you can see in the above graph, the substep deltatime never goes above the max substep delta time but it can go a lot below it. This will guarantee that the game won’t explode (if we choose the right max deltatime) but can result in a computational overhead if we don’t pay attention to the values used. In particular, if the game is CPU bounded, the substepping mechanism could collapse and slow down the frame rate below the Limit FPS value. This because when the frame rate drops, the engine will put some extra substeps which will require more CPU cycles and, being your game CPU bounded, will slow down even more the frame rate. This chain effect will stop only when the frame rate will reach the Limit FPS value where no more substeps will be added.

The default values for the graph above show a possible configuration for a game tuned at a target physics frame rate of 40fps, and designed to run at an average speed of 60fps on high end machines and 30fps on low end ones. You can see from the graph that the physics will run at 60fps (16ms) both at 30fps and 60fps. Small frame rate fluctuations around these target rates won’t trigger any extra substep. Only when the rate slows down to 40fps we have 2 substeps at 80fps. In this way we can guarantee physics stability without any large CPU overhead.

Substep Tick Function

So you followed this article, activated the substepping and chose some suitable values for max substep deltatime and max substeps, but your game physics is still exploding and you didn’t see any substep influence when looking at your tick functions: they are still tied to the rendering frame rate and are still receiving the frame rendering deltatime. So where are these substeps?

The fact is that Unreal Engine substepping is running only under the hood and the tick functions in your actors and components are still being called at each rendering frame. This means that while the internal physics simulation is running smoother (collisions and motion integration), if your spring model is computed inside a tick function, it will gain no benefit from the substepping. In fact, if you compute the forces applied to a body every rendering frame and then the physics engine integrates those forces for N substeps at deltatime/N step intervals, the result is the same of integrating in a single step of deltatime duration. This because the forces are constant during each substep iteration.

To fix this problem you have to tell the engine to call some function in your code for each substep. There is a discussion in the Unreal Engine forum about substepping where Epic’s developers explain how to assign callbacks to be called for each substep tick[ ].

Here I’m showing you a little code example of how to setup a substep callback in your project. The first thing to note is that you can access substepping only from an object that can have a rigid body, i.e. any Component extending UPrimitiveComponent. Here I’m subclassing an UStaticMeshComponent.

Here is the header code:

///////////////////////////// // MyComponent.h // Add "PhysX" and "APEX" to PublicDependencyModuleNames in yourgame.Build.cs // You will have to apply forces directly to PhysX body if you want to apply forces! If you want to read body coordinates, read them from PhysX bodies! This is important for sub-steps as transformations aren't updated until the end of physics tick #include "Components/StaticMeshComponent.h" #include "MyComponent.generated.h" UCLASS() class YOURGAME_API MyComponent : public UStaticMeshComponent { GENERATED_BODY() public: MyComponent(); virtual void BeginPlay() override; virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; FCalculateCustomPhysics OnCalculateCustomPhysics; void SubstepTick(float DeltaTime, FBodyInstance* BodyInstance); physx::PxRigidBody* PRigidBody; FORCEINLINE FVector GetCurrentLocation(); FORCEINLINE FRotator GetCurrentRotation(); FORCEINLINE FVector GetCurrentVelocity(); FORCEINLINE FVector GetCurrentAngularVelocity(); }; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 ///////////////////////////// // MyComponent.h // Add "PhysX" and "APEX" to PublicDependencyModuleNames in yourgame.Build.cs // You will have to apply forces directly to PhysX body if you want to apply forces! If you want to read body coordinates, read them from PhysX bodies! This is important for sub-steps as transformations aren't updated until the end of physics tick #include "Components/StaticMeshComponent.h" #include "MyComponent.generated.h" UCLASS ( ) class YOURGAME_API MyComponent : public UStaticMeshComponent { GENERATED_BODY ( ) public : MyComponent ( ) ; virtual void BeginPlay ( ) override ; virtual void TickComponent ( float DeltaTime , ELevelTick TickType , FActorComponentTickFunction * ThisTickFunction ) override ; FCalculateCustomPhysics OnCalculateCustomPhysics ; void SubstepTick ( float DeltaTime , FBodyInstance * BodyInstance ) ; physx :: PxRigidBody * PRigidBody ; FORCEINLINE FVector GetCurrentLocation ( ) ; FORCEINLINE FRotator GetCurrentRotation ( ) ; FORCEINLINE FVector GetCurrentVelocity ( ) ; FORCEINLINE FVector GetCurrentAngularVelocity ( ) ; } ;

In the above code I’m overriding the main tick function wich will be called by the main rendering thread. I’m also defining a delegate OnCalculateCustomPhysics of type FCalculateCustomPhysics , a pointer to the rigid body and the SubstepTick function to be called during substepping. Finally I’m defining some utility inline functions to get position, rotation, velocities directly from the rigid body. This because body transformations aren’t updated until the end of physics tick, so you have to read them from PhysX rigid bodies to get the updated values from the last substep.

Here are all the function definitions:

///////////////////////////// // MyComponent.cpp #include "MyComponent.h" #include <PhysXIncludes.h> #include <PhysicsEngine/BodyInstance.h> MyComponent::MyComponent() { PrimaryComponentTick.bCanEverTick = true; PrimaryComponentTick.bStartWithTickEnabled = true; OnCalculateCustomPhysics.BindUObject(this, &MyComponent::SubstepTick); } void MyComponent::BeginPlay() { Super::BeginPlay(); PRigidBody = GetBodyInstance()->GetPxRigidBody_AssumesLocked(); } void MyComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); GetBodyInstance()->AddCustomPhysics(OnCalculateCustomPhysics); } void MyComponent::SubstepTick(float DeltaTime, FBodyInstance* BodyInstance) { // Here get your custom forces FVector f = GetForce(); FVector t = GetTorque(); // And apply them to the rigid body PRigidBody->addForce(PxVec3(f.X, f.Y, f.Z), physx::PxForceMode::eFORCE, true); PRigidBody->addTorque(PxVec3(t.X, t.Y, t.Z), PxForceMode::eFORCE, true); } FVector MyComponent::GetCurrentLocation() { PxTransform PTransform = PRigidBody->getGlobalPose(); return FVector(PTransform.p.x, PTransform.p.y, PTransform.p.z); } FRotator MyComponent::GetCurrentRotation() { PxTransform PTransform = PRigidBody->getGlobalPose(); return FRotator(FQuat(PTransform.q.x, PTransform.q.y, PTransform.q.z, PTransform.q.w)); } FVector MyComponent::GetCurrentAngularVelocity() { PxVec3 PAngVelocity = PRigidBody->getAngularVelocity(); return FMath::RadiansToDegrees(FVector(PAngVelocity.x, PAngVelocity.y, PAngVelocity.z)); } FVector MyComponent::GetCurrentVelocity() { PxVec3 PVelocity = PRigidBody->getLinearVelocity(); return FVector(PVelocity.x, PVelocity.y, PVelocity.z); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 ///////////////////////////// // MyComponent.cpp #include "MyComponent.h" #include <PhysXIncludes.h> #include <PhysicsEngine/BodyInstance.h> MyComponent :: MyComponent ( ) { PrimaryComponentTick . bCanEverTick = true ; PrimaryComponentTick . bStartWithTickEnabled = true ; OnCalculateCustomPhysics . BindUObject ( this , & ; MyComponent :: SubstepTick ) ; } void MyComponent :: BeginPlay ( ) { Super :: BeginPlay ( ) ; PRigidBody = GetBodyInstance ( ) - > ; GetPxRigidBody_AssumesLocked ( ) ; } void MyComponent :: TickComponent ( float DeltaTime , ELevelTick TickType , FActorComponentTickFunction * ThisTickFunction ) { Super :: TickComponent ( DeltaTime , TickType , ThisTickFunction ) ; GetBodyInstance ( ) - > ; AddCustomPhysics ( OnCalculateCustomPhysics ) ; } void MyComponent :: SubstepTick ( float DeltaTime , FBodyInstance * BodyInstance ) { // Here get your custom forces FVector f = GetForce ( ) ; FVector t = GetTorque ( ) ; // And apply them to the rigid body PRigidBody - > ; addForce ( PxVec3 ( f . X , f . Y , f . Z ) , physx :: PxForceMode :: eFORCE , true ) ; PRigidBody - > ; addTorque ( PxVec3 ( t . X , t . Y , t . Z ) , PxForceMode :: eFORCE , true ) ; } FVector MyComponent :: GetCurrentLocation ( ) { PxTransform PTransform = PRigidBody - > ; getGlobalPose ( ) ; return FVector ( PTransform . p . x , PTransform . p . y , PTransform . p . z ) ; } FRotator MyComponent :: GetCurrentRotation ( ) { PxTransform PTransform = PRigidBody - > ; getGlobalPose ( ) ; return FRotator ( FQuat ( PTransform . q . x , PTransform . q . y , PTransform . q . z , PTransform . q . w ) ) ; } FVector MyComponent :: GetCurrentAngularVelocity ( ) { PxVec3 PAngVelocity = PRigidBody - > ; getAngularVelocity ( ) ; return FMath :: RadiansToDegrees ( FVector ( PAngVelocity . x , PAngVelocity . y , PAngVelocity . z ) ) ; } FVector MyComponent :: GetCurrentVelocity ( ) { PxVec3 PVelocity = PRigidBody - > ; getLinearVelocity ( ) ; return FVector ( PVelocity . x , PVelocity . y , PVelocity . z ) ; }

As you can see above, I’m binding the OnCalculateCustomPhysics delegate to the SubstepTick function:

OnCalculateCustomPhysics.BindUObject(this, &UStaticMeshComponent::SubstepTick);

Then, for every main tick, inside the TickComponent function, you have to tell the BodyInstance to call the OnCalculateCustomPhysics delegate (which will eventually call SubstepTick )

GetBodyInstance()->AddCustomPhysics(OnCalculateCustomPhysics);

In order to compile this code, you have to add “PhysX” and “APEX” to the PublicDependencyModuleNames array in yourgame.Build.cs module configuration file.

One big caveat when using substep callbacks is the fact that physics sub-stepping is running in a separate physics thread, allowing the game thread to continue doing work. This means that there are a lot of functions that you can’t call from a substep callback, because they cannot work under concurrency (many engine functions start with a check(MainThread) ). So if you first designed your game without substepping callbacks and then you are trying to move all your physics code into substep callbacks in a late development stage, don’t expect the task to be easy; you’ll probably find that many things won’t just work under substepping, so be prepared to a lot of refactoring.

Post Physics Tick Function

Sometimes you need to adjust something just after the physics has been evaluated in order to be sure that what you are setting will go unchanged to the rendering phase.

If you set your primary tick to pre-physics, you need to register a secondary tick function for post-physics. You have to create a struct extending the struct FTickFunction , initialize it and then register it.

Here is the header code:

///////////////////////////// // MyComponent.h #include "Components/StaticMeshComponent.h" #include "MyComponent.generated.h" USTRUCT() struct FMySecondaryTickFunction : public FTickFunction { GENERATED_USTRUCT_BODY() class MyComponent* Target; YOURGAME_API virtual void ExecuteTick( float DeltaTime, ELevelTick TickType, ENamedThreads::Type CurrentThread, const FGraphEventRef& CompletionGraphEvent) override; YOURGAME_API virtual FString DiagnosticMessage() override; }; template<> struct TStructOpsTypeTraits<FMySecondaryTickFunction> : public TStructOpsTypeTraitsBase2<FMySecondaryTickFunction> { enum { WithCopy = false }; }; UCLASS() class YOURGAME_API MyComponent : public UStaticMeshComponent { GENERATED_BODY() public: MyComponent(); FShipSecondaryTickFunction SecondaryComponentTick; virtual void BeginPlay() override; virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; void TickPostPhysics(float DeltaSeconds, ELevelTick TickType, FMySecondaryTickFunction& ThisTickFunction); }; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 ///////////////////////////// // MyComponent.h #include "Components/StaticMeshComponent.h" #include "MyComponent.generated.h" USTRUCT ( ) struct FMySecondaryTickFunction : public FTickFunction { GENERATED_USTRUCT_BODY ( ) class MyComponent * Target ; YOURGAME_API virtual void ExecuteTick ( float DeltaTime , ELevelTick TickType , ENamedThreads :: Type CurrentThread , const FGraphEventRef & ; CompletionGraphEvent ) override ; YOURGAME_API virtual FString DiagnosticMessage ( ) override ; } ; template < ; > ; struct TStructOpsTypeTraits < ; FMySecondaryTickFunction > ; : public TStructOpsTypeTraitsBase2 < ; FMySecondaryTickFunction > ; { enum { WithCopy = false } ; } ; UCLASS ( ) class YOURGAME_API MyComponent : public UStaticMeshComponent { GENERATED_BODY ( ) public : MyComponent ( ) ; FShipSecondaryTickFunction SecondaryComponentTick ; virtual void BeginPlay ( ) override ; virtual void TickComponent ( float DeltaTime , ELevelTick TickType , FActorComponentTickFunction * ThisTickFunction ) override ; void TickPostPhysics ( float DeltaSeconds , ELevelTick TickType , FMySecondaryTickFunction & ; ThisTickFunction ) ; } ;

As you can see above, I’m adding a pointer to a MyComponent instance inside the struct FMySecondaryTickFunction . This pointer will be used inside the ExecuteTick callback to invoke a tick function inside MyComponent .

Here are the function definitions:

///////////////////////////// // MyComponent.cpp #include "MyComponent.h" void FMySecondaryTickFunction::ExecuteTick( float DeltaTime, ELevelTick TickType, ENamedThreads::Type CurrentThread, const FGraphEventRef& CompletionGraphEvent) { if (Target && !Target->IsPendingKill() && !Target->IsUnreachable()) { FScopeCycleCounterUObject ActorScope(Target); Target->TickPostPhysics(DeltaTime, TickType, *this); } } FString FShipSecondaryTickFunction::DiagnosticMessage() { return Target->GetFullName() + TEXT("[MyComponent Post Physics Tick]"); } MyComponent::MyComponent() { PrimaryComponentTick.bCanEverTick = true; PrimaryComponentTick.bStartWithTickEnabled = true; PrimaryComponentTick.TickGroup = TG_PrePhysics; SecondaryComponentTick.TickGroup = TG_PostPhysics; SecondaryComponentTick.bCanEverTick = true; SecondaryComponentTick.bStartWithTickEnabled = true; } void MyComponent::BeginPlay() { Super::BeginPlay(); if (!IsTemplate() && SecondaryComponentTick.bCanEverTick) { SecondaryComponentTick.Target = this; SecondaryComponentTick.SetTickFunctionEnable(SecondaryComponentTick.bStartWithTickEnabled); SecondaryComponentTick.RegisterTickFunction(GetOwner()->GetLevel()); } } void MyComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); // Put here your pre-physics code } void MyComponent::TickPostPhysics( float DeltaSeconds, ELevelTick TickType, FMySecondaryTickFunction& ThisTickFunction ) { // Non-player update. const bool bShouldTick = ((TickType != LEVELTICK_ViewportsOnly) || GetOwner()->ShouldTickIfViewportsOnly()); if (bShouldTick) { if (!IsPendingKill() && GetWorld()) { if (GetOwner()->GetWorldSettings() != NULL && !IsRunningDedicatedServer()) { // Put here your post-physics code } } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 ///////////////////////////// // MyComponent.cpp #include "MyComponent.h" void FMySecondaryTickFunction :: ExecuteTick ( float DeltaTime , ELevelTick TickType , ENamedThreads :: Type CurrentThread , const FGraphEventRef & ; CompletionGraphEvent ) { if ( Target & ; & ; ! Target - > ; IsPendingKill ( ) & ; & ; ! Target - > ; IsUnreachable ( ) ) { FScopeCycleCounterUObject ActorScope ( Target ) ; Target - > ; TickPostPhysics ( DeltaTime , TickType , * this ) ; } } FString FShipSecondaryTickFunction :: DiagnosticMessage ( ) { return Target - > ; GetFullName ( ) + TEXT ( "[MyComponent Post Physics Tick]" ) ; } MyComponent :: MyComponent ( ) { PrimaryComponentTick . bCanEverTick = true ; PrimaryComponentTick . bStartWithTickEnabled = true ; PrimaryComponentTick . TickGroup = TG_PrePhysics ; SecondaryComponentTick . TickGroup = TG_PostPhysics ; SecondaryComponentTick . bCanEverTick = true ; SecondaryComponentTick . bStartWithTickEnabled = true ; } void MyComponent :: BeginPlay ( ) { Super :: BeginPlay ( ) ; if ( ! IsTemplate ( ) & ; & ; SecondaryComponentTick . bCanEverTick ) { SecondaryComponentTick . Target = this ; SecondaryComponentTick . SetTickFunctionEnable ( SecondaryComponentTick . bStartWithTickEnabled ) ; SecondaryComponentTick . RegisterTickFunction ( GetOwner ( ) - > ; GetLevel ( ) ) ; } } void MyComponent :: TickComponent ( float DeltaTime , ELevelTick TickType , FActorComponentTickFunction * ThisTickFunction ) { Super :: TickComponent ( DeltaTime , TickType , ThisTickFunction ) ; // Put here your pre-physics code } void MyComponent :: TickPostPhysics ( float DeltaSeconds , ELevelTick TickType , FMySecondaryTickFunction & ; ThisTickFunction ) { // Non-player update. const bool bShouldTick = ( ( TickType != LEVELTICK_ViewportsOnly ) || GetOwner ( ) - > ; ShouldTickIfViewportsOnly ( ) ) ; if ( bShouldTick ) { if ( ! IsPendingKill ( ) & ; & ; GetWorld ( ) ) { if ( GetOwner ( ) - > ; GetWorldSettings ( ) != NULL & ; & ; ! IsRunningDedicatedServer ( ) ) { // Put here your post-physics code } } } }

You can set a post-physics tick inside an Actor in a similar way as well.

By combining all these examples, you can easily setup a component listening to pre-physics, substeps and post-physics ticks.

Comparison with Unity 3D

Unity physics timestep is very clean and simple. While Unreal Engine is using a semi-fixed timestep, Unity uses a fixed timestep instead. For each render frame, Unity executes a number of fixed physics timesteps (each one lasting exactly a fixed timestep number of seconds) in order to consume the frame delta time:

NTimesteps = Math.floor(FrameDeltaTime / FixedTimestep)

Because the number of timesteps is an integer, there could be a remainder at each frame in the consumed delta time. This remainder is collected at each frame and added to the next frame delta time, eventually leading to the execution of an extra physics timestep when its value becomes greater or equal to FixedTimestep.

Here is some pseudo-code describing the fixed timestep:

float remainder = 0; double currentTime = time(); while(true) { double newTime = time(); double frameTime = newTime - currentTime; currentTime = newTime; if(frameTime > MaxAllowedTimestep) frameTime = MaxAllowedTimestep; int nTimesteps = Math.floor((frameTime + remainder) / fixedTimestep); remainder = frameTime + remainder - nTimesteps * fixedTimestep; for(int i=0; i<nTimesteps; i++) DoPhysics(fixedTimestep); RenderScene(); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 float remainder = 0 ; double currentTime = time ( ) ; while ( true ) { double newTime = time ( ) ; double frameTime = newTime - currentTime ; currentTime = newTime ; if ( frameTime > ; MaxAllowedTimestep ) frameTime = MaxAllowedTimestep ; int nTimesteps = Math . floor ( ( frameTime + remainder ) / fixedTimestep ) ; remainder = frameTime + remainder - nTimesteps * fixedTimestep ; for ( int i = 0 ; i < ; nTimesteps ; i ++ ) DoPhysics ( fixedTimestep ) ; RenderScene ( ) ; }

The parameter Maximum Allowed Timestep sets a maximum value for the frame delta time (not for the timestep, which is fixed). This means that if the frame delta time is greater than this value, the physics will cap the delta time and the simulation will slow down. This is similar to what we have seen in Unreal Engine with Max Physics DeltaTime or with Max Substeps. Don’t confuse this with the Max Substep Delta Time property which is the equivalent of Unity’s Fixed Timestep. There is a bit of naming confusion here, so be careful.

Apart from the fixed vs semi-fixed model, one on the main difference between Unity and Unreal Engine is the fact that in Unity you have a simple and direct access to the physics tick through the FixedUpdate method of the Monobehaviour interface. And since this function is called in the main thread, there are no limits in what you can execute inside a physics tick.

While the fixed timestep is the ideal model for game physics, it is not problem free. The main issue with fixed timestep is temporal aliasing caused by the accumulation of a remainder at each frame. When the remainder becomes equal or greater then the fixed timestep, an extra physics step will be executed, resulting in a little stuttering of the animation. This is more evident when the rendering framerate is not a multiple of fixedtimestep.

Another problem with fixed timestep, tied to the previous, is the fact that the main frame tick (the Update method of Monobehaviour) is often off synch with the physics tick (the FixedUpdate method of Monobehaviour). This is a well known problem for Unity developers which experience stuttering every time some object position computed in the Update is tied to another object position evaluted in a FixedUpdate . Fortunately simply moving the Update code into the FixedUpdate function will fix this problem. This is one of the problem that made Epic’s developers choose the semi-fixed timestep method for Unreal Engine; the fact that the physics timestep is not easily exposed in c++ and not at all in blueprint will make very difficult to cope with this issue in this engine[ ] :

We actually had a debate about these two techniques [fixed and semi-fixed timestep] and eventually decided on semi-fixed, and here’s why: If you use [fixed timestep] you have to use a timebank. If you want to tick physics at 60fps and you have a frame that took a little bit more than 1/60 you will need to have some left over time. Then you will tick the physics engine with the perfect 1/60 delta time, leaving the remainder for the next frame. The problem is that the rest of the engine still uses the original delta time. This means that things like blueprint will be given a delta time of 1/60 + a bit.

Example Project

I have put an example project on github here: https://github.com/gportelli/UEPhysicsExample.

Here is a video of the example running:

As you can see, there is a cube oscillating according to a damped spring model. You can change the physics parameters using the exposed editor properties. Using the default properties the oscillation will explode if you disable the substepping. The engine substepping is tuned to run at 60fps when the render frame rate is 30fps. When I disable the substepping in the video, you can see how the amplitude increases at each oscillation, eventually going to infinite. By enabling logging, you can see what is actually happening in the physics engine: a pre-physics tick, two substep ticks and a post-physics tick for each rendered frame.

References

Notes