본문 바로가기
언리얼엔진 개발/VR

[UnrealEngine5]버튼을 누르면 문 열고 닫기 C++ (1)

by yeni_0224 2023. 4. 23.
728x90
반응형

현재 VR 게임을 개발 중에 있다. 지난 짧은 기간동안 언리얼엔진을 열심히 공부하고, 정말 이제는 공부한 내용들을 실전 적용해야할 때가 온 것이다. 작은 부분이라도 로직을 짜고, 코딩해나가는 중이다. 극 초반의 내 상태에 비하면 매우 발전했다고 생각하지만, 엔진은 공부해야할 부분이 정말 많기 때문에 긴장을 놓치지 않고 열심히 공부하며 작업하는 중이다.

 

각 스테이지를 클리어 했을 때, 버튼을 누르면 문이 열리고, 닫히도록 구현할 것이다.

스테이지를 클리어 했는지는 퍼즐을 관리하는 쪽에서 Delegate를 보내줄 것이고, 문쪽에서 그 신호를 받아 문을 열고 닫을 수 있도록 할 것이다. 또 문이 열리고 닫히기 위한 조건들이 또 여러가지 있지만 일단은 문이 열고 닫히도록 하는 것부터 구현할 것이다. 천천히, 차례대로..!!

 

처음 문을 구성할 때는 문이 열고 닫힐 수 있는 조건에 대한 부분이 완성되지 못했던 상황이었고

레벨 작업이 내 담당이었는데, 공간을 구성하면서 방의 크기 및 공간 구성을 위해 문이 필요했기 때문에 문 작업부터 먼저 해주었다. 일단은 퍼즐 시스템이 완성되기 전까지 다른 팀원들도 테스트 할 수 있도록 Trigger Box에 닿으면 문이 열리고 닫힐 수 있도록 구성해주었다.

 

How To Open the Sliding Door with Button?

그냥 문의 위치값만 지정해주면 문이 뿅! 뿅! 위치만 바뀌기 때문에 Lerp 함수를 사용해주어야했다.

하지만 Lerp 함수를 사용하는 방법이 익숙치 않기 때문에

일단 위치값만 지정하고 뿅 뿅 이동할 수 있도록하고 Lerp를 사용할 수 있도록 환경을 만들어주기로 했다.

Lerp만 사용하면 뭔가 사악 하고 열고 닫히는 느낌이 살짝 아쉽기 때문에 FTimeLineHandle을 사용해서 움직임에 약간의 엣지를 더해주었다.

Lerp를 사용해 문열고 닫는 방법의 튜토리얼들은 많이 올라와있었지만

지금 현재 내가 구현해야하는 환경에 다시 한번 맞춰주는데 시간이 걸렸다.

 

개발 환경 : Unreal Engine 5.1

Trigger Box에 닿으면 문이 열리고 닫힐 수 있도록

#pragma once

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

class UCurveFloat;

UCLASS()
class SPACEESCAPE_API ADoors : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	ADoors();

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

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

	/*퍼즐풀기를 완료하면 양 옆으로 열리는 문*/
	UPROPERTY(EditDefaultsOnly, Category = "Door Settings")
	class UStaticMeshComponent* doorMesh;

	//일단 trigger box에 닿으면 열리도록
	UPROPERTY(EditDefaultsOnly, Category="DoorSettings")
	class UBoxComponent* triggerboxComp;

	UFUNCTION()
	void OnTriggeredOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
		UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
	UFUNCTION()
	void OnTriggeredEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
	
    UFUNCTION()
	void Open();
	UFUNCTION()
	void Close();

	//문 여는 충돌감지 진행중인가
	bool bIsOpenOverlaping = true;
	UFUNCTION()
	void ChangeDoorOverlaping();

	/*문의 부드러운 이동*/
	UFUNCTION()
	void TimeLineProgress(float val);

	FTimeline curveTimeline;
	UPROPERTY()
	FVector startPoint;
	UPROPERTY()
	FVector endPoint;
	UPROPERTY(EditDefaultsOnly, Category = "DoorTimeLine")
	float yOffset = 200;
	UPROPERTY(EditDefaultsOnly, Category = "DoorTimeLine")
	UCurveFloat* curveFloat;
	
	//문 최초 위치
	FVector initLoc;

};

 

블루프린트의 TimeLine, Lerp, Set Relative Location을 사용해줄것이다.

