Hello Folks! Welcome to Our Blog.

In an earlier post, Open Sesame — Art of opening doors…, I discussed a method for opening doors in Unreal Engine that used lerps and easing functions to control motion. For this post, I’d like to revisit that idea and change it up to use timelines instead. The advantages of this approach are:

  • Fewer lines of C++ code
  • Increased flexibility as the motion is no longer baked into the code
  • Motion curve defined in a graphical editor making it easier to create and modify
  • Easier to swap curves without modifying the underlying C++ code

The fundamental design is still the same: a state machine approach that defines the static (locked, closed and open) states and the active states (opening and closing). The main difference between the static and active states being whether the Tick() is enabled or not.

Fig. 1 THORN, 2022 State machine to control opening and closing doors

In C++ we can implement this very simply as shown in the code fragment below. Note the Locked state has been omitted for clarity and left as an exercise to the reader.

bool ADoor::Activate_Implementation()
{
    switch (doorState)
    {
    case Open:
        timeline.Reverse();
        doorState = Closing;
        break;

    case Opening:
        timeline.Reverse();
        doorState = Closing;
        break;

    case Closing:
        timeline.Play();
        doorState = Opening;
        break;

    case Closed:
        timeline.Play();
        doorState = Opening;
        break;

    default:
        break;
    }

    PrimaryActorTick.SetTickFunctionEnable(true);

    return true;
}

When the player activates the door, the state machine invokes the appropriate function, sets the new door state and then enables the Tick(). In my previous implementation, it was within the Tick() or one of the functions it called where we performed the calculations and moved the door, causing it to open or close.

With the timeline approach, our Tick() becomes far simpler. All we need to do now is to tick the timeline:

void ADoor::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

    if (timeline.IsPlaying())
    {
        timeline.TickTimeline(DeltaTime);
    }
}

So, how do we make the door move?

The timeline requires three things in order to operate:

  • A curve that defines the motion of the object it is controlling
  • A callback function that moves the object
  • A callback function that gets called at the end of the motion

In this example, we can do all the initialisation within BeginPlay() as shown in Figure 2 and the code fragment below.

Fig. 2 THORN, 2022 Timeline initialisation (simplified)
void ADoor::BeginPlay()
{
    Super::BeginPlay();
    
    if (DoorCurve)
    {
        FOnTimelineFloat TimelineCallback;
        FOnTimelineEventStatic TimelineFinishedCallback;

        
        timeline.AddInterpFloat(DoorCurve, TimelineCallback);

        TimelineCallback.BindUFunction(this, FName("operateDoor"));
        TimelineFinishedCallback.BindUFunction(this, FName{ TEXT("playbackCompleted") });

        timeline.SetTimelineFinishedFunc(TimelineFinishedCallback);
    }
}

Once set up, we just need to start the timeline and send it ticks to make it run, as described in the previous section.

Fig. 3 THORN, 2022 Activating and ticking the timeline

When running, the timeline walks the curve we gave it and generates a float value (in the case of a float curve), typically between 0.0f and 1.0f, that it passes to the callback function we gave it in the call to TimelineCallback.BindUFunction(), namely

TimelineCallback.BindUFunction(this, FName("operateDoor"));

Our ADoor::operateDoor() function then just needs to move the door, for example:

void ADoor::operateDoor()
{
    float timelineValue = timeline.GetPlaybackPosition();
    float curveFloatValue = DoorOpenAngle * DoorCurve->GetFloatValue(timelineValue);

    FQuat newRotation = FQuat(FRotator(0.f, curveFloatValue, 0.f));

    doorMesh->SetRelativeRotation(newRotation);
}

ADoor::operateDoor() is called every tick until we reach the end of the motion at which point the second callback is called, in our case ADoor::playbackCompleted(). This function is simply responsible for shutting down the tick and advancing the state machine to the next state, for example:

void ADoor::playbackCompleted()
{
    PrimaryActorTick.SetTickFunctionEnable(false);

    switch (doorState)
    {
    case Open:
    case Closed:
        break;

    case Opening:
        doorState = Open;
        break;

    case Closing:
        doorState = Closed;
        break;

    default:
        break;
    }
}

What about the curve?

The curve is defined within Unreal Engine. A new curve is created by right clicking in the content browser and selecting Miscellaneous | Curve. Double click to open the curve tool (Figure 5) and add a float curve. We can then create our desired motion curve by adding keys to the graph pane and adjusting to taste.

Fig. 4 THORN, 2022 Unreal Engine Curve
Fig. 5 THORN, 2022 Screenshot of the Unreal Engine Curve Tool

With the curve created, now need to add a new property to our ADoor C++ class as follows:

    //! @brief 
    //! Animation curve used to control the motion of the door
    UPROPERTY(EditAnywhere)
        UCurveFloat* DoorCurve;

Next, create a new Blueprint class based on our ADoor C++ class and assign the curve we created to the DoorCurve property. DoorCurve is then added to the timeline during it’s initialisation. In our case this was within the BeginPlay() function:

timeline.AddInterpFloat(DoorCurve, TimelineCallback);

Conclusion

Personally, I prefer the timeline approach compared to my previous post, Open Sesame — Art of opening doors…, because of the flexibility it gives when creating and assigning curved. Previously I used easing functions which, whilst the certainly did the job, were not as flexible when it came to tuning the motion profile. The easing functions were also tightly bound to the C++ meaning the source files needed to be edited in order to swap them out.

List of Figures

Figure 1. THORN, 2022 State machine to control opening and closing doors

Figure 2. THORN, 2022 Timeline initialisation (simplified)

Figure 3. THORN, 2022 Activating and ticking the timeline

Figure 4. THORN, 2022 Unreal Engine Curve

Figure 5. THORN, 2022 Screenshot of the Unreal Engine Curve Tool


Photo by Jeremy Bishop on Unsplash

Share your thoughts...

text/x-generic footer.php ( PHP script text )
ScaryBlankPage®