40 KiB
Part 2: Create the Player
← Previous: Part 1 - Project Setup | Back to Index | Next: Part 3 - Create the Bullet →
Step 2.1: Create Player Blueprint
-
In Content Browser, double-click "Blueprints" folder to open it
-
Right-click in empty space → Blueprint Class
-
In the popup window "Pick Parent Class":
- Click "Pawn" (NOT Character - we want simple 2D control)
- Click "Select"
-
Name the new Blueprint:
BP_Player -
Double-click "BP_Player" to open the Blueprint Editor
Expected Result
A new tab opens showing the Blueprint Editor with:
- Components panel on the left
- Viewport in the center
- Details panel on the right
- Tab bar above the Viewport with: Viewport | Construction Script | Event Graph (third from left)
Step 2.2: Add Player Visual Components
-
In the Components panel (left side), you'll see "DefaultSceneRoot"
- Click on "DefaultSceneRoot" to select it (this ensures new components are added as children)
-
With DefaultSceneRoot selected, click "Add" button (green, top of Components panel)
- Search for "Paper Sprite"
- Click "Paper Sprite" to add it (it will be added as a child of DefaultSceneRoot)
- Rename it to
PlayerSprite(click on it, then press F2)
NOTE: If Paper Sprite is not available:
- Go to Edit → Plugins (from main menu bar)
- Search for "Paper2D"
- Make sure "Paper2D" plugin is ENABLED
- Restart the editor if prompted
-
With "PlayerSprite" selected, look at Details panel (right side):
- Find "Source Sprite" property
- For now, leave it empty (we'll create sprites later)
- Under "Transform", set Scale to
(1.0, 1.0, 1.0)
-
With DefaultSceneRoot still selected, click "Add" button again:
- Search for "Box Collision"
- Click "Box Collision" to add it (it will be added as a child of DefaultSceneRoot)
- Rename it to
PlayerCollision
-
With "PlayerCollision" selected, in Details panel:
- Under "Shape", set Box Extent to:
X=25, Y=25, Z=10 - Under "Collision", click "Collision Presets" dropdown
- Select "Custom..."
- Set "Collision Enabled" to "Query Only (No Physics Collision)"
- Enable "Generate Overlap Events" box
- Under "Shape", set Box Extent to:
-
With DefaultSceneRoot still selected, click "Add" button:
- Search for "Arrow"
- Click "Arrow" to add it (it will be added as a child of DefaultSceneRoot)
- This shows which direction is "forward" (useful for debugging)
-
With DefaultSceneRoot still selected, click "Add" button:
- Search for "Cube" → add "Cube" (Static Mesh)
- Rename it to
TempVisual - In Details panel, set Scale to
(0.5, 0.5, 0.1)so it's small and flat - This provides a visible placeholder until we add proper sprites in Part 9
-
With DefaultSceneRoot still selected, click "Add" button:
- Search for "Camera" → add "Camera"
- Rename it to
PlayerCamera - In Details panel, set Location to:
X=0, Y=0, Z=1000(1000 units above player) - Set Rotation to:
X=0, Y=-90, Z=0(looking straight down) - In Details panel, find "Activation" category → "Auto Activate" checkbox
- This should already be ENABLED by default
- If not, check/enable it - this tells Unreal to use THIS camera when playing
- This camera will follow the player, making testing easy
NOTE: This temporary camera ensures you can see the player during testing. In Part 8, we'll set up a proper fixed camera for the final game.
Expected Result
Components panel shows:
DefaultSceneRoot
├── PlayerSprite (Paper Sprite)
├── PlayerCollision (Box Collision)
├── Arrow
├── TempVisual (Cube)
└── PlayerCamera (Camera)
-
IMPORTANT - Enable Auto Possession for testing:
- Click on "BP_Player (Self)" at the top of the Components panel (the root)
- In Details panel, find "Pawn" category
- Find "Auto Possess Player" dropdown (currently set to "Disabled")
- Change it to "Player 0"
- This makes the engine automatically possess this pawn when playing, which activates the PlayerCamera
NOTE: This setting will be overridden later when we set up the GameMode in Part 8, which handles possession automatically.
Step 2.3: Create Player Variables
(Continue in the same BP_Player Blueprint Editor tab that was opened in Step 2.1)
-
In the Blueprint Editor, look at the left panel (below the Components panel)
-
Find "My Blueprint" section
-
Under "Variables", click the "+" button
-
Create these variables one by one (click +, then set properties in Details):
NOTE: Variables are NOT added to the Components hierarchy. They appear in a separate "Variables" list within the "My Blueprint" panel. Variables store data values, while Components are physical/visual parts of the actor.
| # | Variable Name | Type | Default Value |
|---|---|---|---|
| 1 | MoveSpeed |
Float | 750.0 |
| 2 | BoundsMin |
Vector 2D | X=-850, Y=-450 |
| 3 | BoundsMax |
Vector 2D | X=850, Y=450 |
| 4 | FireInterval |
Float | 0.08 |
| 5 | FireTimer |
Float | 0.0 |
| 6 | BulletSpeed |
Float | 2200.0 |
| 7 | MaxLives |
Integer | 3 |
| 8 | CurrentLives |
Integer | 3 |
| 9 | VolleySize |
Integer | 3 |
| 10 | VolleySpread |
Float | 12.0 |
| 11 | SpecialUsed |
Boolean | false (unchecked) |
| 12 | BulletClass |
Class Reference* | (set later) |
*For BulletClass: In the type dropdown, go to "Object Types" category → "Actor" → select "Class Reference" (the subcategory under Actor). This will hold reference to bullet blueprint class for spawning.
TIP: For Variable 1 (MoveSpeed), click the "eye" icon to make it public/editable. Compile the blueprint (Click on "Compile" third option from left or simply
Ctrl + Alt) before setting the default value.
- Click "Compile" button (top left, blue checkmark icon)
- Click "Save" button (floppy disk icon next to Compile)
Expected Result
My Blueprint panel shows:
- All 12 variables you created listed under "Variables"
- A "Components" category with 4 component references (DefaultSceneRoot, PlayerSprite, PlayerCollision, Arrow) - these are automatically created from the components you added in Step 2.2
Step 2.4: Set Up Enhanced Input System
UE5 uses the Enhanced Input System. Before creating movement logic in our Blueprint, we first need to create Input Actions and an Input Mapping Context.
0. ENABLE ENHANCED INPUT IN PROJECT SETTINGS (CRITICAL)
Before creating any input assets, you MUST configure the project to use Enhanced Input as the default input system:
- Go to Edit → Project Settings
- In the left sidebar, scroll down to "Engine" section
- Click on "Input"
- Find "Default Classes" section (near the top)
- Set "Default Player Input Class" to:
EnhancedPlayerInput - Set "Default Input Component Class" to:
EnhancedInputComponent - Close Project Settings
⚠️ WARNING: If you skip this step, WASD controls will NOT work even if everything else is set up correctly!
1. SET UP INPUT ASSETS (in Content Drawer)
A) CREATE INPUT ACTIONS
-
Open Content Drawer (
Ctrl+Spaceor click "Content Drawer" at bottom) -
In Content folder, right-click → New Folder → name it
Input -
Inside Input folder, right-click → Input → Input Action
- Name it
IA_Move - Double-click to open
- Set "Value Type" to
Axis2D (Vector2D) - Save and close
- Name it
-
Create another Input Action:
IA_Fire- Set "Value Type" to
Digital (bool)(default)
- Set "Value Type" to
-
Create another Input Action:
IA_Special- Set "Value Type" to
Digital (bool)
- Set "Value Type" to
B) CREATE INPUT MAPPING CONTEXT
-
In Input folder, right-click → Input → Input Mapping Context
- Name it
IMC_Default - Double-click to open
- Name it
-
Click "+" next to "Mappings" to add IA_Move:
- Select "IA_Move" from dropdown
- Click "+" under IA_Move to add key bindings:
NOTE: In Unreal's top-down view, X axis = up/down, Y axis = left/right
For W key (move UP on screen):
- Click "+", select "W"
- No modifiers needed (outputs X positive = up)
- TOTAL MODIFIERS FOR W: 0
For S key (move DOWN on screen):
- Click "+", select "S"
- Click "+" next to "Modifiers" → Add "Negate"
- TOTAL MODIFIERS FOR S: 1 (Negate only)
For A key (move LEFT on screen):
- Click "+", select "A"
- Click "+" next to "Modifiers" → Add "Swizzle Input Axis Values" → Order: "YXZ"
- Click "+" next to "Modifiers" AGAIN → Add "Negate"
- TOTAL MODIFIERS FOR A: 2 (Swizzle AND Negate)
For D key (move RIGHT on screen):
- Click "+", select "D"
- Click "+" next to "Modifiers" → Add "Swizzle Input Axis Values" → Order: "YXZ"
- TOTAL MODIFIERS FOR D: 1 (Swizzle only)
Summary Table - verify your IMC_Default matches this:
Key Modifiers Output Vector W (none) (1, 0) = UP S Negate (-1, 0) = DOWN A Swizzle Input Axis Values (YXZ) + Negate (0, -1) = LEFT D Swizzle Input Axis Values (YXZ) (0, 1) = RIGHT After adding all keys, press
Ctrl+Sto SAVE IMC_Default! -
Click "+" to add IA_Fire:
- Select "IA_Fire"
- Add key:
Z - Add key:
Left Mouse Button
-
Click "+" to add IA_Special:
- Select "IA_Special"
- Add key:
X - Add key:
Right Mouse Button
-
Save the Input Mapping Context
2. NOW OPEN BP_PLAYER BLUEPRINT
- In Content Drawer, navigate to Content → Blueprints
- Double-click "BP_Player" to open the Blueprint Editor
- Click on "Event Graph" tab (above the main view, third tab from left, next to Construction Script)
You should see three default events (red nodes):
- Event BeginPlay
- Event ActorBeginOverlap
- Event Tick
3. ADD MAPPING CONTEXT IN BEGINPLAY
From "Event BeginPlay" node:
-
Right-click in empty space → search
Get Player Controller→ add it- Use the one under "Game" → "Player" category
- It has a "Player Index" input (default 0) and "Return Value" output
-
From the blue "Return Value" output, drag and search
Get Enhanced Input Local Player Subsystem -
From that blue output, drag and search
Add Mapping Context -
For "Mapping Context" input:
- Click dropdown and select
IMC_Default - Or drag IMC_Default from Content Drawer
- Click dropdown and select
-
Set "Priority" to
0 -
CRITICAL - Connect the EXECUTION wire (white wire):
-
The WHITE TRIANGLE pins are different from the BLUE CIRCLE pins!
-
Blue circles = DATA (what values to use)
-
White triangles = EXECUTION (when to run)
-
On "Event BeginPlay", find the WHITE TRIANGLE on the RIGHT side
-
On "Add Mapping Context", find the WHITE TRIANGLE on the LEFT side
-
Click and DRAG from BeginPlay's white triangle to Add Mapping Context's white triangle
WITHOUT THIS WHITE WIRE, THE CODE NEVER RUNS!
-
Your graph should look like this:
┌──────────────────┐ ┌─────────────────────────────────────┐
│ Event BeginPlay │ │ Add Mapping Context │
│ ├────────►│► │
│ │ WHITE │ Target ○────────────────┐ │
└──────────────────┘ WIRE │ Mapping Context: IMC_Default │
│ Priority: 0 │
└─────────────────────────────────────┘
▲
┌──────────────────┐ │
│Get Player │ ┌─────────────────────────┐
│Controller ├───►│ Enhanced Input Local │
│ Player Index [0] │ │ Player Subsystem ├───┘
└──────────────────┘ └─────────────────────────┘
(blue data wires)
VERIFY: You must have BOTH:
- Blue data wires connecting the nodes (passes the subsystem reference)
- White execution wire from Event BeginPlay to Add Mapping Context (makes it run)
4. INPUT DEBUG TEST (verify input works - separate from movement logic)
Before creating movement, let's verify input is working with a separate debug setup. This debug logic is COMPLETELY INDEPENDENT from movement - you can delete it later without affecting anything.
a) Create debug variables (these are ONLY for debugging):
- In My Blueprint panel → Variables → click "+"
- Name:
DEBUG_LastMoveInput - Type: Vector 2D
- Default Value: (0, 0)
- Compile to save
b) Create a SEPARATE debug event using a Custom Event:
- Right-click in empty space → search
Custom Event→ add it - Name it
DEBUG_PrintInput - This keeps debug logic completely isolated
c) Build the debug logic from DEBUG_PrintInput:
-
Get the current input value:
- Right-click → "Get Player Controller"
- Drag → "Get Enhanced Input Local Player Subsystem"
- Drag → search "IA_Move" (under Input → Enhanced Action Values)
-
Compare to last value (only print on change):
- Right-click → "Not Equal (Vector2D)"
- Connect IA_Move to TOP input
- "Get DEBUG_LastMoveInput" → connect to BOTTOM input
-
Branch on change:
- From DEBUG_PrintInput WHITE pin → "Branch"
- Connect != result to Condition
-
On TRUE (changed):
- "Set DEBUG_LastMoveInput" = IA_Move value
- Then → "Print String" with IA_Move value
d) Call the debug event from Event Tick:
- From "Event Tick" WHITE pin → search
DEBUG_PrintInput - This adds a node that calls your custom event
Visual (two separate areas in your graph):
AREA 1 - Event Tick (will later have movement):
┌─────────────┐ ┌─────────────────────┐
│ Event Tick │─────►│ DEBUG_PrintInput │
└─────────────┘ │ (call custom event) │
└─────────────────────┘
AREA 2 - Debug logic (completely separate):
┌─────────────────────┐
│ DEBUG_PrintInput │
│ (Custom Event) │
└──────────┬──────────┘
│
▼
┌─────────────────────────────┐
│ Branch │
│ Condition: IA_Move != Last │
└──────┬──────────────────────┘
│ TRUE
▼
┌──────────────────┐ ┌──────────────┐
│Set DEBUG_LastMove│────►│ Print String │
└──────────────────┘ └──────────────┘
e) Compile and Save
f) Drag BP_Player into level, Press Play (Alt+P)
g) IMPORTANT: Click inside the game viewport to give it keyboard focus!
h) Press WASD keys and look at top-left of screen
Expected: Message appears ONLY when you press or release a key:
- Press W: prints "X=1.0 Y=0.0"
- Release W: prints "X=0.0 Y=0.0"
- Log is NOT spammed every frame
TO DISABLE DEBUG LATER: Simply delete the "DEBUG_PrintInput" call node from Event Tick. The Custom Event and its logic can stay (unused) or be deleted entirely - movement will be unaffected either way.
Debugging Steps (if you see "X=0.0 Y=0.0" always):
DEBUGGING STEP 1 - Verify the pawn is possessed
- When playing, look at the Outliner (right panel)
- Find BP_Player in the list
- If it shows a small controller icon next to it, it's possessed
- If NOT possessed: Check that "Auto Possess Player" = "Player 0" in BP_Player
DEBUGGING STEP 2 - Verify the game window has keyboard focus
- After pressing Play, CLICK INSIDE THE GAME VIEWPORT
- The viewport must have focus to receive keyboard input
- Try pressing
Shift+F1to release mouse, then click viewport again - Then press WASD again
DEBUGGING STEP 3 - Test with a simpler Print String
- Delete the current Print String setup
- From Event Tick, drag → Print String
- Type "Tick is running" in the In String field (just plain text)
- Play the game - you should see "Tick is running" spam in top-left
- If you DON'T see this: The blueprint isn't running at all
DEBUGGING STEP 4 - Test BeginPlay is running
- From Event BeginPlay, AFTER the Add Mapping Context node
- Add another Print String with text "BeginPlay executed"
- Connect the white wire: BeginPlay → Add Mapping Context → Print String
- Play the game - you should see "BeginPlay executed" once at start
- If you DON'T see this: BeginPlay isn't running (pawn not spawned?)
DEBUGGING STEP 5 - Check if there are MULTIPLE BP_Player in level
- Look in Outliner for multiple BP_Player instances
- Delete ALL of them
- Drag in exactly ONE fresh BP_Player from Content Drawer
- Try again
DEBUGGING STEP 6 - Nuclear option (create fresh test)
- Create a NEW blueprint: "BP_InputTest" (Pawn class)
- Add ONLY: Auto Possess Player = Player 0
- In Event Graph: Event BeginPlay → Add Mapping Context (IMC_Default)
- In Event Graph: Event Tick → Print String (connected to Get IA_Move)
- Drag this minimal test blueprint into level
- If THIS works, something is wrong with your BP_Player
IF THIS TEST WORKS: Continue to step 5. You can optionally delete the DEBUG_PrintInput call from Event Tick to stop the debug prints. IF THIS TEST FAILS: Work through debugging steps above.
5. MOVEMENT LOGIC - Create this node network
NOTE: If you added debug from step 4, Event Tick already has a wire to DEBUG_PrintInput. To have BOTH debug AND movement run from Event Tick, you need a Sequence node (one output pin = one wire only).
a) INSERT A SEQUENCE NODE between Event Tick and debug:
- Delete the wire from Event Tick to DEBUG_PrintInput
- Right-click → search
Sequence→ add it - Connect Event Tick → Sequence (input)
- Connect Sequence "Then 0" → DEBUG_PrintInput
- Sequence "Then 1" will be used for movement
RESULT:
Event Tick → Sequence ─┬─ Then 0 → DEBUG_PrintInput
│
└─ Then 1 → (movement, next steps)
If you skipped debug, just connect Event Tick directly to movement.
b) Starting point for movement:
- If using Sequence: drag from "Then 1" output
- If no debug: drag from Event Tick directly
c) Right-click → search Get Player Controller and add it
(This is a NEW Get Player Controller - separate from the one in debug)
d) From Player Controller output, drag → Get Enhanced Input Local Player Subsystem
e) From subsystem output, drag and right-click → search IA_Move
- Look under "Input" → "Enhanced Action Values" → select "IA_Move"
- This node returns the current value of the IA_Move input action
f) The output is an Input Action Value (Vector2D since we set IA_Move to Axis2D).
Right-click the output pin → "Split Struct Pin" to get X and Y components, OR drag from output and search "To Vector 2D" to convert it
g) Right-click → Make Vector
- Connect the X from movement input to X
- Connect the Y from movement input to Y
- Set Z to 0
h) Right-click → Normalize
- Connect the vector output to Normalize input
i) Right-click → Get World Delta Seconds
j) Right-click → Get MoveSpeed (your variable)
k) Right-click → Multiply (float * float)
- Connect Delta Seconds output to first input
- Connect MoveSpeed output to second input
l) Right-click → Multiply (vector * float)
- Connect Normalized vector (from step h) to vector input
- Connect (DeltaSeconds * MoveSpeed) result (from step k) to float input
- This output is the "movement delta" - how far to move this frame
m) Right-click → Get Actor Location
n) Right-click → Add (vector + vector)
- Connect current location (from step m) to first input
- Connect movement delta (from step l) to second input
o) CLAMP X COORDINATE:
First, break apart the new position vector from step n:
- Right-click on the output pin of the Add node (from step n) → "Split Struct Pin"
- This splits the vector into three separate pins: X, Y, Z
Now clamp the X value:
- Right-click in empty space → search
Clamp (float)→ add it - Connect the "X" output (from the split Add node) to "Value" input of Clamp
Get the min bound:
- Right-click → search
Get BoundsMin(your variable) → add it - Right-click on BoundsMin output pin → "Split Struct Pin" (splits into X, Y)
- Connect BoundsMin's "X" to Clamp's "Min" input
Get the max bound:
- Right-click → search
Get BoundsMax(your variable) → add it - Right-click on BoundsMax output pin → "Split Struct Pin"
- Connect BoundsMax's "X" to Clamp's "Max" input
The Clamp node now outputs the X position clamped within bounds.
p) CLAMP Y COORDINATE:
- Right-click → add another
Clamp (float)node - Connect the "Y" output (from the split Add node in step o) to "Value"
- Connect BoundsMin's "Y" (already split) to "Min"
- Connect BoundsMax's "Y" (already split) to "Max"
q) Right-click → Make Vector
- Connect clamped X
- Connect clamped Y
- Set Z to 0
r) Right-click → Set Actor Location
- Connect the clamped vector to "New Location"
s) CRITICAL - Connect the execution wire (white wire):
- If using Sequence (from step a): drag from "Then 1" to Set Actor Location
- If no debug: drag from Event Tick to Set Actor Location
- Without this execution wire, the movement code NEVER runs!
Final Structure (with debug - using Sequence node):
Event Tick → Sequence ─┬─ Then 0 → DEBUG_PrintInput
│
└─ Then 1 → [movement] → Set Actor Location
Final Structure (without debug):
Event Tick → [movement logic] → Set Actor Location
TO REMOVE DEBUG: Delete the wire from "Then 0" to DEBUG_PrintInput. Movement on "Then 1" is completely unaffected.
6. Click Compile and Save
Expected Result after Compile:
- Compile button shows GREEN checkmark (no errors)
- No warnings about unconnected pins
How to Test at This Stage:
- Open any level (or the default "Untitled" level)
- From Content Drawer, drag BP_Player into the viewport
- Press Play (
Alt+P) - Click inside the game viewport to give it keyboard focus
- You should see the cube (TempVisual added in Step 2.2) and control it with WASD
Expected Result when tested:
- Player pawn moves smoothly when pressing WASD keys
- Movement is frame-rate independent (consistent speed)
- Player cannot move outside the screen bounds (stops at edges)
- Releasing keys immediately stops movement (no drift)
NOTE: Full game testing will be possible after completing Part 8 (Level Setup).
Visual Diagram of Movement Nodes:
┌─────────────┐ ┌──────────┐
│ Event Tick │────►│ Sequence │─┬─ Then 0 ──► DEBUG_PrintInput (optional)
└─────────────┘ └──────────┘ │
└─ Then 1 ──► Movement logic ──► Set Actor Location
(If no debug, skip Sequence and connect Event Tick directly to movement)
┌──────────────────────────────────┐ ┌────────────────────┐
│ Get Enhanced Input Subsystem │ │ Set Actor Location │
│ → Get Action Value (IA_Move) │ └────────────────────┘
│ → Get as Vector2D │ ▲
└────────────────┬─────────────────┘ │
│ ┌───────────────┐
▼ │ Clamped Vector│
┌────────────────────────────┐ └───────────────┘
│ Make Vector │───►Normalize───► ▲
│ X=Input.X, Y=Input.Y, Z=0 │ (direction) │
└────────────────────────────┘ │ ┌─────────────┐
▼ │ + Location │
┌──────────────┐ └─────────────┘
│ * MoveSpeed │─────────┘
│ * DeltaTime │
└──────────────┘
Step 2.5: Create Player Firing Logic
-
Continue in Event Graph, we'll add firing after movement
-
After the movement logic, add firing check using Enhanced Input:
a) Right-click → search IA_Fire
- Look under "Input" → "Enhanced Action Events" category (diamond ◇ icons)
- Select "IA_Fire" (the one with diamond icon, NOT the square icon)
- This creates an EVENT node (red, like Event Tick) that fires when the button is pressed
NOTE:
- "Enhanced Action Events" (diamond ◇) = triggers when button pressed
- "Enhanced Action Values" (square □) = reads current value continuously
For firing, we want the EVENT.
b) From the "Triggered" execution pin, build the fire rate limiter:
-
Get current timer value:
- Right-click →
Get FireTimer(your variable)
- Right-click →
-
Subtract frame time from it:
- Right-click →
Get World Delta Seconds(time since last frame, ~0.016 at 60fps) - Right-click → search "float - float" or "Subtract" → add Subtract node
- Connect FireTimer to the TOP input (A)
- Connect Delta Seconds to the BOTTOM input (B)
- Result = FireTimer minus DeltaSeconds
- Right-click →
-
Store the new timer value:
- Right-click →
Set FireTimer - Connect the Subtract result to it
- Connect execution wire: Triggered → Set FireTimer
- Right-click →
-
Check if we can fire:
- From Set FireTimer, drag execution →
Branch - Right-click →
<= (float)(less than or equal) - Connect FireTimer (use the output from Set, or Get it again) to top input
- Type
0in bottom input - Connect result to Branch's Condition
- From Set FireTimer, drag execution →
-
On TRUE branch (timer expired, can fire):
- Right-click →
Set FireTimer - Right-click →
Get FireInterval(your variable) - Connect FireInterval to Set FireTimer (resets the cooldown)
- For now, add a Print String with text "FIRE!" to verify it works
- Leave the execution wire after this Set FireTimer OPEN (we'll connect FireVolley function here in Step 3.4)
- Right-click →
Visual:
┌──────────────────┐
│ IA_Fire Triggered│
└────────┬─────────┘
│ (white wire)
▼
┌─────────────────────────────────────┐
│ Set FireTimer │
│ = FireTimer - GetWorldDeltaSeconds │
└────────┬────────────────────────────┘
│
▼
┌─────────────────┐ TRUE ┌─────────────────────┐
│ Branch │───────────►│ Set FireTimer │──► Print "FIRE!"
│ FireTimer <= 0 │ │ = FireInterval │ (placeholder)
└────────┬────────┘ └─────────────────────┘
│ FALSE
▼
(do nothing)
TEST NOW: Press Play, hold Z key. You should see "FIRE!" spam in top-left at rapid intervals (every 0.08 seconds = FireInterval).
NOTE: Since IA_Fire was set up with Z key and Left Mouse Button in the Input Mapping Context, both inputs will trigger this action automatically.
3. PLACEHOLDER FOR BULLET SPAWNING
The Print String "FIRE!" is a placeholder. We will replace it with actual bullet spawning in STEP 3.4, AFTER creating BP_Bullet in Part 3.
Leave the execution wire open after the "Set FireTimer = FireInterval" node. We'll connect FireVolley function there in STEP 3.4.
4. Compile and Save
Expected Result after Compile:
- Compile button shows GREEN checkmark
- Fire rate limiter logic works (Print String spams when holding Z)
- Actual bullets will be added in STEP 3.4 after BP_Bullet is created
Step 2.6: Create Player Damage and Special Ability
IMPORTANT - Dependency Note: The
HandleDeathfunction needs to callBP_GameDirector, which we create in Part 6. In this step, we will create placeholder functions that compile but aren't fully functional. We will completeHandleDeathin Part 6 after GameDirector exists.
1. CREATE "HandleDeath" FUNCTION (Placeholder):
We create this FIRST because TakeHit will call it. For now, it's a placeholder.
a) Create the function:
- In My Blueprint panel (left side), find "Functions" section
- Click the "+" button next to "Functions"
- Name it
HandleDeath - Double-click to open the function graph
b) Add placeholder logic (will be completed in Part 6):
-
The function graph opens with a purple "HandleDeath" entry node
-
Hide the player:
- From the entry node's WHITE execution pin, drag and search
Set Actor Hidden in Game - Check the "New Hidden" checkbox (set to TRUE)
- This makes the player invisible when they die
- From the entry node's WHITE execution pin, drag and search
-
Add a placeholder comment:
- Right-click in empty space → search
Add Comment(or press C) - Type: "TODO: Call GameDirector.HandlePlayerDeath (Part 6)"
- Position it near the end of the function
- Right-click in empty space → search
-
Connect execution:
HandleDeath (entry) ──► Set Actor Hidden in Game │ ▼ [TODO comment - no connection needed] -
Click "Compile" - should show GREEN checkmark
NOTE: This function is incomplete! We'll add the GameDirector call in Part 6, Step 6.3.
2. CREATE "TakeHit" FUNCTION:
Now we can create TakeHit, which calls HandleDeath.
a) Create the function:
- In My Blueprint panel → Functions → click "+"
- Name it
TakeHit - Double-click to open the function graph
b) Add input parameter:
- With the function graph open, look at the Details panel (right side)
- Find "Inputs" section
- Click "+" to add a new input
- Name:
Damage - Type:
Integer - The purple entry node now shows a "Damage" output pin
c) Build the function logic:
Step 1 - Get current lives:
- Right-click in empty space → search
Get CurrentLives→ add it - This reads the CurrentLives variable value
Step 2 - Subtract damage from lives:
- Right-click → search
integer - integerorSubtract (int)→ add it - Connect
CurrentLives(from Step 1) to the TOP input (A) - Connect
Damage(from the purple entry node) to the BOTTOM input (B) - Result = CurrentLives minus Damage
Step 3 - Clamp the result (prevent negative lives):
- Right-click → search
Clamp (int)→ add the "Clamp (integer)" node - Connect the Subtract result (from Step 2) to the "Value" input
- Set "Min" to
0(type directly in the field) - Set "Max" to
99(or any high number - we just want to prevent negatives) - The output is the clamped value (0 or higher)
Step 4 - Store the new lives value:
- Right-click → search
Set CurrentLives→ add it - Connect the Clamp output (from Step 3) to the input
- IMPORTANT: Connect execution wire from entry node → Set CurrentLives
Step 5 - Check if player should die:
- From
Set CurrentLives, drag execution → searchBranch→ add it - Right-click → search
<= (integer)(less than or equal) → add it - Connect
CurrentLives(drag from Set node's output, or add new Get) to TOP input - Type
0in BOTTOM input - Connect the
<=result (boolean) to Branch's "Condition" input
Step 6 - On TRUE (player is dead):
- From Branch's "True" execution pin, drag → search
HandleDeath→ add it - This calls the function we created in step 1
Step 7 - On FALSE (player still alive):
- Leave the "False" pin unconnected (do nothing, player survives)
d) Visual diagram of TakeHit function:
┌────────────────────────┐
│ TakeHit (entry) │
│ Damage ○───────┼──────────────────────┐
└───────────┬────────────┘ │
│ (white execution wire) │
▼ ▼
┌───────────────────────────────────────────────────────┐
│ Set CurrentLives │
│ = Clamp( CurrentLives - Damage, Min=0, Max=99 ) │
└───────────┬───────────────────────────────────────────┘
│
▼
┌─────────────────────────────┐
│ Branch │
│ Condition: CurrentLives <= 0│
└───────┬─────────────┬───────┘
│ TRUE │ FALSE
▼ ▼
┌───────────────┐ (nothing)
│ HandleDeath │
└───────────────┘
e) Click Compile and Save
3. SPECIAL ABILITY (Screen Clear) - Input Setup Only:
IMPORTANT - Dependency Note: The special ability needs to destroy BP_Enemy and BP_Bullet actors, which don't exist yet. In this step, we create ONLY the input handling and mark-as-used logic. We will complete the destruction logic in Part 4, Step 4.6 after BP_Enemy exists.
Using Enhanced Input (IA_Special was already set up with X and Right Mouse in Step 2.4):
a) Create the event node:
- Go back to the Event Graph tab (not inside a function)
- Right-click in empty space → search
EnhancedInputAction IA_Special- Look under "Input" → "Enhanced Action Events" category
- Select "IA_Special" (the EVENT with diamond ◇ icon)
- This creates a red event node that fires when X or Right Mouse is pressed
b) Check if special was already used:
- From the "Triggered" execution pin, drag → search
Branch→ add it - Right-click → search
Get SpecialUsed→ add it - Right-click → search
NOT Boolean→ add it - Connect SpecialUsed → NOT → Branch Condition
- We branch on "NOT SpecialUsed" (true = hasn't been used yet)
c) On TRUE (special available) - placeholder:
-
From Branch's "True" pin, drag → search
Set SpecialUsed→ add it -
Check the box to set it to TRUE (marks ability as used)
-
Add placeholder Print String:
- From Set SpecialUsed, drag → search
Print String→ add it - Type: "SPECIAL ABILITY ACTIVATED!" in the In String field
- This verifies the input works; we'll replace it with actual destruction in Part 4
- From Set SpecialUsed, drag → search
-
Add a placeholder comment:
- Right-click in empty space → search
Add Comment(or press C) - Type: "TODO: Add enemy/bullet destruction (Part 4)"
- Position it after the Print String
- Right-click in empty space → search
d) On FALSE (special already used):
- Leave unconnected (nothing happens on second press)
e) Visual diagram (placeholder version):
┌─────────────────────────────┐
│ EnhancedInputAction │
│ IA_Special - Triggered │
└───────────┬─────────────────┘
│
▼
┌─────────────────────────────┐
│ Branch │
│ Condition: NOT SpecialUsed │
└───────┬─────────────┬───────┘
│ TRUE │ FALSE
▼ ▼
┌───────────────┐ (nothing - ability already used)
│Set SpecialUsed│
│ = true │
└───────┬───────┘
│
▼
┌────────────────────────────────────┐
│ Print String │
│ "SPECIAL ABILITY ACTIVATED!" │
└────────────────────────────────────┘
│
▼
[TODO comment - destruction logic added in Part 4]
f) Compile and Save
g) Test the placeholder:
- Press Play
- Press X key (or Right Mouse Button)
- You should see "SPECIAL ABILITY ACTIVATED!" appear once
- Pressing X again should do nothing (SpecialUsed = true)
REMINDER: Complete the special ability with actual enemy/bullet destruction in Part 4, Step 4.6 after creating BP_Enemy.
4. Compile and Save
Expected Result after Compile:
- Compile button shows GREEN checkmark
- "TakeHit" and "HandleDeath" functions appear under Functions in My Blueprint panel
- Event Graph shows IA_Special event with branching logic
Expected Result in Play mode (at this stage):
- Press X key: "SPECIAL ABILITY ACTIVATED!" appears once in top-left
- Press X again: Nothing happens (SpecialUsed is now true)
- TakeHit and HandleDeath functions exist but cannot be tested yet (nothing calls them)
NOTE: Full testing of damage/death systems requires:
- BP_Bullet collision (Part 3) to call TakeHit
- BP_GameDirector (Part 6) for HandleDeath to notify
- BP_Enemy (Part 4) for special ability to destroy
REMINDER: Complete the
HandleDeathfunction in Part 6, Step 6.3 after creating BP_GameDirector.
← Previous: Part 1 - Project Setup | Back to Index | Next: Part 3 - Create the Bullet →