Opening the door - animation via C++

So, I think there are better ways to do that (in Unity you have animators), but at lesson 98 (“Open the door” challenge) I thought: well, if you stick there a 90° Yaw rotation the door will not “open”, it will “start open”. So I thought of animating it through code.

This is the resulting code. It supports (or should support) opening and closing, and the duration of the animation is set via “blueprint” (Component parameter).

cpp file
// Source code freely available for any kind of use


#include "OpenDoor.h"
#include "GameFramework/Actor.h"

const float UOpenDoor::ANGLE_TO_OPEN = 90.f;

// Sets default values for this component's properties
UOpenDoor::UOpenDoor()
	: bIsOpening(false), bIsClosing(false), RotatedAngle(0)
{
	// Set this component to be initialized when the game starts, and to be ticked every frame.  You can turn these features
	// off to improve performance if you don't need them.
	PrimaryComponentTick.bCanEverTick = true;

	// ...
}


void UOpenDoor::Open()
{
	UE_LOG(LogTemp, Warning, TEXT("parameters: %d, %d, %f"), bIsClosing, bIsOpening, RotatedAngle);

	if (bIsClosing || bIsOpening || IsOpen())
		return;

	UE_LOG(LogTemp, Warning, TEXT("Opening door"));

	bIsOpening = true;
}

void UOpenDoor::Close()
{
	if (bIsClosing || bIsOpening || IsClosed())
		return;

	UE_LOG(LogTemp, Warning, TEXT("Closing door"));

	bIsClosing = true;
}

// Called when the game starts
void UOpenDoor::BeginPlay()
{
	Super::BeginPlay();

	Open();
}

bool UOpenDoor::IsClosed() const
{
	return FMath::Abs(RotatedAngle) <= 0.01f;
}

bool UOpenDoor::IsOpen() const
{
	return FMath::Abs(RotatedAngle - 90) <= 0.01f;
}


// Called every frame
void UOpenDoor::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

	if (bIsOpening || bIsClosing)
	{
		auto CurrentRotation = GetOwner()->GetActorRotation();
		float deltaAngle = GetAngleByDelta(DeltaTime);
		CurrentRotation.Yaw += deltaAngle;
		GetOwner()->SetActorRotation(CurrentRotation);
		UE_LOG(LogTemp, Warning, TEXT("Moving the door by %f"), deltaAngle);

		RotatedAngle += deltaAngle;
		if ((bIsOpening && IsOpen()) || (bIsClosing && IsClosed()))
			StopMoving();
	}


	// ...
}

void UOpenDoor::StopMoving()
{
	bIsOpening = bIsClosing = false;
}

float UOpenDoor::GetAngleByDelta(float DeltaTime) const
{
	float deltaAngle = (ANGLE_TO_OPEN / TimeToOpen) * DeltaTime * (bIsOpening ? 1 : -1);

	float min = bIsOpening ? 0 : -RotatedAngle;
	float max = bIsOpening ? ANGLE_TO_OPEN - RotatedAngle : ANGLE_TO_OPEN;
	
	UE_LOG(LogTemp, Warning, TEXT("GetAngleByDelta: for %f seconds, clamping %f between %f and %f"),
		DeltaTime, deltaAngle, min, max);

	return FMath::Clamp(deltaAngle, min, max);
}
header file
// Source code freely available for any kind of use

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "OpenDoor.generated.h"


UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class ROOMESCAPE_API UOpenDoor : public UActorComponent
{
	GENERATED_BODY()

public:	
	// Sets default values for this component's properties
	UOpenDoor();

	void Open();
	void Close();

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

	bool bIsOpening;
	bool bIsClosing;
	float RotatedAngle;

	bool IsOpen() const;
	bool IsClosed() const;
	void StopMoving();

	float GetAngleByDelta(float DeltaTime) const;

	static const float ANGLE_TO_OPEN;

public:	
	// Called every frame
	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
		float TimeToOpen = 2.f;
		
};

Here’s a GIF with the result (sorry for the quality):
2020.05.03-15.42_3

What would be the correct way to animate an opening door in a given amount of time?

2 Likes

FMath::InterpConstantTo

Members:

float InitialYaw;
constexpr static float OpenAngle = 90.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float TimeToOpen = 2.f;
void UOpenDoor::BeginPlay()
{
	Super::BeginPlay();
	InitialYaw = GetOwner()->GetActorRotation().Yaw;
}
void UOpenDoor::OpenDoor(float DeltaTime)
{
	FRotator DoorRotation = GetOwner()->GetActorRotation();
	const float Speed = OpenAngle / TimeToOpen; // speed = d / t
	DoorRotation.Yaw = FMath::FInterpConstantTo(DoorRotation.Yaw, InitialYaw + OpenAngle, DeltaTime, Speed);
	GetOwner()->SetActorRotation(DoorRotation);
}

Code comments

1 Like

Thank you, I didn’t know about IsNearlyEqual - well done, Epic.
About the mixed usage of initialisation styles, are you referring to TimeToOpen? I thought the initialisation for Blueprint-driven fields was usually done in the header file instead of inside the constructor for some Blueprint macro-related reason, but if it isn’t the case I will happily move it to the inititialisation list.

And finally, thank you again for the code style gotchas, I missed that repository and I’ll be sure to follow those conventions from now on :slight_smile:

1 Like