mirror of
https://github.com/kuhyx/praca_magisterska.git
synced 2026-11-11 21:53:53 +01:00
1009 lines
40 KiB
Markdown
1009 lines
40 KiB
Markdown
# 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)
|
|
|
|
<details>
|
|
<summary><b>DEBUGGING STEP 1</b> - Verify the pawn is possessed</summary>
|
|
|
|
- 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
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><b>DEBUGGING STEP 2</b> - Verify the game window has keyboard focus</summary>
|
|
|
|
- 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
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><b>DEBUGGING STEP 3</b> - Test with a simpler Print String</summary>
|
|
|
|
- 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
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><b>DEBUGGING STEP 4</b> - Test BeginPlay is running</summary>
|
|
|
|
- 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?)
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><b>DEBUGGING STEP 5</b> - Check if there are MULTIPLE BP_Player in level</summary>
|
|
|
|
- Look in Outliner for multiple BP_Player instances
|
|
- Delete ALL of them
|
|
- Drag in exactly ONE fresh BP_Player from Content Drawer
|
|
- Try again
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary><b>DEBUGGING STEP 6</b> - Nuclear option (create fresh test)</summary>
|
|
|
|
- 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
|
|
|
|
</details>
|
|
|
|
**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)
|