I am writing a simple custom sensing component for a game I’m currently working on. The component inherits from UActorComponent and is attached to a custom type of Character (that inherits from ACharacter). I then create blueprints from the custom character class and assign them to spawn volumes for spawning at BeginPlay.
The sensing component has a private USphereComponent member that is used for collision checking (on overlap). I also register two delegates in this class that handle OnComponentBeginOverlap and OnComponentEndOverlap (in this case I just output debug messages).
Here is the SensingComponent.h
#pragma once
#include "Components/ActorComponent.h"
#include "CoreMinimal.h"
#include "SensingComponent.generated.h"
UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class AREA7UNREAL_API USensingComponent : public UActorComponent
{
GENERATED_BODY()
public:
// Sets default values for this component's properties
USensingComponent();
// Called every frame
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
// React to sensing sphere overlaps
UFUNCTION(BlueprintCallable, Category = "Agent|Sensing")
void OnBeginOverlapSensingSphere(class UPrimitiveComponent* HitComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult);
UFUNCTION(BlueprintCallable, Category = "Agent|Sensing")
void OnEndOverlapSensingSphere(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
// EDITOR ONLY: Dynamic property change updates
#if WITH_EDITOR
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
#endif
protected:
// Called when the game starts
virtual void BeginPlay() override;
// The sensing sphere radius
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agent|Sensing")
float sensingSphereRadius;
private:
/** The sensing sphere */
class USphereComponent* SensingSphere;
};
And here’s the SensingComponent.cpp
#include "SensingComponent.h"
#include "Runtime/Engine/Classes/Components/SphereComponent.h"
#include "Runtime/Engine/Classes/Engine/Engine.h"
// Sets default values for this component's properties
USensingComponent::USensingComponent()
{
// Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features
// off to improve performance if you don't need them.
PrimaryComponentTick.bCanEverTick = true;
// Initializing sensing sphere radius
sensingSphereRadius = 500.0f;
// Creating the sensing sphere
SensingSphere = CreateDefaultSubobject<USphereComponent>(TEXT("SensingSphere"));
if (SensingSphere)
{
SensingSphere->SetSphereRadius(sensingSphereRadius);
SensingSphere->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Overlap);
SensingSphere->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
SensingSphere->OnComponentBeginOverlap.AddDynamic(this, &USensingComponent::OnBeginOverlapSensingSphere);
SensingSphere->OnComponentEndOverlap.AddDynamic(this, &USensingComponent::OnEndOverlapSensingSphere);
SensingSphere->bGenerateOverlapEvents = true;
}
else
{
UE_LOG(LogTemp, Error, TEXT("Could not create SensingComponent's sensing sphere."));
}
}
void USensingComponent::OnBeginOverlapSensingSphere(class UPrimitiveComponent* HitComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult)
{
UE_LOG(LogTemp, Warning, TEXT("*** Collision - [BEGIN OVERLAP]. Collider: %s"), *GetOwner()->GetName());
TArray<AActor*> actors;
SensingSphere->GetOverlappingActors(actors);
for (AActor* actor : actors)
{
UE_LOG(LogTemp, Warning, TEXT("*** Collision target: %s"), *actor->GetName());
}
}
void USensingComponent::OnEndOverlapSensingSphere(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
UE_LOG(LogTemp, Warning, TEXT("*** We got a collision - END OVERLAP."));
}
// Dynamically update the sensing sphere's radius in the BP viewport and code when changing the value in BP
#if WITH_EDITOR
void USensingComponent::PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
FName PropertyName = (PropertyChangedEvent.Property != NULL) ? PropertyChangedEvent.Property->GetFName() : NAME_None;
if ((PropertyName == GET_MEMBER_NAME_CHECKED(USensingComponent, sensingSphereRadius)))
{
UFloatProperty* property = static_cast<UFloatProperty*>(PropertyChangedEvent.Property);
sensingSphereRadius = property->GetPropertyValue(&sensingSphereRadius);
SensingSphere->SetSphereRadius(sensingSphereRadius);
}
}
#endif
The main problem
The ‘OnComponentBeginOverlap’ event triggers correctly when I manually place my character blueprints into the level and they are close enough together to have their USpereComponents overlap.
The same does NOT work, though, when the characters are spawned instead of manually placed in the level. Mind you, the characters spawn correctly in the level.
- How do i fix this so it works with spawned actors / characters as well?
Here’s how I spawn the characters (from a spawn volume that spawns a certain type of character). The SpawnAgent() function is triggered at BeginPlay().
void ASpawnVolume::SpawnAgent()
{
// Check if we have something to spawn
if (ActorToSpawn != NULL)
{
// Check for valid world
UWorld* const world = GetWorld();
if (world)
{
// Set up the spawn parameters
FActorSpawnParameters spawnParameters;
spawnParameters.Owner = this;
spawnParameters.Instigator = Instigator;
// Get random location within the volume to spawn at
FVector spawnLocation = GetRandomPointInVolume();
// Rotate the agent around z randomly for a bit of variety
float zRotation = FMath::FRand() * 360.0f;
FRotator spawnRotation = FRotator(0, zRotation, 0);
// Spawn the agent and remember it
AOrisAgentCharacter* const spawnedAgent = world->SpawnActor<AOrisAgentCharacter>(ActorToSpawn, spawnLocation, spawnRotation, spawnParameters);
}
}
else
{
FString debugString = GetName();
UE_LOG(LogTemp, Warning, TEXT("There is no Actor to Spawn."));
}
}
The secondary problem
My characters (using the SensingComponent) also do NOT trigger when overlapping with a static mesh placed in the level (it is set to BlockAll and has GenerateOverlapEvents set to true).
- What am I doing wrong here?
Cheers,
Michael
PS 1: I noticed that BeginOverlap and EndOverlap events are triggered whenever I open/close the blueprint of a character that uses the above SensingComponent…is this intentional behavior when opening blueprints?
PS 2: I also noticed that the USphereComponent’s bounds are shown in the Blueprint editor viewport but NOT in the game, no matter what visibility flags I set on the sphere. Is this to be expected behavior?