Opening Doors

An example use of a timeline in which we set up a classic proximity-based opening door using Blueprints and C++.

Windows
MacOS
Linux

Choose your implementation method:

Blueprints

C++

This tutorial demonstrates the use of a Timeline Blueprint to set up a proximity-based opening door using resources available in the Engine Starter content.

image alt text

Creating the Door Actor

  1. Begin by creating a New > Games > Blank > Blueprint project with Starter Content enabled named TimelineDoorActor.

  2. Click the Add/Import button to create a new Blueprint Actor class named BP_DoorActor.

  3. Double-click the BP_Door Actor from the Content Browser to open it in the Blueprint Editor and open the Class Defaults.

    image alt text

  4. Next, from the Components tab click the Add Component button and select Static Mesh to add a new Static Mesh Component.

    image alt text

  5. Right-click your static mesh component and select Rename to rename it to DoorFrame.

    image alt text

  6. Next, from the Components tab click the Add Component button and select Static Mesh to add a new Static Mesh Component. (Repeating step 3)

  7. Right-click your static mesh component and select Rename to rename it to Door.

    image alt text

  8. Click Add Component, select Box Collision from the dropdown menu, and rename it to Box.

    image alt text

  9. Next, open the Event Graph, right-clickthe graph, and choose Add Timeline from the Blueprint Context Menu. Name your Timeline DoorTimelineComponent.

    image alt text

At this point, the BP_DoorActor's class defaults should now look as they do in the example below:

image alt text

Setting Up the Door Static Mesh

Next, you will need to set up the attachment hierarchy of your Components tab.

You will also need to set up the Static Mesh assets that will visually represent your DoorFrame and Door Static Mesh components.

  1. From the BP_DoorActor's Components tab, select the DoorFrame static mesh and drag it over the DefaultSceneRoot component to make it the new root component.

    image alt text

  2. Next, select the DoorFrame static mesh in the Components tab and from the Details panel change the Static Mesh to SM_DoorFrame.

    image alt text

  3. Next, from the Components panel select the DoorMesh component. Navigate to the Details panel and change the Static Mesh to SM_Door.

    image alt text

  4. Then navigate to the Transform category and change the Y Location value to 45.0.

    image alt text

  5. Click Save and Compile to save these changes to the Bp_DoorActor.

Creating a Timeline Float Track

The Timeline Component requires a Timeline Curve to describe its movement animation. Each curve can contain multiple keys that define a time and value. Curves interpolate these keys to calculate the value at any point during the Timeline.

  1. Begin by double-clicking the DoorTimelineComponent in the Event Graph to open the Timeline Editor.

  2. Click Add Float Curve to add a new curve to the track and name the curve DoorRotationZ.

    image alt text

  3. Right-click to add two keys to your float curve track. One with the value (0,0) and another with the value (5,90).

    image alt text

  4. Shift select both your keys, right-click, and from the Key Interpolation dropdown menu select Auto interpolation.

  5. Save your float track.

Update Event Track Logic

You will now need to create the update logic to rotate your Door static mesh.

  1. Navigate to the BP_DoorActor's Components tab, drag the Door static mesh onto the Event Graph

    image alt text

  2. Drag off the Door pin and from the actions context menu, select SetRelativeRotation.

    image alt text

  3. Right-click the Event Graph, from the context menu select Make Rotator.

    image alt text

  4. From the DoorTimelineComponent node, drag the Door Rotation Z float pin and connect it to the Z(Yaw) pin of the Make Rotator node.

    image alt text

  5. From the DoorTimelineComponent node, drag off of the Update pin, and connect it to the input execution pin of the SetRelativeRotation node.

  6. From the Make Rotator node, connect the Return Value pin to the SetRelativeRotation node's New Rotation pin.

    image alt text

  7. Compile and Save.

Creating Binding Box Collision Overlap Events

