I’ve been attempting to add an explosion to my projectiles in my FPS game that spawn when a projectile hits any other object and should trigger OnHits or OnBeginOverlaps with their SphereComponents to then inflict damage upon Targets in range, but this does not seem to be the case…
AExplosion::AExplosion()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don’t need it.
PrimaryActorTick.bCanEverTick = true;
// create the sphere component
CollisionComponent = CreateDefaultSubobject(TEXT(“SphereComponent”));
// Set the sphere’s collision profile name to “Projectile”
CollisionComponent->BodyInstance.SetCollisionProfileName(TEXT(“Explosion”));
// Set sphere’s radius equal to the member var
CollisionComponent->InitSphereRadius(Radius);
// set collision component as root
RootComponent = CollisionComponent;
// start the particle effects
ExplosionEffect = CreateDefaultSubobject(TEXT(“ParticleSystemComponent”));
ExplosionEffect->SetupAttachment(RootComponent);
if (ExplosionEffect) {
ExplosionEffect->Activate();
}
}
// Called when the game starts or when spawned
void AExplosion::BeginPlay()
{
Super::BeginPlay();
// make a timer that will destroy this explosion after the ammount of time specified by the member var passes
GetWorldTimerManager().SetTimer(ExplosionTimer, this, &AExplosion::EndExplosion, Duration, false);
}
void AExplosion::EndExplosion()
{
Destroy();
}
// Called every frame
void AExplosion::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
Projectile.cpp/OnHit
// add impulse to physicis simulating objects
if (OtherActor != this && OtherComponent->IsSimulatingPhysics())
{
OtherComponent->AddImpulseAtLocation(ProjectileMovementComponent->Velocity * 100.0f, Hit.ImpactPoint);
}
// destroy this projectile
Destroy();
// spawn explosion if one has been attached to this object
if (ProjectileExplosion) {
if (GEngine) {
// Display a debug message for five seconds
// The -1 “Key” value argument prevents the message from being updated or refreshed
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow, FString::Printf(TEXT(“Spawn X: %f, Spawn Y: %f, Spawn Z: %f”), CollisionComponent->GetComponentLocation().X, CollisionComponent->GetComponentLocation().Y, CollisionComponent->GetComponentLocation().Z));
}
GetWorld()->SpawnActor(ProjectileExplosion, CollisionComponent->GetComponentLocation(), CollisionComponent->GetComponentRotation());
}
Target.cpp/OnBeginOverlap
if (OtherActor->GetClass()->IsChildOf(AExplosion::StaticClass())) {
// debug logging Explosion hit
if (GEngine) {
// Display a debug message for five seconds
// The -1 “Key” value argument prevents the message from being updated or refreshed
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Green, TEXT(“Target hit by Explosion”));
}
AExplosion* HitExplosion = static_cast<AExplosion*>(OtherActor);
Health -= HitExplosion->Damage * (GetDistanceTo(HitExplosion) / HitExplosion->Radius);
}
// destroy this object if it’s health has reached 0
if (Health <= 0) {
// debug logging Target destruction
if (GEngine) {
// Display a debug message for five seconds
// The -1 “Key” value argument prevents the message from being updated or refreshed
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Green, TEXT(“Target destroyed”));
}
// destroy the Target
Destroy();
}
The same code is also in OnHit for Target but neither fires. I’ve set the Radius variable in my blueprint to max and still nothing, and I can see the effects going off when I test fire, so I am at a loss here. Any ideas?
Shouldn’t the Target class having its OnHit and OnOverlap dynamics set up be enough? Damage is handled in there by targets, so Explosions don’t have anything to do in their own OnHit/OnOverlap events. I added them and added the dynamic setup in the constructor for Explosions per your suggestion to test it out regardless, but they still don’t register with targets that should be overlapping… Debug logging the Explosion’s OnHit and OnOverlap shows that no overlap events actually trigger, and OnHit only fires when an Explosion hits…another explosion. Double checked that they’re set to generate overlap events in the blueprint and they are, so…what the heck is up with that?
@Dante5050 No, without the bind dynamic factor they are just unused methods.
edit: You are just preparing the function to have the same inputs as the bind function (you can call it what you want) You need to bind a function with the correct inputs & return value. Unreal does not look at the name of the function (it is not an override of an existing virtual function)
It’s just weird because I can remove the OnComponentHit setup for the Projectile class that would be spawning the explosions and those still will trigger OnHits for the Target class.
In any case, the explosions aren’t triggering any of the collision callbacks even with the dynamics hooked up properly. Here’s what Explosion.cpp looks like now:
#include "Explosion.h"
AExplosion::AExplosion()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
// create the sphere component
CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent"));
// Set the sphere's collision profile name to "Projectile"
CollisionComponent->BodyInstance.SetCollisionProfileName(TEXT("Explosion"));
// Set sphere's radius equal to the member var
CollisionComponent->InitSphereRadius(Radius);
// set collision component as root
RootComponent = CollisionComponent;
// start the particle effects
ExplosionEffect = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("ParticleSystemComponent"));
ExplosionEffect->SetupAttachment(RootComponent);
if (ExplosionEffect) {
ExplosionEffect->Activate();
}
}
// Called when the game starts or when spawned
void AExplosion::BeginPlay()
{
Super::BeginPlay();
// set up collision handling
CollisionComponent->OnComponentHit.AddDynamic(this, &AExplosion::OnHit);
CollisionComponent->OnComponentBeginOverlap.AddDynamic(this, &AExplosion::OnOverlap);
// make a timer that will destroy this explosion after the ammount of time specified by the member var passes
GetWorldTimerManager().SetTimer(ExplosionTimer, this, &AExplosion::EndExplosion, Duration, false);
}
void AExplosion::EndExplosion()
{
Destroy();
}
// Called every frame
void AExplosion::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void AExplosion::OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComponent, FVector NormalImpulse, const FHitResult& Hit)
{
// debug logging Explosion hit
if (GEngine) {
// Display a debug message for five seconds
// The -1 "Key" value argument prevents the message from being updated or refreshed
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Green, TEXT("Explosion Hit!"));
}
EndExplosion();
}
void AExplosion::OnOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
// debug logging Explosion hit
if (GEngine) {
// Display a debug message for five seconds
// The -1 "Key" value argument prevents the message from being updated or refreshed
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Green, TEXT("Explosion overlap!"));
}
EndExplosion();
}
Is there something I’m not considering in regards to spawning an object with collision already overlapping other items? I couldn’t find anything detailing how to handle that so I assumed collision events would fire the moment the object existed in the world but maybe there’s more to it than that?
BodyInstance comes from the FPS coding tutorial I was following to initially set up the standard projectiles ( 3 - Implementing Projectiles | Unreal Engine 4.27 Documentation ), I think it was working for those because overlapping wasn’t necessary at the time. The collision profile I did not actually have set up (oops!) but even when I swap it to the Projectile profile that was already set, the events aren’t firing.
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Explosion.generated.h"
UCLASS()
class TIMERTEST_API AExplosion : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AExplosion();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
class USphereComponent* CollisionComponent;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
float Radius = 400;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
float Duration = 4;
FTimerHandle ExplosionTimer;
class UParticleSystemComponent* ExplosionEffect;
UFUNCTION()
void OnOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
UFUNCTION()
void OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComponent, FVector NormalImpulse, const FHitResult& Hit);
void EndExplosion();
};
Cpp
#include "Explosion.h"
#include "Components/SphereComponent.h"
#include "Particles/ParticleSystemComponent.h"
AExplosion::AExplosion()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
// create the sphere component
CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent"));
// Set the sphere's collision profile name to "Projectile"
CollisionComponent->SetCollisionProfileName(TEXT("Explosion"),true);
// Set sphere's radius equal to the member var
CollisionComponent->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
CollisionComponent->InitSphereRadius(Radius);
CollisionComponent->SetGenerateOverlapEvents(true);
// set collision component as root
RootComponent = CollisionComponent;
// start the particle effects
ExplosionEffect = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("ParticleSystemComponent"));
ExplosionEffect->SetupAttachment(RootComponent);
if (ExplosionEffect) {
ExplosionEffect->Activate();
}
CollisionComponent->OnComponentBeginOverlap.AddDynamic(this, &AExplosion::OnOverlap);
CollisionComponent->OnComponentHit.AddDynamic(this, &AExplosion::OnHit);
}
// Called when the game starts or when spawned
void AExplosion::BeginPlay()
{
Super::BeginPlay();
// make a timer that will destroy this explosion after the ammount of time specified by the member var passes
GetWorldTimerManager().SetTimer(ExplosionTimer, this, &AExplosion::EndExplosion, Duration, false);
}
void AExplosion::EndExplosion()
{
Destroy();
}
// Called every frame
void AExplosion::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void AExplosion::OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComponent, FVector NormalImpulse, const FHitResult& Hit)
{
// debug logging Explosion hit
if (GEngine) {
// Display a debug message for five seconds
// The -1 "Key" value argument prevents the message from being updated or refreshed
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Green, TEXT("Explosion Hit!"));
}
EndExplosion();
}
void AExplosion::OnOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
// debug logging Explosion hit
if (GEngine) {
// Display a debug message for five seconds
// The -1 "Key" value argument prevents the message from being updated or refreshed
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Green, TEXT("Explosion overlap!"));
}
EndExplosion();
}
Alright! Rebuilt the codebase with these mods and it’s finally working! Well, actually now its not destroying the Targets even though they register the hits and drop their health to zero, but that is another issue entirely! Thanks for the quick and patient responses!