How do I listen to an event on an object that hasn't been created yet?

Was wondering if I could have some help understanding where I’m going wrong with adding this event listener. How do I listen to an object that hasn’t been created yet like a weapon that hasn’t spawned in yet?

I’m creating a Hit-scan freeze weapon for my player. I’ve set up a timer in my Hit-scan weapon script and am broadcasting an event there. I am currently listening to it in an enemy script but can’t get the listener to fire I’ve set up the listen in the begin play. I’m adding a function here to change a IsFrozen bool, for some reason the listener won’t fire:

Snippet of my enemy script:

void ANPC_Enemy::BeginPlay()
{
    Super::BeginPlay();
    
    AActor* weaponHitscan = UGameplayStatics::GetActorOfClass(GetWorld(), AWeapon_Hitscan::StaticClass());

    _FreezeWeapon = Cast<AWeapon_Hitscan>(weaponHitscan);
    
    _FreezeWeapon->OnEnemyFroze.AddUniqueDynamic(this, &ANPC_Enemy::SetEnemyFreezeState);
}

void ANPC_Enemy::SetEnemyFreezeState(bool isFrozen)
{
    _IsFrozen = isFrozen;
    GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Purple, TEXT("Works"));
    GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Purple, FString::Printf(TEXT("%d"), isFrozen ));
}

I know I’m casting here to an instance in the world but this is just an example blueprint of the class WeaponHitscan I’ve dragged into the scene, I can’t seem to get a reference to the hitscan weapon on the actual player because it’s not loaded in straight away.

I’ve added “_FreezeWeapon” as a TObjectPtr<> in the header. I wanted to use TSubclass<> as that’s what I thought you used for elements that aren’t in the world space but it won’t take any functions.

I’ve checked my broadcast and it fires properly. Not sure on what else to try.

Thanks for reading

You don’t listen to objects that don’t exist.
Typically, you’ll listen to the owner of the object – in this case, the player – but for an arbitrary NPC there may or may not even be a player when it first spawns.

However, I don’t even understand why the enemy would need to know about the weapon at all?
The player is the one firing the weapon? And the weapon then figured out what it hits? And once it hits something, it applies some effect to whatever it hits?
Applying a “freeze” effect could be done using the Gameplay Ability System (GAS) or, if you’re not going that far, by looking for some interface on the target actor, and calling a “freeze” function on that interface. If the player hits some actor that doesn’t implement freeze, simply nothing happens.

Where does the enemy listning to the weapon come in?

1 Like

Hi thanks for your response:

However, I don’t even understand why the enemy would need to know about the weapon at all?

I have an AI Behavior Tree I’m using for the enemy. I want to change the state of this tree (state of the enemies behavior) by changing the value of a bool variable “IsFrozen” as a blackboard key.

I can get the Enemy controller and then the enemy pawn by using the onBecomeRelevant() function. I then can reference a getter function for my original IsFrozen bool on the enemy and change the blackboard one accordingly.

I know it’s a long winded answer to why i need the listener on the enemy specifically but here’s a snippet of what i’m doing in my blackboard service class.

//Get Controller and npc
	ANPC_Enemy_AIController* cont = Cast<ANPC_Enemy_AIController>(OwnerComp.GetAIOwner());
	ANPC_Enemy* npc = Cast<ANPC_Enemy>(cont->GetPawn());

	OwnerComp.GetBlackboardComponent()->SetValueAsBool(
			GetSelectedBlackboardKey(), npc->GetEnemyFreezeState());
type or paste code here

Where does the enemy listening to the weapon come in?

The bound function to the listener sets the bool on the enemy of “isFrozen” to the given parameter. The function: GetEnemyFreezeState() is just returning that value. My plan is to check against this bool in my behavior tree and set the tasks accordingly.

the code in my original post works and will get a dummy instance of my freeze weapon if I put it in the world but the listener wont fire either way.

You should make the action event driven.

Perhaps add a new actor component called status onto your base character that can hold status effects such as isFrozen.
A better way would to have an array of status effects
example

 TArray<FStatusEffect> statusEffects;