The Box component requires the ability to react when an Actor enters or leaves its collision bounds.

  1. Navigate to the Components tab of your BP_DoorActor and select the Box component.

  2. From the Details panel, scroll down to the Events category and click the + icon next to the On Component Begin Overlap event.

    image alt text

  3. From the On Component Begin Overlap(Box) node, drag off the execution pin and connect it into the Play pin of the DoorTimelineComponent node.

    image alt text

  4. Navigate to the Components tab of theBP_DoorActor and select the Box component. Then from the Details panel, scroll down to the Events category and click + icon next to the On Component End Overlap event.

    image alt text

  5. From the On Component End Overlap(Box) node, drag off the execution pin and connect it into the Reverse pin of the TimelineComponent node.

    image alt text

  6. Compile and Save.

Placing Your Actor into the Level

  1. From the Content Browser, select your BP_DoorActor and drag it into the viewport.

    image alt text

  2. Press PIE

    image alt text

    Using the WASD keys, we can control our spectator Pawn. Upon navigating to the collision bounds of our DoorActor, we can observe the timeline play as the door opens. Upon exiting the bounds, we can observe it play in reverse.

Finished Blueprint

BP_DoorActor

image alt text

An example C++ Timeline is used to set up a classic proximity-based opening door using resources available from the Engine Starter content.

image alt text

Creating the Door Actor

  1. Begin by creating a New > Games > Blank > C++ project with Starter Content enabled named TimelineDoorActor.

  2. From the C++ Class Wizard, create a new Actor class named DoorActor. Navigate to your DoorActor.h file and declare the following include:

    #include "Components/TimelineComponent.h"
  3. Next, in your DoorActor class definition, implement the following code:

    protected:
    
        //MeshComponents to represent Door assets
        UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
        UStaticMeshComponent* DoorFrame;
        UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
        UStaticMeshComponent* Door;
    
        //TimelineComponent to animate Door meshes
        UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
        UTimelineComponent* DoorTimelineComp;
    
        //BoxComponent which will be used as our Proximity volume.
        UPROPERTY(EditAnywhere, BlueprintReadWrite)
        class UBoxComponent* DoorProxVolume;
  4. Navigate to DoorActor.cpp. You will need to include the following class library to utilize your Box Component.

    #include "Components/BoxComponent.h"
  5. In the constructor of your ADoorActor::ADoorActor declare the following:

    //Create our Default Components
    DoorFrame = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("DoorFrameMesh"));
    Door = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("DoorMesh"));
    DoorTimelineComp = CreateDefaultSubobject<UTimelineComponent>(TEXT("DoorTimelineComp"));
    DoorProxVolume = CreateDefaultSubobject<UBoxComponent>(TEXT("DoorProximityVolume"));
    
    //Setup our Attachments
    DoorFrame->SetupAttachment(RootComponent);
    Door->AttachToComponent(DoorFrame,FAttachmentTransformRules::KeepRelativeTransform);
    DoorProxVolume->AttachToComponent(DoorFrame, FAttachmentTransformRules::KeepRelativeTransform);

    Note: We keep the door's relative transform as an attachment rule, in order to manipulate it later with a custom method for our Door Actor. For further documentation, see FAttachmentTransformRules.

  6. Hot reload your code in the Editor, by selecting Compile.

    image alt text

Setting Up the Door Static Mesh

You will need to set up the Static Mesh assets that will visually represent your DoorFrame and Door Static Mesh components.

  1. From the Content Browser, navigate to your C++ Classes folder.

  2. Right click your DoorActor class, select Create Blueprint Class based on Door Actor, and name your Blueprint Actor Bp_DoorActor.

  3. From the Components tab select the DoorFrame Static Mesh component.

  4. Navigate to the Details panel and change the Static Mesh to SM_DoorFrame.

    image alt text

  5. Next, from the Components tab, select the DoorMesh component. Navigate to the Details panel and change the static mesh to SM_Door.

    image alt text

  6. Then navigate to the Transform category and change the Y Location value to 45.0.

    image alt text

  7. Click Save to save these changes to the Bp_DoorActor.

