⚙️ Blueprint Functions and Macros
As your Blueprints grow, Event Graphs become sprawling tangles of nodes. Functions and macros are your secret weapons for organization—they let you package complex logic into reusable, named chunks that keep your code clean and maintainable. In this lesson, you'll learn to build modular, professional-quality Blueprints by mastering functions, custom events, macros, and function libraries that make your code readable, testable, and a joy to work with.
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Create and use Blueprint functions with inputs and outputs
- Understand the difference between functions, custom events, and macros
- Use local variables within functions for temporary data
- Create pure functions for calculations without side effects
- Build Blueprint macros for complex reusable node groups
- Organize code with function libraries for project-wide utilities
- Implement function overriding and inheritance
- Follow best practices for naming, documentation, and organization
- Debug functions effectively with breakpoints and logging
Estimated Time: 90-105 minutes
Prerequisites: Lesson 3.3 - Blueprint Flow Control and Logic
📑 In This Lesson
Why Functions Matter
Imagine building a game where enemies take damage. You write the damage logic once in Enemy 1's Blueprint. Then Enemy 2 needs the same logic—so you copy it. Then Enemy 3. Then you realize the damage calculation is wrong and need to fix it in three places. Miss one, and you have inconsistent behavior.
This is why functions exist.
📖 Key Concept
Function: A reusable, named block of logic that accepts inputs, performs operations, and optionally returns outputs. Functions are the fundamental unit of code organization—they turn "spaghetti code" into modular, maintainable systems. Write once, use everywhere.
The Problem: Spaghetti Code
flowchart TD
A[Event BeginPlay - 200 nodes] --> B[Nested branches]
B --> C[More branches]
C --> D[Loops]
D --> E[More logic]
E --> F[Even more logic]
F --> G[Can't find anything]
G --> H[Debugging nightmare]
style A fill:#e74c3c,stroke:#c0392b,color:#fff
style G fill:#ff9800,stroke:#e65100,color:#fff
style H fill:#e74c3c,stroke:#c0392b,color:#fff
Figure: Without functions, Event Graphs become unmanageable tangles.
The Solution: Modular Functions
flowchart TD
A[Event BeginPlay] --> B[InitializePlayer]
A --> C[SetupUI]
A --> D[LoadGameState]
B --> B1[Small, focused logic]
C --> C1[Small, focused logic]
D --> D1[Small, focused logic]
style A fill:#4CAF50,stroke:#2e7d32,color:#fff
style B fill:#2196F3,stroke:#1565c0,color:#fff
style C fill:#2196F3,stroke:#1565c0,color:#fff
style D fill:#2196F3,stroke:#1565c0,color:#fff
style B1 fill:#e3f2fd,stroke:#2196F3
style C1 fill:#e3f2fd,stroke:#2196F3
style D1 fill:#e3f2fd,stroke:#2196F3
Figure: Functions break complex logic into manageable, named pieces.
Benefits of Functions
| Benefit | Description |
|---|---|
| Reusability | Write once, call from anywhere—no copying/pasting |
| Maintainability | Fix bugs in one place, affects all callers |
| Readability | Named functions document what code does |
| Testability | Test individual functions in isolation |
| Organization | Group related logic together, reduce Event Graph clutter |
| Abstraction | Hide complexity behind simple interfaces |
Real-World Example
Without functions:
Enemy 1 TakeDamage logic (50 nodes)
Enemy 2 TakeDamage logic (50 nodes, slightly different)
Enemy 3 TakeDamage logic (50 nodes, different again)
Boss TakeDamage logic (50 nodes, more different)
Total: 200 nodes, inconsistent behavior, maintenance nightmare
With functions:
Function: CalculateDamage(BaseDamage, Armor, IsCritical)
→ Returns final damage value
→ 15 nodes, one place
Enemy 1: Call CalculateDamage
Enemy 2: Call CalculateDamage
Enemy 3: Call CalculateDamage
Boss: Call CalculateDamage
Total: 15 nodes in function + 4 function calls = 19 nodes
Consistent behavior, easy to maintain
Creating Blueprint Functions
Functions live in the My Blueprint panel alongside variables and event graphs.
How to Create a Function
- Open your Blueprint
- In My Blueprint panel, find the Functions section
- Click the + Function button
- Name your function (e.g.,
CalculateDamage) - Press Enter
- A new function graph opens—this is your workspace
Figure: Functions appear in My Blueprint panel and have their own graph workspace.
Function Structure
Every function has two special nodes automatically created:
- Entry Node (Green): Where execution begins when function is called
- Return Node (Red): Where execution ends, returns to caller
You add your logic between these two nodes.
Simple Function Example: Say Hello
- Create function:
SayHello - Between Entry and Return:
- Add Print String node
- Set text to "Hello, World!"
- Connect: Entry → Print String → Return
- Compile
Using the function:
- In Event Graph, drag the function from My Blueprint panel
- Connect execution flow to it
- When executed, it prints "Hello, World!"
Event BeginPlay → SayHello → (prints "Hello, World!")
✅ Function Naming Conventions
- Use verb-noun format:
CalculateDamage,ApplyHealing,CheckInventory - Be descriptive:
GetPlayerHealthnotGetPH - Booleans: Start with "Is", "Has", "Can":
IsAlive,HasKey,CanJump - Use PascalCase:
OpenDoornotopendoororopen_door
Function Inputs and Outputs
Functions become powerful when they accept inputs (parameters) and return outputs (results).
📖 Key Concept
Function Signature: The combination of a function's name, inputs, and outputs. It's the "contract" that defines what the function needs to work and what it provides back. Think of it like a vending machine: inputs are coins (what you give), outputs are snacks (what you get).
Adding Inputs (Parameters)
- Select your function in My Blueprint panel
- In Details panel, find Inputs section
- Click + (New Parameter)
- Name it (e.g.,
DamageAmount) - Set type (e.g.,
Float) - Optionally set default value
- Compile
The input appears as a pin on the Entry node and when calling the function.
Adding Outputs (Return Values)
- Select your function in My Blueprint panel
- In Details panel, find Outputs section
- Click + (New Parameter)
- Name it (e.g.,
FinalDamage) - Set type (e.g.,
Float) - Compile
The output appears as an input pin on the Return node and as an output pin when calling the function.
Figure: Function with inputs and outputs showing data flow.
Example: Calculate Damage Function
Function Definition:
Function: CalculateDamage
Inputs:
- BaseDamage (Float)
- Armor (Float)
- IsCritical (Boolean)
Output:
- FinalDamage (Float)
Logic:
1. Subtract Armor from BaseDamage
2. If IsCritical, multiply by 2.0
3. Return result as FinalDamage
Using the function:
Enemy TakeDamage Event
→ Call CalculateDamage(BaseDamage=50, Armor=10, IsCritical=false)
→ Returns FinalDamage=40
→ Subtract FinalDamage from Enemy Health
Multiple Inputs and Outputs
Functions can have multiple inputs and multiple outputs:
Function: GetPlayerStats
Inputs: (none)
Outputs:
- CurrentHealth (Float)
- MaxHealth (Float)
- Level (Integer)
- ExperiencePoints (Float)
- IsAlive (Boolean)
All outputs appear as pins on the function call node, giving you access to all return values simultaneously.
Pass by Reference (Advanced)
By default, inputs are "pass by value" (copies). You can make inputs pass by reference to modify the original:
- Select the input parameter in function details
- Check "Pass by Reference"
- The input becomes bidirectional—you can modify it
Example: A function that modifies an array directly instead of returning a modified copy.
⚠️ Pass by Reference: Use Sparingly
Pass by reference can make functions harder to understand—it's not obvious they modify inputs. Use only when necessary for performance (avoiding array copies) or when a function needs to modify multiple things.
Prefer returning new values for clarity.
Local Variables
Functions can have their own local variables—temporary storage that only exists while the function runs.
📖 Definition
Local Variable: A variable that exists only within a single function. It's created when the function starts, lives during execution, and is destroyed when the function returns. Local variables can't be accessed outside their function—they're perfect for intermediate calculations.
Creating Local Variables
Method 1: Promote to Local Variable
- Inside your function graph, create a node that outputs a value
- Right-click the output pin
- Choose "Promote to Local Variable"
- Name it
- A Set node appears for that local variable
Method 2: Add from My Blueprint Panel
- With function graph open, click + Variable in My Blueprint
- The variable is automatically local to this function
- Name and configure it
Local vs. Instance Variables
| Aspect | Local Variable | Instance Variable |
|---|---|---|
| Scope | Only within one function | Entire Blueprint |
| Lifetime | Created on function call, destroyed on return | Exists as long as Blueprint exists |
| Visibility | Not visible to other functions | Visible to all functions and events |
| Memory | Temporary—no memory cost when not running | Always in memory |
| Use Case | Intermediate calculations, loop counters | Actor state, configuration, persistent data |
Example: Calculate Average Function
Function: CalculateAverage
Inputs: NumberArray (Array of Floats)
Output: Average (Float)
Local Variables:
- Sum (Float) = 0.0
- Count (Integer) = 0
Logic:
1. ForEach Loop through NumberArray
→ Add each number to Sum
→ Increment Count
2. Divide Sum by Count
3. Return as Average
Sum and Count are local—they only exist during this function call and can't be accessed elsewhere.
flowchart TD
A[Function Called] --> B[Create Local Variables: Sum=0, Count=0]
B --> C[ForEach Loop: Process Array]
C --> D[Update Sum and Count]
D --> E{More Elements?}
E -->|Yes| C
E -->|No| F[Calculate: Average = Sum / Count]
F --> G[Return Average]
G --> H[Destroy Local Variables]
style A fill:#4CAF50,stroke:#2e7d32,color:#fff
style B fill:#2196F3,stroke:#1565c0,color:#fff
style C fill:#ff9800,stroke:#e65100,color:#fff
style D fill:#2196F3,stroke:#1565c0,color:#fff
style E fill:#ff9800,stroke:#e65100,color:#fff
style F fill:#2196F3,stroke:#1565c0,color:#fff
style G fill:#e74c3c,stroke:#c0392b,color:#fff
style H fill:#9e9e9e,stroke:#616161,color:#fff
Figure: Local variables are created on function entry and destroyed on exit.
✅ When to Use Local Variables
- Intermediate calculations that no other function needs
- Loop counters and temporary accumulators
- Breaking complex expressions into readable steps
- Avoiding clutter in Blueprint's variable list
Golden Rule: If only one function uses it, make it local!
Pure Functions
Pure functions are special—they have no execution pins and can be called from data wires. They're perfect for calculations.
📖 Definition
Pure Function: A function with no side effects—it only takes inputs and produces outputs without modifying any state or triggering actions. Pure functions execute instantly when their inputs are ready, don't require execution wires, and can be called multiple times without consequences. Think of them like mathematical formulas.
Pure vs. Impure Functions
Figure: Pure functions have no execution pins and connect via data wires only.
Creating a Pure Function
- Create a function normally
- Select the function in My Blueprint
- In Details panel, check "Pure"
- Execution pins disappear from Entry and Return nodes
- Function can now be called from data wires
Pure Function Rules
For a function to be pure, it must:
- ✅ Only perform calculations (no Print String, Set Variable, Spawn Actor, etc.)
- ✅ Have at least one output (otherwise, what's the point?)
- ✅ Not modify any state (no setting variables, calling impure functions)
- ✅ Always return the same output for the same inputs (deterministic)
Pure Function Examples
| Function | Pure? | Why? |
|---|---|---|
Add(A, B) |
✅ Yes | Only calculates, no side effects |
GetDistance(PointA, PointB) |
✅ Yes | Mathematical calculation only |
IsInRange(Value, Min, Max) |
✅ Yes | Comparison, returns boolean |
ApplyDamage(Target, Amount) |
❌ No | Modifies Target's health (side effect) |
GetRandomNumber() |
❌ No | Non-deterministic (different each call) |
PrintDebug(Message) |
❌ No | Side effect (prints to screen) |
Advantages of Pure Functions
- ✅ No execution wires: Cleaner graphs, less clutter
- ✅ Call anywhere: Can be used in any data context
- ✅ Automatic caching: Unreal may cache results if called multiple times with same inputs
- ✅ Easier to test: No setup needed—just pass inputs, check outputs
- ✅ Predictable: Same inputs always produce same result
✅ When to Make Functions Pure
If your function:
- Only does math or comparisons
- Doesn't change anything in the world
- Doesn't print, spawn, play sounds, or trigger events
- Would work as a calculator function
Then make it pure! You'll appreciate the cleaner graphs.
Custom Events
Custom Events are like functions but with key differences. They're perfect for event-driven scenarios and Blueprint communication.
📖 Definition
Custom Event: A user-defined event that can be called like a function but behaves like an event node. Custom Events can be triggered from other Blueprints, used with event dispatchers, support replication for multiplayer, and enable delayed/asynchronous execution. They're the bridge between functions and events.
Creating a Custom Event
- Right-click in Event Graph
- Search "Add Custom Event"
- Name it (e.g.,
OnPlayerDeath) - The custom event node appears
- Add logic after the event node
Calling a Custom Event
Once created, you can call it like a function:
- Right-click in Event Graph
- Search for your custom event name
- Select "Call [EventName]"
- Connect execution flow
Adding Inputs to Custom Events
- Select the custom event node
- In Details panel, find Inputs
- Click + (New)
- Name and set type
- The input appears as a pin on both the event and the call node
Note: Custom Events cannot have outputs (return values).
flowchart LR
A[Event BeginPlay] --> B[Do Setup Logic]
B --> C[Call Custom Event: OnGameStart]
D[Custom Event: OnGameStart] --> E[Initialize UI]
E --> F[Spawn Player]
F --> G[Load Game State]
C -.->|Triggers| D
style A fill:#e74c3c,stroke:#c0392b,color:#fff
style B fill:#2196F3,stroke:#1565c0,color:#fff
style C fill:#ff9800,stroke:#e65100,color:#fff
style D fill:#4CAF50,stroke:#2e7d32,color:#fff
style E fill:#2196F3,stroke:#1565c0,color:#fff
style F fill:#2196F3,stroke:#1565c0,color:#fff
style G fill:#2196F3,stroke:#1565c0,color:#fff
Figure: Custom Events are called like functions but execute as event nodes.
Functions vs. Custom Events
Both organize code, but they have important differences. Choosing the right one matters.
Detailed Comparison
| Feature | Functions | Custom Events |
|---|---|---|
| Return Values | ✅ Can return multiple outputs | ❌ Cannot return values |
| Local Variables | ✅ Support local variables | ❌ No local variables |
| Execution | Synchronous (immediate) | Can be delayed or asynchronous |
| Can Be Pure | ✅ Yes (if no side effects) | ❌ No |
| Replication | ❌ Not replicated | ✅ Can be set to replicate (multiplayer) |
| Event Dispatchers | ❌ Can't bind to dispatchers | ✅ Can bind to dispatchers |
| Delay Nodes | ❌ Cannot use Delay | ✅ Can use Delay |
| Timeline Nodes | ❌ Cannot use Timelines | ✅ Can use Timelines |
| Call from Other BPs | ✅ Yes (with reference) | ✅ Yes (with reference) |
| Best For | Calculations, getting values, reusable logic with returns | Event handling, async operations, multiplayer, callbacks |
Decision Guide
flowchart TD
A{Need return value?} -->|Yes| B[Use Function]
A -->|No| C{Need Delay or Timeline?}
C -->|Yes| D[Use Custom Event]
C -->|No| E{Multiplayer replication?}
E -->|Yes| D
E -->|No| F{Bind to Event Dispatcher?}
F -->|Yes| D
F -->|No| G{Need local variables?}
G -->|Yes| B
G -->|No| H[Either works - prefer Function for organization]
style B fill:#4CAF50,stroke:#2e7d32,color:#fff
style D fill:#ff9800,stroke:#e65100,color:#fff
style H fill:#2196F3,stroke:#1565c0,color:#fff
Figure: Decision tree for choosing between Functions and Custom Events.
Usage Examples
✅ Use Functions For:
CalculateDamage()→ returns damage valueGetPlayerHealth()→ returns healthIsInRange()→ returns booleanConvertToMeters()→ returns converted valueFindClosestEnemy()→ returns enemy reference
✅ Use Custom Events For:
OnPlayerDeath()→ triggered when player diesServerSpawnItem()→ replicated spawnPlayCutscene()→ uses delays/timelinesOnLevelComplete()→ event dispatch triggerShowNotification()→ timed UI display
✅ Rule of Thumb
If you need an answer (return value), use a Function. If you need to trigger a sequence of actions (especially with timing/multiplayer), use a Custom Event.
Blueprint Macros
Macros are like functions on steroids—they can have multiple execution outputs and collapse complex node groups into single reusable nodes.
📖 Definition
Blueprint Macro: A collapsed node graph that can have multiple execution inputs and outputs, allowing you to create custom flow control nodes. Unlike functions, macros are "copy-pasted" into each usage location during compilation, making them perfect for complex control flow that functions can't handle.
Why Macros Exist
Functions have limitations:
- ❌ Only one execution output (Entry → Return)
- ❌ Can't use Delay nodes
- ❌ Can't create custom flow control (like Branch with 3+ outputs)
Macros solve these problems—they can have multiple execution outputs, making them perfect for custom logic flow.
Creating a Macro
- In My Blueprint panel, find Macros section
- Click + Macro
- Name it (e.g.,
SafeDivide) - A macro graph opens with Inputs and Outputs nodes
- Build your logic between them
Macro Structure
Macros have special tunnel nodes:
- Inputs Node: Where execution and data enter
- Outputs Node: Where execution and data exit (can have multiple execution outputs!)
Figure: Macro with multiple execution outputs enables custom flow control.
Macro vs. Function
| Feature | Function | Macro |
|---|---|---|
| Execution Outputs | One only (Return) | Multiple (custom flow control) |
| Compilation | Compiled once, called | Copy-pasted into each usage |
| Delay Nodes | ❌ Not allowed | ✅ Allowed |
| Local Variables | ✅ Supported | ❌ Not supported |
| Performance | More efficient (one copy) | Slight overhead (copied each use) |
| Debugging | Easier (one location) | Harder (multiple copies) |
| Best For | Standard reusable logic | Custom flow control, complex branching |
When to Use Macros
- ✅ Need multiple execution outputs (custom flow control)
- ✅ Creating utility nodes used in many places
- ✅ Simplifying complex node groups into single nodes
- ✅ Building custom Branch-like nodes
Example use cases:
SafeDivide- Success/Error pathsTryGetActor- Found/NotFound pathsValidateInput- Valid/Invalid pathsSwitchOnHealth- High/Medium/Low/Critical paths
⚠️ Macro Caution
Because macros are copy-pasted, using a macro 10 times means 10 copies of that logic in your compiled Blueprint. This can bloat file size and make debugging harder.
Rule: Use functions for standard logic. Reserve macros for when you absolutely need multiple execution outputs.
Function Libraries
Function Libraries are collections of static functions available project-wide—perfect for utility functions you use everywhere.
📖 Definition
Blueprint Function Library: A special Blueprint class that contains only static (class-level) functions accessible from any Blueprint in your project without needing an instance. Think of it as a toolbox of utility functions—math helpers, string manipulations, common calculations—available everywhere.
Creating a Function Library
- Content Browser → Right-click → Blueprint Class
- Search for and select Blueprint Function Library
- Name it (e.g.,
BP_MathUtilities,BP_StringHelpers) - Open it
- Add functions (all are automatically static)
Example: Math Utilities Library
Blueprint Function Library: BP_MathUtilities
Functions:
- ClampValue(Value, Min, Max) → Returns clamped value
- IsInRange(Value, Min, Max) → Returns boolean
- GetRandomPointInRadius(Center, Radius) → Returns Vector
- CalculatePercentage(Part, Whole) → Returns Float
- RoundToNearest(Value, Increment) → Returns rounded value
Now any Blueprint in your project can call these functions without needing a reference to the library—they just appear in the node menu!
Using Function Library Functions
- In any Blueprint, right-click in Event Graph
- Search for your function name
- It appears under the library name
- Use it like any other function
flowchart TD
A[BP_MathUtilities Library] --> B[ClampValue Function]
A --> C[IsInRange Function]
A --> D[GetRandomPoint Function]
E[BP_Player] -.->|Can call| B
F[BP_Enemy] -.->|Can call| B
G[BP_Weapon] -.->|Can call| C
H[BP_SpawnManager] -.->|Can call| D
I[Available project-wide without instances!]
style A fill:#9c27b0,stroke:#6a1b9a,color:#fff
style B fill:#2196F3,stroke:#1565c0,color:#fff
style C fill:#2196F3,stroke:#1565c0,color:#fff
style D fill:#2196F3,stroke:#1565c0,color:#fff
style E fill:#4CAF50,stroke:#2e7d32,color:#fff
style F fill:#4CAF50,stroke:#2e7d32,color:#fff
style G fill:#4CAF50,stroke:#2e7d32,color:#fff
style H fill:#4CAF50,stroke:#2e7d32,color:#fff
style I fill:#ff9800,stroke:#e65100,color:#fff
Figure: Function Libraries provide project-wide utility functions accessible everywhere.
Best Practices for Function Libraries
- ✅ Group by category:
BP_MathUtilities,BP_StringHelpers,BP_ArrayUtils - ✅ Make functions pure when possible (no side effects)
- ✅ Document well—add tooltips to each function
- ✅ Keep functions generic and reusable
- ❌ Don't put game-specific logic here—keep it universal
Common Function Library Categories
| Library | Example Functions |
|---|---|
| Math Utilities | Clamp, Lerp, Normalize, RoundTo, RandomInRange |
| String Helpers | Contains, StartsWith, EndsWith, Split, Join, Capitalize |
| Array Utilities | Shuffle, RemoveDuplicates, FindClosest, SortByDistance |
| Vector/Transform | GetDirectionTo, GetDistance2D, RotateAround, LookAt |
| Debug Helpers | DrawDebugLine, LogWarning, PrintStats, DumpVariables |
Organization and Best Practices
Function Naming Conventions
| Type | Pattern | Examples |
|---|---|---|
| Actions | Verb + Object | ApplyDamage, OpenDoor, SpawnEnemy |
| Getters | Get + Property | GetHealth, GetPosition, GetPlayerName |
| Setters | Set + Property | SetHealth, SetPosition, SetPlayerName |
| Booleans | Is/Has/Can + State | IsAlive, HasKey, CanJump |
| Calculations | Calculate/Compute + What | CalculateDamage, ComputeDistance |
| Events | On + Event | OnPlayerDeath, OnLevelComplete |
Documentation: Tooltips and Categories
Adding tooltips:
- Select your function in My Blueprint
- In Details panel, find Tooltip field
- Write a brief description of what it does
- This appears when hovering over the node
Adding categories:
- In Details panel, find Category field
- Type category name (e.g., "Combat", "Movement", "Utilities")
- Functions are grouped by category in My Blueprint and node menu
Organization Tips
✅ Do This
- Group related functions in categories
- Keep functions small (single responsibility)
- Add descriptive tooltips
- Use consistent naming patterns
- Comment complex logic inside functions
- Create function libraries for utilities
- Make functions pure when possible
❌ Avoid This
- Giant "god functions" that do everything
- Vague names like
DoStufforProcess - Mixing unrelated logic in one function
- Copy-pasting function code instead of reusing
- Leaving functions undocumented
- Too many parameters (>5 usually means split it)
Function Complexity Guidelines
- Small: 5-15 nodes - Perfect! Easy to understand and test
- Medium: 15-40 nodes - Acceptable, consider breaking into helper functions
- Large: 40-100 nodes - Too complex, definitely split it up
- Huge: 100+ nodes - Red flag! This should be multiple functions
Debugging Functions
Techniques:
- Print String: Add temporary prints to track execution flow
- Breakpoints: Right-click node → Add Breakpoint (pauses execution)
- Watch Values: Right-click variable → Watch This Value
- Step Through: When paused at breakpoint, use F10 to step node-by-node
- Return Values: Print return values to verify calculations
🏋️ Hands-On Exercise: Health System with Functions
Build a complete health system using functions, demonstrating all the concepts from this lesson.
Part 1: Create the Blueprint
- Create
BP_HealthSystem(Actor) - Add variables:
CurrentHealth(Float) = 100.0MaxHealth(Float) = 100.0 (Instance Editable)MinHealth(Float) = 0.0RegenerationRate(Float) = 5.0 (Instance Editable)
Part 2: Create Pure Functions
Function: GetHealthPercentage
- Inputs: (none)
- Output:
Percentage(Float) - Check "Pure"
- Logic: CurrentHealth / MaxHealth
Function: IsAlive
- Inputs: (none)
- Output:
bAlive(Boolean) - Check "Pure"
- Logic: CurrentHealth > 0
Function: IsLowHealth
- Inputs:
Threshold(Float, default 0.25) - Output:
bIsLow(Boolean) - Check "Pure"
- Logic: GetHealthPercentage() < Threshold
Part 3: Create Action Functions
Function: ApplyDamage
Inputs: DamageAmount (Float)
Output: ActualDamage (Float)
Logic:
1. Clamp DamageAmount to be >= 0
2. Subtract from CurrentHealth
3. Clamp CurrentHealth (Min=MinHealth, Max=MaxHealth)
4. Return actual damage dealt
5. If CurrentHealth <= 0, call custom event OnDeath
Function: ApplyHealing
Inputs: HealAmount (Float)
Output: ActualHealing (Float)
Logic:
1. Store old health
2. Add HealAmount to CurrentHealth
3. Clamp CurrentHealth (Min=MinHealth, Max=MaxHealth)
4. Calculate actual healing = CurrentHealth - OldHealth
5. Return actual healing
Part 4: Create Custom Event
Custom Event: OnDeath
Logic:
1. Print "Player has died!"
2. Delay 2 seconds
3. Print "Respawning..."
4. Set CurrentHealth = MaxHealth
Part 5: Test in Event Graph
Event BeginPlay
→ Print: GetHealthPercentage() * 100 + "%"
Event Tick (simple regeneration)
→ Branch: IsAlive() AND IsLowHealth(0.5)
→ True: ApplyHealing(RegenerationRate * DeltaSeconds)
Keyboard Event: 1
→ ApplyDamage(10)
→ Print: "Health: " + CurrentHealth
Keyboard Event: 2
→ ApplyHealing(25)
→ Print: "Health: " + CurrentHealth
Part 6: Test
- Place BP_HealthSystem in level
- Play
- Press 1 repeatedly to damage (watch health decrease)
- Damage to 0 - "Player has died!" appears, then respawns
- Press 2 to heal
- Damage to below 50% and watch auto-regeneration
✅ Checkpoint: What did you learn?
This exercise combined:
- Pure Functions: GetHealthPercentage, IsAlive, IsLowHealth
- Functions with I/O: ApplyDamage, ApplyHealing with return values
- Custom Events: OnDeath with Delay for timed sequence
- Local Variables: OldHealth in ApplyHealing function
- Organization: Clean separation of concerns
Challenge: Create a Function Library
- Create
BP_MathUtilities(Function Library) - Add function:
ClampValue(Value, Min, Max)→ Returns clamped Float - Make it Pure
- Add tooltip: "Clamps a value between min and max"
- Use it in your ApplyDamage/ApplyHealing functions instead of the built-in Clamp node
- Verify it works from any Blueprint in your project!
Summary
Functions and macros are the organizational backbone of professional Blueprint development. Master them and your code becomes readable, maintainable, and a pleasure to work with.
Key Takeaways
- ⚙️ Functions are reusable, named blocks of logic—write once, use everywhere
- 📥 Inputs and Outputs make functions flexible—accept parameters, return values
- 📍 Local Variables exist only within functions—perfect for temporary calculations
- ✨ Pure Functions have no side effects, no execution pins—ideal for calculations
- 📡 Custom Events can't return values but support Delay, Timelines, and replication
- 🔀 Functions vs. Events: Functions for returns, Events for async/multiplayer/delays
- 🎭 Macros have multiple execution outputs—use for custom flow control only
- 📚 Function Libraries provide project-wide utilities accessible everywhere
- 📝 Best Practices: Descriptive names, tooltips, categories, small functions, consistent patterns
- 🐛 Debugging: Breakpoints, Print String, Watch Values, step-through execution
What's Next?
With functions and macros mastered, the next lesson explores Lesson 3.5: Blueprint Project Organization. You'll learn folder structures, naming conventions, parent/child Blueprint hierarchies, and how to architect large-scale Blueprint projects that scale gracefully as your game grows.
✅ Self-Check Quiz
Before moving on, make sure you can answer these questions:
- What's the difference between a function and a macro?
- How do you add inputs and outputs to a function?
- What are local variables and when should you use them?
- What makes a function "pure" and what are the benefits?
- When should you use a Custom Event instead of a Function?
- What are Function Libraries and why are they useful?
- Name three best practices for function organization.
📝 Show Answers
- Functions have one execution output and are compiled once. Macros can have multiple execution outputs and are copy-pasted into each usage. Use functions for standard logic, macros only when you need multiple execution paths.
- Select the function in My Blueprint, then in Details panel use the Inputs and Outputs sections to add parameters with names and types.
- Local variables exist only within a single function, created on entry and destroyed on exit. Use them for intermediate calculations and temporary data that no other function needs.
- Pure functions have no side effects and no execution pins—they only perform calculations. Benefits: cleaner graphs, can be called from data wires, easier to test, predictable results.
- Use Custom Events when you need: Delay nodes, Timeline nodes, multiplayer replication, or binding to Event Dispatchers. Use Functions when you need return values or local variables.
- Function Libraries are collections of static utility functions available project-wide without needing instances. They're perfect for math helpers, string utilities, and common calculations used throughout your project.
- Use descriptive verb-noun names, add tooltips for documentation, organize with categories, keep functions small (single responsibility), make pure when possible, document complex logic with comments.