🕹️언리얼엔진/기초

[UE4] 선형보간법(Linear Interpolation)을 이용하여 움직이는 객체를 만들자!

Mawile 2021. 12. 30.
728x90

안녕하세요! 이번에 노트북 새로 장만한 기념으로 언리얼엔진을 공부하고있습니다.

앞으로는 자체엔진공부 & 언리얼엔진공부를 집중적으로 해보려구요!!

 

[UE4] 선형보간법(Linear Interpolation)을 이용하여 움직이는 객체를 만들자!

선형 보간법(線型補間法, linear interpolation)은 끝점의 값이 주어졌을 때 그 사이에 위치한 값을 추정하기 위하여 직선 거리에 따라 선형적으로 계산하는 방법입니다.

 

만약에 이러한 상황이 있다고 가정해봅시다.

어떠한 정점A를 (10, 5)이라고 하고 어떠한 정점 B를 (-1, 0)라고 했을때,
일정한 속도로 A에서 B로가는데 5초가 걸렸다고 해봅시다.
그러면 1.7초가 걸렸을때는 어디에 위치할까요?

 

흠...

아무래도 선형보간법을 사용하지않고 암산으로 풀기에는 한계가 있죠.

선형보간법은 이러한때에 사용할 수 있습니다.

 

우선 선형보간법을 실제 게임에 적용하기 위해서는 선형보간법이 어떻게 구현되는지 알아야합니다.

이 부분은 저가 설명하는것보다 더 정확한 자료가 나와있으니 참고해주시기 바랍니다.

 

C++
#include <iostream>

struct Matrix {
	float x, y, z;
};

float lerp(float p1, float p2, float d1) {
  return (((1-d1) * p1) + (d1 * p2));
}

int main() {
	Matrix v0, v1;
	float t;
	
	v0 = { 10, 5, 0 };
	v1 = { -1, 0, 0 };
	t = 1.7f / 5.0f;
	
	float result_x = lerp(v0.x, v1.x, t);
	float result_y = lerp(v0.y, v1.y, t);
	float result_z = lerp(v0.z, v1.z, t);
	
	std::cout << "x: " << result_x << std::endl;
	std::cout << "y: " << result_y << std::endl;
	std::cout << "z: " << result_z << std::endl;
}

 

그렇습니다..

1.7초가 지났을때는 (6.26, 3.3)의 위치에 가게 된다고합니다.

 

 

실제 게임에 적용하기

실제 게임에 적용하기 위해서 저는 언리얼엔진을 사용하겠습니다.

(빈 프로젝트) + (C++) + (추가 콘텐츠 포함) 을 선택합니다.

 

우선 저는 UStaticMeshComponent를 이용해서 액터를 하나 만들고 해당 액터에 선형보간법을 적용하여, 초단위의 특정주기로 오브젝트를 움직이는 예제를 만들어보았습니다.

AMovingBox.h
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include <Engine/Classes/Components/StaticMeshComponent.h>
#include "MovingBox.generated.h"

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

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

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

	bool isOpen;

	UPROPERTY(EditAnywhere)
	float waitTime;

	float timeDeltaTime;
	float fixedZ;

	FTimerHandle fTimeHandler;

	FVector fActorLocation;

	UPROPERTY(EditAnywhere)
	UStaticMeshComponent* uStaticMeshComponent;

	void BeginMove();
	void EndMove();

};

 

AMovingBox.cpp
// Fill out your copyright notice in the Description page of Project Settings.


#include "MovingBox.h"

// Sets default values
AMovingBox::AMovingBox()
{
 	// 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;

	uStaticMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Box"));
	RootComponent = uStaticMeshComponent;

	waitTime = 0.0f;
	isOpen = false;

	// Z가 0이면은 오브젝트가 땅에 박히니까 그냥 잘보이도록 기본 z값은 50으로 설정
	fixedZ = 50.0f;
}

// Called when the game starts or when spawned
void AMovingBox::BeginPlay()
{
	Super::BeginPlay();
	
	// 10을 곱해준 이유는 그냥 (10, 5, fixedZ)로 하면 움직이는 거리가 너무 적어서 잘 안보임
	SetActorLocation(FVector(10 * 10, 5 * 10, fixedZ));
	fActorLocation = GetActorLocation();
	GetWorldTimerManager().SetTimer(fTimeHandler, this, &AMovingBox::BeginMove, 0.03f, true, waitTime);
}

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

	timeDeltaTime += DeltaTime;
}

void AMovingBox::BeginMove() {
	if (!isOpen) {
		isOpen = true;
		timeDeltaTime = 0.0f;
	}

	FVector bActorLocation;

	// 선형보간법(Linear Interpolation)
	// 10을 곱해준 이유는 그냥 (10, 5, fixedZ)로 하면 움직이는 거리가 너무 적어서 잘 안보임
	bActorLocation = fActorLocation +
		FVector(FMath::Lerp(10 * 10, -1 * 10, timeDeltaTime),
			FMath::Lerp(5 * 10, 0 * 10, timeDeltaTime),
			FMath::Lerp(fixedZ, fixedZ, timeDeltaTime));

	SetActorLocation(bActorLocation);

	// 타이머가 끝나면
	if (timeDeltaTime > 1.0f) {
		GetWorldTimerManager().ClearTimer(fTimeHandler);
		GetWorldTimerManager().SetTimer(fTimeHandler, this, &AMovingBox::EndMove, 0.03f, true, waitTime);
	}
}

void AMovingBox::EndMove() {
	if (isOpen) {
		isOpen = false;
		timeDeltaTime = 0.0f;
	}

	FVector bActorLocation;

	// 선형보간법(Linear Interpolation)
	// 10을 곱해준 이유는 그냥 (10, 5, fixedZ)로 하면 움직이는 거리가 너무 적어서 잘 안보임
	bActorLocation = fActorLocation +
		FVector(FMath::Lerp(-1 * 10, 10 * 10, timeDeltaTime),
			FMath::Lerp(0 * 10, 5 * 10, timeDeltaTime),
			FMath::Lerp(fixedZ, fixedZ, timeDeltaTime));

	SetActorLocation(bActorLocation);

	// 타이머가 끝나면
	if (timeDeltaTime > 1.0f) {
		GetWorldTimerManager().ClearTimer(fTimeHandler);
		GetWorldTimerManager().SetTimer(fTimeHandler, this, &AMovingBox::BeginMove, 0.03f, true, waitTime);
	}
}

 

우선 저는 위의 예제와 같이 두 A(10, 5), B(-1, 0)의 정점을 사용하였고, 다른점이라고 한다면 Z축을 50으로 설정하였고, 매번 10을 곱한다는점입니다.

 

우선 언리얼엔진4의 좌표평면은 상향벡터가 z인 xy평면좌표계입니다.

저는 공중에 오브젝트를 띄우는것이 더 잘보일것같아서 50으로 했구요, 10을 곱한것은 곱하지않을때보다 변화가 더 눈에 뜁니다.

실행

 

와우!! 굿!! 아주 잘됩니다.

그럼 안녕!

 


728x90

댓글