테스트용으로 임시로 만들어둔 문이다. 나중에 에셋 추가하면 깐지나게 바뀔것이지

#include "Doors.h"
#include "Components/TimelineComponent.h"
#include "DoorButton.h"
#include "EngineUtils.h"
#include "Components/BoxComponent.h"

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

	this->RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootComponent"));
	triggerboxComp = CreateDefaultSubobject<UBoxComponent>(TEXT("triggerboxComp"));
	triggerboxComp->SetupAttachment(RootComponent);
	triggerboxComp->SetRelativeLocation(FVector(0, 0, 0));
	triggerboxComp->SetRelativeScale3D(FVector(5, 10, 1));
	doorMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("doorMesh"));
	doorMesh->SetupAttachment(RootComponent);
	doorMesh->SetRelativeLocation(FVector(0, 0, 0));
	doorMesh->SetRelativeScale3D(FVector(1, 4.625f, 2.4f));
	initLoc = GetActorLocation();
}

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

	initLoc = GetActorLocation();
	if (curveFloat)
	{
		FOnTimelineFloat TimeLineProgress;
		TimeLineProgress.BindUFunction(this, FName("TimeLineProgress"));
		curveTimeline.AddInterpFloat(curveFloat, TimeLineProgress);
		triggerboxComp->OnComponentBeginOverlap.AddDynamic(this, &ADoors::OnTriggeredOverlap);
		triggerboxComp->OnComponentEndOverlap.AddDynamic(this, &ADoors::OnTriggeredEndOverlap);
	}

}

// Called every frame
void ADoors::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	curveTimeline.TickTimeline(DeltaTime);
}

void ADoors::OnTriggeredOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
	UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{	
	bIsOpenOverlaping = true;
	Open();
}
void ADoors::OnTriggeredEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
                                   UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
	if(bIsOpenOverlaping == false)
	{
		Close();		
	}
}

void ADoors::ChangeDoorOverlaping()
{
	bIsOpenOverlaping == true ? Open() : Close();
}

void ADoors::Open()
{
	startPoint = GetActorLocation();
	endPoint = startPoint + FVector(0, yOffset, 0);
	curveTimeline.PlayFromStart();
	UE_LOG(LogTemp, Warning, TEXT("Overlapped : Open Door"))
	bIsOpenOverlaping = false;
}

void ADoors::Close()
{
	if (bIsOpenOverlaping == true) return;
	startPoint = GetActorLocation();	
	endPoint = initLoc;
	curveTimeline.PlayFromStart();
	bIsOpenOverlaping = true;
	UE_LOG(LogTemp, Warning, TEXT("EndOverlapped : Close Door"))
}

void ADoors::TimeLineProgress(float val)
{
	FVector newLoc = FMath::Lerp(startPoint, endPoint, val);
	SetActorLocation(newLoc);	
}

블루프린트에 할당해줄 것

bool 변수의 위치만 바뀌었는데도 불구하고 문이 생뚱맞은 방향으로 튀거나

문이 열고 닫히는 과정에서 튀는 듯한 현상들이 발생했었고, 그 부분은 수정되었다.

시작점과 끝점을 지정하는 과정을 계속 생각하고 맞춰봐야했다.

한 번 더 확실하게 해주는 차원에서 문의 초기 위치값을 지정해주고, 문이 닫혔을 때 그 지점으로 문이 이동할 수 있도록 해주었다.

 

블루프린트 상에서 TimeLine을 사용해, 느낌있게 스윽 열고 스윽 닫히는 효과를 적용하려 했지만

코드에서도 적용할 수 있다는 것을 알고 코드로 구현해보았다.

그렇지만 엔진 상에서 curve를 만들어주고, Door 블루프린트에서 curve를 할당해주어야한다.

할당해준 curve는 Contents Brower 창에서 우클릭 >> Miscellaneous >> Curve >> Curve Float 

아치형태로 그래프를 그리면 문이 제자리로 돌아오더라

Overlaping 할때마다 열고 닫을 수 있도록 설정해주었는데

문을 열어야하는 상태인지, 닫아야하는 상태인지 확인해주는 함수를 하나 만들고,

 그 함수를 통해서 여는 상태면, 여는 함수로 연결해주고

닫아야하는 상태면 닫는 함수로 연결해주었다.

 

튜토리얼 참고

https://www.youtube.com/watch?v=1y-FnncH-XQ 

728x90
반응형