第一次提交

This commit is contained in:
不明不惑
2026-03-03 01:23:02 +08:00
commit 3e434877e8
1053 changed files with 102411 additions and 0 deletions

View File

@@ -0,0 +1,303 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Feedback/GES_AnimNotify_ContextEffects.h"
#include "Components/SkeletalMeshComponent.h"
#include "Engine/World.h"
#include "Kismet/GameplayStatics.h"
#include "NiagaraFunctionLibrary.h"
#include "NiagaraSystem.h"
#include "Feedback/GES_ContextEffectsInterface.h"
#include "Feedback/GES_ContextEffectsLibrary.h"
#include "Feedback/GES_ContextEffectsPreviewSetting.h"
#include "Feedback/GES_ContextEffectsSubsystem.h"
#include "Kismet/KismetMathLibrary.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GES_AnimNotify_ContextEffects)
bool UGES_ContextEffectsSpawnParametersProvider::ProvideParameters_Implementation(USkeletalMeshComponent* InMeshComp, const UGES_AnimNotify_ContextEffects* InNotifyNotify,
UAnimSequenceBase* InAnimation, FVector& OutSpawnLocation,
FRotator& OutSpawnRotation) const
{
return false;
}
UGES_AnimNotify_ContextEffects::UGES_AnimNotify_ContextEffects(const FObjectInitializer& ObjectInitializer): Super(ObjectInitializer)
{
#if WITH_EDITORONLY_DATA
NotifyColor = FColor::Blue;
#endif
}
void UGES_AnimNotify_ContextEffects::PostLoad()
{
Super::PostLoad();
}
#if WITH_EDITOR
void UGES_AnimNotify_ContextEffects::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
}
#endif
FString UGES_AnimNotify_ContextEffects::GetNotifyName_Implementation() const
{
// If the Effect Tag is valid, pass the string name to the notify name
if (Effect.IsValid())
{
return Effect.ToString();
}
return Super::GetNotifyName_Implementation();
}
void UGES_AnimNotify_ContextEffects::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation,
const FAnimNotifyEventReference& EventReference)
{
Super::Notify(MeshComp, Animation, EventReference);
if (!MeshComp)
{
return;
}
FVector SpawnLocation = FVector::ZeroVector;
FRotator SpawnRotation = FRotator::ZeroRotator;
bool bValidProvider = !bAttached && IsValid(SpawnParametersProvider) && SpawnParametersProvider->ProvideParameters(MeshComp, this, Animation, SpawnLocation, SpawnRotation);
if (!bValidProvider && MeshComp->GetOwner())
{
SpawnRotation = MeshComp->GetOwner()->GetActorTransform().TransformRotation(RotationOffset.Quaternion()).Rotator();
SpawnLocation = MeshComp->GetOwner()->GetActorTransform().TransformPosition(LocationOffset);
}
// else
// {
// SpawnRotation = UKismetMathLibrary::ComposeRotators(SpawnRotation, RotationOffset);
// SpawnLocation = SpawnLocation + UKismetMathLibrary::Quat_RotateVector(SpawnRotation.Quaternion(), LocationOffset);
// }
// Make sure both MeshComp and Owning Actor is valid
if (AActor* OwningActor = MeshComp->GetOwner())
{
// Prepare Trace Data
bool bHitSuccess = false;
FHitResult HitResult;
FCollisionQueryParams QueryParams;
if (TraceProperties.bIgnoreActor)
{
QueryParams.AddIgnoredActor(OwningActor);
}
QueryParams.bReturnPhysicalMaterial = true;
if (bPerformTrace)
{
// If trace is needed, set up Start Location to Attached
FVector TraceStart = bAttached ? MeshComp->GetSocketLocation(SocketName) + LocationOffset : SpawnLocation;
// Make sure World is valid
if (UWorld* World = OwningActor->GetWorld())
{
// Call Line Trace, Pass in relevant properties
bHitSuccess = World->LineTraceSingleByChannel(HitResult, TraceStart, (TraceStart + TraceProperties.EndTraceLocationOffset),
TraceProperties.TraceChannel, QueryParams, FCollisionResponseParams::DefaultResponseParam);
}
}
// Prepare Contexts in advance
FGameplayTagContainer SourceContext;
FGameplayTagContainer TargetContext;
// Set up Array of Objects that implement the Context Effects Interface
TArray<UObject*> Implementers;
// Determine if the Owning Actor is one of the Objects that implements the Context Effects Interface
if (OwningActor->Implements<UGES_ContextEffectsInterface>())
{
// If so, add it to the Array
Implementers.Add(OwningActor);
}
// Cycle through Owning Actor's Components and determine if any of them is a Component implementing the Context Effect Interface
for (const auto Component : OwningActor->GetComponents())
{
if (Component)
{
// If the Component implements the Context Effects Interface, add it to the list
if (Component->Implements<UGES_ContextEffectsInterface>())
{
Implementers.Add(Component);
}
}
}
FGES_SpawnContextEffectsInput Input;
Input.EffectName = Effect;
Input.bAttached = bAttached;
Input.Bone = SocketName;
Input.ComponentToAttach = MeshComp;
Input.Location = bHitSuccess?HitResult.Location:SpawnLocation;
Input.Rotation = SpawnRotation;
Input.LocationOffset = LocationOffset;
Input.RotationOffset = RotationOffset;
Input.AnimationSequence = Animation;
Input.bHitSuccess = bHitSuccess;
Input.HitResult = HitResult;
Input.SourceContext = SourceContext;
Input.TargetContext = TargetContext;
Input.VFXScale = VFXProperties.Scale;
Input.AudioVolume = AudioProperties.VolumeMultiplier;
Input.AudioPitch = AudioProperties.PitchMultiplier;
// Cycle through all objects implementing the Context Effect Interface
for (UObject* Implementer : Implementers)
{
// If the object is still valid, Execute the AnimMotionEffect Event on it, passing in relevant data
if (Implementer)
{
IGES_ContextEffectsInterface::Execute_PlayContextEffectsWithInput(Implementer, Input);
}
}
#if WITH_EDITORONLY_DATA
PerformEditorPreview(OwningActor, SourceContext, TargetContext, MeshComp);
#endif
}
}
#if WITH_EDITOR
void UGES_AnimNotify_ContextEffects::ValidateAssociatedAssets()
{
Super::ValidateAssociatedAssets();
}
void UGES_AnimNotify_ContextEffects::SetParameters(FGameplayTag EffectIn, FVector LocationOffsetIn, FRotator RotationOffsetIn,
FGES_ContextEffectAnimNotifyVFXSettings VFXPropertiesIn, FGES_ContextEffectAnimNotifyAudioSettings AudioPropertiesIn,
bool bAttachedIn, FName SocketNameIn, bool bPerformTraceIn, FGES_ContextEffectAnimNotifyTraceSettings TracePropertiesIn)
{
Effect = EffectIn;
LocationOffset = LocationOffsetIn;
RotationOffset = RotationOffsetIn;
VFXProperties.Scale = VFXPropertiesIn.Scale;
AudioProperties.PitchMultiplier = AudioPropertiesIn.PitchMultiplier;
AudioProperties.VolumeMultiplier = AudioPropertiesIn.VolumeMultiplier;
bAttached = bAttachedIn;
SocketName = SocketNameIn;
bPerformTrace = bPerformTraceIn;
TraceProperties.EndTraceLocationOffset = TracePropertiesIn.EndTraceLocationOffset;
TraceProperties.TraceChannel = TracePropertiesIn.TraceChannel;
TraceProperties.bIgnoreActor = TracePropertiesIn.bIgnoreActor;
}
void UGES_AnimNotify_ContextEffects::PerformEditorPreview(AActor* OwningActor, FGameplayTagContainer& SourceContext, FGameplayTagContainer& TargetContext, USkeletalMeshComponent* MeshComp)
{
if (!bAttached)
{
return;
}
const UGES_ContextEffectsSettings* ContextEffectsSettings = GetDefault<UGES_ContextEffectsSettings>();
if (ContextEffectsSettings == nullptr)
{
return;
}
// This is for Anim Editor previewing, it is a deconstruction of the calls made by the Interface and the Subsystem
if (!ContextEffectsSettings->bPreviewInEditor || ContextEffectsSettings->PreviewSetting.IsNull())
return;
UWorld* World = OwningActor->GetWorld();
// Get the world, make sure it's an Editor Preview World
if (!World || World->WorldType != EWorldType::EditorPreview)
return;
// const auto& PreviewProperties = ContextEffectsSettings->PreviewProperties;
const UGES_ContextEffectsPreviewSetting* PreviewSetting = ContextEffectsSettings->PreviewSetting.LoadSynchronous();
if (PreviewSetting == nullptr)
{
return;
}
// Add Preview contexts if necessary
SourceContext.AppendTags(PreviewSetting->PreviewSourceContext);
TargetContext.AppendTags(PreviewSetting->PreviewTargetContext);
// Convert given Surface Type to Context and Add it to the Contexts for this Preview
if (PreviewSetting->bPreviewPhysicalSurfaceAsContext)
{
TEnumAsByte<EPhysicalSurface> PhysicalSurfaceType = PreviewSetting->PreviewPhysicalSurface;
if (const FGameplayTag* SurfaceContextPtr = ContextEffectsSettings->SurfaceTypeToContextMap.Find(PhysicalSurfaceType))
{
FGameplayTag SurfaceContext = *SurfaceContextPtr;
SourceContext.AddTag(SurfaceContext);
}
}
for (int i = 0; i < PreviewSetting->PreviewContextEffectsLibraries.Num(); ++i)
{
// Libraries are soft referenced, so you will want to try to load them now
if (UObject* EffectsLibrariesObj = PreviewSetting->PreviewContextEffectsLibraries[i].TryLoad())
{
// Check if it is in fact a UGES_ContextEffectLibrary type
if (UGES_ContextEffectsLibrary* EffectLibrary = Cast<UGES_ContextEffectsLibrary>(EffectsLibrariesObj))
{
// Prepare Sounds and Niagara System Arrays
TArray<USoundBase*> TotalSounds;
TArray<UNiagaraSystem*> TotalNiagaraSystems;
TArray<UParticleSystem*> TotalParticleSystems;
// Attempt to load the Effect Library content (will cache in Transient data on the Effect Library Asset)
EffectLibrary->LoadEffects();
// If the Effect Library is valid and marked as Loaded, Get Effects from it
if (EffectLibrary && EffectLibrary->GetContextEffectsLibraryLoadState() == EGES_ContextEffectsLibraryLoadState::Loaded)
{
// Prepare local arrays
TArray<USoundBase*> Sounds;
TArray<UNiagaraSystem*> NiagaraSystems;
TArray<UParticleSystem*> ParticleSystems;
// Get the Effects
EffectLibrary->GetEffects(Effect, SourceContext, TargetContext, Sounds, NiagaraSystems, ParticleSystems);
// Append to the accumulating arrays
TotalSounds.Append(Sounds);
TotalNiagaraSystems.Append(NiagaraSystems);
TotalParticleSystems.Append(ParticleSystems);
}
// Cycle through Sounds and call Spawn Sound Attached, passing in relevant data
for (USoundBase* Sound : TotalSounds)
{
UGameplayStatics::SpawnSoundAttached(Sound, MeshComp, SocketName, LocationOffset, RotationOffset, EAttachLocation::KeepRelativeOffset,
false, AudioProperties.VolumeMultiplier, AudioProperties.PitchMultiplier, 0.0f, nullptr, nullptr, true);
}
// Cycle through Niagara Systems and call Spawn System Attached, passing in relevant data
for (UNiagaraSystem* NiagaraSystem : TotalNiagaraSystems)
{
UNiagaraFunctionLibrary::SpawnSystemAttached(NiagaraSystem, MeshComp, SocketName, LocationOffset,
RotationOffset, VFXProperties.Scale, EAttachLocation::KeepRelativeOffset, true, ENCPoolMethod::None, true, true);
}
// Cycle through Particle Systems and call Spawn System Attached, passing in relevant data
for (UParticleSystem* ParticleSystem : TotalParticleSystems)
{
UGameplayStatics::SpawnEmitterAttached(ParticleSystem, MeshComp, SocketName, LocationOffset,
RotationOffset, VFXProperties.Scale, EAttachLocation::KeepRelativeOffset, true, EPSCPoolMethod::None, true);
}
}
}
}
}
#endif

