https://github.com/K-Tia20/Basic-Actor-Control-Project.git

과제 조건

  • 스테이지 테마를 본인이 원하는 분위기(SF, 중세, 공장 지형 등)로 자유롭게 설정
  • C++ Actor 클래스 최소 2개 생성
  • StaticMeshComponent 포함 맵에 배치 가능한 형태
  • 두 클래스는 서로 다른 동작 로직(회전용 / 이동용 등) 수행해야합니다.
  • 회전 기능 (Rotating Actor)
  • Tick(float DeltaTime) 에서 AddActorLocalRotation()매 프레임 회전 적용
  • 이동 기능 (Moving Platform Actor)
  • Tick(float DeltaTime)에서 위치를 변경하여 왕복 이동 구현
  • StartLocation(시작 위치) 저장/기준점으로 사용
  • MoveSpeed(이동 속도) 적용
  • MaxRange(왕복 범위) 기준으로 이동 제한
  • 이동/회전 시 반드시 DeltaTime 을 활용
  • 리플렉션 적용
  • 주요 변수 (회전 속도, 이동 속도, 이동 범위 등)를 UPROPERTY로 선언하여 에디터에서 조정
  • EditAnywhere, BlueprintReadWrite, Category 등을 활용하여 Details 패널에서 편집 가능하게 만들기\
  • 플레이 중 Details 패널에서 값 변경 시 즉시 반영되는지 확인합니다.
  • C++ 클래스의 자식 블루프린트를 만들어 시각적 효과 등의 추가 로직을 Blueprint에서 구현해도 좋습니다.
  • 레벨에 제작한 회전 발판과 이동 플랫폼을 여러 개 배치
  • 각각 다른 속도, 이동범위, 회전값 적용
  • 플레이 모드에서 오브젝트가 의도대로 움직이는지 확인
  • 속도 변경이 실시간 반영되는지 확인

  • FTimerHandleGetWorld()->GetTimerManager().SetTimer(...)를 사용해 특정 시간 후 또는 주기적으로 함수를 호출
  • 매 프레임 호출(Tick)보다 효율적인 퍼포먼스를 제공
  • 일정 시간 후 발판이 사라지거나, 주기적으로 다른 위치로 이동하는 로직 추가
  • 게임 시작 시 SpawnActor를 통해 회전 발판/이동 플랫폼을 임의 좌표에 여러 개 배치
  • 회전/이동 속도, 이동 범위, 각도 등을 FMath::RandRange로 생성하여 매번 다른 퍼즐 코스를 구성

RotatingActor.h

// RotatingActor.h
 
#pragma once
 
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "RotatingActor.generated.h"
 
UCLASS()
class P3P4_API ARotatingActor : public AActor
{
	GENERATED_BODY()
	
public:	
	ARotatingActor();
 
protected:
	virtual void BeginPlay() override;
	virtual void Tick(float DeltaTime) override;
 
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
	USceneComponent* SceneRoot;
	
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Components")
	UStaticMeshComponent* StaticMeshComp;
	
public:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Control")
	float RotationSpeed;
	
protected:
	FTimerHandle TeleportTimerHandle;
	void TeleportRandomly();
};
 

RotatingActor.cpp

// RotatingActor.cpp
 
 
#include "Actor/RotatingActor.h"
 
ARotatingActor::ARotatingActor()
{
	PrimaryActorTick.bCanEverTick = true;
 
	SceneRoot = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot"));
	SetRootComponent(SceneRoot);
	
	StaticMeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMesh"));
	StaticMeshComp->SetupAttachment(SceneRoot);
	
	RotationSpeed = 90.0f;
}
 
void ARotatingActor::BeginPlay()
{
	Super::BeginPlay();
	
	GetWorld()->GetTimerManager().SetTimer(TeleportTimerHandle, this, &ARotatingActor::TeleportRandomly, 5.0f, true);
 
}
 
void ARotatingActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
 
	if (!FMath::IsNearlyZero(RotationSpeed))
	{
		AddActorLocalRotation(FRotator(0.0f, RotationSpeed * DeltaTime, 0.0f));
	}
}
 
