How to wait for a sets of events all fired / executed / activated

Hi there, I’m new to unreal engine and in recent projects I often meet a situation like this:

Event A is bind on another actor event, and Event B is also bind on something. And the sequence to be activated of the two events is not always the same. Sometimes Event A reaches earlier, and sometimes Event B reaches eariler. And I have to wait for thoes two event all activated so the timing is correct.

Althogh the macro node in picture can work, it has two bad outcomes when I’m using it:

  1. When I need to wait for more events, I have to make a new macro and add more execute pin at the front.

  2. Inside the macro I only use one local integer to count the event num, thus I need to use DoOnce on every event incase some event fired too frequently.

I think many developers would meet this situation, but I cannot find tutorials contained or metioned the best way to handle this situation. So put this question on the forum for the best answer.

PS:

  1. I can’t create an array contains many delegate pins. (Delegate pin is the red pin on the event node.)
  2. The delegate pin doesn’t have GetEventName() function.
  3. I could use a string to represet the event using event name, and check the string inside the macro like this:

But this node means I need to have a variable to record the current event name (linked to the Event Name pin), which is I want to avoid.

Event A sets a bool, event B sets a bool. You have a timed event running every second or so, that checks A and B.

Or Event A sets a bool, Event B sets a bool, and in both, you check if they are set, and do the thing

I feel like there might be a different way to approach the whole problem, but would require higher level details.

I can’t think of any way to create a general purpose node for this only in blueprint, but there might be some things i’m not aware of.

You could try like this:

My Products

Replace that for loop with a Array->Contains->False. If any are false, then you can exit.

I’m still thinking if there’s any way that isn’t more hell than it’s worth to give it a variable number of inputs, but I’m pretty sure you’d have to go into some fairly complex native code to get there. Might be doable with a simple CommutativeAssociativeBinaryOperator function, but I’ve never used that, so not entirely sure. Probably can if there’s a way to suss out how many pins are enabled.

Though, overall I question if this is needed frequently enough to try to make a generic solution for… why is it needed so much?

1 Like

1 Like

ope! i do have another possible idea. IF none of the events can happen multiple times, you can use a MultiGate!

If they can happen multiple times, you could use Once in conjunction with a MultiGate

2 Likes

In general, for N events, you need N separate flags (bools?) that you can set.
Then, after setting a flag, you check whether all flags are set, and if they all are, do the next thing.
You could wrap this up in some state object that keeps an array of bool values, for example, if you want to make it re-usable and re-sizable.

However, this kind of situation is exactly what the gameplay ability framework is designed to do.
You define gates, and events, and actions, and the system will take care of activating the right outcome when the right circumstances occur.
You do need a little bit of C++ to set up the GAS, but once that’s done (Reubs has a good YouTube video that shows you how in half an hour,) then it’s pretty easy and flexible to use.

Oh thanks, I will watch that video and try go deeper in gameplay ability framework

Hi there, I’m back from gameplay ability system.

Although GAS is too complicated for this problem, I still feel enlightened after review its structure. Now I created an UObject for this problem:

UCLASS(BlueprintType,Blueprintable)
class UMultiEventReceiver : public UObject
{
	GENERATED_BODY()

public:

	UMultiEventReceiver() : CurCanAddEvent(true), CurCanCommit(true), WaitNum(0), CurTimerActivatedNum(0) {};

	UFUNCTION(BlueprintCallable)
	bool AddEvent(FName EventName);

	UFUNCTION(BlueprintCallable)
	bool Commit(int32 NewWaitNum);

	/*BP signal of event finished*/
	UFUNCTION(BlueprintCallable)
	void OneEventFinish(FName EventName);

	UFUNCTION(BlueprintCallable)
	void TryTerminate();

private:

	/*Call by inner timer*/
	void TryActivateResult();

	bool AllReachedCheck();

public:

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
	TMap<FName, bool> EventsStatus;

	UPROPERTY(BlueprintAssignable)
	FOnReceiverReachEnd OnReceiverReachEnd;

private:

	bool CurCanAddEvent;
	bool CurCanCommit;
	FTimerHandle EventInspectorTimer;
	int32 WaitNum;
	int32 CurTimerActivatedNum;

};

The implementation of this object is simple, but I need to talk about WHY I use an object for this problem.

Basically the macro I posted have many faults:

  • High coupling. Passing signal using event could be very complicated, and once an event failed to fire, all program could stuck at this macro.

  • Bad for debug. When the macro is stucked, it even can’t tell me which one is stucked.

So now I use this Event Receiver, and these problem is solved:

  • Object can use timer. Now I can set a time boundary to avoid stuck.

  • Low coupling. The finished signal would only be fired by this object.

  • The object knows every signal status. Good for debug.

Here is some inner implementation:

bool UMultiEventReceiver::Commit(int32 NewWaitNum)
{
	if (CurCanCommit == false)
	{
		return false;
	}

	const UWorld* const World = GetWorld();
	if (World == nullptr)
	{
		return false;
	}

	this->CurCanAddEvent = false;
	this->CurCanCommit = false;
	this->WaitNum = NewWaitNum;

	World->GetTimerManager().SetTimer(this->EventInspectorTimer,
		FTimerDelegate::CreateLambda([this]() {
			this->TryActivateResult();
			}), 0.1f, true, 0.f);

	return true;
}

void UMultiEventReceiver::TryActivateResult()
{
	++CurTimerActivatedNum;
	if (this->AllReachedCheck() || CurTimerActivatedNum >= WaitNum)
	{
		GetWorld()->GetTimerManager().ClearTimer(this->EventInspectorTimer);
		OnReceiverReachEnd.Broadcast(FEventStatusMessage(this->EventsStatus));
		this->MarkPendingKill();
	}

	return;
}

But since it’s using timer to check and I set interval is 0.1 sec, the overhead could be a problem. And the trigger is no longer precise collision, which means it’s not suitable for action game and shooting game.

1 Like