View File

@@ -0,0 +1,266 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Feedback/GES_ContextEffectComponent.h"
#include "GameplayTagAssetInterface.h"
#include "Engine/World.h"
#include "Feedback/GES_ContextEffectsSubsystem.h"
#include "PhysicalMaterials/PhysicalMaterial.h"
#include "GES_LogChannels.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GES_ContextEffectComponent)
class UAnimSequenceBase;
class USceneComponent;
// Sets default values for this component's properties
UGES_ContextEffectComponent::UGES_ContextEffectComponent()
{
// Disable component tick, enable Auto Activate
PrimaryComponentTick.bCanEverTick = false;
bAutoActivate = true;
// ...
}
// Called when the game starts
void UGES_ContextEffectComponent::BeginPlay()
{
Super::BeginPlay();
// ...
CurrentContexts.AppendTags(DefaultEffectContexts);
CurrentContextEffectsLibraries = DefaultContextEffectsLibraries;
// On Begin Play, Load and Add Context Effects pairings
if (const UWorld* World = GetWorld())
{
if (UGES_ContextEffectsSubsystem* ContextEffectsSubsystem = World->GetSubsystem<UGES_ContextEffectsSubsystem>())
{
ContextEffectsSubsystem->LoadAndAddContextEffectsLibraries(GetOwner(), CurrentContextEffectsLibraries);
}
}
if (bAutoSetupTagsProvider)
{
SetupTagsProvider();
}
}
void UGES_ContextEffectComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
// On End PLay, remove unnecessary context effects pairings
if (const UWorld* World = GetWorld())
{
if (UGES_ContextEffectsSubsystem* ContextEffectsSubsystem = World->GetSubsystem<UGES_ContextEffectsSubsystem>())
{
ContextEffectsSubsystem->UnloadAndRemoveContextEffectsLibraries(GetOwner());
}
}
Super::EndPlay(EndPlayReason);
}
void UGES_ContextEffectComponent::SetupTagsProvider()
{
if (GetOwner()->GetClass()->ImplementsInterface(UGameplayTagAssetInterface::StaticClass()))
{
SetGameplayTagsProvider(GetOwner());
}
else
{
TArray<UActorComponent*> Components = GetOwner()->GetComponentsByInterface(UGameplayTagAssetInterface::StaticClass());
if (Components.IsValidIndex(0))
{
SetGameplayTagsProvider(Components[0]);
}
}
}
void UGES_ContextEffectComponent::PlayContextEffectsWithInput_Implementation(FGES_SpawnContextEffectsInput Input)
{
AggregateContexts(Input);
InjectPhysicalSurfaceToContexts(Input.HitResult, Input.SourceContext);
// Prep Components
TArray<UAudioComponent*> AudioComponentsToAdd;
TArray<UNiagaraComponent*> NiagaraComponentsToAdd;
TArray<UParticleSystemComponent*> ParticleComponentsToAdd;
// Cycle through Active Audio Components and cache
for (UAudioComponent* ActiveAudioComponent : ActiveAudioComponents)
{
if (ActiveAudioComponent)
{
AudioComponentsToAdd.Add(ActiveAudioComponent);
}
}
// Cycle through Active Niagara Components and cache
for (UNiagaraComponent* ActiveNiagaraComponent : ActiveNiagaraComponents)
{
if (ActiveNiagaraComponent)
{
NiagaraComponentsToAdd.Add(ActiveNiagaraComponent);
}
}
// Cycle through Active Particle Components and cache
for (UParticleSystemComponent* ActiveParticleComponent : ActiveParticleComponents)
{
if (ActiveParticleComponent)
{
ParticleComponentsToAdd.Add(ActiveParticleComponent);
}
}
// Get World
if (const UWorld* World = GetWorld())
{
// Get Subsystem
if (UGES_ContextEffectsSubsystem* ContextEffectsSubsystem = World->GetSubsystem<UGES_ContextEffectsSubsystem>())
{
// Set up components
FGES_SpawnContextEffectsOutput Output;
// Spawn effects
ContextEffectsSubsystem->SpawnContextEffectsExt(GetOwner(), Input, Output);
// Append resultant effects
AudioComponentsToAdd.Append(Output.AudioComponents);
NiagaraComponentsToAdd.Append(Output.NiagaraComponents);
ParticleComponentsToAdd.Append(Output.ParticlesComponents);
}
}
// Append Active Audio Components
ActiveAudioComponents.Empty();
ActiveAudioComponents.Append(AudioComponentsToAdd);
// Append Active
ActiveNiagaraComponents.Empty();
ActiveNiagaraComponents.Append(NiagaraComponentsToAdd);
ActiveParticleComponents.Empty();
ActiveParticleComponents.Append(ParticleComponentsToAdd);
}
void UGES_ContextEffectComponent::AggregateContexts(FGES_SpawnContextEffectsInput& Input) const
{
if (Input.SourceContextType == EGES_EffectsContextType::Merge)
{
FGameplayTagContainer TotalContexts;
// Aggregate contexts
TotalContexts.AppendTags(Input.SourceContext);
TotalContexts.AppendTags(CurrentContexts);
if (IGameplayTagAssetInterface* TagAssetInterface = Cast<IGameplayTagAssetInterface>(GameplayTagsProvider))
{
FGameplayTagContainer RetTags;
TagAssetInterface->GetOwnedGameplayTags(RetTags);
TotalContexts.AppendTags(RetTags);
}
Input.SourceContext = TotalContexts;
}
}
void UGES_ContextEffectComponent::InjectPhysicalSurfaceToContexts(const FHitResult& InHitResult, FGameplayTagContainer& Contexts)
{
// Check if converting Physical Surface Type to Context
if (!bConvertPhysicalSurfaceToContext)
{
return;
}
// Get Phys Mat Type Pointer
TWeakObjectPtr<UPhysicalMaterial> PhysicalSurfaceTypePtr = InHitResult.PhysMaterial;
// Check if pointer is okay
if (PhysicalSurfaceTypePtr.IsValid())
{
// Get the Surface Type Pointer
TEnumAsByte<EPhysicalSurface> PhysicalSurfaceType = PhysicalSurfaceTypePtr->SurfaceType;
// If Settings are valid
if (const UGES_ContextEffectsSettings* ContextEffectsSettings = GetDefault<UGES_ContextEffectsSettings>())
{
if (ContextEffectsSettings->SurfaceTypeToContextMap.IsEmpty())
{
GES_CLOG(Warning, "No surface type to context map, Please check ContextEffectsSetting in ProjectSettings!");
if (FallbackPhysicalSurface.IsValid())
{
Contexts.AddTag(FallbackPhysicalSurface);
}
}
else
{
// Convert Surface Type to known
if (const FGameplayTag* SurfaceContextPtr = ContextEffectsSettings->SurfaceTypeToContextMap.Find(PhysicalSurfaceType))
{
FGameplayTag SurfaceContext = *SurfaceContextPtr;
Contexts.AddTag(SurfaceContext);
}
else
{
GES_CLOG(Warning, "No surface type(%d) to context map found, Please check ContextEffectsSetting in ProjectSettings!", PhysicalSurfaceType.GetValue());
if (FallbackPhysicalSurface.IsValid())
{
Contexts.AddTag(FallbackPhysicalSurface);
}
}
}
}
}
else
{
if (FallbackPhysicalSurface.IsValid())
{
Contexts.AddTag(FallbackPhysicalSurface);
}
}
}
void UGES_ContextEffectComponent::SetGameplayTagsProvider(UObject* Provider)
{
if (!IsValid(Provider))
{
return;
}
if (IGameplayTagAssetInterface* TagAssetInterface = Cast<IGameplayTagAssetInterface>(Provider))
{
GameplayTagsProvider = Provider;
}
else
{
GES_CLOG(Warning, "Passed in GameplayTagsProvider(%s) Doesn't implement GameplayTagAssetInterface, it can't provide gameplay tags.", *GetNameSafe(Provider->GetClass()));
}
}
void UGES_ContextEffectComponent::UpdateEffectContexts(FGameplayTagContainer NewEffectContexts)
{
// Reset and update
CurrentContexts.Reset(NewEffectContexts.Num());
CurrentContexts.AppendTags(NewEffectContexts);
}
void UGES_ContextEffectComponent::UpdateLibraries(
TSet<TSoftObjectPtr<UGES_ContextEffectsLibrary>> NewContextEffectsLibraries)
{
// Clear out existing Effects
CurrentContextEffectsLibraries = NewContextEffectsLibraries;
// Get World
if (const UWorld* World = GetWorld())
{
// Get Subsystem
if (UGES_ContextEffectsSubsystem* ContextEffectsSubsystem = World->GetSubsystem<UGES_ContextEffectsSubsystem>())
{
// Load and Add Libraries to Subsystem
ContextEffectsSubsystem->LoadAndAddContextEffectsLibraries(GetOwner(), CurrentContextEffectsLibraries);
}
}
}

