[UE5] Function vs Macro: Separating a Skill's Check from Its Cooldown

Created: 2025-12-12Last updated: 2026-09-05

Choose between Blueprint Functions and Macros by return values, exec pins, and Delay. Separate 'can I use this skill' from a 3-second anti-mash window, with a hands-on that verifies mana going 50 → 25 → 0.

"Check whether I can use the skill right now" and "block mashing for 3 seconds after it fires." Both are logic you want to bundle, but the first is a job that returns an answer while the second lets execution through or holds it.

Blueprint's Function and Macro are tools for bundling such logic into named parts. Using a skill as our subject, we put the condition check in a Function and the cooldown in a Macro.

While reducing mana from 50 to 25 and then to 0, we confirm "mashing does not add up," "waiting lets you use the next one," and "you cannot use it without enough mana."

A box that calls logic and a stamp that copies a group of logic. A figure comparing Function and Macro

What You'll Learn

  • The difference between a Function's return values and a Macro's exec pins
  • Pure Functions, and where a Delay can live
  • How to build the check and the anti-mash separately
  • How to verify holding the key, the cooldown, and insufficient mana distinctly

Sponsored

Function: call logic and receive a result

A Function calls a bundle of logic by name. For instance, it can check "do I have at least 25 mana" and return true when usable and false when short. The type handling that true/false is a Boolean.

Values handed to a function are inputs and the value it returns is the return value. The caller uses that answer in the next step. Fix the function's contents and the change reaches every place using it.

Functions can also bundle mutating logic such as reducing HP. They need not return a value. Here, as one use of them, we build "a function that checks a condition and answers."

White exec lines and value lines

Blueprint's white triangle pins are the entry and exit connecting when logic runs. Colored value pins pass numbers, true/false, and so on.

A normal Function call has one white exec input and one output. Number and other value inputs and outputs, meanwhile, can be multiple. "One exit" does not mean one value returned.

You can place several Return Nodes inside a function for different conditions. Whichever Return finishes, the caller's white exit is the same (→ choosing between variables and return values).

A Pure Function computes when the value is needed

A normal Function has white exec entry and exit; a Pure Function has none. Value pins can be multiple, and Macros can add exec exits too

A Pure Function has no white exec pins and returns the values needed. Turn "Pure" on in the function's Details and the calling side takes this appearance.

Connect a Pure Function returning "is mana sufficient" to a Branch's Condition, for example, and the answer is used when the Branch evaluates the condition. Branch splits execution to True on true and False on false.

Put calculations and state checks into Pure. Split mutating logic such as reducing mana or adding an item into logic with white exec lines. Since it computes whenever the value is needed, making it Pure does not store the result or make it compute only once.

Macro: turn an execution flow into a part

A Macro lets you use a group of nodes as a single node. Like a Function, fixing the original definition reaches every place it is used. The difference is that you can add your own white exec entries and exits.

You can build a part with "an exit for when it passes" and "an exit for when it was rejected," for instance. The DoOnce and For Each Loop you use daily are provided as standard macros.

Internally, picture a stamp. On Compile, the Macro's contents are expanded into each place it is used. That does not mean a fresh copy is made each time it runs.

A Function calls shared logic and returns; a Macro expands its contents into each use site at Compile time

Logic that waits differs in where it can live

Delay is a node that waits a specified number of seconds and then resumes the logic after it. Unlike a pause that stops the whole game, it holds only that execution's continuation.

Since a Function finishes the called logic and returns, you cannot insert a Delay partway. A Macro used in an Actor-derived Blueprint's Event Graph, as here, can bundle a flow including a Delay.

You cannot put a Delay inside a Function. Split waiting logic into the Event Graph or a Macro used there

But putting a Macro containing a Delay inside a Function does not enable waiting inside the Function. Consider not only the nodes you put inside but where you use that Macro.

Sorting out how to choose

ComparisonFunctionMacro
Value inputs and outputsCan be multipleCan be multiple
White exec pinsNormally 1 in, 1 out. Pure calls have noneYou can create multiple entries and exits
Logic containing a DelayCannot go inside a functionCan go in the Macro we use in the Event Graph here
Named Local VariablesCan be created inside a functionNo creation panel equivalent to a function's
Asking another Actor to run logicCall the target's function through a referenceNot a tool for calling with a Target the way a function does

