[47. Floating Platform]
![](https://blog.kakaocdn.net/dn/dt60SE/btrLE8jTCN3/qhTRIUVi8RjsjUVTkQ1cN1/img.png)
공중에 떠있는 발판이다. 보통 퍼즐 플래포머 게임의 기반이 된다.
예시 스샷처럼 목표 지점에 도착하면 일정시간 대기했다가 다시 목표지점으로 가는 방식 등등이 있다.
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는 선형보간이다.
![](https://blog.kakaocdn.net/dn/cZz05j/btrLE2qAzS5/0isMUsiVL1UsUK1wcayqeK/img.png)
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 함수들에게 리플렉션을 설정해 주었으므로 자식 클래스에서는 리플렉션을 설정하지 않아도 된다. 상위버전 언리얼에서는 컴파일 에러가 난다 (하위버전에서는 해줘야함)
![](https://blog.kakaocdn.net/dn/bPfgYN/btrLEaKfYkn/1zwP4nU8f8gsnnOvfBhCkk/img.png)
자식 클래스에서 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 |