[47. Floating Platform]

 

(예시 스샷)

공중에 떠있는 발판이다. 보통 퍼즐 플래포머 게임의 기반이 된다.

예시 스샷처럼 목표 지점에 도착하면 일정시간 대기했다가 다시 목표지점으로 가는 방식 등등이 있다.

 

Actor를 상속하는 C++클래스 FloatingPlatform를 만든다. 

 

// FloatPlatform.h
public:
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Platform")
    UStaticMeshComponent* Mesh;

    UPROPERTY(EditAnywhere, Category = "Platform")
    FVector StartPoint;

    UPROPERTY(EditAnywhere, meta=(MakeEditWidget = "true"))
    FVector EndPoint;
    
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Platform")
    float InterpSpeed;
    
// FloatPlatform.cpp
AFloatingPlatform::AFloatingPlatform()
{
    Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
    RootComponent = Mesh;

    StartPoint = FVector(0.f);
    EndPoint = FVector(0.f);
    
    InterpSpeed = 4.f;
}

void AFloatingPlatform::BeginPlay()
{
    Super::BeginPlay();
    
    StartPoint = GetActorLocation();
    EndPoint += StartPoint;
}

void AFloatingPlatform::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

    FVector CurrentLocation = GetActorLocation();
    FVector Interp = FMath::VInterpTo(CurrentLocation, EndPoint, DeltaTime, InterpSpeed);
    SetActorLocation(Interp);
}

 

블루프린트 클래스를 만들고 메시를 붙여주는데, 해당 메시에 [10면체 Z 단순화 콜리전 추가] 를 해준다.

*K면체 단순화 콜리전(K-DOP: K Discrete Oriented Polytope): 축에 평행한 면의 개수가 K개인 다면체

 

두 지점을 왕복하기 위해 FVector 변수 두개를 추가해준다.

그리고 이동속도를 위해 float 변수도 하나 추가해준다.

 

VInterpTo는 선형보간이다.

 

EndPoint의 UPROPERTY 속성에 있는 meta=(MakeEditWidget="true") 옵션은 위의 스샷과 같이 에디터 상에 변수명이 표시가 되며, 해당 변수를 끌어서 위치를 변경할수도 있다.

물론 디테일 탭에서도 수정이 가능하다 .

 

EndPoint를 적당한 거리에 두고 실행하면 해당 위치로 부드럽게 이동한다.

 

참고로 EndPoint는 월드 좌표가 아닌 로컬 좌표이다.

 

 

이제 StartPoint와 EndPoint 사이를 왕복해야 하기 때문에 추가 구현을 한다.

 

// FloatingPlatform.h
public:
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Platform")
    float InterpTime;

    FTimerHandle InterpTimer;

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Platform")
    bool bInterping;
    
    float Distance;
    
    void ToggleInterping();

    void SwapVectors(FVector& VecOne, FVector& VecTwo);
    
// FloatingPlatform.cpp
AFloatingPlatform::AFloatingPlatform()
{
    bInterping = false;
    InterpTime = 1.f;
}

void AFloatingPlatform::BeginPlay()
{
    GetWorldTimerManager().SetTimer(InterpTimer, this, &AFloatingPlatform::ToggleInterping, InterpSpeed);
    
    Distance = (EndPoint - StartPoint).Size();
}

void AFloatingPlatform::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

    if (bInterping)
    {
        FVector CurrentLocation = GetActorLocation();
        FVector Interp = FMath::VInterpTo(CurrentLocation, EndPoint, DeltaTime, InterpSpeed);
        SetActorLocation(Interp);
        
        float DistanceTraveled = (GetActorLocation() - StartPoint).Size();
        if (Distance - DistanceTraveled <= 1.f)
        {
            ToggleInterping();

            GetWorldTimerManager().SetTimer(InterpTimer, this, &AFloatingPlatform::ToggleInterping, InterpSpeed);
            SwapVectors(StartPoint, EndPoint);
        }
    }
}

void AFloatingPlatform::ToggleInterping()
{
    bInterping = !bInterping;
}

void AFloatingPlatform::SwapVectors(FVector& VecOne, FVector& VecTwo)
{
    FVector Temp = VecOne;
    VecOne = VecTwo;
    VecTwo = Temp;
}

 

타이머 매니저를 이용해서 두 점을 왕복하는 발판을 만들게 됐다.

다만 하나의 문제점이 있다. 발판을 여러개 배치해서 서로 거리를 다르게 두고 실행해보면 거리에 따른 도착시간의 오차때문에 동시에 진행되지 못하고 시간이 흐를수록 발판들이 따로 움직인다.

이 부분은 동기화 알고리즘을 찾아서 따로 해결해야 한다.

 

이동 속도나 시간 등등 발판의 기초적인 부분을 구현해두고 상속시켜서 사용하면 다양한 발판을 만들 수 있어서 좋을 것 같다.

 

