# Part 2: Create the Player [← Previous: Part 1 - Project Setup](part-1-project-setup.md) | [Back to Index](README.md) | [Next: Part 3 - Create the Bullet →](part-3-create-bullet.md) --- ## 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](part-9-final-setup.md) 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](part-8-game-mode-level.md), 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) ``` 9. **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](part-8-game-mode-level.md), 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. 5. Click **"Compile"** button (top left, blue checkmark icon) 6. 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)](part-8-game-mode-level.md). ### 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](part-3-create-bullet.md#step-34-complete-player-firing-logic-bp_player)) **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](part-3-create-bullet.md#step-34-complete-player-firing-logic-bp_player), 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](part-3-create-bullet.md#step-34-complete-player-firing-logic-bp_player) 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](part-6-game-director.md). > In this step, we will create **placeholder functions** that compile but aren't fully functional. > We will complete `HandleDeath` in [Part 6](part-6-game-director.md#step-63-game-director-update-logic) 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](part-6-game-director.md#step-63-game-director-update-logic). --- ### 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](part-4-create-enemy.md#step-46-complete-special-ability-in-bp_player) 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](part-4-create-enemy.md#step-46-complete-special-ability-in-bp_player) 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](part-6-game-director.md#step-63-game-director-update-logic) after creating BP_GameDirector. --- [← Previous: Part 1 - Project Setup](part-1-project-setup.md) | [Back to Index](README.md) | [Next: Part 3 - Create the Bullet →](part-3-create-bullet.md)