Custom Flow Control Macro Library - Request for Feedback

Edit: A more recent version of this library can be found here

A while back I recall finding myself unsatisfied with the number of flow control nodes in UE4 and set out to create my own library to meet the needs of my projects.

I was recently reviewing the library and was wondering why no one else had thought of creating these. I’m no computer programmer unfortunately, so I have no idea if what I created is effective or not (or heresy to the standards of programming), but I thought I would post them here to see if anyone had any feedback regarding them (or if anyone would find them useful to use themselves).

I’ll first explain each of the macros and what their purpose is, then go into detail regarding how they work. The macros I’ve developed are as follows:

OnFlowStop


As its name implies, this node will fire an execution pulse on the frame that it no longer receives an execution pulse. In the above example, “No Longer Walking” will be printed to screen when character movement switches away from Walking mode.

This node is very useful for situations where a follow through or cleanup actions are needed to be done upon leaving a state.

OnFlowStart


Again simply named, OnFlowStart will only allow pass the first execution pulse of a flow. In the example provided, the message “Started Walking” will only be printed to screen upon the character entering the Walking state. Note that without the OnFlowStart node, the message would be printed every frame that the character was in the Walking State. The node also has a Resend pin that, upon receiving an execution pulse, will refire the output pulse, regardless of what’s going on with the Flow pin.

Combining this node with the OnFlowStop node can be very powerful as it allows for both initialization of a state and cleanup of a state. Kind of similar to a constructor and destructor.

FlowChargeUp


This node will prevent a flow of execution pulses from passing for a limited time. If the flow is interrupted during the “charging” of the node, the timer will reset back to zero without allowing any execution pulses through. In the example above (a simple modification from the First Person Shooter Template), InputAxis Fire has been mapped to the left mouse button. Holding down the left mouse button will open the branch, allowing the execution flow to begin charging the FlowChargeUp node. After the specified time (2 seconds) the flow will be let passed and projectiles will be spawned every frame for as long as the left mouse button is held down. The node has a reset pin which, upon receiving an execution pulse, will set the charge of the node back to zero, requiring it to be charged back up before allowing the flow passed again. The node also has a pause bool which will freeze the node in whatever state it was when the bool switched to true. During the freeze, only the Reset pin will work and the Flow pin can stop receiving inputs without the charge dissipating. Once the Pause bool is set back to false, the charge will dissipate unless the Flow pin is already receiving a continuous execution flow. A %Charged float ranging from 0 to 1 is also part of the output and is very useful for creating HUD elements that indicate how far along the charge is to full.

Have you ever wanted to make a gun that charges up before firing? Well this is exactly the node you need.

LimitedFlow


As opposed to FlowChargeUp, the LimitedFlow node will only allow a flow of execution pulses to pass for a limited time before closing preventing any more execution pulses from passing. Upon interuption of the execution flow, the node will open back up, allowing the flow to pass again once it starts back up. In the above image, holding down the fire button will spawn a projectile every frame for only 3 seconds. The fire button will then need to be released and pressed again before anymore projectiles can be spawned. I’ve included a Manual Reset pin and a Start Spent bool (pretty explanatory I hope) which are more for situations when it will be receiving input from the get go (such as if connected to Event Tick).

I don’t use this node as much as the FlowChargeUp node, but it still has some very useful applications. A good example is fighting games, where certain follow up actions are only available for a limited time after the first action.

HoldGate


The HoldGate is similar to a normal gate, except that the opening mechanism is a second execution pin that must be receiving a continuous flow of execution pulses. In the given example, InputAction Fire will only work when InputAxis Disable Safety is held down.

I don’t use this one very often either, but a good application for this would also be in fighting games, where multi-button commands are often found (eg. holding down on the control pad and pressing a face button will lead to a different result then just pressing the same face button).

FlowDiscretiser


The FlowDiscretiser node is a more sophisticated method of chopping a flow of execution pulses compared to a simple delay timer. Not only will it immedately stop sending pulses when the incoming flow stops, it will also multipulse if the period is set lower than the world delta time. The FlowDiscretiser will pulse first before waiting (compared to a simple delay timer which will wait first, then pulse). The main advantage over a simple delay timer is its ability to be interrupted (eg. if you let go of the fire button at t=0.1, the FlowDiscretiser will not fire at t=1.0 whereas the simple delay timer will fire at t=1.0)

I do recall this node being the most buggy one out of all of them, but I haven’t had too many issues with it of late.

AxisNeutralizer


Essentially, if the AxisNeutralizer node starts receiving a flow of execution pulses AND an axis value that is not zero, it will not let the flow pass until the axis value becomes zero. It will check every continous flow of execution pulses in this way. In the above blueprint sequence, holding down the InputAxis Disable Safety button followed by holding down the InputAxis Fire button will spawn projectiles, but reversing the order in which you held down the buttons will result in nothing happening.

