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);
      1. StaticMesh 컴포넌트가 리지드 바디 충돌을 감지하도록 활성화한다.
      2. 다른 오브젝트와 충돌할 때 충돌 이벤트를 발생시킬 수 있게 한다.
    • StaticMesh->OnComponentHit.AddDynamic(this, &ATimePlatform::OnPlatformStepped);
      1. OnComponentHit는 UPrimitiveComponent 클래스의 델리게이트로, 컴포넌트가 다른 오브젝트와 충돌할 때 호출된다.
      2. AddDynamic는 델리게이트에 동적으로 함수를 바인딩하는 역할을 한다.
      3. this는 현재 클래스의 인스턴스를 가리키며, &ATimePlatform::OnPlatformStepped는 호출할 함수의 포인터이다.

델리게이트(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()

고체 상태 전환

  • 일정 시간 고체 상태로 복구한다.

 

✨결과물

 

🟣오늘의 옵시디언 현황