Unreal 개발 본 캠프 34일차

HappyCircle·2026년 1월 15일

Unreal 개발

목록 보기
51/163

오늘 학습 진행 내용

[6번 과제] 회전 발판과 움직이는 장애물

📘 TIL – 회전 발판 & 움직이는 장애물 시스템 구현 (인터페이스 기반 설계)

📌 작업 개요

이번 과제에서는 회전 발판과 움직이는 장애물을 단순히 “동작만 구현”하는 방식이 아니라,
확장성과 유지보수를 고려한 구조 설계를 목표로 작업을 진행했다.

장애물마다 동작 방식은 다르지만,

이동

회전

스폰

충돌 처리

와 같은 공통 개념은 동일하다고 판단했고,
이를 인터페이스 기반 구조로 정리했다.

또한 Tick을 활용한 동적 Transform 변경,
DeltaTime 기반의 프레임 독립성 확보,
UPROPERTY를 통한 리플렉션 적용까지 필수 과제 요구사항을 모두 만족하도록 구현했다.

🧱 1. 장애물 인터페이스 설계

🔹 IIObstacleInterface

장애물이 반드시 가져야 할 공통 행동 규약을 인터페이스로 정의했다.

UINTERFACE(MinimalAPI)
class UIObstacleInterface : public UInterface
{
	GENERATED_BODY()
};

class IIObstacleInterface
{
	GENERATED_BODY()

public:
	virtual void OnObstcaleOverlap(AActor* OverlapActor) = 0;
	virtual void OnObstacalEndOverlap(AActor* OverlapActor) = 0;
	virtual FName GetObtacleType() const = 0;

	virtual void Move(float MaxRange, float MoveSpeed, FVector StartLocation) = 0;
	virtual void Rotate() = 0;
	virtual void Spawn() = 0;
};

✔ 설계 의도

장애물의 종류가 늘어나더라도 동일한 규약을 강제하기 위해 인터페이스를 사용했다.

이동, 회전, 스폰과 같은 핵심 동작을 인터페이스 단계에서 정의하여
“구현했는지 / 안 했는지”를 컴파일 단계에서 바로 확인할 수 있도록 했다.

추후 장애물 타입별 분기 처리나 관리 로직을 만들 때
GetObstacleType() 기반으로 확장 가능하도록 설계했다.

단순 상속이 아닌 인터페이스를 사용함으로써
행동 중심 설계(Behavior-based design)에 가깝게 구조를 구성했다.

🧱 2. BaseObstacle – 공통 부모 클래스

모든 장애물 Actor가 상속받는 기본 베이스 클래스를 구성했다.
인터페이스는 “무엇을 할 수 있는지”를 정의하고,
BaseObstacle은 “공통적으로 무엇을 가지고 있는지”를 담당한다.

ABaseObstacle::ABaseObstacle()
{
	SceneRoot = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot"));
	SetRootComponent(SceneRoot);

	StaticMeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMesh"));
	StaticMeshComp->SetupAttachment(SceneRoot);

	PrimaryActorTick.bCanEverTick = true;
}

✔ 역할

모든 장애물이 공통으로 사용하는 SceneComponent / StaticMeshComponent 관리

BeginPlay, Tick 등 Actor 생명주기 기본 구조 제공

인터페이스 함수의 기본 구현 지점 제공 (필요 시 자식 클래스에서 오버라이드)

장애물 제거 책임을 명확히 분리

void ABaseObstacle::DestoryObstacle()
{
	Destroy();
}

✔ 설계 포인트

파생 클래스에서 Destroy()를 직접 호출하지 않고
BaseObstacle이 제거 책임을 가지도록 설계했다.

이후 타이머 기반 제거, 조건부 제거 등으로 확장하기 쉬운 구조를 유지했다.

🔁 3. Tick 기반 동적 Transform 처리

🔹 회전 기능 구현

void APatrolObstacle::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	AddActorLocalRotation(FRotator(0.f, RotationSpeed * DeltaTime, 0.f));
}

✔ 구현 설명

Tick 함수에서 매 프레임 회전을 적용했다.

RotationSpeed * DeltaTime 방식으로 회전량을 계산해
프레임 수에 관계없이 항상 동일한 회전 속도를 유지하도록 했다.

✔ 핵심 포인트

DeltaTime을 사용하지 않으면 PC 성능에 따라 회전 속도가 달라지는 문제가 발생

Tick 기반 동작의 기본 원칙을 정확히 이해하고 적용

🔹 이동 기능 (왕복 이동)

void APatrolObstacle::Move(float InMaxRange, float InMoveSpeed, FVector InStartLocation)
{
	const FVector CurrentLocation = GetActorLocation();
	const float Distance = FVector::Dist(CurrentLocation, InStartLocation);

	if (Distance >= InMaxRange)
	{
		MoveDirection *= -1.f;
	}

	AddActorWorldOffset(MoveDirection * InMoveSpeed * GetWorld()->GetDeltaSeconds());
}

✔ 구현 설명

장애물의 시작 위치를 기준으로 현재 위치와의 거리를 계산

이동 거리가 MaxRange를 초과하면 이동 방향을 반전

AddActorWorldOffset + DeltaTime 조합으로 왕복 이동 구현

✔ 설계 포인트

단순한 좌표 이동이 아니라 기준 위치(StartLocation)를 명확히 설정

이동 범위 조정이 쉬운 구조

다른 장애물 타입에서도 재사용 가능한 이동 로직

🎛 4. 리플렉션 적용 (UPROPERTY)

주요 파라미터를 에디터에서 즉시 조정할 수 있도록
UPROPERTY를 활용해 리플렉션을 적용했다.

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Obstacle|Move")
float MoveSpeed = 200.f;

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Obstacle|Move")
float MaxRange = 300.f;

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Obstacle|Rotate")
float RotationSpeed = 90.f;

✔ 효과

Details 패널에서 이동 속도 / 범위 / 회전 속도를 실시간으로 수정 가능

플레이 중 값 변경 시 즉시 반영되는지 직접 확인

코드 수정 없이 레벨 디자이너가 장애물 난이도 조절 가능

디자이너 친화적인 데이터 중심 설계 구조 확보

profile
개발합시다!

0개의 댓글