View File

@@ -0,0 +1,6 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Feedback/GES_ContextEffectsEnumLibrary.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GES_ContextEffectsEnumLibrary)

View File

@@ -0,0 +1,157 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Feedback/GES_ContextEffectsLibrary.h"
#include "NiagaraSystem.h"
#include "Sound/SoundBase.h"
#include "UObject/ObjectSaveContext.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GES_ContextEffectsLibrary)
void UGES_ContextEffectsLibrary::GetEffects(const FGameplayTag Effect, const FGameplayTagContainer& SourceContext, const FGameplayTagContainer& TargetContext,
TArray<USoundBase*>& Sounds, TArray<UNiagaraSystem*>& NiagaraSystems, TArray<UParticleSystem*>& ParticleSystems)
{
// Make sure Effect is valid and Library is loaded
if (Effect.IsValid() && SourceContext.IsValid() && EffectsLoadState == EGES_ContextEffectsLibraryLoadState::Loaded)
{
// Loop through Context Effects
for (const auto& ActiveContextEffect : ActiveContextEffects)
{
bool bMatchesEffectTag = Effect.MatchesTagExact(ActiveContextEffect->EffectTag);
bool bMatchesSourceContext = ActiveContextEffect->SourceTagQuery.Matches(SourceContext) && (ActiveContextEffect->SourceTagQuery.IsEmpty() == SourceContext.IsEmpty());
// Target context is optional
bool bMatchesTargetContext = ActiveContextEffect->TargetTagQuery.IsEmpty() || ActiveContextEffect->TargetTagQuery.Matches(TargetContext);
// Make sure the Effect is an exact Tag Match and ensure the Context has all tags in the Effect (and neither or both are empty)
if (bMatchesEffectTag && bMatchesSourceContext && bMatchesTargetContext)
{
// Get all Matching Sounds and Niagara Systems
Sounds.Append(ActiveContextEffect->Sounds);
NiagaraSystems.Append(ActiveContextEffect->NiagaraSystems);
ParticleSystems.Append(ActiveContextEffect->ParticleSystems);
}
}
}
}
void UGES_ContextEffectsLibrary::LoadEffects()
{
// Load Effects into Library if not currently loading
if (EffectsLoadState != EGES_ContextEffectsLibraryLoadState::Loading)
{
// Set load state to loading
EffectsLoadState = EGES_ContextEffectsLibraryLoadState::Loading;
// Clear out any old Active Effects
ActiveContextEffects.Empty();
// Call internal loading function
LoadEffectsInternal();
}
}
EGES_ContextEffectsLibraryLoadState UGES_ContextEffectsLibrary::GetContextEffectsLibraryLoadState()
{
// Return current Load State
return EffectsLoadState;
}
void UGES_ContextEffectsLibrary::LoadEffectsInternal()
{
// TODO Add Async Loading for Libraries
// Copy data for async load
TArray<FGES_ContextEffects> LocalContextEffects = ContextEffects;
// Prepare Active Context Effects Array
TArray<UGES_ActiveContextEffects*> ActiveContextEffectsArray;
// Loop through Context Effects
for (const FGES_ContextEffects& ContextEffect : LocalContextEffects)
{
// Make sure Tags are Valid
if (ContextEffect.EffectTag.IsValid() && !ContextEffect.SourceTagQuery.IsEmpty())
{
// Create new Active Context Effect
UGES_ActiveContextEffects* NewActiveContextEffects = NewObject<UGES_ActiveContextEffects>(this);
// Pass relevant tag data
NewActiveContextEffects->EffectTag = ContextEffect.EffectTag;
NewActiveContextEffects->SourceTagQuery = ContextEffect.SourceTagQuery;
NewActiveContextEffects->TargetTagQuery = ContextEffect.TargetTagQuery;
// Try to load and add Effects to New Active Context Effects
for (const FSoftObjectPath& Effect : ContextEffect.Effects)
{
if (UObject* Object = Effect.TryLoad())
{
if (Object->IsA(USoundBase::StaticClass()))
{
if (USoundBase* SoundBase = Cast<USoundBase>(Object))
{
NewActiveContextEffects->Sounds.Add(SoundBase);
}
}
else if (Object->IsA(UNiagaraSystem::StaticClass()))
{
if (UNiagaraSystem* NiagaraSystem = Cast<UNiagaraSystem>(Object))
{
NewActiveContextEffects->NiagaraSystems.Add(NiagaraSystem);
}
}
else if (Object->IsA(UParticleSystem::StaticClass()))
{
if (UParticleSystem* ParticleSystem = Cast<UParticleSystem>(Object))
{
NewActiveContextEffects->ParticleSystems.Add(ParticleSystem);
}
}
}
}
// Add New Active Context to the Active Context Effects Array
ActiveContextEffectsArray.Add(NewActiveContextEffects);
}
}
// TODO Call Load Complete after Async Load
// Mark loading complete
this->OnContextEffectLibraryLoadingComplete(ActiveContextEffectsArray);
}
void UGES_ContextEffectsLibrary::OnContextEffectLibraryLoadingComplete(
TArray<UGES_ActiveContextEffects*> InActiveContextEffects)
{
// Flag data as loaded
EffectsLoadState = EGES_ContextEffectsLibraryLoadState::Loaded;
// Append incoming Context Effects Array to current list of Active Context Effects
ActiveContextEffects.Append(InActiveContextEffects);
}
#if WITH_EDITOR
void UGES_ContextEffectsLibrary::PreSave(FObjectPreSaveContext SaveContext)
{
for (FGES_ContextEffects& ContextEffect : ContextEffects)
{
if (!ContextEffect.Context.IsEmpty())
{
FGameplayTagQueryExpression Expression;
Expression.AllTagsMatch().AddTags(ContextEffect.Context);
ContextEffect.SourceTagQuery.Build(Expression, FString::Format(TEXT("Has all tags({0})"), {ContextEffect.Context.ToStringSimple()}));
ContextEffect.Context = FGameplayTagContainer();
}
ContextEffect.EditorFriendlyName = ContextEffect.EffectTag.IsValid()
? FString::Format(TEXT("Effect({0}) Source({1}) Target({2})"),
{
ContextEffect.EffectTag.GetTagName().ToString(), ContextEffect.SourceTagQuery.GetDescription(),
ContextEffect.TargetTagQuery.GetDescription()
})
: TEXT("Invalid Effect");
}
Super::PreSave(SaveContext);
}
#endif

