DeltaTime을 사용한 프레임 독립 처리#include "BaseObstacle.h"
#include "Components/StaticMeshComponent.h"
ABaseObstacle::ABaseObstacle()
{
PrimaryActorTick.bCanEverTick = true;
SceneRoot = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot"));
SetRootComponent(SceneRoot);
StaticMeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMesh"));
StaticMeshComp->SetupAttachment(SceneRoot);
}
void ABaseObstacle::BeginPlay()
{
Super::BeginPlay();
MoveSettings.StartLocation = GetActorLocation();
RotationSettings.StartRotation = GetActorRotation();
RotationSettings.AccumulatedRotation = 0.f;
}
void ABaseObstacle::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
Move(DeltaTime);
Rotate(DeltaTime);
}
이동 로직 (Move)
void ABaseObstacle::Move(float DeltaTime)
{
if (MoveSettings.MoveSpeed <= 0.f || MoveSettings.MaxRange <= 0.f) return;
const FVector Cur = GetActorLocation();
const float Dist = FVector::Dist(Cur, MoveSettings.StartLocation);
if (Dist >= MoveSettings.MaxRange)
{
MoveSettings.MoveSign *= -1.f;
}
const FVector Dir = MoveSettings.MoveAxis.GetSafeNormal() * MoveSettings.MoveSign;
AddActorWorldOffset(Dir * MoveSettings.MoveSpeed * DeltaTime);
}
회전 로직 (Rotate)
void ABaseObstacle::Rotate(float DeltaTime)
{
if (!RotationSettings.bEnableRotation) return;
if (RotationSettings.RotationSpeedPerSecond.IsNearlyZero()) return;
AddActorLocalRotation(RotationSettings.RotationSpeedPerSecond * DeltaTime);
}
핵심 아이디어
엔진 이벤트(델리게이트) → Handle 함수
실제 반응 로직 → OnObstacleBeginOverlap
인터페이스 규약을 유지한 설계
👉 “엔진 의존 코드”와 “게임 규칙 코드”를 분리
PatrolObstacle.h (핵심)
UCLASS()
class ROTATINGANDMOVING_API APatrolObstacle : public ABaseObstacle
{
GENERATED_BODY()
public:
APatrolObstacle();
protected:
virtual void BeginPlay() override;
virtual void Tick(float DeltaTime) override;
virtual void OnObstacleBeginOverlap(AActor* OtherActor) override;
virtual void OnObstacleEndOverlap(AActor* OtherActor) override;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Collision|Push")
float PushStrength = 900.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Collision|Push")
float PushUpStrength = 120.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Collision|Push")
bool bPushUseObstacleToPlayerDir = true;
private:
UFUNCTION()
void HandleBeginOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex,
bool bFromSweep, const FHitResult& SweepResult);
UFUNCTION()
void HandleEndOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
};
PatrolObstacle.cpp – Overlap & 밀어내기 반응
#include "PatrolObstacle.h"
#include "GameFramework/Character.h"
APatrolObstacle::APatrolObstacle()
{
PrimaryActorTick.bCanEverTick = true;
if (StaticMeshComp)
{
StaticMeshComp->SetGenerateOverlapEvents(true);
StaticMeshComp->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
StaticMeshComp->SetCollisionResponseToAllChannels(ECR_Overlap);
}
}
void APatrolObstacle::BeginPlay()
{
Super::BeginPlay();
if (StaticMeshComp)
{
StaticMeshComp->OnComponentBeginOverlap.AddDynamic(this, &APatrolObstacle::HandleBeginOverlap);
StaticMeshComp->OnComponentEndOverlap.AddDynamic(this, &APatrolObstacle::HandleEndOverlap);
}
}
플레이어 밀어내기 로직
void APatrolObstacle::OnObstacleBeginOverlap(AActor* OtherActor)
{
ACharacter* Character = Cast<ACharacter>(OtherActor);
if (!Character) return;
FVector PushDir;
if (bPushUseObstacleToPlayerDir)
{
PushDir = Character->GetActorLocation() - GetActorLocation();
PushDir.Z = 0.f;
PushDir = PushDir.GetSafeNormal();
}
else
{
PushDir = (MoveSettings.MoveAxis.GetSafeNormal() * MoveSettings.MoveSign);
PushDir.Z = 0.f;
PushDir = PushDir.GetSafeNormal();
}
const FVector LaunchVel =
PushDir * PushStrength + FVector(0.f, 0.f, PushUpStrength);
Character->LaunchCharacter(LaunchVel, true, true);
}
설계 포인트
SpawnVolume이 Bounds를 관리
Obstacle은 “경계 안에서만 움직인다”는 책임만 가짐
Clamp 발생 시 이동 방향 반사
BaseObstacle – Bounds 설정
void ABaseObstacle::SetSpawnBounds(const FBox& InBounds)
{
SpawnBounds = InBounds;
bUseSpawnBounds = true;
}
ClampToBounds 구현
bool ABaseObstacle::ClampToBounds()
{
if (!bUseSpawnBounds) return false;
FVector Loc = GetActorLocation();
const FVector Min = SpawnBounds.Min;
const FVector Max = SpawnBounds.Max;
FVector Clamped;
Clamped.X = FMath::Clamp(Loc.X, Min.X, Max.X);
Clamped.Y = FMath::Clamp(Loc.Y, Min.Y, Max.Y);
Clamped.Z = FMath::Clamp(Loc.Z, Min.Z, Max.Z);
const bool bOut = !Loc.Equals(Clamped, 0.1f);
if (bOut)
{
SetActorLocation(Clamped);
}
return bOut;
}
SpawnVolume
└─ Bounds 생성
└─ PatrolObstacle에 SetSpawnBounds 전달
├─ Tick
│ ├─ Move (DeltaTime)
│ ├─ Rotate (DeltaTime)
│ └─ ClampToBounds → 반사
└─ Overlap
└─ OnObstacleBeginOverlap → 플레이어 밀어내기
Tick 기반 Move / Rotate 구조는 과제 요구사항을 충족하면서도 직관적
DeltaTime을 모든 이동/회전에 적용해 프레임 독립 확보
Overlap 이벤트를
엔진 델리게이트 → 인터페이스 규약 함수로 위임하니
설계가 깔끔하게 유지됨
SpawnBounds는 스포너가 관리하고
장애물은 “경계 안에서 행동”만 책임지는 구조가 가장 이상적
👉 다음 확장 포인트
Push 쿨타임 추가
Obstacle 타입별 반응 차별화
AI Pawn과의 공용 인터페이스화