Creating UCurveFloat and Timeline Event Tracks

The Timeline Component requires a Timeline Curve. Each curve can contain multiple keys that define a time and value.Curves interpolate these keys to calculate the value at any point during the Timeline.

For this example, we will be using a UCurveFloat.

  1. Navigate to the ADoorActor class definition in DoorActor.h and declare the following variables:

    public:
        // Variable to hold the Curve asset
        UPROPERTY(EditAnywhere)
        UCurveFloat* DoorTimelineFloatCurve;
    
    private:
        //Float Track Signature to handle our update track event
        FOnTimelineFloat UpdateFunctionFloat;
    
        //Function which updates our Door's relative location with the timeline graph
        UFUNCTION()
        void UpdateTimelineComp(float Output);
  2. Next, navigate to DoorActor.cpp and implement the UpdateTimelineComp method:

    void ADoorActor::UpdateTimelineComp(float Output)
    {
        // Create and set our Door's new relative location based on the output from our Timeline Curve
        FRotator DoorNewRotation = FRotator(0.0f, Output, 0.f);
        Door->SetRelativeRotation(DoorNewRotation);
    }
  3. Then, in the BeginPlay method, add the following code:

    //Binding our float track to our UpdateTimelineComp Function's output
    UpdateFunctionFloat.BindDynamic(this, &ADoorActor::UpdateTimelineComp);
    
    //If we have a float curve, bind it's graph to our update function
    if (DoorTimelineFloatCurve)
    {
       DoorTimelineComp->AddInterpFloat(DoorTimelineFloatCurve, UpdateFunctionFloat);
    }

Work-In-Progress Code

DoorActor.h

// Copyright 1998-2021 Epic Games, Inc. All Rights Reserved. 
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/TimelineComponent.h"
#include "DoorActor.generated.h"

UCLASS()
class TIMELINEDOORACTOR_API ADoorActor : public AActor
{
    GENERATED_BODY()

public: 
    // Sets default values for this actor's properties
    ADoorActor();

    /*Variable to hold Curve asset*/
    UPROPERTY(EditAnywhere)
    UCurveFloat* DoorTimelineFloatCurve;

protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

    //MeshComponents to represents Door assets
    UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
    UStaticMeshComponent* DoorFrame;
    UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
    UStaticMeshComponent* Door;

    //TimelineComponent to animate door meshes
    UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
    UTimelineComponent* DoorTimelineComp;

    //BoxComponent which will be used as our proximity volume.
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    class UBoxComponent* DoorProxVolume;

    //Float Track Signature to handle our update track event
    FOnTimelineFloat UpdateFunctionFloat;

    //Function which updates our Door's relative location with the timeline graph
    UFUNCTION()
    void UpdateTimelineComp(float Output);

public: 
    // Called every frame
    virtual void Tick(float DeltaTime) override;
};

DoorActor.cpp

//Copyright 1998-2021 Epic Games, Inc. All Rights Reserved.

#include "DoorActor.h"
#include "Components/BoxComponent.h"

// Sets default values
ADoorActor::ADoorActor()
{
    // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;

    //Create our Default Components
    DoorFrame = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("DoorFrameMesh"));
    Door = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("DoorMesh"));
    DoorTimelineComp = CreateDefaultSubobject<UTimelineComponent>(TEXT("DoorTimelineComp"));
    DoorProxVolume = CreateDefaultSubobject<UBoxComponent>(TEXT("DoorProximityVolume"));

    //Setup our Attachments
    DoorFrame->SetupAttachment(RootComponent);
    Door->AttachToComponent(DoorFrame,FAttachmentTransformRules::KeepRelativeTransform);
    DoorProxVolume->AttachToComponent(DoorFrame, FAttachmentTransformRules::KeepRelativeTransform); 
}