void ARotatingActor::TeleportRandomly()
{
	FVector CurrentLocation = GetActorLocation();
	
	CurrentLocation.X += FMath::RandRange(-200.0f, 200.0f);
	CurrentLocation.Y += FMath::RandRange(-200.0f, 200.0f);
	
	SetActorLocation(CurrentLocation);
}

MovingActor.h

// MovingActor.h
 
#pragma once
 
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MovingActor.generated.h"
 
UCLASS()
class P3P4_API AMovingActor : public AActor
{
	GENERATED_BODY()
	
public:	
	AMovingActor();
 
protected:
	virtual void BeginPlay() override;
	virtual void Tick(float DeltaTime) override;
	
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
	USceneComponent* SceneRoot;
	
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Components")
	UStaticMeshComponent* StaticMeshComp;
	
public:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Control")
	float MoveSpeed;
	
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Control")
	float MaxRange;
	
private:
	FVector StartLocation;
	int32 MoveDirection;
};
 

MovingActor.cpp

// MovingActor.cpp
 
 
#include "Actor/MovingActor.h"
 
AMovingActor::AMovingActor()
{
	PrimaryActorTick.bCanEverTick = true;
 
	SceneRoot = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot"));
	SetRootComponent(SceneRoot);
 
	StaticMeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMesh"));
	StaticMeshComp->SetupAttachment(SceneRoot);
	
	MoveSpeed = 200.0f;
	MaxRange = 500.0f;
	MoveDirection = 1;
	
}
 
void AMovingActor::BeginPlay()
{
	Super::BeginPlay();
	StartLocation = GetActorLocation();
}
 
void AMovingActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
 
	float FrameMovement = MoveSpeed * MoveDirection * DeltaTime;
	
	FVector CurrentLocation = GetActorLocation();
	FVector MovementVector = FVector(FrameMovement, 0.0f, 0.0f);
	FVector NextLocation = CurrentLocation + MovementVector;
	SetActorLocation(NextLocation);
	
	float DistanceMoved = FVector::Dist(StartLocation, NextLocation);
	
	if (DistanceMoved >= MaxRange)
	{
		MoveDirection *= -1;
	}
}
 
 

SpawnActor.h

// SpawnActor.h
 
#pragma once
 
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "SpawnActor.generated.h"
 
UCLASS()
class P3P4_API ASpawnActor : public AActor
{
	GENERATED_BODY()
	
public:	
	ASpawnActor();
 
protected:
	virtual void BeginPlay() override;
 
	UPROPERTY(EditAnywhere, Category = "Spawn Settings")
	TSubclassOf<AActor> RotatingActorClass;
	
	UPROPERTY(EditAnywhere, Category = "Spawn Settings")
	TSubclassOf<AActor> MovingActorClass;
	
	UPROPERTY(EditAnywhere, Category = "Spawn Settings")
	int32 SpawnCount;
	
};
 

SpawnActor.cpp

// SpawnActor.cpp
 
 
#include "Actor/SpawnActor.h"
#include "Actor/RotatingActor.h"
#include "Actor/MovingActor.h"
 
 
ASpawnActor::ASpawnActor()
{
	PrimaryActorTick.bCanEverTick = false;
	SpawnCount = 30;
}
 
void ASpawnActor::BeginPlay()
{
	Super::BeginPlay();
	
	for (int32 i = 0; i < SpawnCount; i++)
	{
		FVector RandomLocation = FVector(FMath::RandRange(-1500.0f, 1500.0f), FMath::RandRange(-1500.0f, 1500.0f), 200.0f);
		FRotator RandomRotation = FRotator(0.0f, FMath::RandRange(0.0f, 360.0f), 0.0f);
	
		if (RotatingActorClass)
		{
			ARotatingActor* NewRotator = GetWorld()->SpawnActor<ARotatingActor>(RotatingActorClass, RandomLocation, RandomRotation);
			if (NewRotator)
			{
				NewRotator->RotationSpeed = FMath::RandRange(30.0f, 200.0f);
			}
		}
		
		RandomLocation.Y += 300.0f;
		RandomLocation.Z = 20.0f;
		
		if (MovingActorClass)
		{
			AMovingActor* NewMover = GetWorld()->SpawnActor<AMovingActor>(MovingActorClass, RandomLocation, FRotator::ZeroRotator);
			if (NewMover)
			{
				NewMover->MoveSpeed = FMath::RandRange(100.0f, 400.0f);
				NewMover->MaxRange = FMath::RandRange(200.0f, 600.0f);
			}
		}
	}
}
 

