생각해보니 만들어놓은 자기장에 줄어드는 효과를 적용하지 않았었다.
그리고 시각적 효과도 따로 만들어두지 않았다.
그래서 이번엔 그걸 적용하려한다.
//CRBlueZone.h
...
class COPANDROBBER_API ACRBlueZone : public AActor
{
...
public:
...
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
UStaticMeshComponent* ZoneMesh;
...
UFUNCTION(BlueprintCallable, Category="Zone")
void SetZoneRadius(float NewRadius);
UFUNCTION()
void OnRep_ZoneRadius();
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
protected:
UPROPERTY(EditAnywhere, Category="Zone")
float ShrinkDelay = 10.f;
UPROPERTY(EditAnywhere, Category="Zone")
float ShrinkDuration = 10.f;
UPROPERTY(EditAnywhere, Category="Zone")
float TargetRadius = 2000.f;
FTimerHandle ShrinkTimer;
bool bIsShrinking = false;
float StartRadius;
float ElapsedTime = 0.f;
void StartShrink();
virtual void Tick( float DeltaSeconds ) override;
private:
void SyncMeshToCapsuleRadius();
void ApplyZoneMaterialOnce();
UPROPERTY(EditDefaultsOnly, Category="Zone|Visual")
UMaterialInterface* ZoneBaseMaterial = nullptr;
UPROPERTY(Transient)
UMaterialInstanceDynamic* ZoneMID = nullptr;
};
블루존 액터 헤더 파일이다.
자기장 축소와 관련된 변수, 함수들과 자기장의 메시를 설정하고 이를 콜리전과 연동하는 함수가 있다.
//CRBlueZone.cpp
static constexpr float kBaseCylinderRadius = 50.f;
static constexpr float kBaseCylinderHeight = 100.f;
ACRBlueZone::ACRBlueZone()
{
...
ZoneMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ZoneMesh"));
ZoneMesh->SetupAttachment(SafeZone);
ZoneMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
...
PrimaryActorTick.bCanEverTick = true;
}
소스 파일로 넘어와서,
이따가 실린더를 스태틱 메시로 사용할 것이기 때문에 기존 실린더의 크기를 정의해놓는다.
그리고 메시를 설정해주고 콜리젼을 없애주고,
틱을 쓸 것이기 때문에 활성화해준다.
void ACRBlueZone::SetZoneRadius(float NewRadius)
{
ZoneRadius = NewRadius;
SafeZone->SetCapsuleRadius(ZoneRadius);
SyncMeshToCapsuleRadius();
if (ZoneMID)
{
ZoneMID->SetScalarParameterValue(TEXT("ZoneRadius"), ZoneRadius);
}
}
SetZoneRadius() 함수이다.
자기장의 크기를 새로운 크기로 변환시켜주는 함수로, 메시의 크기도 같이 연동해서 바꾸어준다.
void ACRBlueZone::OnRep_ZoneRadius()
{
SafeZone->InitCapsuleSize(ZoneRadius, ZoneHalfHeight);
const float XY = ZoneRadius / kBaseCylinderRadius;
const float Z = (ZoneHalfHeight * 2.f) / kBaseCylinderHeight;
ZoneMesh->SetRelativeScale3D(FVector(XY, XY, Z));
ApplyZoneMaterialOnce();
if (ZoneMID)
{
ZoneMID->SetScalarParameterValue(TEXT("ZoneRadius"), ZoneRadius);
}
}
void ACRBlueZone::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ACRBlueZone, ZoneRadius);
}
자기장의 크기를 복제해서 넣어주는 함수이다.
void ACRBlueZone::BeginPlay()
{
...
ApplyZoneMaterialOnce();
SafeZone->InitCapsuleSize(ZoneRadius, ZoneHalfHeight);
SyncMeshToCapsuleRadius();
...
StartRadius = SafeZone->GetUnscaledCapsuleRadius();
GetWorld()->GetTimerManager().SetTimer(
ShrinkTimer,
this,
&ACRBlueZone::StartShrink,
ShrinkDelay,
false
);
}
그리고 BeginPlay() 함수에서 머티리얼과 크기, 싱크를 모두 한 번씩 설정을 해준다.
그리고 자기장의 초기 크기를 저장해두고, ShrinkDelay 시간 이후에 StartShrink() 함수를 호출한다.
void ACRBlueZone::StartShrink()
{
bIsShrinking = true;
ElapsedTime = 0.f;
}
StartShrink() 함수에서는 bIsShrinking 을 true 로 바꾸고 ElapsedTime 을 0.f 로 다시 초기화해준다.
void ACRBlueZone::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
if (bIsShrinking)
{
ElapsedTime += DeltaSeconds;
float Alpha = FMath::Clamp(ElapsedTime / ShrinkDuration, 0.f, 1.f);
float NewRadius = FMath::Lerp(StartRadius, TargetRadius, Alpha);
SetZoneRadius(NewRadius);
if (Alpha >= 1.f)
{
bIsShrinking = false;
}
}
}
Tick() 함수이다.
자기장이 줄어들고있다면 ElapsedTime 에 DeltaSeconds 를 담고,
Alpha 라는 새로운 플롯 변수에 최소 0부터 1까지의 값을 ElapsedTime / ShrinkDuration 으로 가져온다.
그리고 새로운 자기장 크기 NewRadius 에 Lerp 로 보간을 통해 크기를 바꿔준다.
또한 매 틱마다 그 새로운 크기를 SetZoneRadius() 함수를 통해 설정해주면 된다.
만약 자기장이 줄어드는게 끝나면 bIsShrinking 을 false 로 변경해준다.
void ACRBlueZone::SyncMeshToCapsuleRadius()
{
if (!SafeZone || !ZoneMesh) return;
const float CapsuleRadius = SafeZone->GetScaledCapsuleRadius();
const float XYScale = CapsuleRadius / kBaseCylinderRadius;
const FVector Cur = ZoneMesh->GetRelativeScale3D();
ZoneMesh->SetRelativeScale3D(FVector(XYScale, XYScale, ZoneHalfHeight));
}
SyncMeshToCapsuleRadius() 함수이다.
말 그대로 메시를 자기장 콜리젼 크기에 싱크를 맞춰주는 함수이다.
void ACRBlueZone::ApplyZoneMaterialOnce()
{
if (!ZoneMesh || ZoneMID) return;
UMaterialInterface* Base = ZoneBaseMaterial ? ZoneBaseMaterial : ZoneMesh->GetMaterial(0);
if (!Base || Base->GetBlendMode() != BLEND_Translucent) return;
ZoneMID = UMaterialInstanceDynamic::Create(Base, this);
ZoneMesh->SetMaterial(0, ZoneMID);
ZoneMID->SetScalarParameterValue(TEXT("ZoneRadius"), ZoneRadius);
}
ApplyZoneMaterialOnce() 함수는 메시에 머티리얼을 한 번 적용시켜주는 함수이다.
또 생각해보니 카운트 끝나면 탈락시키는 걸 주석 처리만 해놓고 구현을 안헀다.
캐릭터 GAS 쪽에 death 관련 태그가 있으니 그걸 부여하기만 하면 될 것 같다.
//CRZoneCountdownComponent.h
...
class COPANDROBBER_API UCRZoneCountdownComponent : public UActorComponent
{
...
protected:
...
UPROPERTY(EditDefaultsOnly)
TSubclassOf<class UGameplayEffect> DeathEffect;
...
};
먼저 헤더 파일에서 GE 를 선언해준다.
//CRZoneCountdownComponent.cpp
...
#include "Character/CRCharacter.h"
#include "GameFramework/Pawn.h"
#include "GAS/CRAbilitySystemComponent.h"
...
void UCRZoneCountdownComponent::TickOneSecond()
{
...
if (RemainingSeconds <= 0)
{
if (ACRCharacter* CRCharacter = Cast<ACRCharacter>(GetOwner()))
{
if (CRCharacter->GetCRAbilitySystemComponent() && DeathEffect)
{
CRCharacter->GetCRAbilitySystemComponent()->ApplyGameplayEffect(DeathEffect);
UE_LOG(LogTemp, Warning, TEXT("Dead"));
}
}
...
}
}
소스 파일에서는 TickOneSecond() 함수에서 탈락 처리라고 주석을 써놨던 곳에 Dead 태그를 활성화시켜준다.

캐릭터에 달아놓은 자기장 카운트 컴포넌트에 BP_GE_Dead 를 넣어주면 된다.

머티리얼을 하나 만들어줬다.
Details 패널에서 Material > Blend Mode 를 Translucent 로,
Shading Model 은 Unlit,
Two Sided 는 체크를 해준다.

머티리얼 그래프에서는 다음과 같이 노드를 짜준다.

이후 BP_CRBlueZone 으로 가서 스태틱 메시를 Cylinder 로, 머티리얼을 방금 만든 M_BlueZone 으로 설정해준다.
일단 GAS 는 진짜 너무 어렵다.
함수들도 뭐가 뭔지 아직도 잘 모르겠다...
분명 열심히 만들고 있는데 잘 모르겠어서 지피티한테 도움을 받는게 대부분인 것 같아서 조금 아쉽다.
물론 코드 분석하면서 공부를 하고 있긴 하지만, 직접 떠올려서 한 게 아니라 더 분발해야겠다.