The essence of the comparison is whether what you want back is "a computed value" or what you want to bundle is "an execution flow." Macros can compute values too, but when either would work, choose the form whose intent reads more clearly.

A comparison lining up what Functions and Macros can and cannot do

Consider a Function first, and move needed flows to a Macro

When unsure, name "what this logic does" as a Function first. CalculateDamage computes damage; CanUseSkill answers whether it is usable. Keeping the role small makes it easier to handle.

On top of that, look at a Macro when you want to bundle several exec exits or a flow containing a Delay. Merely waiting can be built directly in the Event Graph, so there is no requirement to make it a Macro. Here we build a Macro as practice at bundling the anti-mash logic for reuse.

Sponsored

Hands-On: combining the check with a 3-second cooldown

A cooldown is the wait after using a skill before you can use it again. Here, pressing F fires the skill and consumes 25 mana. For 3 seconds after firing, no number of presses fires it again.

First we display that it fired and the remaining mana with Print String. That confirms the check and the wait work before adding bullets and effects.

The first F takes mana from 50 to 25; mashing within 3 seconds changes nothing; waiting and pressing again takes it to 0. Waiting alone does not auto-fire

1. Prepare values on the character

Prepare a Third Person Blueprint project, choosing None in versions with a Variant option. Open the BP_ThirdPersonCharacter you play as and create the following under Variables. Set the defaults after Compile.

VariableTypeDefaultPurpose
CurrentManaFloat50.0Current mana
SkillCostFloat25.0Mana consumed per use
bIsStunnedBooleanfalseWhether you cannot act

Float is the type for decimals. We test with the whole numbers 50 and 25 here, but we align on Float so mana regen amounts and the like can use decimals.

2. Return the usable condition from a Function

Create CanUseSkill from the "+" under Functions and turn Pure on in its Details. Add a Boolean bResult to Outputs. Add no input pins; it reads this character's variables to decide.

The conditions are "mana is sufficient" and "not stunned." Connect them as follows.

  1. Put A = Get CurrentMana and B = Get SkillCost into a Float >=.
  2. Put Get bIsStunned into NOT Boolean. NOT flips true and false, so it becomes true when not stunned.
  3. Put both results into AND Boolean and connect the result to the Return Node's bResult. AND is true only when both are true.
  4. Connect a white exec line from the function's entry to the Return Node and Compile.
Combining the CurrentMana/SkillCost comparison with the inverted stun state via AND and returning it to the Return Node's bResult

Even when Pure, the function graph you are authoring has an entry and a Return. What loses white exec pins is the appearance of the node that calls this function.

CanUseSkill only answers "is it usable"; it does not reduce mana. Actual consumption happens later, only when it fires.

3. Block mashing for 3 seconds with a Macro

Create Cooldown from the "+" under Macros in the same Blueprint. Add the following inputs and outputs in the Details.

SideNameType
InputsInExec
InputsTimeFloat
OutputsThenExec

Exec is the type for creating white exec pins. Inside the Macro there are Inputs and Outputs nodes representing the entry and exit, where the pins you just configured appear.

Place DoOnce, Sequence, and Delay inside. Leave DoOnce's Start Closed off and connect in this order.

  1. Inputs' In → DoOnce's normal exec input.
  2. DoOnce's Completed → Sequence's exec input.
  3. Sequence's Then 0 → Outputs' Then. This lets this firing through.
  4. Sequence's Then 1 → Delay's exec input. Connect Inputs' Time to Duration.
  5. Delay's Completed → the same DoOnce's Reset input.

DoOnce lets the first execution through and blocks the next until execution reaches Reset. Sequence advances through Then 0 and Then 1 in order. Since one exec output cannot branch directly into two, we use Sequence here.

The first input lets the firing through, and immediately begins the wait. When the Delay ends, that same DoOnce returns to a state that can let execution through. The point is running the line back into the original DoOnce, not creating a separate node named Reset.

We split the wiring across two diagrams. Both are parts of the same Macro, so do not add another DoOnce or Sequence for the second one.

From Inputs through DoOnce and Sequence, with Then 0 letting the firing through to Outputs. Then 1 connects to the Delay in the next diagram

The other branch is the wait that re-permits the next firing.

From Sequence's Then 1 into Delay, with Completed returning to the same DoOnce's Reset. Inputs' Time goes to Delay's Duration

Input arriving during the cooldown is not queued to run later. It stops at DoOnce, and after 3 seconds it simply becomes "able to accept the next input."