View File

@@ -0,0 +1,6 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Feedback/GES_ContextEffectsPreviewSetting.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GES_ContextEffectsPreviewSetting)

View File

@@ -0,0 +1,7 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Feedback/GES_ContextEffectsStructLibrary.h"
#include "Animation/AnimSequenceBase.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GES_ContextEffectsStructLibrary)

View File

@@ -0,0 +1,289 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Feedback/GES_ContextEffectsSubsystem.h"
#include "Feedback/GES_ContextEffectsLibrary.h"
#include "Kismet/GameplayStatics.h"
#include "NiagaraFunctionLibrary.h"
#include "NiagaraSystem.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GES_ContextEffectsSubsystem)
class AActor;
class UAudioComponent;
class UNiagaraSystem;
class USceneComponent;
class USoundBase;
void UGES_ContextEffectsSubsystem::SpawnContextEffects(UObject* WorldContextObject, TSoftObjectPtr<UGES_ContextEffectsLibrary> EffectsLibrary, FGES_SpawnContextEffectsInput Input,
FGES_SpawnContextEffectsOutput& Output)
{
if (WorldContextObject == nullptr || WorldContextObject->GetWorld() == nullptr)
{
return;
}
if (EffectsLibrary.IsNull())
{
return;
}
// Prepare Arrays for Sounds and Niagara Systems
TArray<USoundBase*> TotalSounds;
TArray<UNiagaraSystem*> TotalNiagaraSystems;
TArray<UParticleSystem*> TotalParticleSystems;
// Cycle through Effect Libraries
if (UGES_ContextEffectsLibrary* EffectLibrary = EffectsLibrary.LoadSynchronous())
{
if (EffectLibrary && EffectLibrary->GetContextEffectsLibraryLoadState() == EGES_ContextEffectsLibraryLoadState::Unloaded)
{
// Sync load effects
EffectLibrary->LoadEffects();
}
// Check if the Effect Library is valid and data Loaded
if (EffectLibrary->GetContextEffectsLibraryLoadState() == EGES_ContextEffectsLibraryLoadState::Loaded)
{
// Set up local list of Sounds and Niagara Systems
TArray<USoundBase*> Sounds;
TArray<UNiagaraSystem*> NiagaraSystems;
TArray<UParticleSystem*> ParticleSystems;
// Get Sounds and Niagara Systems
EffectLibrary->GetEffects(Input.EffectName, Input.SourceContext, Input.TargetContext, Sounds, NiagaraSystems, ParticleSystems);
// Append to accumulating array
TotalSounds.Append(Sounds);
TotalNiagaraSystems.Append(NiagaraSystems);
TotalParticleSystems.Append(ParticleSystems);
}
}
// Cycle through found Sounds
for (USoundBase* Sound : TotalSounds)
{
if (Input.bAttached)
{
// Spawn Sounds Attached, add Audio Component to List of ACs
UAudioComponent* AudioComponent = UGameplayStatics::SpawnSoundAttached(Sound, Input.ComponentToAttach, Input.Bone, Input.LocationOffset, Input.RotationOffset,
EAttachLocation::KeepRelativeOffset,
false, Input.AudioVolume, Input.AudioPitch, 0.0f, nullptr, nullptr, true);
Output.AudioComponents.Add(AudioComponent);
}
else
{
UAudioComponent* AudioComponent = UGameplayStatics::SpawnSoundAtLocation(WorldContextObject, Sound, Input.Location, Input.Rotation, Input.AudioVolume, Input.AudioPitch, 0.0f, nullptr,
nullptr, true);
Output.AudioComponents.Add(AudioComponent);
}
}
// Cycle through found Niagara Systems
for (UNiagaraSystem* NiagaraSystem : TotalNiagaraSystems)
{
if (Input.bAttached)
{
// Spawn Niagara Systems Attached, add Niagara Component to List of NCs
UNiagaraComponent* NiagaraComponent = UNiagaraFunctionLibrary::SpawnSystemAttached(NiagaraSystem, Input.ComponentToAttach, Input.Bone, Input.LocationOffset,
Input.RotationOffset, Input.VFXScale, EAttachLocation::KeepRelativeOffset, true,
ENCPoolMethod::None,
true,
true);
Output.NiagaraComponents.Add(NiagaraComponent);
}
else
{
UNiagaraComponent* NiagaraComponent = UNiagaraFunctionLibrary::SpawnSystemAtLocation(WorldContextObject, NiagaraSystem, Input.Location,
Input.Rotation, Input.VFXScale, true, true,
ENCPoolMethod::None, true);
Output.NiagaraComponents.Add(NiagaraComponent);
}
}
// Cycle through found Particle Systems
for (UParticleSystem* ParticleSystem : TotalParticleSystems)
{
if (Input.bAttached)
{
// Spawn Particle Systems Attached, add Niagara Component to List of NCs
UParticleSystemComponent* ParticleComponent = UGameplayStatics::SpawnEmitterAttached(ParticleSystem, Input.ComponentToAttach, Input.Bone, Input.LocationOffset,
Input.RotationOffset, Input.VFXScale, EAttachLocation::KeepRelativeOffset, true,
EPSCPoolMethod::None,
true);
Output.ParticlesComponents.Add(ParticleComponent);
}
else
{
UParticleSystemComponent* ParticleComponent = UGameplayStatics::SpawnEmitterAtLocation(WorldContextObject, ParticleSystem, Input.Location, Input.Rotation, Input.VFXScale, true,
EPSCPoolMethod::None, true);
Output.ParticlesComponents.Add(ParticleComponent);
}
}
}
void UGES_ContextEffectsSubsystem::SpawnContextEffectsExt(const AActor* SpawningActor, const FGES_SpawnContextEffectsInput& Input, FGES_SpawnContextEffectsOutput& Output)
{
// First determine if this Actor has a matching Set of Libraries
if (TObjectPtr<UGES_ContextEffectsSet>* EffectsLibrariesSetPtr = ActiveActorEffectsMap.Find(SpawningActor))
{
// Validate the pointers from the Map Find
if (UGES_ContextEffectsSet* EffectsLibraries = *EffectsLibrariesSetPtr)
{
// Prepare Arrays for Sounds and Niagara Systems
TArray<USoundBase*> TotalSounds;
TArray<UNiagaraSystem*> TotalNiagaraSystems;
TArray<UParticleSystem*> TotalParticleSystems;
// Cycle through Effect Libraries
for (UGES_ContextEffectsLibrary* EffectLibrary : EffectsLibraries->ContextEffectsLibraries)
{
// Check if the Effect Library is valid and data Loaded
if (EffectLibrary && EffectLibrary->GetContextEffectsLibraryLoadState() == EGES_ContextEffectsLibraryLoadState::Loaded)
{
// Set up local list of Sounds and Niagara Systems
TArray<USoundBase*> Sounds;
TArray<UNiagaraSystem*> NiagaraSystems;
TArray<UParticleSystem*> ParticleSystems;
// Get Sounds and Niagara Systems
EffectLibrary->GetEffects(Input.EffectName, Input.SourceContext, Input.TargetContext, Sounds, NiagaraSystems, ParticleSystems);
// Append to accumulating array
TotalSounds.Append(Sounds);
TotalNiagaraSystems.Append(NiagaraSystems);
TotalParticleSystems.Append(ParticleSystems);
}
else if (EffectLibrary && EffectLibrary->GetContextEffectsLibraryLoadState() == EGES_ContextEffectsLibraryLoadState::Unloaded)
{
// Else load effects
EffectLibrary->LoadEffects();
}
}
// Cycle through found Sounds
for (USoundBase* Sound : TotalSounds)
{
if (Input.bAttached)
{
// Spawn Sounds Attached, add Audio Component to List of ACs
UAudioComponent* AudioComponent = UGameplayStatics::SpawnSoundAttached(Sound, Input.ComponentToAttach, Input.Bone, Input.LocationOffset, Input.RotationOffset,
EAttachLocation::KeepRelativeOffset,
false, Input.AudioVolume, Input.AudioPitch, 0.0f, nullptr, nullptr, true);
Output.AudioComponents.Add(AudioComponent);
}
else
{
UAudioComponent* AudioComponent = UGameplayStatics::SpawnSoundAtLocation(SpawningActor, Sound, Input.Location, Input.Rotation, Input.AudioVolume, Input.AudioPitch, 0.0f, nullptr,
nullptr, true);
Output.AudioComponents.Add(AudioComponent);
}
}
// Cycle through found Niagara Systems
for (UNiagaraSystem* NiagaraSystem : TotalNiagaraSystems)
{
if (Input.bAttached)
{
// Spawn Niagara Systems Attached, add Niagara Component to List of NCs
UNiagaraComponent* NiagaraComponent = UNiagaraFunctionLibrary::SpawnSystemAttached(NiagaraSystem, Input.ComponentToAttach, Input.Bone, Input.LocationOffset,
Input.RotationOffset, Input.VFXScale, EAttachLocation::KeepRelativeOffset, true,
ENCPoolMethod::None,
true,
true);
Output.NiagaraComponents.Add(NiagaraComponent);
}
else
{
UNiagaraComponent* NiagaraComponent = UNiagaraFunctionLibrary::SpawnSystemAtLocation(SpawningActor, NiagaraSystem, Input.Location,
Input.Rotation, Input.VFXScale, true, true,
ENCPoolMethod::None, true);
Output.NiagaraComponents.Add(NiagaraComponent);
}
}
// Cycle through found Particle Systems
for (UParticleSystem* ParticleSystem : TotalParticleSystems)
{
if (Input.bAttached)
{
// Spawn Particle Systems Attached, add Niagara Component to List of NCs
UParticleSystemComponent* ParticleComponent = UGameplayStatics::SpawnEmitterAttached(ParticleSystem, Input.ComponentToAttach, Input.Bone, Input.LocationOffset,
Input.RotationOffset, Input.VFXScale, EAttachLocation::KeepRelativeOffset, true,
EPSCPoolMethod::None,
true);
Output.ParticlesComponents.Add(ParticleComponent);
}
else
{
UParticleSystemComponent* ParticleComponent = UGameplayStatics::SpawnEmitterAtLocation(SpawningActor, ParticleSystem, Input.Location, Input.Rotation, Input.VFXScale, true,
EPSCPoolMethod::None, true);
Output.ParticlesComponents.Add(ParticleComponent);
}
}
}
}
}
bool UGES_ContextEffectsSubsystem::GetContextFromSurfaceType(
TEnumAsByte<EPhysicalSurface> PhysicalSurface, FGameplayTag& Context)
{
// Get Project Settings
if (const UGES_ContextEffectsSettings* ProjectSettings = GetDefault<UGES_ContextEffectsSettings>())
{
// Find which Gameplay Tag the Surface Type is mapped to
if (const FGameplayTag* GameplayTagPtr = ProjectSettings->SurfaceTypeToContextMap.Find(PhysicalSurface))
{
Context = *GameplayTagPtr;
}
}
// Return true if Context is Valid
return Context.IsValid();
}
void UGES_ContextEffectsSubsystem::LoadAndAddContextEffectsLibraries(AActor* OwningActor,
TSet<TSoftObjectPtr<UGES_ContextEffectsLibrary>> ContextEffectsLibraries)
{
// Early out if Owning Actor is invalid or if the associated Libraries is 0 (or less)
if (OwningActor == nullptr || ContextEffectsLibraries.Num() <= 0)
{
return;
}
// Create new Context Effect Set
UGES_ContextEffectsSet* EffectsLibrariesSet = NewObject<UGES_ContextEffectsSet>(this);
// Cycle through Libraries getting Soft Obj Refs
for (const TSoftObjectPtr<UGES_ContextEffectsLibrary>& ContextEffectSoftObj : ContextEffectsLibraries)
{
// Load Library Assets from Soft Obj refs
// TODO Support Async Loading of Asset Data
if (UGES_ContextEffectsLibrary* EffectsLibrary = ContextEffectSoftObj.LoadSynchronous())
{
// Call load on valid Libraries
EffectsLibrary->LoadEffects();
// Add new library to Set
EffectsLibrariesSet->ContextEffectsLibraries.Add(EffectsLibrary);
}
}
// Update Active Actor Effects Map
ActiveActorEffectsMap.Emplace(OwningActor, EffectsLibrariesSet);
}
void UGES_ContextEffectsSubsystem::UnloadAndRemoveContextEffectsLibraries(AActor* OwningActor)
{
// Early out if Owning Actor is invalid
if (OwningActor == nullptr)
{
return;
}
// Remove ref from Active Actor/Effects Set Map
ActiveActorEffectsMap.Remove(OwningActor);
}

