diff --git a/hwanyoung2/Plugins/FMODStudioNiagara/Content/ENiagara_FMODParamType.uasset b/hwanyoung2/Plugins/FMODStudioNiagara/Content/ENiagara_FMODParamType.uasset new file mode 100644 index 00000000..70bc54e7 Binary files /dev/null and b/hwanyoung2/Plugins/FMODStudioNiagara/Content/ENiagara_FMODParamType.uasset differ diff --git a/hwanyoung2/Plugins/FMODStudioNiagara/Content/PlayFMODEvent.uasset b/hwanyoung2/Plugins/FMODStudioNiagara/Content/PlayFMODEvent.uasset new file mode 100644 index 00000000..214cbe3c Binary files /dev/null and b/hwanyoung2/Plugins/FMODStudioNiagara/Content/PlayFMODEvent.uasset differ diff --git a/hwanyoung2/Plugins/FMODStudioNiagara/Content/PlayPersistentFMODEvent.uasset b/hwanyoung2/Plugins/FMODStudioNiagara/Content/PlayPersistentFMODEvent.uasset new file mode 100644 index 00000000..80520813 Binary files /dev/null and b/hwanyoung2/Plugins/FMODStudioNiagara/Content/PlayPersistentFMODEvent.uasset differ diff --git a/hwanyoung2/Plugins/FMODStudioNiagara/Content/Templates/PlayFMODEventEmitter.uasset b/hwanyoung2/Plugins/FMODStudioNiagara/Content/Templates/PlayFMODEventEmitter.uasset new file mode 100644 index 00000000..00099a16 Binary files /dev/null and b/hwanyoung2/Plugins/FMODStudioNiagara/Content/Templates/PlayFMODEventEmitter.uasset differ diff --git a/hwanyoung2/Plugins/FMODStudioNiagara/Content/UpdatePersistentFMODEvent.uasset b/hwanyoung2/Plugins/FMODStudioNiagara/Content/UpdatePersistentFMODEvent.uasset new file mode 100644 index 00000000..dab8830f Binary files /dev/null and b/hwanyoung2/Plugins/FMODStudioNiagara/Content/UpdatePersistentFMODEvent.uasset differ diff --git a/hwanyoung2/Plugins/FMODStudioNiagara/FMODStudioNiagara.uplugin b/hwanyoung2/Plugins/FMODStudioNiagara/FMODStudioNiagara.uplugin new file mode 100644 index 00000000..e5cc627a --- /dev/null +++ b/hwanyoung2/Plugins/FMODStudioNiagara/FMODStudioNiagara.uplugin @@ -0,0 +1,32 @@ +{ + "FileVersion": 3, + + "FriendlyName": "FMOD Studio Niagara Integration", + "Version" : 20226, + "VersionName" : "2.02.26", + "CreatedBy": "Firelight Technologies", + "CreatedByURL" : "http://fmod.com", + "Description" : "FMOD Studio with Niagara Integration.", + "Category": "Audio", + + "CanContainContent": true, + "Installed": true, + + "Modules": [ + { + "Name": "FMODStudioNiagara", + "Type": "Runtime", + "LoadingPhase": "Default" + } + ], + "Plugins": [ + { + "Name": "FMODStudio", + "Enabled": true + }, + { + "Name": "Niagara", + "Enabled": true + } + ] +} \ No newline at end of file diff --git a/hwanyoung2/Plugins/FMODStudioNiagara/Source/FMODStudioNiagara/FMODStudioNiagara.Build.cs b/hwanyoung2/Plugins/FMODStudioNiagara/Source/FMODStudioNiagara/FMODStudioNiagara.Build.cs new file mode 100644 index 00000000..1a63bd8e --- /dev/null +++ b/hwanyoung2/Plugins/FMODStudioNiagara/Source/FMODStudioNiagara/FMODStudioNiagara.Build.cs @@ -0,0 +1,37 @@ +// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2024. + +using UnrealBuildTool; +using System; + +public class FMODStudioNiagara : ModuleRules +{ + public FMODStudioNiagara(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicDependencyModuleNames.AddRange( + new[] + { + "Core" + }); + + PrivateDependencyModuleNames.AddRange( + new[] + { + "CoreUObject", + "Engine", + "Slate", + "SlateCore", + "Projects", + "FMODStudio", + + // Data interface dependencies + "Niagara", "NiagaraCore", "VectorVM", "RenderCore", "RHI", + }); + + if (Target.bBuildEditor == true) + { + PrivateDependencyModuleNames.Add("UnrealEd"); + } + } +} diff --git a/hwanyoung2/Plugins/FMODStudioNiagara/Source/FMODStudioNiagara/Private/FMODNiagaraEventPlayer.cpp b/hwanyoung2/Plugins/FMODStudioNiagara/Source/FMODStudioNiagara/Private/FMODNiagaraEventPlayer.cpp new file mode 100644 index 00000000..f7a3fa8f --- /dev/null +++ b/hwanyoung2/Plugins/FMODStudioNiagara/Source/FMODStudioNiagara/Private/FMODNiagaraEventPlayer.cpp @@ -0,0 +1,620 @@ + +#include "FMODNiagaraEventPlayer.h" + +#include "FMODBlueprintStatics.h" +#include "FMODSettings.h" + +#include "NiagaraTypes.h" +#include "NiagaraCustomVersion.h" +#include "NiagaraSystemInstance.h" +#include "NiagaraWorldManager.h" +#include "Kismet/GameplayStatics.h" +#include "Engine/World.h" +#include "Sound/SoundBase.h" + +DEFINE_LOG_CATEGORY_STATIC(LogFMODNiagara, Log, All); + +const FName UFMODNiagaraEventPlayer::PlayAudioName(TEXT("PlayEventAtLocation")); +const FName UFMODNiagaraEventPlayer::PlayPersistentAudioName(TEXT("PlayPersistentEvent")); +const FName UFMODNiagaraEventPlayer::SetPersistentAudioLocationName(TEXT("UpdateEventLocation")); +const FName UFMODNiagaraEventPlayer::SetPersistentAudioRotationName(TEXT("UpdateEventRotation")); +const FName UFMODNiagaraEventPlayer::SetPersistentAudioFloatParamName(TEXT("SetFloatParameter")); +const FName UFMODNiagaraEventPlayer::PausePersistentAudioName(TEXT("SetPaused")); + +struct FNiagaraAudioPlayerDIFunctionVersion +{ + enum Type + { + InitialVersion = 0, + LWCConversion = 1, + + VersionPlusOne, + LatestVersion = VersionPlusOne - 1 + }; +}; + +UFMODNiagaraEventPlayer::UFMODNiagaraEventPlayer(FObjectInitializer const& ObjectInitializer) + : Super(ObjectInitializer) +{ + EventToPlay = nullptr; + bLimitPlaysPerTick = true; + MaxPlaysPerTick = 10; +} + +#if WITH_EDITORONLY_DATA +bool UFMODNiagaraEventPlayer::UpgradeFunctionCall(FNiagaraFunctionSignature& FunctionSignature) +{ + // always upgrade to the latest version + if (FunctionSignature.FunctionVersion < FNiagaraAudioPlayerDIFunctionVersion::LatestVersion) + { + TArray AllFunctions; + GetFunctions(AllFunctions); + for (const FNiagaraFunctionSignature& Sig : AllFunctions) + { + if (FunctionSignature.Name == Sig.Name) + { + FunctionSignature = Sig; + return true; + } + } + } + + return false; +} +#endif + +void UFMODNiagaraEventPlayer::PostInitProperties() +{ + Super::PostInitProperties(); + + if (HasAnyFlags(RF_ClassDefaultObject)) + { + ENiagaraTypeRegistryFlags Flags = ENiagaraTypeRegistryFlags::AllowAnyVariable | ENiagaraTypeRegistryFlags::AllowParameter; + FNiagaraTypeRegistry::Register(FNiagaraTypeDefinition(GetClass()), Flags); + } +} + +bool UFMODNiagaraEventPlayer::InitPerInstanceData(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance) +{ + FEventPlayerInterface_InstanceData* PIData = new (PerInstanceData) FEventPlayerInterface_InstanceData; + PIData->LWCConverter = SystemInstance->GetLWCConverter(); + if (bLimitPlaysPerTick) + { + PIData->MaxPlaysPerTick = MaxPlaysPerTick; + } + PIData->bStopWhenComponentIsDestroyed = bStopWhenComponentIsDestroyed; +#if WITH_EDITORONLY_DATA + PIData->bOnlyActiveDuringGameplay = bOnlyActiveDuringGameplay; +#endif + return true; +} + +#if WITH_EDITOR +void UFMODNiagaraEventPlayer::PostEditChangeProperty(FPropertyChangedEvent& e) +{ + FName PropertyName = (e.Property != NULL) ? e.Property->GetFName() : NAME_None; + if (PropertyName == GET_MEMBER_NAME_CHECKED(UFMODNiagaraEventPlayer, EventToPlay)) + { + ParameterNames.Empty(); + bDefaultParameterValuesCached = false; + } + CacheDefaultParameterValues(); + + Super::PostEditChangeProperty(e); +} + +void UFMODNiagaraEventPlayer::CacheDefaultParameterValues() +{ + if (EventToPlay) + { + if (!bDefaultParameterValuesCached) + { + TArray ParameterDescriptions; + EventToPlay->GetParameterDescriptions(ParameterDescriptions); + for (const FMOD_STUDIO_PARAMETER_DESCRIPTION& ParameterDescription : ParameterDescriptions) + { + if (ShouldCacheParameter(ParameterDescription)) + { + ParameterNames.Add(ParameterDescription.name); + } + } + bDefaultParameterValuesCached = true; + } + } +} + +bool UFMODNiagaraEventPlayer::ShouldCacheParameter(const FMOD_STUDIO_PARAMETER_DESCRIPTION& ParameterDescription) +{ + const UFMODSettings& Settings = *GetDefault(); + + if (((ParameterDescription.flags & FMOD_STUDIO_PARAMETER_GLOBAL) == 0) && + (ParameterDescription.type == FMOD_STUDIO_PARAMETER_GAME_CONTROLLED) && + ParameterDescription.name != Settings.OcclusionParameter && + ParameterDescription.name != Settings.AmbientVolumeParameter && + ParameterDescription.name != Settings.AmbientLPFParameter) + { + return true; + } + return false; +} +#endif // WITH_EDITOR + +void UFMODNiagaraEventPlayer::DestroyPerInstanceData(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance) +{ + FEventPlayerInterface_InstanceData* InstData = (FEventPlayerInterface_InstanceData*)PerInstanceData; + for (const auto& Entry : InstData->PersistentAudioMapping) + { + if (Entry.Value.IsValid()) + { + Entry.Value->StudioInstance->stop(FMOD_STUDIO_STOP_IMMEDIATE); + } + } + InstData->~FEventPlayerInterface_InstanceData(); +} + +bool UFMODNiagaraEventPlayer::PerInstanceTick(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance, float DeltaSeconds) +{ + FEventPlayerInterface_InstanceData* PIData = (FEventPlayerInterface_InstanceData*)PerInstanceData; + if (!PIData) + { + return true; + } + + if (IsValid(EventToPlay) && SystemInstance) + { + PIData->EventToPlay = EventToPlay; + PIData->ParameterNames = ParameterNames; + } + else + { + PIData->EventToPlay.Reset(); + PIData->ParameterNames.Reset(); + } + + return false; +} + +bool UFMODNiagaraEventPlayer::PerInstanceTickPostSimulate(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance, float DeltaSeconds) +{ + FEventPlayerInterface_InstanceData* PIData = (FEventPlayerInterface_InstanceData*)PerInstanceData; + UNiagaraSystem* System = SystemInstance->GetSystem(); + UWorld* World = SystemInstance->GetWorld(); + +#if WITH_EDITORONLY_DATA + if (World->HasBegunPlay() == false && PIData->bOnlyActiveDuringGameplay) + { + PIData->PlayAudioQueue.Empty(); + PIData->PersistentAudioMapping.Empty(); + return false; + } +#endif + + if (!PIData->PlayAudioQueue.IsEmpty() && System) + { + //Drain the queue into an array here + TArray Data; + FEventParticleData Value; + while (PIData->PlayAudioQueue.Dequeue(Value)) + { + Data.Add(Value); + if (PIData->MaxPlaysPerTick > 0 && Data.Num() >= PIData->MaxPlaysPerTick) + { + // discard the rest of the queue if over the tick limit + PIData->PlayAudioQueue.Empty(); + break; + } + } + + if (World && PIData->EventToPlay.IsValid()) + { + for (const FEventParticleData& ParticleData : Data) + { + UFMODBlueprintStatics::PlayEventAtLocation(World, EventToPlay, FTransform(ParticleData.Rotation, ParticleData.Position), true); + } + } + } + + // process the persistent audio updates + FPersistentEventParticleData Value; + while (PIData->PersistentAudioActionQueue.Dequeue(Value)) + { + UFMODAudioComponent* AudioComponent = nullptr; + if (Value.AudioHandle > 0) + { + auto MappedValue = PIData->PersistentAudioMapping.Find(Value.AudioHandle); + if (MappedValue && MappedValue->IsValid()) + { + AudioComponent = MappedValue->Get(); + } + } + // since we are in the game thread here, it is safe for the callback to access the audio component + if (Value.UpdateCallback) + { + Value.UpdateCallback(PIData, AudioComponent, SystemInstance); + } + } + return false; +} + +bool UFMODNiagaraEventPlayer::Equals(const UNiagaraDataInterface* Other) const +{ + if (!Super::Equals(Other)) + { + return false; + } + + const UFMODNiagaraEventPlayer* OtherPlayer = CastChecked(Other); + return OtherPlayer->EventToPlay == EventToPlay && OtherPlayer->bLimitPlaysPerTick == bLimitPlaysPerTick && OtherPlayer->MaxPlaysPerTick == MaxPlaysPerTick; +} + +void UFMODNiagaraEventPlayer::GetFunctions(TArray& OutFunctions) +{ + FNiagaraFunctionSignature Sig; + Sig.Name = PlayAudioName; +#if WITH_EDITORONLY_DATA + Sig.Description = NSLOCTEXT("FMODStudio", "PlayEventFunctionDescription", "This function plays an event at the given location after the simulation has ticked."); + Sig.FunctionVersion = FNiagaraAudioPlayerDIFunctionVersion::LatestVersion; +#endif + Sig.bMemberFunction = true; + Sig.bRequiresContext = false; + Sig.bSupportsGPU = false; + Sig.bRequiresExecPin = true; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Event Player"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetBoolDef(), TEXT("Play Event"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetPositionDef(), TEXT("PositionWS"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("RotationWS"))); + Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetBoolDef(), TEXT("Success"))); + OutFunctions.Add(Sig); + + Sig = FNiagaraFunctionSignature(); + Sig.Name = PlayPersistentAudioName; +#if WITH_EDITORONLY_DATA + Sig.Description = NSLOCTEXT("FMODStudio", "PlayPersistentEventFunctionDescription", "This function plays an Event at the given location after the simulation has ticked. The returned handle can be used to control the sound in subsequent ticks."); + Sig.FunctionVersion = FNiagaraAudioPlayerDIFunctionVersion::LatestVersion; +#endif + Sig.bMemberFunction = true; + Sig.bRequiresContext = false; + Sig.bSupportsGPU = false; + Sig.bRequiresExecPin = true; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Event Player"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetBoolDef(), TEXT("Play Event"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Existing Event Handle"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetPositionDef(), TEXT("Position WS"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Rotation WS"))); + Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Event Handle"))); + OutFunctions.Add(Sig); + + Sig = FNiagaraFunctionSignature(); + Sig.Name = SetPersistentAudioFloatParamName; +#if WITH_EDITORONLY_DATA + Sig.Description = NSLOCTEXT("FMODStudio", "SetPersistentEventFloatParamFunctionDescription", "If an active event effect can be found for the given handle then the given sound cue parameter will be set on it."); + Sig.FunctionVersion = FNiagaraAudioPlayerDIFunctionVersion::LatestVersion; +#endif + Sig.bMemberFunction = true; + Sig.bRequiresContext = false; + Sig.bSupportsGPU = false; + Sig.bRequiresExecPin = true; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Event Player"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Event Handle"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Parameter Name Index"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Parameter Value"))); + OutFunctions.Add(Sig); + + Sig = FNiagaraFunctionSignature(); + Sig.Name = SetPersistentAudioLocationName; +#if WITH_EDITORONLY_DATA + Sig.Description = NSLOCTEXT("FMODStudio", "SetPersistentEventLocationFunctionDescription", "If an active event effect can be found for the given handle then the this will adjusts its world position."); + Sig.FunctionVersion = FNiagaraAudioPlayerDIFunctionVersion::LatestVersion; +#endif + Sig.bMemberFunction = true; + Sig.bRequiresContext = false; + Sig.bSupportsGPU = false; + Sig.bRequiresExecPin = true; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Event Player"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Event Handle"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetPositionDef(), TEXT("Position WS"))); + OutFunctions.Add(Sig); + + Sig = FNiagaraFunctionSignature(); + Sig.Name = SetPersistentAudioRotationName; +#if WITH_EDITORONLY_DATA + Sig.Description = NSLOCTEXT("FMODStudio", "SetPersistentEventRotationFunctionDescription", "If an active event effect can be found for the given handle then the this will adjusts its rotation in the world."); + Sig.FunctionVersion = FNiagaraAudioPlayerDIFunctionVersion::LatestVersion; +#endif + Sig.bMemberFunction = true; + Sig.bRequiresContext = false; + Sig.bSupportsGPU = false; + Sig.bRequiresExecPin = true; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Event Player"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Event Handle"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Rotation WS"))); + OutFunctions.Add(Sig); + + Sig = FNiagaraFunctionSignature(); + Sig.Name = PausePersistentAudioName; +#if WITH_EDITORONLY_DATA + Sig.Description = NSLOCTEXT("FMODStudio", "SetPersistentEventPausedDescription", "If an active event effect can be found for the given handle then the this will either pause or unpause the effect."); + Sig.FunctionVersion = FNiagaraAudioPlayerDIFunctionVersion::LatestVersion; +#endif + Sig.bMemberFunction = true; + Sig.bRequiresContext = false; + Sig.bSupportsGPU = false; + Sig.bRequiresExecPin = true; + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Event Player"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Event Handle"))); + Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetBoolDef(), TEXT("Pause Event"))); + OutFunctions.Add(Sig); +} + +DEFINE_NDI_DIRECT_FUNC_BINDER(UFMODNiagaraEventPlayer, PlayOneShotAudio); +DEFINE_NDI_DIRECT_FUNC_BINDER(UFMODNiagaraEventPlayer, PlayPersistentAudio); +DEFINE_NDI_DIRECT_FUNC_BINDER(UFMODNiagaraEventPlayer, SetParameterFloat); +DEFINE_NDI_DIRECT_FUNC_BINDER(UFMODNiagaraEventPlayer, UpdateLocation); +DEFINE_NDI_DIRECT_FUNC_BINDER(UFMODNiagaraEventPlayer, UpdateRotation); +DEFINE_NDI_DIRECT_FUNC_BINDER(UFMODNiagaraEventPlayer, SetPausedState); +void UFMODNiagaraEventPlayer::GetVMExternalFunction(const FVMExternalFunctionBindingInfo& BindingInfo, void* InstanceData, FVMExternalFunction& OutFunc) +{ + if (BindingInfo.Name == PlayAudioName) + { + NDI_FUNC_BINDER(UFMODNiagaraEventPlayer, PlayOneShotAudio)::Bind(this, OutFunc); + } + else if (BindingInfo.Name == PlayPersistentAudioName) + { + NDI_FUNC_BINDER(UFMODNiagaraEventPlayer, PlayPersistentAudio)::Bind(this, OutFunc); + } + else if (BindingInfo.Name == SetPersistentAudioFloatParamName) + { + NDI_FUNC_BINDER(UFMODNiagaraEventPlayer, SetParameterFloat)::Bind(this, OutFunc); + } + else if (BindingInfo.Name == SetPersistentAudioLocationName) + { + NDI_FUNC_BINDER(UFMODNiagaraEventPlayer, UpdateLocation)::Bind(this, OutFunc); + } + else if (BindingInfo.Name == SetPersistentAudioRotationName) + { + NDI_FUNC_BINDER(UFMODNiagaraEventPlayer, UpdateRotation)::Bind(this, OutFunc); + } + else if (BindingInfo.Name == PausePersistentAudioName) + { + NDI_FUNC_BINDER(UFMODNiagaraEventPlayer, SetPausedState)::Bind(this, OutFunc); + } + else + { + UE_LOG(LogFMODNiagara, Error, TEXT("Could not find event player external function. Expected Name: %s Actual Name: %s"), *PlayAudioName.ToString(), *BindingInfo.Name.ToString()); + } +} + +void UFMODNiagaraEventPlayer::SetParameterFloat(FVectorVMExternalFunctionContext& Context) +{ + VectorVM::FUserPtrHandler InstData(Context); + + FNDIInputParam AudioHandleInParam(Context); + FNDIInputParam NameIndexParam(Context); + FNDIInputParam ValueParam(Context); + checkfSlow(InstData.Get(), TEXT("Event player has invalid instance data. %s"), *GetPathName()); + + for (int32 i = 0; i < Context.GetNumInstances(); ++i) + { + int32 Handle = AudioHandleInParam.GetAndAdvance(); + int32 NameIndex = NameIndexParam.GetAndAdvance(); + float Value = ValueParam.GetAndAdvance(); + + if (Handle > 0 && InstData->ParameterNames.IsValidIndex(NameIndex)) + { + FName ParameterName = InstData->ParameterNames[NameIndex]; + FPersistentEventParticleData AudioData; + AudioData.AudioHandle = Handle; + AudioData.UpdateCallback = [ParameterName, Value](FEventPlayerInterface_InstanceData*, UFMODAudioComponent* AudioComponent, FNiagaraSystemInstance*) + { + if (AudioComponent && AudioComponent->IsPlaying()) + { + AudioComponent->SetParameter(ParameterName, Value); + } + }; + InstData->PersistentAudioActionQueue.Enqueue(AudioData); + } + } +} + +void UFMODNiagaraEventPlayer::UpdateLocation(FVectorVMExternalFunctionContext& Context) +{ + VectorVM::FUserPtrHandler InstData(Context); + + FNDIInputParam AudioHandleInParam(Context); + FNDIInputParam LocationParam(Context); + checkfSlow(InstData.Get(), TEXT("Audio player interface has invalid instance data. %s"), *GetPathName()); + + for (int32 i = 0; i < Context.GetNumInstances(); ++i) + { + int32 Handle = AudioHandleInParam.GetAndAdvance(); + FVector Location = InstData->LWCConverter.ConvertSimulationVectorToWorld(LocationParam.GetAndAdvance()); + + if (Handle > 0) + { + FPersistentEventParticleData AudioData; + AudioData.AudioHandle = Handle; + AudioData.UpdateCallback = [Location](FEventPlayerInterface_InstanceData*, UFMODAudioComponent* AudioComponent, FNiagaraSystemInstance*) + { + if (AudioComponent && AudioComponent->IsPlaying()) + { + AudioComponent->SetWorldLocation(Location); + } + }; + InstData->PersistentAudioActionQueue.Enqueue(AudioData); + } + } +} + +void UFMODNiagaraEventPlayer::UpdateRotation(FVectorVMExternalFunctionContext& Context) +{ + VectorVM::FUserPtrHandler InstData(Context); + + FNDIInputParam AudioHandleInParam(Context); + FNDIInputParam RotationParam(Context); + checkfSlow(InstData.Get(), TEXT("Event player has invalid instance data. %s"), *GetPathName()); + + for (int32 i = 0; i < Context.GetNumInstances(); ++i) + { + int32 Handle = AudioHandleInParam.GetAndAdvance(); + FVector3f Rotation = RotationParam.GetAndAdvance(); + + if (Handle > 0) + { + FPersistentEventParticleData AudioData; + AudioData.AudioHandle = Handle; + AudioData.UpdateCallback = [Rotation](FEventPlayerInterface_InstanceData*, UFMODAudioComponent* AudioComponent, FNiagaraSystemInstance*) + { + if (AudioComponent && AudioComponent->IsPlaying()) + { + FRotator NewRotator(Rotation.X, Rotation.Y, Rotation.Z); + AudioComponent->SetWorldRotation(NewRotator); + } + }; + InstData->PersistentAudioActionQueue.Enqueue(AudioData); + } + } +} + +void UFMODNiagaraEventPlayer::SetPausedState(FVectorVMExternalFunctionContext& Context) +{ + VectorVM::FUserPtrHandler InstData(Context); + + FNDIInputParam AudioHandleInParam(Context); + FNDIInputParam PausedParam(Context); + checkfSlow(InstData.Get(), TEXT("Event player has invalid instance data. %s"), *GetPathName()); + + for (int32 i = 0; i < Context.GetNumInstances(); ++i) + { + int32 Handle = AudioHandleInParam.GetAndAdvance(); + bool IsPaused = PausedParam.GetAndAdvance(); + + if (Handle > 0) + { + FPersistentEventParticleData AudioData; + AudioData.AudioHandle = Handle; + AudioData.UpdateCallback = [IsPaused](FEventPlayerInterface_InstanceData*, UFMODAudioComponent* AudioComponent, FNiagaraSystemInstance*) + { + if (AudioComponent) + { + AudioComponent->SetPaused(IsPaused); + } + }; + InstData->PersistentAudioActionQueue.Enqueue(AudioData); + } + } +} + +void UFMODNiagaraEventPlayer::PlayOneShotAudio(FVectorVMExternalFunctionContext& Context) +{ + VectorVM::FUserPtrHandler InstData(Context); + + VectorVM::FExternalFuncInputHandler PlayDataParam(Context); + + VectorVM::FExternalFuncInputHandler PositionParamX(Context); + VectorVM::FExternalFuncInputHandler PositionParamY(Context); + VectorVM::FExternalFuncInputHandler PositionParamZ(Context); + + VectorVM::FExternalFuncInputHandler RotationParamX(Context); + VectorVM::FExternalFuncInputHandler RotationParamY(Context); + VectorVM::FExternalFuncInputHandler RotationParamZ(Context); + + VectorVM::FExternalFuncRegisterHandler OutSample(Context); + + checkfSlow(InstData.Get(), TEXT("Event player has invalid instance data. %s"), *GetPathName()); + bool ValidSoundData = InstData->EventToPlay.IsValid(); + + for (int32 i = 0; i < Context.GetNumInstances(); ++i) + { + FNiagaraBool ShouldPlay = PlayDataParam.GetAndAdvance(); + FEventParticleData Data; + FNiagaraPosition SimulationPosition(PositionParamX.GetAndAdvance(), PositionParamY.GetAndAdvance(), PositionParamZ.GetAndAdvance()); + Data.Position = InstData->LWCConverter.ConvertSimulationPositionToWorld(SimulationPosition); + Data.Rotation = FRotator(RotationParamX.GetAndAdvance(), RotationParamY.GetAndAdvance(), RotationParamZ.GetAndAdvance()); + + FNiagaraBool Valid; + if (ValidSoundData && ShouldPlay) + { + Valid.SetValue(InstData->PlayAudioQueue.Enqueue(Data)); + } + *OutSample.GetDestAndAdvance() = Valid; + } +} + +void UFMODNiagaraEventPlayer::PlayPersistentAudio(FVectorVMExternalFunctionContext& Context) +{ + VectorVM::FUserPtrHandler InstData(Context); + + FNDIInputParam PlayAudioParam(Context); + FNDIInputParam AudioHandleInParam(Context); + FNDIInputParam PositionParam(Context); + FNDIInputParam RotationParam(Context); + + FNDIOutputParam AudioHandleOutParam(Context); + + checkfSlow(InstData.Get(), TEXT("Event player has invalid instance data. %s"), *GetPathName()); + + for (int32 i = 0; i < Context.GetNumInstances(); ++i) + { + bool ShouldPlay = PlayAudioParam.GetAndAdvance(); + int32 Handle = AudioHandleInParam.GetAndAdvance(); + FVector Position = InstData->LWCConverter.ConvertSimulationVectorToWorld(PositionParam.GetAndAdvance()); + FVector3f InRot = RotationParam.GetAndAdvance(); + FRotator Rotation = FRotator(InRot.X, InRot.Y, InRot.Z); + + FPersistentEventParticleData AudioData; + if (ShouldPlay) + { + if (Handle <= 0) + { + // play a new sound + Handle = InstData->HandleCount.Increment(); + AudioData.AudioHandle = Handle; + AudioData.UpdateCallback = [Handle, Position, Rotation](FEventPlayerInterface_InstanceData* InstanceData, UFMODAudioComponent*, FNiagaraSystemInstance* SystemInstance) + { + USceneComponent* NiagaraComponent = SystemInstance->GetAttachComponent(); + TWeakObjectPtr Sound = InstanceData->EventToPlay; + if (NiagaraComponent && Sound.IsValid()) + { + bool bStopWithEffect = InstanceData->bStopWhenComponentIsDestroyed; + TWeakObjectPtr AudioComponent = UFMODBlueprintStatics::PlayEventAttached(Sound.Get(), NiagaraComponent, NAME_None, Position, EAttachLocation::KeepWorldPosition, bStopWithEffect, true, true); + + AudioComponent->Play(); + + InstanceData->PersistentAudioMapping.Add(Handle, AudioComponent); + } + }; + InstData->PersistentAudioActionQueue.Enqueue(AudioData); + } + AudioHandleOutParam.SetAndAdvance(Handle); + continue; + } + + if (Handle > 0) + { + AudioData.AudioHandle = Handle; + + InstData->PersistentAudioActionQueue.Enqueue(AudioData); + } + AudioHandleOutParam.SetAndAdvance(0); + } +} + +bool UFMODNiagaraEventPlayer::CopyToInternal(UNiagaraDataInterface* Destination) const +{ + if (!Super::CopyToInternal(Destination)) + { + return false; + } + + UFMODNiagaraEventPlayer* OtherTyped = CastChecked(Destination); + OtherTyped->EventToPlay = EventToPlay; + OtherTyped->bLimitPlaysPerTick = bLimitPlaysPerTick; + OtherTyped->MaxPlaysPerTick = MaxPlaysPerTick; + OtherTyped->ParameterNames = ParameterNames; + OtherTyped->bStopWhenComponentIsDestroyed = bStopWhenComponentIsDestroyed; + +#if WITH_EDITORONLY_DATA + OtherTyped->bOnlyActiveDuringGameplay = bOnlyActiveDuringGameplay; +#endif + return true; +} diff --git a/hwanyoung2/Plugins/FMODStudioNiagara/Source/FMODStudioNiagara/Private/FMODStudioNiagara.cpp b/hwanyoung2/Plugins/FMODStudioNiagara/Source/FMODStudioNiagara/Private/FMODStudioNiagara.cpp new file mode 100644 index 00000000..0271e04a --- /dev/null +++ b/hwanyoung2/Plugins/FMODStudioNiagara/Source/FMODStudioNiagara/Private/FMODStudioNiagara.cpp @@ -0,0 +1,20 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "FMODStudioNiagara.h" +#include "Misc/Paths.h" +#include "Interfaces/IPluginManager.h" + +#define LOCTEXT_NAMESPACE "FFMODStudioNiagaraModule" + +void FFMODStudioNiagaraModule::StartupModule() +{ + IPluginManager::Get().FindPlugin(TEXT("FMODStudioNiagara"))->GetBaseDir(); +} + +void FFMODStudioNiagaraModule::ShutdownModule() +{ +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FFMODStudioNiagaraModule, FMODStudioNiagara) \ No newline at end of file diff --git a/hwanyoung2/Plugins/FMODStudioNiagara/Source/FMODStudioNiagara/Public/FMODNiagaraEventPlayer.h b/hwanyoung2/Plugins/FMODStudioNiagara/Source/FMODStudioNiagara/Public/FMODNiagaraEventPlayer.h new file mode 100644 index 00000000..afa95c98 --- /dev/null +++ b/hwanyoung2/Plugins/FMODStudioNiagara/Source/FMODStudioNiagara/Public/FMODNiagaraEventPlayer.h @@ -0,0 +1,135 @@ +#pragma once + +#include "FMODEvent.h" +#include "FMODAudioComponent.h" + +#include "NiagaraCommon.h" +#include "NiagaraDataInterface.h" +#include "NiagaraShared.h" +#include "NiagaraSystem.h" + +#include "Sound/SoundAttenuation.h" + +#include "FMODStudioNiagara.h" +#include "FMODNiagaraEventPlayer.generated.h" + +struct FEventParticleData +{ + FVector Position; + FRotator Rotation; +}; + +struct FPersistentEventParticleData +{ + int32 AudioHandle = 0; + + /** The update callback is executed in PerInstanceTickPostSimulate, which runs on the game thread */ + TFunction UpdateCallback; +}; + +struct FEventPlayerInterface_InstanceData +{ + /** We use a lock-free queue here because multiple threads might try to push data to it at the same time. */ + TQueue PlayAudioQueue; + TQueue PersistentAudioActionQueue; + FThreadSafeCounter HandleCount; + + TSortedMap> PersistentAudioMapping; + + TWeakObjectPtr EventToPlay; + TArray ParameterNames; + + FNiagaraLWCConverter LWCConverter; + int32 MaxPlaysPerTick = 0; + bool bStopWhenComponentIsDestroyed = true; + +#if WITH_EDITORONLY_DATA + bool bOnlyActiveDuringGameplay = true; +#endif +}; + +/** This Data Interface can be used to play one-shot audio effects driven by particle data. */ +UCLASS(EditInlineNew, Category = "Audio", meta = (DisplayName = "FMOD Event Player")) +class FMODSTUDIONIAGARA_API UFMODNiagaraEventPlayer : public UNiagaraDataInterface +{ + GENERATED_UCLASS_BODY() + +public: + /** Reference to the audio asset to play */ + UPROPERTY(EditAnywhere, Category = "Audio") + UFMODEvent* EventToPlay; + + /** A set of parameter names that can be referenced via index when setting sound cue parameters on persistent audio */ + UPROPERTY(VisibleAnywhere, NoClear, Category = "Parameters") + TArray ParameterNames; + + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "Audio", meta = (InlineEditConditionToggle)) + bool bLimitPlaysPerTick; + + /** This sets the max number of sounds played each tick. + * If more particles try to play a sound in a given tick, then it will play sounds until the limit is reached and discard the rest. + * The particles to discard when over the limit are *not* chosen in a deterministic way. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "Audio", meta = (EditCondition = "bLimitPlaysPerTick", ClampMin = "0", UIMin = "0")) + int32 MaxPlaysPerTick; + + /** If false then it the audio component keeps playing after the niagara component was destroyed. Looping sounds are always stopped when the component is destroyed. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "Audio") + bool bStopWhenComponentIsDestroyed = true; + +#if WITH_EDITORONLY_DATA + /** If true then this data interface only processes sounds during active gameplay. This is useful when you are working in the preview window and the sounds annoy you. + * Currently the FMOD Niagara integration will not play in Editor. + */ + UPROPERTY(VisibleAnywhere, AdvancedDisplay, Category = "Audio") + bool bOnlyActiveDuringGameplay = true; + + virtual bool UpgradeFunctionCall(FNiagaraFunctionSignature& FunctionSignature) override; +#endif + + //UObject Interface + virtual void PostInitProperties() override; +#if WITH_EDITOR + virtual void PostEditChangeProperty(FPropertyChangedEvent& e); +#endif + //UObject Interface End + + //UNiagaraDataInterface Interface + virtual void GetFunctions(TArray& OutFunctions) override; + virtual void GetVMExternalFunction(const FVMExternalFunctionBindingInfo& BindingInfo, void* InstanceData, FVMExternalFunction& OutFunc) override; + virtual bool InitPerInstanceData(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance) override; + virtual void DestroyPerInstanceData(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance) override; + virtual int32 PerInstanceDataSize() const override { return sizeof(FEventPlayerInterface_InstanceData); } + virtual bool PerInstanceTick(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance, float DeltaSeconds) override; + virtual bool PerInstanceTickPostSimulate(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance, float DeltaSeconds) override; + virtual bool Equals(const UNiagaraDataInterface* Other) const override; + virtual bool CanExecuteOnTarget(ENiagaraSimTarget Target) const override { return Target == ENiagaraSimTarget::CPUSim; } + + virtual bool HasPreSimulateTick() const override { return true; } + virtual bool HasPostSimulateTick() const override { return true; } + virtual bool PostSimulateCanOverlapFrames() const { return false; } + //UNiagaraDataInterface Interface + + virtual void PlayOneShotAudio(FVectorVMExternalFunctionContext& Context); + virtual void PlayPersistentAudio(FVectorVMExternalFunctionContext& Context); + virtual void SetParameterFloat(FVectorVMExternalFunctionContext& Context); + virtual void UpdateLocation(FVectorVMExternalFunctionContext& Context); + virtual void UpdateRotation(FVectorVMExternalFunctionContext& Context); + virtual void SetPausedState(FVectorVMExternalFunctionContext& Context); + +#if WITH_EDITOR + void CacheDefaultParameterValues(); + bool ShouldCacheParameter(const FMOD_STUDIO_PARAMETER_DESCRIPTION& ParameterDescription); + bool bDefaultParameterValuesCached; +#endif + +protected: + virtual bool CopyToInternal(UNiagaraDataInterface* Destination) const override; + +private: + static const FName PlayAudioName; + static const FName PlayPersistentAudioName; + static const FName SetPersistentAudioLocationName; + static const FName SetPersistentAudioRotationName; + static const FName SetPersistentAudioFloatParamName; + static const FName PausePersistentAudioName; +}; \ No newline at end of file diff --git a/hwanyoung2/Plugins/FMODStudioNiagara/Source/FMODStudioNiagara/Public/FMODStudioNiagara.h b/hwanyoung2/Plugins/FMODStudioNiagara/Source/FMODStudioNiagara/Public/FMODStudioNiagara.h new file mode 100644 index 00000000..66b57bb5 --- /dev/null +++ b/hwanyoung2/Plugins/FMODStudioNiagara/Source/FMODStudioNiagara/Public/FMODStudioNiagara.h @@ -0,0 +1,15 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class FFMODStudioNiagaraModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +};