카메라 이펙트에 필요한 정보를 어떻게 받아오지 고민하다가 Gameplay Cue를 사용하기로 했다.
일단 태그를 정리해준다.
#pragma once
#include "NativeGameplayTags.h"
namespace GYGameplayTags
{
// GameplayCue - 일단 임시 구조, 추후 수정 가능
/* 플레이어 피격 */
GY_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(GameplayCue_Combat_Hit_Light);
GY_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(GameplayCue_Combat_Hit_Heavy);
GY_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(GameplayCue_Combat_Hit_Critical);
/* 보스 액션 */
GY_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(GameplayCue_Combat_Boss_Slam);
GY_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(GameplayCue_Combat_Boss_Roar);
GY_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(GameplayCue_Combat_Boss_Laser);
GY_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(GameplayCue_Combat_Boss_PhaseTransition);
/* 플레이어 액션 */
GY_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(GameplayCue_Combat_Player_Dash);
GY_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(GameplayCue_Combat_Player_Parry);
GY_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(GameplayCue_Combat_Player_Ultimate);
/* 상태 이상 */
GY_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(GameplayCue_Status_Stun);
GY_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(GameplayCue_Status_Burn);
GY_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(GameplayCue_Status_Frozen);
/* 카메라 이펙트 */
GY_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(GameplayCue_Camera_HitLight);
GY_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(GameplayCue_Camera_HitHeavy);
GY_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(GameplayCue_Camera_ZoomIn);
GY_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(GameplayCue_Camera_BossSlam);
GY_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(GameplayCue_Camera_Execution);
}
이펙트가 필요한 각 상황을 게임플레이 큐로 만들고
이펙트 로직은 줌/쉐이크/푸시 로 나눠 구현하려고 한다.
#pragma once
#include "CoreMinimal.h"
#include "GYCameraEffectTypes.generated.h"
UENUM(BlueprintType)
enum class EGYCameraDirectionSource : uint8
{
None, // 방향 미사용
HitNormal, // 피격 노멀 방향
Instigator // 시전자 전방 방향
};
UENUM(BlueprintType)
enum class EGYCameraEffectType : uint8
{
Shake,
Zoom,
Push
}; //태그로 바꾸는게 나을지..?
USTRUCT(BlueprintType)
struct FGYCameraEffectContext
{
GENERATED_BODY()
UPROPERTY(BlueprintReadWrite)
EGYCameraEffectType Type =
EGYCameraEffectType::Shake;
UPROPERTY(BlueprintReadWrite)
FVector WorldLocation = FVector::ZeroVector;
UPROPERTY(BlueprintReadWrite)
FVector Direction = FVector::ZeroVector;
UPROPERTY(BlueprintReadWrite)
float Intensity = 1.f;
UPROPERTY(BlueprintReadWrite)
float Duration = 0.2f;
UPROPERTY(BlueprintReadWrite)
float ElapsedTime = 0.f;
// Zoom 전용
UPROPERTY(BlueprintReadWrite)
float ZoomAmount = 0.f;
};
게임플레이큐에서 받아올 정보를 정리했다.
#pragma once
#include "CoreMinimal.h"
#include "GameplayCueNotify_Static.h"
#include "Camera/GYCameraEffectTypes.h"
#include "GYGameplayCueNotify_Camera.generated.h"
enum class EGYCameraEffectType : uint8;
UCLASS(Abstract, Blueprintable)
class GY_API UGYGameplayCueNotify_Camera : public UGameplayCueNotify_Static
{
GENERATED_BODY()
public:
virtual bool OnExecute_Implementation(
AActor* MyTarget,
const FGameplayCueParameters& Parameters
) const override;
protected:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
EGYCameraEffectType EffectType =
EGYCameraEffectType::Shake;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
float Intensity = 20.f;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
float Duration = 0.2f;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
float ZoomAmount = 0.f;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
EGYCameraDirectionSource DirectionSource =
EGYCameraDirectionSource::HitNormal;
};
#include "Core/GameplayCue/GYGameplayCueNotify_Camera.h"
#include "Camera/GYCameraComponent.h"
#include "Logging/GYLogManager.h"
bool UGYGameplayCueNotify_Camera::OnExecute_Implementation(AActor* MyTarget,
const FGameplayCueParameters& Parameters) const
{
GY_WARN(Player,CYS,"카메라 게임플레이큐 실행");
APawn* Pawn =
Cast<APawn>(MyTarget);
if (!Pawn)
{
return false;
}
UGYCameraComponent* CameraComp =
Pawn->FindComponentByClass<
UGYCameraComponent>();
if (!CameraComp)
{
return false;
}
FGYCameraEffectContext Context;
Context.Type = EffectType;
Context.Intensity = Intensity;
Context.Duration = Duration;
Context.ZoomAmount = ZoomAmount;
// 방향 계산
switch (DirectionSource)
{
case EGYCameraDirectionSource::HitNormal:
Context.Direction = Parameters.Normal;
break;
case EGYCameraDirectionSource::Instigator:
if (Parameters.Instigator.IsValid())
{
Context.Direction =
Parameters.Instigator->GetActorForwardVector();
}
break;
default:
break;
}
Context.WorldLocation = Parameters.Location;
CameraComp->PushCameraEffect(Context);
return true;
}
게임플레이 큐에서는 큐에 따라 이펙트 수치값과 타입 등을 가지고 있고 카메라로 전달해준다.
void UGYCameraComponent::PushCameraEffect(const FGYCameraEffectContext& Context)
{
const TSubclassOf<UGYCameraEffectBase>* FoundClass =
CameraEffectMap.Find(Context.Type);
if (!FoundClass || !(*FoundClass))
{
return;
}
UGYCameraEffectBase* NewEffect =
NewObject<UGYCameraEffectBase>(
this,
*FoundClass);
if (!NewEffect)
{
return;
}
GY_WARN(Player, CYS, "카메라 이펙트 전달");
NewEffect->Initialize(Context);
ActiveEffects.Add(NewEffect);
}
카메라 컴포넌트에서는 해당 이펙트를 처리한다.
#pragma once
#include "CoreMinimal.h"
#include "GYCameraEffectTypes.h"
#include "UObject/Object.h"
#include "GYCameraEffectBase.generated.h"
class UGYCameraComponent;
class UGYCameraEffectData;
struct FGYCameraView;
struct FGYCameraEffectContext;
UCLASS(Abstract)
class GY_API UGYCameraEffectBase : public UObject
{
GENERATED_BODY()
public:
virtual void Initialize(const FGYCameraEffectContext& InContext);
virtual void UpdateEffect(float DeltaTime, FGYCameraView& InOutView);
virtual bool IsFinished() const;
protected:
UPROPERTY()
FGYCameraEffectContext Context;
float ElapsedTime = 0.f;
};
#include "Camera/GYCameraEffectBase.h"
void UGYCameraEffectBase::Initialize(const FGYCameraEffectContext& InContext)
{
Context = InContext;
ElapsedTime=0.f;
}
void UGYCameraEffectBase::UpdateEffect(float DeltaTime, FGYCameraView& InOutView)
{
ElapsedTime += DeltaTime;
}
bool UGYCameraEffectBase::IsFinished() const
{
return ElapsedTime >= Context.Duration;
}
타입에 따라 베이스를 상속받아 로직을 짜고 지속시간만큼 카메라 뷰를 처리할 수 있도록 한다.
// 이펙트 적용
for (int32 i = ActiveEffects.Num() - 1; i >= 0; --i)
{
UGYCameraEffectBase* Effect = ActiveEffects[i];
if (!Effect)
{
ActiveEffects.RemoveAt(i);
continue;
}
Effect->UpdateEffect(
DeltaTime,
CurrentView);
if (Effect->IsFinished())
{
ActiveEffects.RemoveAt(i);
}
}
ApplyCameraView(CurrentView);
}
카메라 컴포넌트 틱에서 모드 계산이 끝난 뷰에 해당 이펙트를 적용한다.
'팀프로젝트 > 제목없음.최종플젝' 카테고리의 다른 글
| [TIL] 2026-05-27 | 카메라 볼륨 액터 구현, 블루프린트에서 C++로! (0) | 2026.05.27 |
|---|---|
| [TIL] 2026-05-21 | 카메라 이펙트 (0) | 2026.05.21 |
| [TIL] 2026-05-20 | Gameplay tag를 사용한 탑다운 카메라 제어하기 (0) | 2026.05.20 |
| [TIL] 2026-05-15 | UE 로그 매니저 / 로그 시스템 (0) | 2026.05.16 |
| [TIL] 2026-05-15 | 퀘스트 데이터 구조 (0) | 2026.05.15 |