From 6ec77258e3084dd2ced39909e816ef5a6838fbf0 Mon Sep 17 00:00:00 2001 From: Ji Yoon Rhee Date: Sun, 2 Feb 2025 00:17:13 +0900 Subject: [PATCH] newly added updated FMODStudio plugin for migration pt 2 --- .../Content/ENiagara_FMODParamType.uasset | Bin 0 -> 129 bytes .../Content/PlayFMODEvent.uasset | Bin 0 -> 131 bytes .../Content/PlayPersistentFMODEvent.uasset | Bin 0 -> 131 bytes .../Templates/PlayFMODEventEmitter.uasset | Bin 0 -> 131 bytes .../Content/UpdatePersistentFMODEvent.uasset | Bin 0 -> 130 bytes .../FMODStudioNiagara.uplugin | 32 + .../FMODStudioNiagara.Build.cs | 37 ++ .../Private/FMODNiagaraEventPlayer.cpp | 620 ++++++++++++++++++ .../Private/FMODStudioNiagara.cpp | 20 + .../Public/FMODNiagaraEventPlayer.h | 135 ++++ .../Public/FMODStudioNiagara.h | 15 + 11 files changed, 859 insertions(+) create mode 100644 hwanyoung2/Plugins/FMODStudioNiagara/Content/ENiagara_FMODParamType.uasset create mode 100644 hwanyoung2/Plugins/FMODStudioNiagara/Content/PlayFMODEvent.uasset create mode 100644 hwanyoung2/Plugins/FMODStudioNiagara/Content/PlayPersistentFMODEvent.uasset create mode 100644 hwanyoung2/Plugins/FMODStudioNiagara/Content/Templates/PlayFMODEventEmitter.uasset create mode 100644 hwanyoung2/Plugins/FMODStudioNiagara/Content/UpdatePersistentFMODEvent.uasset create mode 100644 hwanyoung2/Plugins/FMODStudioNiagara/FMODStudioNiagara.uplugin create mode 100644 hwanyoung2/Plugins/FMODStudioNiagara/Source/FMODStudioNiagara/FMODStudioNiagara.Build.cs create mode 100644 hwanyoung2/Plugins/FMODStudioNiagara/Source/FMODStudioNiagara/Private/FMODNiagaraEventPlayer.cpp create mode 100644 hwanyoung2/Plugins/FMODStudioNiagara/Source/FMODStudioNiagara/Private/FMODStudioNiagara.cpp create mode 100644 hwanyoung2/Plugins/FMODStudioNiagara/Source/FMODStudioNiagara/Public/FMODNiagaraEventPlayer.h create mode 100644 hwanyoung2/Plugins/FMODStudioNiagara/Source/FMODStudioNiagara/Public/FMODStudioNiagara.h diff --git a/hwanyoung2/Plugins/FMODStudioNiagara/Content/ENiagara_FMODParamType.uasset b/hwanyoung2/Plugins/FMODStudioNiagara/Content/ENiagara_FMODParamType.uasset new file mode 100644 index 0000000000000000000000000000000000000000..70bc54e754de7d6e69e60ef7d5730795b7054e7b GIT binary patch literal 129 zcmWm4!4bkB5CFhGRnUL|!Vw_dz;OsODjARDVfE^3KYP)e`*_JV=fS&D_dair%FEAo z<^{)7@j<9ABStThTUy<=lXbpksJ;Z_gTbV2LZg8Ivabkn1Bh#=Kn`=P+7u|!BTy;- M3xfNHq}9QRFUf5unE(I) literal 0 HcmV?d00001 diff --git a/hwanyoung2/Plugins/FMODStudioNiagara/Content/PlayFMODEvent.uasset b/hwanyoung2/Plugins/FMODStudioNiagara/Content/PlayFMODEvent.uasset new file mode 100644 index 0000000000000000000000000000000000000000..214cbe3c7ea3f6f7bfc4d53d38e57f479625f8f1 GIT binary patch literal 131 zcmWN?K@!3s3;@78uiyigCYk{K4S`gcQRxWw;OliSd+K|%{<3w>V{Fdd+q^vXvHY(e zS#p26acFiYnBIyVH6ig9)gxlym_ke|2T&ppsF_fN*+WiAs0dy&mmLLfuxc(8J%Iof MGul@zcp_x;1NImvWB>pF literal 0 HcmV?d00001 diff --git a/hwanyoung2/Plugins/FMODStudioNiagara/Content/PlayPersistentFMODEvent.uasset b/hwanyoung2/Plugins/FMODStudioNiagara/Content/PlayPersistentFMODEvent.uasset new file mode 100644 index 0000000000000000000000000000000000000000..8052081343a5ae63a074e51367d2197d9ae757d7 GIT binary patch literal 131 zcmWN?K@!3s3;@78uiyg~!T^Q-CIlkPsB{eW;OliSd$qTW`Oe1%y$zxls z-@KLnQsbneE+cwFIcl=bA3GUmJX@la!8+y?0a4$q>bP;h(8~lC@}y4 literal 0 HcmV?d00001 diff --git a/hwanyoung2/Plugins/FMODStudioNiagara/Content/UpdatePersistentFMODEvent.uasset b/hwanyoung2/Plugins/FMODStudioNiagara/Content/UpdatePersistentFMODEvent.uasset new file mode 100644 index 0000000000000000000000000000000000000000..dab8830ff6feadb117ff66fd12abd989a27a3046 GIT binary patch literal 130 zcmWN^%MHUI3;@tOQ?Nh-1Y$$B;bRI?TcVoe(CM4g)4S-)eSBn_^Wa^nXP=K(<>j`W zY02ZQ_@JyVBStThTU 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; +};