So I have a timed particle effect in my animation. It works great. However, I want to switch it out for a different particle effect when the character has a certain weapon. Rather than copying/pasting the same animation and switching out the particle effect, is there a way I can switch out the particle effect in my anim bp or in C++ ? (either one works for me). I’m sure this is doable, but I just can’t find anything out there on it.
Hopefully this helps someone! I ended up discovering the answer to this question. I first decided to override the UAnimNotifyState_TimedParticleEffect class. However, there were some compile issues with that. Subsequently, I created a Custom_TimedNotify class and copied the exact same code from UAnimNotifyState_TimedParticleEffect except for the places that were not compiling. IT was just debug stuff that wasn’t compiling well. Specifically this location of code:
#if WITH_EDITORONLY_DATA
// The following arrays are used to handle property changes during a state. Because we can't
// store any stateful data here we can't know which emitter is ours. The best metric we have
// is an emitter on our Mesh Component with the same template and socket name we have defined.
// Because these can change at any time we need to track previous versions when we are in an
// editor build. Refactor when stateful data is possible, tracking our component instead.
UPROPERTY(transient)
TArray<UParticleSystem*> PreviousPSTemplates;
UPROPERTY(transient)
TArray<FName> PreviousSocketNames;
#endif
No matter. I ended up creating my own class and then adding my own code into the notify begin area.
// Fill out your copyright notice in the Description page of Project Settings.
#include "ShooterGame.h"
#include "Custom_TimedNotify.h"
#include "ShooterCharacter.h"
#include "ShooterWeapon.h"
#include "Components/SkeletalMeshComponent.h"
#include "Particles/ParticleSystemComponent.h"
#include "Kismet/GameplayStatics.h"
UCustom_TimedNotify::UCustom_TimedNotify(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
PSTemplate = nullptr;
LocationOffset.Set(0.0f, 0.0f, 0.0f);
RotationOffset = FRotator(0.0f, 0.0f, 0.0f);
}
void UCustom_TimedNotify::ClearEffects()
{
PSTemplate = NULL;
SocketName = "";
}
void UCustom_TimedNotify::SetParticleEffect(UParticleSystem* particle)
{
PSTemplate = particle;
}
void UCustom_TimedNotify::SetAttachPoint(FName socket)
{
SocketName = socket;
}
void UCustom_TimedNotify::NotifyBegin(USkeletalMeshComponent * MeshComp, class UAnimSequenceBase * Animation, float TotalDuration)
{
/* Start of custom code to grab the right weapon effects and spawn them in the proper location */
AShooterCharacter* MyOwner = Cast<AShooterCharacter>(MeshComp->GetOwner());
if (MyOwner)
{
AShooterWeapon* theweapon = Cast<AShooterWeapon>(MyOwner->GetWeapon());
if (theweapon)
{
SocketName = theweapon->GetMuzzleAttachPoint();
PSTemplate = theweapon->GetMuzzleFX();
}
}
/* End of custom code to spawn my own effects in the proper location */
// Only spawn if we've got valid params
if (ValidateParameters(MeshComp))
{
UParticleSystemComponent* NewComponent = UGameplayStatics::SpawnEmitterAttached(PSTemplate, MeshComp, SocketName, LocationOffset, RotationOffset);
}
Received_NotifyBegin(MeshComp, Animation, TotalDuration);
}
void UCustom_TimedNotify::NotifyTick(USkeletalMeshComponent * MeshComp, class UAnimSequenceBase * Animation, float FrameDeltaTime)
{
Received_NotifyTick(MeshComp, Animation, FrameDeltaTime);
}
void UCustom_TimedNotify::NotifyEnd(USkeletalMeshComponent * MeshComp, class UAnimSequenceBase * Animation)
{
TArray<USceneComponent*> Children;
MeshComp->GetChildrenComponents(false, Children);
for (USceneComponent* Component : Children)
{
if (UParticleSystemComponent* ParticleComponent = Cast<UParticleSystemComponent>(Component))
{
bool bSocketMatch = ParticleComponent->GetAttachSocketName() == SocketName;
bool bTemplateMatch = ParticleComponent->Template == PSTemplate;
if (bSocketMatch && bTemplateMatch && !ParticleComponent->bWasDeactivated)
{
// Either destroy the component or deactivate it to have it's active particles finish.
// The component will auto destroy once all particle are gone.
if (bDestroyAtEnd)
{
ParticleComponent->DestroyComponent();
}
else
{
ParticleComponent->bAutoDestroy = true;
ParticleComponent->DeactivateSystem();
}
// Removed a component, no need to continue
break;
}
}
}
Received_NotifyEnd(MeshComp, Animation);
}
bool UCustom_TimedNotify::ValidateParameters(USkeletalMeshComponent* MeshComp)
{
bool bValid = true;
if (!PSTemplate)
{
bValid = false;
}
else if (!MeshComp->DoesSocketExist(SocketName) && MeshComp->GetBoneIndex(SocketName) == INDEX_NONE)
{
bValid = false;
}
return bValid;
}
FString UCustom_TimedNotify::GetNotifyName_Implementation() const
{
if (PSTemplate)
{
return PSTemplate->GetName();
}
return UAnimNotifyState::GetNotifyName_Implementation();
}
Then voila, you get your own custom notify class when editing your animation!