// Called when the game starts or when spawned
void ADoorActor::BeginPlay()
{
    Super::BeginPlay();

    //Binding our float track to our UpdateTimelineComp Function's output
    UpdateFunctionFloat.BindDynamic(this, &ADoorActor::UpdateTimelineComp);

    //If we have a float curve, bind it's graph to our update function
    if (DoorTimelineFloatCurve)
    {
    DoorTimelineComp->AddInterpFloat(DoorTimelineFloatCurve, UpdateFunctionFloat);

    }
}

void ADoorActor::UpdateTimelineComp(float Output)
{
    //Create and set our Door's new relative location based on the output from our Timeline Curve
    FRotator DoorNewRotation = FRotator(0.0f,Output,0.f);
    Door->SetRelativeRotation(DoorNewRotation);
}

// Called every frame
void ADoorActor::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
}

Creating and Binding Collision Overlap Events

The Box Component requires the ability to react when an Actor enters or leaves its collision bounds.

  1. Navigate to the class definition of your TimelineDoorActor.h file and declare the following:

    // Begin and End Overlap Events for our DoorProxVolume
    UFUNCTION()
    void OnOverlapBegin(class UPrimitiveComponent* OverlappedComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
    
    UFUNCTION()
    void OnOverlapEnd(class UPrimitiveComponent* OverlappedComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
  2. Next, navigate to your DoorActor.cpp file to implement your OnOverlapBegin and OnOverlapEnd class methods:

    void ADoorActor::OnOverlapBegin(UPrimitiveComponent * OverlappedComp, AActor * OtherActor, UPrimitiveComponent * OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult)
    {
        DoorTimelineComp->Play();
    }
    
    void ADoorActor::OnOverlapEnd(UPrimitiveComponent * OverlappedComp, AActor * OtherActor, UPrimitiveComponent * OtherComp, int32 OtherBodyIndex)
    {
        DoorTimelineComp->Reverse();
    }
  3. Bind your overlap functions in the BeginPlay method, as follows:

    //Binding our Proximity Box Component to our Overlap Functions
    DoorProxVolume->OnComponentBeginOverlap.AddDynamic(this, &ADoorActor::OnOverlapBegin);
    DoorProxVolume->OnComponentEndOverlap.AddDynamic(this, &ADoorActor::OnOverlapEnd);
  4. Compile your code.

Creating a Curve Asset in the Unreal Editor

You must create a Curve Asset in the Unreal Editor to assign to your Timeline Actor Blueprint.

  1. From the Content Browser, select Add/Import > Miscellaneous > Curve.

    image alt text

  2. Select CurveFloat and name your CurveFloat asset DoorCurveFloat

  3. Double click your DoorCurveFloat asset to open the Timeline Editor.

  4. Add two keys to your Float Curve and give one key the time-value (0,0), and the other key the time-value of (4,90).

    image alt text

    For more information on editing Timeline curves, see Keys and Curves.

  5. Shift click to select both your keys, and set them to Auto Cubic interpolation, then save your Curve.

    image alt text

  6. Open your Bp_DoorActor and select Bp_DoorActor from the Components tab.

  7. From the Details panel, select DoorCurveFloat from the Door Timeline Float Curve dropdown menu.

    image alt text

  8. From the Level Editor, place the Bp_DoorActor into the scene.

  9. Compile and Save, then press PIE.

    image alt text

    Using input with WASD, we can control our spectator Pawn. Upon navigating to the collision bounds of our DoorActor, we can observe the timeline play, and upon exiting the bounds, we can observe it play in reverse.

Finished Code

DoorActor.h

// Copyright 1998-2021 Epic Games, Inc. All Rights Reserved.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/TimelineComponent.h"
#include "DoorActor.generated.h"

UCLASS()
class TIMELINEDOORACTOR_API ADoorActor : public AActor
{
    GENERATED_BODY()

public: 
    // Sets default values for this actor's properties
    ADoorActor();
    //Variable to hold Curve asset
    UPROPERTY(EditAnywhere)
    UCurveFloat* DoorTimelineFloatCurve;

protected:
    // Called when the game starts or when spawned 
    virtual void BeginPlay() override;

    /*MeshComponents to represents Door assets*/
    UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
    UStaticMeshComponent* DoorFrame;
    UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
    UStaticMeshComponent* Door;

    //TimelineComponent to animate door meshes
    UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
    UTimelineComponent* DoorTimelineComp;

    //BoxComponent which will be used as our proximity volume.
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    class UBoxComponent* DoorProxVolume;

    //Float Track Signature to handle our update track event
    FOnTimelineFloat UpdateFunctionFloat;

    //Function which updates our Door's relative location with the timeline graph
    UFUNCTION()
    void UpdateTimelineComp(float Output);

    /*Begin and End Overlap Events for our DoorProxVolume*/
    UFUNCTION()
    void OnOverlapBegin(class UPrimitiveComponent* OverlappedComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
    UFUNCTION()
    void OnOverlapEnd(class UPrimitiveComponent* OverlappedComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);

public: 
    // Called every frame
    virtual void Tick(float DeltaTime) override;
};

DoorActor.cpp

// Copyright 1998-2021 Epic Games, Inc. All Rights Reserved.

#include "DoorActor.h"
#include "Components/BoxComponent.h"

// Sets default values
ADoorActor::ADoorActor()
{
    // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;

    //Create our Default Components
    DoorFrame = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("DoorFrameMesh"));
    Door = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("DoorMesh"));
    DoorTimelineComp = CreateDefaultSubobject<UTimelineComponent>(TEXT("DoorTimelineComp"));
    DoorProxVolume = CreateDefaultSubobject<UBoxComponent>(TEXT("DoorProximityVolume"));

    //Setup our Attachments
    DoorFrame->SetupAttachment(RootComponent);
    Door->AttachToComponent(DoorFrame,FAttachmentTransformRules::KeepRelativeTransform);
    DoorProxVolume->AttachToComponent(DoorFrame, FAttachmentTransformRules::KeepRelativeTransform); 
}

// Called when the game starts or when spawned
void ADoorActor::BeginPlay()
{
    Super::BeginPlay();

    UpdateFunctionFloat.BindDynamic(this, &ADoorActor::UpdateTimelineComp);

    //If we have a float curve, bind it's graph to our update function
    if (DoorTimelineFloatCurve)
    {
        DoorTimelineComp->AddInterpFloat(DoorTimelineFloatCurve, UpdateFunctionFloat);
    }

    //Binding our Proximity Box Component to our Overlap Functions
    DoorProxVolume->OnComponentBeginOverlap.AddDynamic(this, &ADoorActor::OnOverlapBegin);
    DoorProxVolume->OnComponentEndOverlap.AddDynamic(this, &ADoorActor::OnOverlapEnd);
}

void ADoorActor::UpdateTimelineComp(float Output)
{
    FRotator DoorNewRotation = FRotator(0.0f,Output,0.f);
    Door->SetRelativeRotation(DoorNewRotation);
}

void ADoorActor::OnOverlapBegin(UPrimitiveComponent * OverlappedComp, AActor * OtherActor, UPrimitiveComponent * OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult)
{
    DoorTimelineComp->Play();
}

void ADoorActor::OnOverlapEnd(UPrimitiveComponent * OverlappedComp, AActor * OtherActor, UPrimitiveComponent * OtherComp, int32 OtherBodyIndex)
{
    DoorTimelineComp->Reverse();
}

// Called every frame
void ADoorActor::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
}
Help shape the future of Unreal Engine documentation! Tell us how we're doing so we can serve you better.
Take our survey
Dismiss