This was a node I created specifically for UMG applications. The issue that it directly combated was a situation where a menu was opened while holding the up or down button. Doing this would cause the menu cursor to appear to have started one space up or one space down from where it should have started. The AxisNeutralizer node is a simple fix to this issue.

So how do these all work?

The blueprints for each one of these macros are remarkably simple, save for one node (I’ll explain at the end):

OnFlowStop

OnFlowStart

FlowChargeUp

LimitedFlow

HoldGate

AxisNeutralizer

FlowDiscretiser

You’re probably wondering what that CustomRDelay node is. I never got around to doing any documentation for that node, but I seem to recall it being an almost exact replica of the RetriggerableDelay node. The only difference I think was that RetriggerableDelay could fire on the frame that it was initialized if the delay was small enough, while CustomRDelay had to wait at least one frame before firing. I tried taking a look into the C++ files, but I could not tell you how it works for the life of me. I don’t mind posting the files themselves if anyone’s interested though.

Anyways, that was quite a bit longer than I expected it to be. If anyone wants to copy these nodes for their own use, please feel free to do so.

Feedback regarding the effectiveness of these nodes is also welcome.

Happy programming

Edit: I should mention that I’m running on version 4.13.2. I should also mention that UE4’s macro system is fairly buggy. In particular, embedding macros inside other macros will result in unpredictable behavior and changing a macro library will only sometimes update the macro nodes used in other blueprints. It’s best to use Refresh All Nodes on a blueprint if you’ve recently changed the macro library.

Thanks for showing these off. I would have inevitably needed to do them anyway. For some of these I wonder why they aren’t default.

Nice! I could definitely find a use for some of these. Have you thought about exporting the library into a file so it can be downloaded?

I’ve attached the library to this post. Just copy the library file into the content folder of your project (via Windows Explorer) to get them to show up in your Content Browser. Note that none of the nodes will work until you create the CustomRDelay node.

The code for the CustomRDelay has 3 C++ files:

MyNode.h


#pragma once

#include "Kismet/BlueprintFunctionLibrary.h"
#include "MyNode.generated.h"

/**
*
*/

UCLASS()
class MYTESTPROJECT_API UMyNode : public UBlueprintFunctionLibrary
{
	GENERATED_BODY()

public:
	UFUNCTION(BlueprintCallable, meta = (Latent, LatentInfo = "LatentInfo", WorldContext = "WorldContextObject", Duration = "0.2", Keywords = "retriggerable"), Category = "C++")
		static void	CustomRDelay(UObject* WorldContextObject, float Duration, FLatentActionInfo LatentInfo);


};

MyNode.cpp


#pragma once

#include "MyTestProject.h"
#include "MyNode.h"
#include "MyNode2.h"

void UMyNode::CustomRDelay(UObject* WorldContextObject, float Duration, FLatentActionInfo LatentInfo)
{
	if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject))
	{
		FLatentActionManager& LatentActionManager = World->GetLatentActionManager();
		FDelayAction* Action = LatentActionManager.FindExistingAction<FDelayAction>(LatentInfo.CallbackTarget, LatentInfo.UUID);
		if (Action == NULL)
		{
			LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, new FDelayAction(Duration, LatentInfo));
		}
		else
		{
			// Reset the existing delay to the new duration
			Action->TimeRemaining = Duration;
			Action->JustReset = true;

		}
	}
}

MyNode2.h


#pragma once

#include "Kismet/BlueprintFunctionLibrary.h"
#include "LatentActions.h"
/**
*
*/
class FDelayAction : public FPendingLatentAction
{
public:
	float TimeRemaining;
	bool JustReset;
	FName ExecutionFunction;
	int32 OutputLink;
	FWeakObjectPtr CallbackTarget;

	FDelayAction(float Duration, const FLatentActionInfo& LatentInfo)
		: TimeRemaining(Duration)
		, ExecutionFunction(LatentInfo.ExecutionFunction)
		, OutputLink(LatentInfo.Linkage)
		, CallbackTarget(LatentInfo.CallbackTarget)
	{
	}

	virtual void UpdateOperation(FLatentResponse& Response) override
	{
		TimeRemaining -= Response.ElapsedTime();
		if (JustReset == false) {
			Response.FinishAndTriggerIf(TimeRemaining <= 0.0f, ExecutionFunction, OutputLink, CallbackTarget);
		}
		JustReset = false;
	}

#if WITH_EDITOR
	// Returns a human readable description of the latent operation's current state
	virtual FString GetDescription() const override
	{
		return FString::Printf(*NSLOCTEXT("DelayAction", "DelayActionTime", "Delay (%.3f seconds left)").ToString(), TimeRemaining);
	}
#endif
};

there’s also a MyNode2.cpp, but it’s empty:


#include "MyTestProject.h"
#include "MyNode2.h"

I’ve highlighted in red, places that you’ll probably need to change for your project.