Once you fire the weapon at the enemy if it’s a freeze ray => create a new FStatusEffect where you have a duration, and start time in the struct.
Add it to the statusEffects.

sync the statusEffects in the blackboad and use it in the service.

Iterate over the statusEffects comparing elapsed time to duration to see if the effect has warn off.

Then you can have

FStatusEffect like frozen, onFire, stunned, asleep, unconscious, poisoned etc

In my experience, that’s just the wrong way around for game logic (and distributed simulation in general.)

You should have the trigger thing (in this case, the freeze gun) emit the events (“things got frozen, yo”) and then either use some kind of generalized event bus, or have the emitter decide whom to emit the events to at the time of emitting.

“Decide whom to emit to” is your typical hit scan or impact based weapon; find which entities overlap a ray, or which entities collide with a sphere or something; those entities get the message.

An event bus would instead be a shared mechanism for finding entities that listen to particular events. For example, you could have an “on freeze” event, and each NPC listens on the bus for that event, and each freeze gun sends that event. The event bus can be as easy as a global map from event name to list of weak-pointers-to-listeners (so the map doesn’t keep otherwise dead objects alive.)

The “event” can then take a bunch of different shapes. For “damage,” there are methods built into the Actor system, but for more specialized events, you’ll typically either add some kind of component that knows how to receive the event, or you’ll implement some interface that has appropriate functions for receiving the event. The sender will then typically use dynamic casting/introspection to “get components of kind” or “send to interface if implemented” to deliver the event.

And, because you’re in C++, you really should consider the Gameplay Ability System. It has a whole system set up for triggers, chains of triggers, effects, stacking effects, visualizations, rate limits, and so on and so forth. Once you’re in that system, building particular effects will be quite straightforward and “fit in” to the overall system.

But, even if you don’t, the event should be triggered by the sender dynamically finding which objects should be notified.

You should have the trigger thing (in this case, the freeze gun) emit the events (“things got frozen, yo”) and then either use some kind of generalized event bus, or have the emitter decide whom to emit the events to at the time of emitting.

(Other users’ comment)

You should make the action event driven.

I’m not sure if I’m misunderstanding here but I feel like my weapon is emitting/Broadcasting my event. I have a fire function in my weapon that does the following:

	if(UKismetSystemLibrary::LineTraceSingle(GetWorld(), StartLoc, EndLoc, UEngineTypes::ConvertToTraceType(ECC_Visibility), true, {this, GetOwner()},
		EDrawDebugTrace::ForDuration, Hit, true, FLinearColor::Red,FLinearColor::Green, 5.0f))
	{

		if(Hit.GetActor()->ActorHasTag("GameRuleTarget"))
		{
			_TimeToFreeze = 5;

			_IsFrozen = true;
            //Emitting Event
			OnEnemyFroze.Broadcast(_IsFrozen);
			
			//Start timer
			GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Black, TEXT("Started Timer"));
			GetWorld()->GetTimerManager().SetTimer(_TimerDecrease, this, &AWeapon_Hitscan::DecreaseCountdown, 1.f, false);
			return true;
		}
		
	}

The decreaseCountdown just broadcasts setting the value back to false after the timer is up.

The event is created and broadcasted in the freeze weapon and is emitted on the action of the player shooting the target. The problem I have is the event listener on the enemy class (Shown in original post) won’t work. I think there is something wrong in that first code snippet.

The connection between the freeze weapon and the enemy is the issue. I know it isn’t the best practice to have these lose connections between classes but I think it is warranted in this case for use in my behaviour tree.

What you’re not doing is having your weapon decide who is currently affected, at the time of triggering.
The NPCs may need to know what it means to be “frozen” (e g, implement an OnFreeze() function interface) but they don’t need to know anything about the freeze gun.
Think of it this way: Maybe other things will cause freeze too? Freeze grenade? Freeze trap? Freeze zone?
It shouldn’t be that every object needs to know about every thing that can cause a particular status. There needs to be some “meet in the middle” that de-couples the “causer” and the “causee” so they don’t know directly about each other.

3 Likes

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.