[48. Pickups #1]

 

플레이어와 상호작용 하는 오브젝트는 다양하다.

어떤 아이템을 획득하는 것일수도 있고 함정을 밟는것일수도 있다.

트리거에 의한 이벤트 발생이라는점이 공통이므로 해당 부분을 부모 클래스로 한 파생 클래스들을 만들 예정이다.

 

영상에 나온대로 리소스를 가져온다.

 

// Item.h
class FIRSTPROJECT_API AItem : public AActor
{
    GENERATED_BODY()

public:	
    AItem();

    /* Base shape collision */
    UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Item | Collision")
    class USphereComponent* CollisionVolume;

protected:
    virtual void BeginPlay() override;

public:	
    virtual void Tick(float DeltaTime) override;

    UFUNCTION()
    virtual void OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);

    UFUNCTION()
    virtual void OnOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
};

// Item.cpp
#include "Components/SphereComponent.h"

AItem::AItem()
{
    PrimaryActorTick.bCanEverTick = true;

    CollisionVolume = CreateDefaultSubobject<USphereComponent>(TEXT("CollisionVolume"));
    RootComponent = CollisionVolume;
}

void AItem::BeginPlay()
{
    Super::BeginPlay();

    CollisionVolume->OnComponentBeginOverlap.AddDynamic(this, &AItem::OnOverlapBegin);
    CollisionVolume->OnComponentEndOverlap.AddDynamic(this, &AItem::OnOverlapEnd);
}

void AItem::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
}

void AItem::OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
    UE_LOG(LogTemp, Warning, TEXT("OnOverlapBegin()"));
}

void AItem::OnOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
    UE_LOG(LogTemp, Warning, TEXT("OnOverlapEnd()"));
}

 

구체 콜리전을 가진 트리거 기능만 구현해서 만들었다.

 

 

// Explosive.h
public:
    AExplosive();

public:
    virtual void OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) override;
    virtual void OnOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex) override;

// Explosive.cpp
AExplosive::AExplosive()
{
}

void AExplosive::OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
    Super::OnOverlapBegin(OverlappedComponent, OtherActor, OtherComp, OtherBodyIndex, bFromSweep, SweepResult);

    UE_LOG(LogTemp, Warning, TEXT("OnOverlapBegin()"));
}

void AExplosive::OnOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
    Super::OnOverlapEnd(OverlappedComponent, OtherActor, OtherComp, OtherBodyIndex);

    UE_LOG(LogTemp, Warning, TEXT("OnOverlapEnd()"));
}

 

// Pickup.h
public:
    APickup();
public:
    virtual void OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) override;
    virtual void OnOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex) override;

// Pickup.cpp
APickup::APickup()
{
}

void APickup::OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
    Super::OnOverlapBegin(OverlappedComponent, OtherActor, OtherComp, OtherBodyIndex, bFromSweep, SweepResult);

    UE_LOG(LogTemp, Warning, TEXT("Pickup::OnOverlapBegin()"));
}

void APickup::OnOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
    Super::OnOverlapEnd(OverlappedComponent, OtherActor, OtherComp, OtherBodyIndex);

    UE_LOG(LogTemp, Warning, TEXT("Pickup::OnOverlapEnd()"));
}

 

부모 클래스인 Item 클래스에서 OnOverlap 함수들에게 리플렉션을 설정해 주었으므로 자식 클래스에서는 리플렉션을 설정하지 않아도 된다. 상위버전 언리얼에서는 컴파일 에러가 난다 (하위버전에서는 해줘야함)

 

자식 클래스에서 Super로 상위 클래스를 호출하므로 부모->자식 순으로 실행된다.

다시 상기하자면 Tick이 필요없으므로 Item의 생성자에서 틱을 비활성화 시켜주면 된다.

특정 자식 클래스에서 필요하면 거기서 다시 활성화 시켜주면 된다.

 

 

 

[정리]

 

  • 부모 클래스의 함수가 UFUNCTION()으로 리플렉션 캡처가 되었다면 자식 클래스는 리플렉션을 빼야한다. 그렇지 않으면 컴파일 에러가 발생한다.
  • 당연한 얘기지만 오버라이딩 한 함수에서 Super를 호출하면 부모->자식 순으로 실행된다.
  • 상속성을 잘 이용하자.

'언리얼 엔진 > UE4 C++' 카테고리의 다른 글

[UE C++] Gameplay Mechanics #5  (0) 2022.09.08
[UE C++] Gameplay Mechanics #4  (0) 2022.09.08
[UE C++] Gameplay Mechanics #2  (0) 2022.09.07
[UE C++] Gameplay Mechanics #1  (0) 2022.09.06
[UE C++] The Character Class #2  (0) 2022.09.06

+ Recent posts