Hey everyone!
Here is my variant of Building Escape section. The assets have been taken from Modular Lost Ruin Kit at EGS Store for free. I have added custom door though in order to open in downwards (change Z location i.o. rotation, see my cpp files for more details). Also, I have locked the rotation of grabbed object.
OpenDoor.h
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "Engine/TriggerVolume.h"
#include "OpenDoor.generated.h"
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class DUNGEONESCAPE_API UOpenDoor : public UActorComponent
{
GENERATED_BODY()
public:
// Sets default values for this component's properties
UOpenDoor();
// Called every frame
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
void LowerTheGate(float DeltaTime);
void UpperTheGate(float DeltaTime);
float TotalMassOfActors() const;
void FindAudioComponent();
void FindPressurePlate();
protected:
// Called when the game starts
virtual void BeginPlay() override;
private:
bool bOpenSoundWasPlayed = false;
bool bCloseSoundWasPlayed = true;
float DoorInitial_Z;
float DoorCurrent_Z;
float DoorTarget_Z = -170.f;
UPROPERTY(EditAnywhere)
float OpeningSpeed = 30.f;
float DoorLastOpened = 0.f;
UPROPERTY(EditAnywhere)
float DoorCloseDelay = 1.f;
UPROPERTY(EditAnywhere)
float DoorCloseSpeed = 2.5f;
UPROPERTY(EditAnywhere)
float RequiredMassToOpenDoor = 20.f;
UPROPERTY()
UAudioComponent* AudioComponent = nullptr;
UPROPERTY(EditAnywhere)
ATriggerVolume* PressurePlate = nullptr;
};
OpenDoor.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "OpenDoor.h"
#include "Components/AudioComponent.h"
#include "GameFramework/Actor.h"
#include "Components/PrimitiveComponent.h"
#define OUT
// Sets default values for this component's properties
UOpenDoor::UOpenDoor()
{
// 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;
}
// Called when the game starts
void UOpenDoor::BeginPlay()
{
Super::BeginPlay();
DoorInitial_Z = GetOwner()->GetActorLocation().Z;
DoorCurrent_Z = DoorInitial_Z;
FindPressurePlate();
FindAudioComponent();
}
void UOpenDoor::FindPressurePlate()
{
if (!PressurePlate)
{
UE_LOG(LogTemp, Error, TEXT("%s has OpenDoor component, but no PressurePlate set!!!"), *GetOwner()->GetName());
}
}
void UOpenDoor::FindAudioComponent()
{
AudioComponent = GetOwner()->FindComponentByClass<UAudioComponent>();
if (!AudioComponent)
{
UE_LOG(LogTemp, Error, TEXT("No AudioComponent is missing in %s"), *GetOwner()->GetName());
}
}
// Called every frame
void UOpenDoor::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
if (TotalMassOfActors() > RequiredMassToOpenDoor)
{
LowerTheGate(DeltaTime);
DoorLastOpened = GetWorld()->GetTimeSeconds();
}
else
{
if (GetWorld()->GetTimeSeconds() >= DoorLastOpened + DoorCloseDelay) UpperTheGate(DeltaTime);
}
}
void UOpenDoor::LowerTheGate(float DeltaTime)
{
DoorCurrent_Z = FMath::FInterpConstantTo(DoorCurrent_Z, DoorTarget_Z, DeltaTime, OpeningSpeed);
FVector DoorLocation = GetOwner()->GetActorLocation();
DoorLocation.Z = DoorCurrent_Z;
GetOwner()->SetActorLocation(DoorLocation);
if (!AudioComponent) return;
if (bOpenSoundWasPlayed == false)
{
UE_LOG(LogTemp, Warning, TEXT("Lower The Gate!"));
AudioComponent->Play(); // open the gate
bOpenSoundWasPlayed = true;
}
}
void UOpenDoor::UpperTheGate(float DeltaTime)
{
DoorCurrent_Z = FMath::FInterpConstantTo(DoorCurrent_Z, DoorInitial_Z, DeltaTime, OpeningSpeed);
FVector DoorLocation = GetOwner()->GetActorLocation();
DoorLocation.Z = DoorCurrent_Z;
GetOwner()->SetActorLocation(DoorLocation);
if (!AudioComponent) return;
if (bCloseSoundWasPlayed == false)
{
UE_LOG(LogTemp, Warning, TEXT("Upper The Gate!"));
AudioComponent->Play(); // close the gate
bCloseSoundWasPlayed = true;
bOpenSoundWasPlayed = false;
}
}
float UOpenDoor::TotalMassOfActors() const
{
float TotalMassOfActor = 0.f;
//Find all overlapping actors
TArray<AActor*> OverlappingActors;
if (!PressurePlate) // protect against nullptr
{
return TotalMassOfActor;
}
PressurePlate->GetOverlappingActors(OUT OverlappingActors);
//add up their masses
for (AActor* Actor : OverlappingActors)
{
TotalMassOfActor += Actor->FindComponentByClass<UPrimitiveComponent>()->GetMass();
}
return TotalMassOfActor;
}
Grabber.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "PhysicsEngine/PhysicsHandleComponent.h"
#include "Grabber.generated.h"
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class DUNGEONESCAPE_API UGrabber : public UActorComponent
{
GENERATED_BODY()
public:
// Sets default values for this component's properties
UGrabber();
protected:
// Called when the game starts
virtual void BeginPlay() override;
public:
// Called every frame
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
float Reach = 200.f;
UPROPERTY()
UPhysicsHandleComponent* PhysicsHandle = nullptr;
UPROPERTY()
UInputComponent* InputComponent = nullptr;
void FindPhysicsHandle();
void SetUpInputComponent();
void Grab();
void Release();
// Return the first Actor within reach with PhysicsBody
FHitResult GetFirstPhysicsBodyInReach() const;
// return the vector's end of our line trace
FVector GetPlayersReach() const;
// return player's position in the world
FVector GetPlayersWorldPos() const;
};
Grabber.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "Grabber.h"
#define OUT
// Sets default values for this component's properties
UGrabber::UGrabber()
{
// 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;
// ...
}
// Called when the game starts
void UGrabber::BeginPlay()
{
Super::BeginPlay();
FindPhysicsHandle();
SetUpInputComponent();
}
// Called every frame
void UGrabber::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
//if physics handle is attached (if we actually grabbed something)
if (!PhysicsHandle) // protect against nullptr
{
return;
}
if (PhysicsHandle->GrabbedComponent)
{
//move the object we are holding
PhysicsHandle->SetTargetLocation(GetPlayersReach());
}
}
void UGrabber::FindPhysicsHandle()
{
PhysicsHandle = GetOwner()->FindComponentByClass<UPhysicsHandleComponent>();
if (!PhysicsHandle)
{
UE_LOG(LogTemp, Error, TEXT("The component PhysicsHandle in class %s was NOT found!"), *GetOwner()->GetName());
}
}
void UGrabber::SetUpInputComponent()
{
InputComponent = GetOwner()->FindComponentByClass<UInputComponent>();
if (InputComponent)
{
InputComponent->BindAction("Grab", IE_Pressed, this, &UGrabber::Grab);
InputComponent->BindAction("Grab", IE_Released, this, &UGrabber::Release);
}
else UE_LOG(LogTemp, Error, TEXT("The component InputComponent in class %s was NOT found!"), *GetOwner()->GetName());
}
void UGrabber::Grab()
{
// only ray cast when key is pressed and see if we reach any actor with a Physics Body Collision Channel set.
FHitResult HitResult = GetFirstPhysicsBodyInReach();
UPrimitiveComponent* ComponentToGrab = HitResult.GetComponent();
// + lock component from rotating when grabbed
const FRotator ZeroRotation = {0,0,0};
//if we hit something, then attach the physics handle
AActor* ActorHit = HitResult.GetActor();
if (ActorHit)
{
if (!PhysicsHandle) // protect against nullptr
{
return;
}
PhysicsHandle->GrabComponentAtLocationWithRotation(
ComponentToGrab,
NAME_None,
GetPlayersReach(),
ZeroRotation
);
}
}
void UGrabber::Release()
{
if (!PhysicsHandle) // protect against nullptr
{
return;
}
PhysicsHandle->ReleaseComponent();
}
FHitResult UGrabber::GetFirstPhysicsBodyInReach() const
{
// ray-cast out to a certain distance (reach)
FHitResult Hit;
FCollisionQueryParams TraceParams(FName(TEXT("")), false, GetOwner());
GetWorld()->LineTraceSingleByObjectType(
OUT Hit,
GetPlayersWorldPos(),
GetPlayersReach(),
FCollisionObjectQueryParams(ECollisionChannel::ECC_PhysicsBody),
TraceParams
);
return Hit;
}
FVector UGrabber::GetPlayersReach() const
{
FVector PlayerViewPoint_Location;
FRotator PlayerViewPoint_Rotation;
GetWorld()->GetFirstPlayerController()->GetPlayerViewPoint(
OUT PlayerViewPoint_Location,
OUT PlayerViewPoint_Rotation
);
return PlayerViewPoint_Location + PlayerViewPoint_Rotation.Vector() * Reach;;
}
FVector UGrabber::GetPlayersWorldPos() const
{
FVector PlayerViewPoint_Location;
FRotator PlayerViewPoint_Rotation;
GetWorld()->GetFirstPlayerController()->GetPlayerViewPoint(
OUT PlayerViewPoint_Location,
OUT PlayerViewPoint_Rotation
);
return PlayerViewPoint_Location;
}