View File

@@ -0,0 +1,47 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "GES_LogChannels.h"
#include "UObject/Object.h"
#include "GameFramework/Actor.h"
#include "Components/ActorComponent.h"
DEFINE_LOG_CATEGORY(LogGES)
FString GetGESLogContextString(const UObject* ContextObject)
{
ENetRole Role = ROLE_None;
FString RoleName = TEXT("None");
FString Name = "None";
if (const AActor* Actor = Cast<AActor>(ContextObject))
{
Role = Actor->GetLocalRole();
Name = Actor->GetName();
}
else if (const UActorComponent* Component = Cast<UActorComponent>(ContextObject))
{
if (AActor* ActorOwner = Cast<AActor>(Component->GetOuter()))
{
Role = ActorOwner->GetLocalRole();
Name = ActorOwner->GetName();
}
else
{
const AActor* Owner = Component->GetOwner();
Role = IsValid(Owner) ? Owner->GetLocalRole() : ROLE_None;
Name = IsValid(Owner) ? Owner->GetName() : TEXT("None");
}
}
else if (IsValid(ContextObject))
{
Name = ContextObject->GetName();
}
if (Role != ROLE_None)
{
RoleName = (Role == ROLE_Authority) ? TEXT("Server") : TEXT("Client");
}
return FString::Printf(TEXT("[%s] (%s)"), *RoleName, *Name);
}

View File

@@ -0,0 +1,9 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "GES_Tags.h"
namespace GMS_MovementModeTags
{
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Root, FName{TEXTVIEW("GES")},"Generic Effects System")
}

View File

@@ -0,0 +1,22 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "GenericEffectsSystem.h"
#define LOCTEXT_NAMESPACE "FGenericEffectsSystemModule"
void FGenericEffectsSystemModule::StartupModule()
{
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
}
void FGenericEffectsSystemModule::ShutdownModule()
{
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
// we call this function before unloading the module.
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FGenericEffectsSystemModule, GenericEffectsSystem)