4. Connect the F key to the check and the firing

Create an F key event in the Event Graph. What we use is Pressed, which delivers once at the moment of the press. Confirming with a Print String that F input is arriving before moving to the next connections makes isolation easier.

  1. F's Pressed → Branch's exec input.
  2. Drag CanUseSkill from My Blueprint to place a call node, and connect its bResult → Branch's Condition.
  3. Branch's True → Cooldown's In. Set Time to 3.0. The False side does nothing.
  4. Cooldown's Then → Set CurrentMana. Feed the value with a Float subtraction of CurrentMana - SkillCost.
  5. Place a Print String after Set CurrentMana and display the updated CurrentMana.
From F's Pressed into a Branch, with CanUseSkill's bResult on Condition. Only True passes through Cooldown and proceeds to mana consumption

For the display, put SKILL / Mana: in Append's A and CurrentMana converted to String in B, and pass the result to Print String's In String. Connecting a Float line to a String input inserts a conversion node. Set Duration to 10 seconds.

Mana is reduced after passing through Cooldown. Reducing it earlier would consume mana even for presses that did not fire.

Subtracting SkillCost from CurrentMana and writing with Set CurrentMana only when execution comes from Cooldown. The updated value is passed to Print String

The input here is for practice. When integrating into your game's controls, you can replace it with an Enhanced Input Input Action. There too you decide whether to call on the moment of the press or repeatedly while held.

5. Verify mashing, the wait, and insufficient mana separately

Compile, save, Play, and click the game view so it receives input.

ActionExpected result
Press F the first timeThe firing message. Mana goes 50 → 25
Release F and press several times before 3 secondsNo new firing message and mana stays 25
Wait over 3 seconds from the first firing and press F againThe second firing. Mana goes 25 → 0
Wait over 3 more seconds and press F againIt does not fire, due to insufficient mana

Float display may read 25.0 and so on, but the values to confirm are 25 and 0. Holding the key and waiting does not fire automatically, because no new Pressed occurs.

Next, stop Play, set bIsStunned's default to true, and try again. This time it never fires. Restore false afterwards. That lets you confirm "blocked because the condition is unmet" and "blocked because it was just used" separately.

Add bullets and sound once it fires

Once this works, add bullet spawning and sound effects to the logic after Cooldown. The bullet Blueprint, the spawn position and rotation, and the flight speed can be built in Spawn Actor and Projectile Movement.

The same split works for dashes, bombs, and door anti-mashing. Separating "the condition under which it may run" from "the interval that prevents consecutive runs" makes it easy to tune one without the other.

Sponsored

Bonus: Good to Know Up Front

Two Cooldowns means two separate waits

The DoOnce inside this Macro holds state per placed Cooldown node. Connecting input A and input B to separate Cooldowns does not produce one shared cooldown.

If the same skill is used from multiple inputs, gather them into one firing path first and then pass through the same Cooldown. When you want separate waits for different skills, separate nodes are what you want.

No Local Variables in Macros?

There is no named Local Variables panel like a function's, but Macros have a mechanism using anonymous local values. We do not cover it in this exercise; when complex calculation is needed, splitting it into a Function and passing the result into the Macro keeps things clearer.

Collapse Nodes is not the same as making a shared part

Selecting nodes and using "Collapse Nodes" folds a group for readability. You can copy it, but it does not become a shared definition where one fix reaches the copies.

When you use the same logic in several places and want a fix in one spot to apply, gather it into a Function or Macro. Keep visual organization separate from the scope you want to share.

When you want to use it in another Blueprint

General calculations go in a Blueprint Function Library and macros in a Macro Library. But there are conditions on usable nodes and target classes. Finish the Cooldown here inside the character first and decide what to extract once sharing becomes necessary.

To reuse state and several pieces of logic together, a Blueprint Component is another option.

Summary

Functions are for "calling logic and receiving an answer"; Macros are for "bundling an execution flow." Here CanUseSkill returned the condition, Cooldown blocked mashing for 3 seconds, and the logic after it reduced mana.

Next time you bundle similar logic, think about whether it is a part that answers something or a part that lets a flow through. Organizing whole graphs is covered in 10 techniques for organizing Blueprints, and other ways to call logic on a timer in designing with Timers and events instead of Tick.

Further Reading

Unreal Engine Notes in this section98