praca_magisterska/games/unreal/tutorial/part-2-create-player.md

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

  1. In Content Browser, double-click "Blueprints" folder to open it

  2. Right-click in empty space → Blueprint Class

  3. In the popup window "Pick Parent Class":

    • Click "Pawn" (NOT Character - we want simple 2D control)
    • Click "Select"
  4. Name the new Blueprint: BP_Player

  5. 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

  1. In the Components panel (left side), you'll see "DefaultSceneRoot"

    • Click on "DefaultSceneRoot" to select it (this ensures new components are added as children)
  2. 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
  3. 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)
  4. 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
  5. 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
  6. 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)
  7. 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
  8. 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)
  1. 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)

  1. In the Blueprint Editor, look at the left panel (below the Components panel)

  2. Find "My Blueprint" section

  3. Under "Variables", click the "+" button

  4. 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.

  1. Click "Compile" button (top left, blue checkmark icon)
  2. 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:

  1. Go to Edit → Project Settings
  2. In the left sidebar, scroll down to "Engine" section
  3. Click on "Input"
  4. Find "Default Classes" section (near the top)
  5. Set "Default Player Input Class" to: EnhancedPlayerInput
  6. Set "Default Input Component Class" to: EnhancedInputComponent
  7. 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

  1. Open Content Drawer (Ctrl+Space or click "Content Drawer" at bottom)

  2. In Content folder, right-click → New Folder → name it Input

  3. 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
  4. Create another Input Action: IA_Fire

    • Set "Value Type" to Digital (bool) (default)
  5. Create another Input Action: IA_Special

    • Set "Value Type" to Digital (bool)

B) CREATE INPUT MAPPING CONTEXT

  1. In Input folder, right-click → Input → Input Mapping Context

    • Name it IMC_Default
    • Double-click to open
  2. 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+S to SAVE IMC_Default!

  3. Click "+" to add IA_Fire:

    • Select "IA_Fire"
    • Add key: Z
    • Add key: Left Mouse Button
  4. Click "+" to add IA_Special:

    • Select "IA_Special"
    • Add key: X
    • Add key: Right Mouse Button
  5. Save the Input Mapping Context

2. NOW OPEN BP_PLAYER BLUEPRINT

  1. In Content Drawer, navigate to Content → Blueprints
  2. Double-click "BP_Player" to open the Blueprint Editor
  3. 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:

  1. 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
  2. From the blue "Return Value" output, drag and search Get Enhanced Input Local Player Subsystem

  3. From that blue output, drag and search Add Mapping Context

  4. For "Mapping Context" input:

    • Click dropdown and select IMC_Default
    • Or drag IMC_Default from Content Drawer
  5. Set "Priority" to 0

  6. 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

  1. 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)
  2. 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
  3. Branch on change:

    • From DEBUG_PrintInput WHITE pin → "Branch"
    • Connect != result to Condition
  4. 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+F1 to 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

  1. Open any level (or the default "Untitled" level)
  2. From Content Drawer, drag BP_Player into the viewport
  3. Press Play (Alt+P)
  4. Click inside the game viewport to give it keyboard focus
  5. 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

  1. Continue in Event Graph, we'll add firing after movement

  2. 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

  1. Get current timer value:

    • Right-click → Get FireTimer (your variable)
  2. 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
  3. Store the new timer value:

    • Right-click → Set FireTimer
    • Connect the Subtract result to it
    • Connect execution wire: Triggered → Set FireTimer
  4. 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 0 in bottom input
    • Connect result to Branch's Condition
  5. 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)

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 HandleDeath function needs to call BP_GameDirector, which we create in Part 6. In this step, we will create placeholder functions that compile but aren't fully functional. We will complete HandleDeath in 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

  1. In My Blueprint panel (left side), find "Functions" section
  2. Click the "+" button next to "Functions"
  3. Name it HandleDeath
  4. Double-click to open the function graph

b) Add placeholder logic (will be completed in Part 6)

  1. The function graph opens with a purple "HandleDeath" entry node

  2. 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
  3. 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
  4. Connect execution:

    HandleDeath (entry) ──► Set Actor Hidden in Game
                                     │
                                     ▼
                           [TODO comment - no connection needed]
    
  5. 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

  1. In My Blueprint panel → Functions → click "+"
  2. Name it TakeHit
  3. Double-click to open the function graph

b) Add input parameter

  1. With the function graph open, look at the Details panel (right side)
  2. Find "Inputs" section
  3. Click "+" to add a new input
  4. Name: Damage
  5. Type: Integer
  6. 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 - integer or Subtract (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 → search Branch → 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 0 in 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

  1. Go back to the Event Graph tab (not inside a function)
  2. Right-click in empty space → search EnhancedInputAction IA_Special
    • Look under "Input" → "Enhanced Action Events" category
    • Select "IA_Special" (the EVENT with diamond ◇ icon)
  3. This creates a red event node that fires when X or Right Mouse is pressed

b) Check if special was already used

  1. From the "Triggered" execution pin, drag → search Branch → add it
  2. Right-click → search Get SpecialUsed → add it
  3. Right-click → search NOT Boolean → add it
  4. Connect SpecialUsed → NOT → Branch Condition
    • We branch on "NOT SpecialUsed" (true = hasn't been used yet)

c) On TRUE (special available) - placeholder

  1. From Branch's "True" pin, drag → search Set SpecialUsed → add it

  2. Check the box to set it to TRUE (marks ability as used)

  3. 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
  4. 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

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

  1. Press Play
  2. Press X key (or Right Mouse Button)
  3. You should see "SPECIAL ABILITY ACTIVATED!" appear once
  4. 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 HandleDeath function in Part 6, Step 6.3 after creating BP_GameDirector.


← Previous: Part 1 - Project Setup | Back to Index | Next: Part 3 - Create the Bullet →