Dev./UE 언리얼 엔진
[TIL_250129] 회전 발판, 움직이는 장애물, 비물질 발판 구현하기 (2) - &충돌, 히트 감지
raindrovvv
2025. 1. 29. 23:11
🗺️마인드맵
📒학습 내용
[TIL_250128] 회전 발판, 움직이는 장애물, 비물질(?) 발판 구현하기
💭회고오늘은 과제를 해보며, 충돌 감지와 타이머 설정을 깊이 있게 공부했다.SetCollisionEnabled와 ECollisionEnabled::QueryAndPhysics로 물체의 충돌 설정을 다루는 방법과 GetWorld()->GetTimerManager().SetTimer()를
raindrovvv.tistory.com
추가 기능 1번 - 시간 제한과 카운트다운 활용
타이머 활용:
> FTimerHandle과 GetWorld()->GetTimerManager().SetTimer(...)를 사용하여 N초마다 특정 함수 호출합니다.
> 시간 제한 로직 구현 → 일정 시간 후 발판이 사라지거나, 일정 주기로 다른 메시/위치로 바뀌는 로직 추가합니다.
추가 기능 2번 - 랜덤 퍼즐 생성
동적 스폰:
> 게임 시작 시 SpawnActor를 통해 회전 발판/이동 플랫폼 등을 임의 좌표에 여러 개 배치합니다.
랜덤 속성 부여:
> 회전/이동 속도, 이동 범위, 각도 등을 난수(FMath::RandRange)로 생성하여 매번 다른 퍼즐 코스를 구성합니다.
🔗참고 자료 : 액터 스폰 및 소멸
1. 헤더(.h)
더보기
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "TimePlatform.generated.h"
UCLASS()
class ONLINELEARNINGKIT_API ATimePlatform : public AActor
{
GENERATED_BODY()
public:
ATimePlatform();
virtual void Tick(float DeltaTime) override;
protected:
virtual void BeginPlay() override;
private:
// 컴포넌트
USceneComponent* SceneRoot;
UPROPERTY(VisibleAnywhere, Category = "Mesh")
UStaticMeshComponent* StaticMesh;
// 머터리얼
UPROPERTY(EditAnywhere, Category = "Materials")
UMaterialInterface* SolidMaterial;
UPROPERTY(EditAnywhere, Category = "Materials")
UMaterialInterface* IntangibleMaterial;
// 오디오
UPROPERTY(EditAnywhere, Category = "Sound")
USoundBase* ChangeStateSound;
// 타이머 핸들
FTimerHandle StateTimerHandle;
FTimerHandle MovementChangeTimer;
FTimerHandle PlayerDetectionTimer;
FTimerHandle RestoreSolidTimer;
// 랜덤 이동/회전 변수
FVector MoveDirection;
float MoveSpeed;
float MoveRange;
bool bIsSolid = true; // 현재 상태
// 상태 전환 주기 (초 단위)
UPROPERTY(EditAnywhere, Category = "Timer")
float StateChangeInterval = 5.0f;
void ToggleState();
void SetSolidState(bool bSolid); // 머티리얼 및 콜리전 설정
void RandomizeMovement();
void DelayedBecomeIntangible(); // ?초 후 비물질화 메서드
void DelayedBecomeSolid(); // ?초 후 물질화 메서드
// **플레이어가 플랫폼을 밟았을 때 감지**
UFUNCTION()
void OnPlatformStepped(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit);
};
1.1 Private 멤버 변수
- 컴포넌트:
- USceneComponent* SceneRoot : 전체 씬의 루트 컴포넌트.
- UStaticMeshComponent* StaticMesh : 플랫폼(발판)의 실제 메시 컴포넌트.
- 머티리얼:
- UMaterialInterface* SolidMaterial : 고체 상태에서 사용할 머티리얼.
- UMaterialInterface* IntangibleMaterial : 비물질 상태에서 사용할 머티리얼.
- 오디오:
- USoundBase* ChangeStateSound : 상태 변환 시 재생될 사운드.
- 타이머 핸들:
- 여러 타이머 관리를 위한 핸들(별칭)
- 랜덤 이동/회전 변수:
- FVector MoveDirection : 이동 방향.
- float MoveSpeed : 이동 속도.
- float MoveRange : 이동 범위.
- 상태 변수:
- bool bIsSolid : 현재 고체 상태인지 여부를 나타내는 변수.
1.2 메서드 선언
- ToggleState():
- 플랫폼의 상태를 고체/비물질로 전환.
- SetSolidState(bool bSolid):
- 주어진 상태에 따라 머티리얼 및 콜리전 설정.
- RandomizeMovement():
- 이동 방향 및 속도를 랜덤으로 설정.
- DelayedBecomeIntangible():
- 일정 시간 후 비물질 상태로 전환.
- DelayedBecomeSolid():
- 일정 시간 후 고체 상태로 전환.
- OnPlatformStepped:
- 플레이어가 플랫폼을 밟았을 때 이벤트 처리.
2. 소스(.cpp)
더보기
#include "TimePlatform.h"
#include "Kismet/GameplayStatics.h"
#include "GameFramework/Character.h" // 플레이어 감지를 위해 필요
ATimePlatform::ATimePlatform()
{
PrimaryActorTick.bCanEverTick = true;
SceneRoot = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot"));
RootComponent = SceneRoot;
StaticMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMesh"));
StaticMesh->SetupAttachment(SceneRoot);
bIsSolid = true;
MoveDirection = FVector::ZeroVector;
MoveSpeed = 0.0f;
MoveRange = 0.0f;
// **충돌 감지 (플랫폼 위에 올라섰을 때)**
StaticMesh->SetNotifyRigidBodyCollision(true);
StaticMesh->OnComponentHit.AddDynamic(this, &ATimePlatform::OnPlatformStepped);
}
void ATimePlatform::BeginPlay()
{
Super::BeginPlay();
SetSolidState(true);
// 타이머 시작, 주기에 따라 이동 패턴 변경
GetWorld()->GetTimerManager().SetTimer(
MovementChangeTimer,
this,
&ATimePlatform::RandomizeMovement,
5.0f,
true // 무한 반복
);
RandomizeMovement();
}
void ATimePlatform::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
// 이동 로직
FVector NewLocation = GetActorLocation() + (MoveDirection * MoveSpeed * DeltaTime);
SetActorLocation(NewLocation);
}
void ATimePlatform::ToggleState()
{
bIsSolid = !bIsSolid;
SetSolidState(bIsSolid);
UGameplayStatics::PlaySoundAtLocation(this, ChangeStateSound, GetActorLocation());// 효과음 재생
}
void ATimePlatform::SetSolidState(bool bSolid)
{
if (bSolid)
{ // Solid 상태: 콜리전 활성화 + 파란 머티리얼
StaticMesh->SetCollisionResponseToAllChannels(ECR_Block);
StaticMesh->SetMaterial(0, SolidMaterial);
UE_LOG(LogTemp, Warning, TEXT("Platform is now SOLID"));
} else
{
// Intangible 상태: 콜리전 비활성화 + 빨간 머티리얼
StaticMesh->SetCollisionResponseToChannel(ECC_Pawn, ECR_Ignore);
StaticMesh->SetMaterial(0, IntangibleMaterial);
UE_LOG(LogTemp, Warning, TEXT("Platform is now INTANGIBLE"));
UGameplayStatics::PlaySoundAtLocation(this, ChangeStateSound, GetActorLocation());
}
}
void ATimePlatform::RandomizeMovement()
{
// 랜덤 이동 방향
MoveDirection = FVector(FMath::FRandRange(-1.0f, 1.0f), FMath::FRandRange(-1.0f, 1.0f), 0).GetSafeNormal();
// 랜덤한 속도와 범위
MoveSpeed = FMath::RandRange(30.0f, 200.0f);
MoveRange = FMath::RandRange(10.0f, 50.0f);
}
void ATimePlatform::OnPlatformStepped(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
if (OtherActor->IsA(ACharacter::StaticClass())) // 플레이어가 밟았을 때
{
UE_LOG(LogTemp, Warning, TEXT("✅Player stepped on the platform! Timer started."));
GetWorld()->GetTimerManager().SetTimer(
PlayerDetectionTimer,
this,
&ATimePlatform::DelayedBecomeIntangible,
2.0f,
false);
UGameplayStatics::PlaySoundAtLocation(this, ChangeStateSound, GetActorLocation());
}
}
void ATimePlatform::DelayedBecomeIntangible() //x초 뒤에 비물질 상태로 변환.
{
SetSolidState(false);
UE_LOG(LogTemp, Warning, TEXT("Platform is now intangible after 5 seconds!"));
GetWorld()->GetTimerManager().SetTimer(
RestoreSolidTimer,
this,
&ATimePlatform::DelayedBecomeSolid,
3.0f,
false);
}
void ATimePlatform::DelayedBecomeSolid()
{
SetSolidState(true);
}
2.1 생성자 ➡️ ATimePlatform::ATimePlatform()
2.1.1 컴포넌트 초기화
- SceneRoot와 StaticMesh 컴포넌트를 생성하고 설정한다.
2.1.2 상태 초기화
- bIsSolid를 true로 설정한다.
2.1.3 충돌 감지 설정
- StaticMesh의 충돌 감지 기능을 활성화하고 이벤트 바인딩을 설정한다.
- StaticMesh->SetNotifyRigidBodyCollision(true);
- StaticMesh 컴포넌트가 리지드 바디 충돌을 감지하도록 활성화한다.
- 다른 오브젝트와 충돌할 때 충돌 이벤트를 발생시킬 수 있게 한다.
- StaticMesh->OnComponentHit.AddDynamic(this, &ATimePlatform::OnPlatformStepped);
- OnComponentHit는 UPrimitiveComponent 클래스의 델리게이트로, 컴포넌트가 다른 오브젝트와 충돌할 때 호출된다.
- AddDynamic는 델리게이트에 동적으로 함수를 바인딩하는 역할을 한다.
- this는 현재 클래스의 인스턴스를 가리키며, &ATimePlatform::OnPlatformStepped는 호출할 함수의 포인터이다.
- StaticMesh->SetNotifyRigidBodyCollision(true);
델리게이트(Delegate)??
더보기
🎈델리게이트는 특정 이벤트 발생 시 호출될 함수를 지정하는 기능
- 함수 포인터처럼 동작
- 이벤트와 함수를 동적으로 연결(바인딩) 가능
- 런타임에 실행될 함수 변경 가능
2.2 BeginPlay()
- 초기 상태 설정
- 부모 클래스의 BeginPlay를 호출하고 SetSolidState(true)를 통해 고체 상태로 설정한다.
- 타이머 시작
- GetTimerManager()를 사용하여 랜덤 이동 패턴을 설정하고 반복 타이머를 시작한다.
2.3 Tick(float DeltaTime)
이동 로직 구현
- 매 프레임마다 MoveDirection과 MoveSpeed를 기반으로 플랫폼의 위치를 업데이트한다.
2.4 ToggleState()
- 상태 전환
- bIsSolid 상태를 토글하고 SetSolidState 메서드를 호출하여 상태를 업데이트한다.
- 사운드 재생
- 상태 전환 효과음을 재생한다.
2.5 SetSolidState(bool bSolid)
- 고체 상태 설정
- 고체 상태일 때 콜리전을 활성화하고 파란 머티리얼을 설정한다.
- 비물질 상태 설정
- 비물질 상태일 때 콜리전을 비활성화하고 빨간 머티리얼을 설정한다.
2.6 RandomizeMovement()
- 랜덤 이동 방향 및 속도 설정
- 이동 방향을 랜덤하게 설정하고 속도와 범위를 랜덤으로 지정한다.
- FMath::FRandRange(-1.0f, 1.0f): -1.0에서 1.0 사이의 랜덤 값을 생성한다.
- FVector: 랜덤 값을 x와 y 축에 할당하고, z 축은 0으로 설정한다.
- .GetSafeNormal(): 벡터의 크기를 1로 정규화한다. 즉, 방향 벡터의 길이를 1로 만들어 이동할 방향을 설정한다. (크기 정보는 제거되고, 방향 정보만 가진다.)
2.7 OnPlatformStepped()
UPrimitiveComponent??
더보기
🎈 언리얼 엔진의 기본 게임 오브젝트를 나타내는 컴포넌트로, 물리적 특성과 충돌 감지 기능을 제공한다.
- 충돌 감지 (OnComponentHit, BeginOverlap, EndOverlap 등)
- 3D 렌더링 처리
- 물리 특성 관리 (질량, 중력, 관성)
- Transform 관리 (위치, 회전, 크기)
상속 구조
UObject
└─ UActorComponent
└─ USceneComponent
└─ UPrimitiveComponent
├─ UStaticMeshComponent
├─ USkeletalMeshComponent
├─ UBoxComponent
└─ USphereComponent
➕
UStaticMeshComponent는 게임에서 움직이지 않는 3D 모델(예: 바위, 건물, 벽 등)을 표현하는 컴포넌트
// 충돌이 발생하면 OnHit 함수가 자동으로 실행되도록 설정
StaticMesh->OnComponentHit.AddDynamic(this, &AMyActor::OnHit);
실제 게임에서는 이런 식으로 활용될 수 있다!
- 플레이어가 벽에 부딪혔을 때 멈추게 하기
- 물체가 바닥에 떨어졌을 때 소리 재생하기
- 캐릭터가 아이템과 부딪혔을 때 획득하기
🤔OnPlatformStepped 메서드에 매개 변수를 다 작성해야 하나?
🤖 Unreal Engine의 델리게이트 시스템에서는 기본적으로 충돌 이벤트 핸들러에 특정 시그니처를 요구한다. 즉, 함수가 올바르게 바인딩되기 위해서는 정의된 모든 매개변수를 포함해야 한다.
2.8 DelayedBecomeIntangible()
- 비물질 상태 전환 메서드
- 일정 시간 후 비물질 상태로 전환하고 타이머를 설정하여 고체 상태로 복구한다.
- GetWorld()->GetTimerManager().SetTimer(RestoreSolidTimer, this, &ATimePlatform::DelayedBecomeSolid, 3.0f, false);
- 타이머를 설정하여 3초 후에 DelayedBecomeSolid 함수가 호출된다.
- 매개변수:
- RestoreSolidTimer : 타이머 핸들로, 타이머를 관리한다.
- this : 타이머가 종료되면 호출할 객체로, 여기서는 ATimePlatform 클래스의 인스턴스를 의미한다.
- &ATimePlatform::DelayedBecomeSolid : 타이머가 종료되면 호출할 함수 포인터이다.
- 3.0f : 타이머 간격으로, 3초 후에 함수를 호출한다.
- false : 타이머가 반복되지 않도록 설정한다. (true는 무한 반복)
2.9 DelayedBecomeSolid()
고체 상태 전환
- 일정 시간 후 고체 상태로 복구한다.
✨결과물