Skip to main content

⚙️ 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

  1. Open your Blueprint
  2. In My Blueprint panel, find the Functions section
  3. Click the + Function button
  4. Name your function (e.g., CalculateDamage)
  5. Press Enter
  6. A new function graph opens—this is your workspace
Function Anatomy in My Blueprint Panel MY BLUEPRINT ▾ Variables • Health (Float) • MaxHealth (Float) ▾ Functions ƒ CalculateDamage ƒ ApplyHealing ƒ CheckDeath + Function ▾ Event Graphs Event Graph Functions appear here Click to edit the function graph CalculateDamage Function Graph Entry Return Node Your function logic goes here Add nodes between Entry and Return

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

  1. Create function: SayHello
  2. Between Entry and Return:
    • Add Print String node
    • Set text to "Hello, World!"
  3. Connect: Entry → Print String → Return
  4. Compile

Using the function:

  1. In Event Graph, drag the function from My Blueprint panel
  2. Connect execution flow to it
  3. When executed, it prints "Hello, World!"
Event BeginPlay → SayHello → (prints "Hello, World!")

✅ Function Naming Conventions

  • Use verb-noun format: CalculateDamage, ApplyHealing, CheckInventory
  • Be descriptive: GetPlayerHealth not GetPH
  • Booleans: Start with "Is", "Has", "Can": IsAlive, HasKey, CanJump
  • Use PascalCase: OpenDoor not opendoor or open_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)

  1. Select your function in My Blueprint panel
  2. In Details panel, find Inputs section
  3. Click + (New Parameter)
  4. Name it (e.g., DamageAmount)
  5. Set type (e.g., Float)
  6. Optionally set default value
  7. Compile

The input appears as a pin on the Entry node and when calling the function.

Adding Outputs (Return Values)

  1. Select your function in My Blueprint panel
  2. In Details panel, find Outputs section
  3. Click + (New Parameter)
  4. Name it (e.g., FinalDamage)
  5. Set type (e.g., Float)
  6. 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:

  1. Select the input parameter in function details
  2. Check "Pass by Reference"
  3. 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

  1. Inside your function graph, create a node that outputs a value
  2. Right-click the output pin
  3. Choose "Promote to Local Variable"
  4. Name it
  5. A Set node appears for that local variable

Method 2: Add from My Blueprint Panel

  1. With function graph open, click + Variable in My Blueprint
  2. The variable is automatically local to this function
  3. 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

  1. Create a function normally
  2. Select the function in My Blueprint
  3. In Details panel, check "Pure"
  4. Execution pins disappear from Entry and Return nodes
  5. 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

  1. Right-click in Event Graph
  2. Search "Add Custom Event"
  3. Name it (e.g., OnPlayerDeath)
  4. The custom event node appears
  5. Add logic after the event node

Calling a Custom Event

Once created, you can call it like a function:

  1. Right-click in Event Graph
  2. Search for your custom event name
  3. Select "Call [EventName]"
  4. Connect execution flow

Adding Inputs to Custom Events

  1. Select the custom event node
  2. In Details panel, find Inputs
  3. Click + (New)
  4. Name and set type
  5. 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 value
  • GetPlayerHealth() → returns health
  • IsInRange() → returns boolean
  • ConvertToMeters() → returns converted value
  • FindClosestEnemy() → returns enemy reference

✅ Use Custom Events For:

  • OnPlayerDeath() → triggered when player dies
  • ServerSpawnItem() → replicated spawn
  • PlayCutscene() → uses delays/timelines
  • OnLevelComplete() → event dispatch trigger
  • ShowNotification() → 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

  1. In My Blueprint panel, find Macros section
  2. Click + Macro
  3. Name it (e.g., SafeDivide)
  4. A macro graph opens with Inputs and Outputs nodes
  5. 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!)
Macro with Multiple Execution Outputs Example: SafeDivide (returns result OR error path if divide by zero) Inputs Exec A B Branch B != 0? True False Divide A / B Outputs Success Error Result Check if B is zero Safe to divide Division by zero! Using the Macro in Event Graph: SafeDivide A B Success Error Result → Use result if success → Handle error if divide by zero

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 paths
  • TryGetActor - Found/NotFound paths
  • ValidateInput - Valid/Invalid paths
  • SwitchOnHealth - 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

  1. Content Browser → Right-click → Blueprint Class
  2. Search for and select Blueprint Function Library
  3. Name it (e.g., BP_MathUtilities, BP_StringHelpers)
  4. Open it
  5. 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

  1. In any Blueprint, right-click in Event Graph
  2. Search for your function name
  3. It appears under the library name
  4. 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:

  1. Select your function in My Blueprint
  2. In Details panel, find Tooltip field
  3. Write a brief description of what it does
  4. This appears when hovering over the node

Adding categories:

  1. In Details panel, find Category field
  2. Type category name (e.g., "Combat", "Movement", "Utilities")
  3. 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 DoStuff or Process
  • 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

  1. Create BP_HealthSystem (Actor)
  2. Add variables:
    • CurrentHealth (Float) = 100.0
    • MaxHealth (Float) = 100.0 (Instance Editable)
    • MinHealth (Float) = 0.0
    • RegenerationRate (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

  1. Place BP_HealthSystem in level
  2. Play
  3. Press 1 repeatedly to damage (watch health decrease)
  4. Damage to 0 - "Player has died!" appears, then respawns
  5. Press 2 to heal
  6. 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
You've built a production-quality, reusable health system!

Challenge: Create a Function Library

  1. Create BP_MathUtilities (Function Library)
  2. Add function: ClampValue(Value, Min, Max) → Returns clamped Float
  3. Make it Pure
  4. Add tooltip: "Clamps a value between min and max"
  5. Use it in your ApplyDamage/ApplyHealing functions instead of the built-in Clamp node
  6. 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:

  1. What's the difference between a function and a macro?
  2. How do you add inputs and outputs to a function?
  3. What are local variables and when should you use them?
  4. What makes a function "pure" and what are the benefits?
  5. When should you use a Custom Event instead of a Function?
  6. What are Function Libraries and why are they useful?
  7. Name three best practices for function organization.
📝 Show Answers
  1. 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.
  2. Select the function in My Blueprint, then in Details panel use the Inputs and Outputs sections to add parameters with names and types.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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.