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에서 구현해도 좋습니다.
- 레벨에 제작한 회전 발판과 이동 플랫폼을 여러 개 배치
- 각각 다른 속도, 이동범위, 회전값 적용
- 플레이 모드에서 오브젝트가 의도대로 움직이는지 확인
- 속도 변경이 실시간 반영되는지 확인
-
FTimerHandle과GetWorld()->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)로 상속받아 만들어야 합니다.
- 콘텐츠 브라우저에서 생성된 C++ 클래스 폴더로 이동합니다.
RotatingActor클래스를 우클릭하고 [Create Blueprint class based on RotatingActor]를 선택하여BP_RotatingActor를 생성합니다.MovingActor클래스도 동일하게 우클릭하여BP_MovingActor를 생성합니다.SpawnActor클래스 역시 우클릭하여BP_SpawnActor를 생성합니다.
2단계: 회전/이동 액터 외형(Static Mesh) 지정
생성한 블루프린트 액터들이 눈에 보이도록 외형 컴포넌트를 지정해 줍니다.
BP_RotatingActor와BP_MovingActor를 각각 더블클릭하여 에디터를 엽니다.- 왼쪽 상단 컴포넌트 창에서
StaticMeshComponent(또는 본인이 생성한 메쉬 컴포넌트)를 선택합니다. - 우측 디테일(Details) 패널의
Static Mesh항목에서 원하는 외형 에셋(예: 큐브, 구체, 콘 등)을 지정합니다. - Compile(컴파일) 후 Save(저장)를 누르고 창을 닫습니다.
3단계: 스폰 액터(SpawnActor)에 대상 등록하기
SpawnActor가 맵에 태어날 때 어떤 액터들을 무작위로 생성할지 에셋을 연결해 주는 핵심 단계입니다.
BP_SpawnActor를 더블클릭하여 엽니다.- 우측 디테일(Details) 패널 또는 변수 창에서 C++로 선언했던 클래스 선택 변수를 찾습니다.
RotatingActorClass항목의 드롭다운을 눌러 방금 만든BP_RotatingActor를 지정합니다.MovingActorClass항목의 드롭다운을 눌러 방금 만든BP_MovingActor를 지정합니다.
- (선택 사항)
SpawnCount변수값을 수정하여 한 번에 스폰할 세트 개수를 조절할 수 있습니다 (기본값: 5). - Compile(컴파일) 후 Save(저장)합니다.
4단계: 레벨 배치 및 테스트
모든 준비가 끝났으므로 게임 월드에 배치하여 구동을 확인합니다.
- 콘텐츠 브라우저에서 최종 설정 마친
BP_SpawnActor를 마우스로 끌어서 현재 열려있는 게임 레벨(맵)의 공중 적당한 위치에 배치합니다.- 주의:
SpawnActor자체는 눈에 보이지 않는 관리자 액터이므로 위치가 크게 중요하지 않으나, 내부 코드에서0.0f ~ 1500.0f범위에 액터들을 뿌리므로 넓은 지형 위쪽이 좋습니다.
- 주의:
- 에디터 상단의 Play (P) 버튼을 누릅니다.
- 게임이 시작됨과 동시에 지정한 범위 내에 회전하는 액터 5개(하늘)와 좌우로 이동하는 액터 5개(지면 근처)가 무작위 속도와 범위로 자동 스폰되어 움직이는 것을 확인할 수 있습니다.