자세한 설명

 
 
 
 
 
 

작성 방법

C++ 액터 클래스 적용 및 월드 배치 가이드

C++ 코드가 모두 작성 및 컴파일 완료된 후, 언리얼 에디터에서 이 액터들을 실제로 작동시키기 위한 최소한의 에디터 설정 및 배치 단계입니다.

1단계: C++ 기반 블루프린트 클래스 생성

C++ 코드는 순수한 기능(뼈대)일 뿐이므로, 에디터에서 외형(Mesh)을 입히고 세부 수치를 조절할 수 있도록 블루프린트 클래스(BP)로 상속받아 만들어야 합니다.

  1. 콘텐츠 브라우저에서 생성된 C++ 클래스 폴더로 이동합니다.
  2. RotatingActor 클래스를 우클릭하고 [Create Blueprint class based on RotatingActor]를 선택하여 BP_RotatingActor를 생성합니다.
  3. MovingActor 클래스도 동일하게 우클릭하여 BP_MovingActor를 생성합니다.
  4. SpawnActor 클래스 역시 우클릭하여 BP_SpawnActor를 생성합니다.
2단계: 회전/이동 액터 외형(Static Mesh) 지정

생성한 블루프린트 액터들이 눈에 보이도록 외형 컴포넌트를 지정해 줍니다.

  1. BP_RotatingActorBP_MovingActor를 각각 더블클릭하여 에디터를 엽니다.
  2. 왼쪽 상단 컴포넌트 창에서 StaticMeshComponent (또는 본인이 생성한 메쉬 컴포넌트)를 선택합니다.
  3. 우측 디테일(Details) 패널의 Static Mesh 항목에서 원하는 외형 에셋(예: 큐브, 구체, 콘 등)을 지정합니다.
  4. Compile(컴파일) 후 Save(저장)를 누르고 창을 닫습니다.
3단계: 스폰 액터(SpawnActor)에 대상 등록하기

SpawnActor가 맵에 태어날 때 어떤 액터들을 무작위로 생성할지 에셋을 연결해 주는 핵심 단계입니다.

  1. BP_SpawnActor를 더블클릭하여 엽니다.
  2. 우측 디테일(Details) 패널 또는 변수 창에서 C++로 선언했던 클래스 선택 변수를 찾습니다.
    • RotatingActorClass 항목의 드롭다운을 눌러 방금 만든 BP_RotatingActor를 지정합니다.
    • MovingActorClass 항목의 드롭다운을 눌러 방금 만든 BP_MovingActor를 지정합니다.
  3. (선택 사항) SpawnCount 변수값을 수정하여 한 번에 스폰할 세트 개수를 조절할 수 있습니다 (기본값: 5).
  4. Compile(컴파일) 후 Save(저장)합니다.
4단계: 레벨 배치 및 테스트

모든 준비가 끝났으므로 게임 월드에 배치하여 구동을 확인합니다.

  1. 콘텐츠 브라우저에서 최종 설정 마친 BP_SpawnActor를 마우스로 끌어서 현재 열려있는 게임 레벨(맵)의 공중 적당한 위치에 배치합니다.
    • 주의: SpawnActor 자체는 눈에 보이지 않는 관리자 액터이므로 위치가 크게 중요하지 않으나, 내부 코드에서 0.0f ~ 1500.0f 범위에 액터들을 뿌리므로 넓은 지형 위쪽이 좋습니다.
  2. 에디터 상단의 Play (P) 버튼을 누릅니다.
  3. 게임이 시작됨과 동시에 지정한 범위 내에 회전하는 액터 5개(하늘)와 좌우로 이동하는 액터 5개(지면 근처)가 무작위 속도와 범위로 자동 스폰되어 움직이는 것을 확인할 수 있습니다.