chore: markdown linter and fixes

This commit is contained in:
Krzysztof kuhy Rudnicki 2026-01-05 18:43:37 +01:00
parent 9bf78a2dc1
commit 472f6dc19d
16 changed files with 837 additions and 436 deletions

View File

@ -0,0 +1,18 @@
{
"default": true,
"MD013": {
"line_length": 500,
"code_blocks": false,
"tables": false
},
"MD024": {
"siblings_only": true
},
"MD033": false,
"MD041": false,
"MD036": false,
"MD040": false,
"MD046": false,
"MD060": false,
"MD029": false
}

View File

@ -0,0 +1,6 @@
{
"proseWrap": "preserve",
"tabWidth": 2,
"useTabs": false,
"printWidth": 300
}

View File

@ -17,11 +17,13 @@ This tutorial recreates the Unity "magisterka_1" bullet-hell shooter in Unreal E
## Table of Contents ## Table of Contents
### Part 1: Project Setup ### Part 1: Project Setup
- [Step 1.1: Create New Project](part-1-project-setup.md#step-11-create-new-project) - [Step 1.1: Create New Project](part-1-project-setup.md#step-11-create-new-project)
- [Step 1.2: Set Up 2D Game View](part-1-project-setup.md#step-12-set-up-2d-game-view) - [Step 1.2: Set Up 2D Game View](part-1-project-setup.md#step-12-set-up-2d-game-view)
- [Step 1.3: Create Folder Structure](part-1-project-setup.md#step-13-create-folder-structure) - [Step 1.3: Create Folder Structure](part-1-project-setup.md#step-13-create-folder-structure)
### Part 2: Create the Player ### Part 2: Create the Player
- [Step 2.1: Create Player Blueprint](part-2-create-player.md#step-21-create-player-blueprint) - [Step 2.1: Create Player Blueprint](part-2-create-player.md#step-21-create-player-blueprint)
- [Step 2.2: Add Player Visual Components](part-2-create-player.md#step-22-add-player-visual-components) - [Step 2.2: Add Player Visual Components](part-2-create-player.md#step-22-add-player-visual-components)
- [Step 2.3: Create Player Variables](part-2-create-player.md#step-23-create-player-variables) - [Step 2.3: Create Player Variables](part-2-create-player.md#step-23-create-player-variables)
@ -30,12 +32,14 @@ This tutorial recreates the Unity "magisterka_1" bullet-hell shooter in Unreal E
- [Step 2.6: Create Player Damage and Special Ability](part-2-create-player.md#step-26-create-player-damage-and-special-ability) - [Step 2.6: Create Player Damage and Special Ability](part-2-create-player.md#step-26-create-player-damage-and-special-ability)
### Part 3: Create the Bullet ### Part 3: Create the Bullet
- [Step 3.1: Create Bullet Blueprint](part-3-create-bullet.md#step-31-create-bullet-blueprint) - [Step 3.1: Create Bullet Blueprint](part-3-create-bullet.md#step-31-create-bullet-blueprint)
- [Step 3.2: Bullet Movement Logic](part-3-create-bullet.md#step-32-bullet-movement-logic) - [Step 3.2: Bullet Movement Logic](part-3-create-bullet.md#step-32-bullet-movement-logic)
- [Step 3.3: Bullet Collision Logic](part-3-create-bullet.md#step-33-bullet-collision-logic) - [Step 3.3: Bullet Collision Logic](part-3-create-bullet.md#step-33-bullet-collision-logic)
- [Step 3.4: Complete Player Firing Logic](part-3-create-bullet.md#step-34-complete-player-firing-logic-bp_player) - [Step 3.4: Complete Player Firing Logic](part-3-create-bullet.md#step-34-complete-player-firing-logic-bp_player)
### Part 4: Create the Enemy ### Part 4: Create the Enemy
- [Step 4.1: Create Enemy Blueprint](part-4-create-enemy.md#step-41-create-enemy-blueprint) - [Step 4.1: Create Enemy Blueprint](part-4-create-enemy.md#step-41-create-enemy-blueprint)
- [Step 4.2: Enemy Initialization](part-4-create-enemy.md#step-42-enemy-initialization) - [Step 4.2: Enemy Initialization](part-4-create-enemy.md#step-42-enemy-initialization)
- [Step 4.3: Enemy Movement Logic](part-4-create-enemy.md#step-43-enemy-movement-logic) - [Step 4.3: Enemy Movement Logic](part-4-create-enemy.md#step-43-enemy-movement-logic)
@ -45,16 +49,19 @@ This tutorial recreates the Unity "magisterka_1" bullet-hell shooter in Unreal E
- [Step 4.7: Complete Bullet Collision Logic](part-4-create-enemy.md#step-47-complete-bullet-collision-logic-bp_bullet) - [Step 4.7: Complete Bullet Collision Logic](part-4-create-enemy.md#step-47-complete-bullet-collision-logic-bp_bullet)
### Part 5: Create Enemy Spawner ### Part 5: Create Enemy Spawner
- [Step 5.1: Create Spawner Blueprint](part-5-create-spawner.md#step-51-create-spawner-blueprint) - [Step 5.1: Create Spawner Blueprint](part-5-create-spawner.md#step-51-create-spawner-blueprint)
- [Step 5.2: Spawn Rate Curve](part-5-create-spawner.md#step-52-spawn-rate-curve) - [Step 5.2: Spawn Rate Curve](part-5-create-spawner.md#step-52-spawn-rate-curve)
- [Step 5.3: Spawning Logic](part-5-create-spawner.md#step-53-spawning-logic) - [Step 5.3: Spawning Logic](part-5-create-spawner.md#step-53-spawning-logic)
### Part 6: Create Game Director ### Part 6: Create Game Director
- [Step 6.1: Create Game Director Blueprint](part-6-game-director.md#step-61-create-game-director-blueprint) - [Step 6.1: Create Game Director Blueprint](part-6-game-director.md#step-61-create-game-director-blueprint)
- [Step 6.2: Game Director Initialization](part-6-game-director.md#step-62-game-director-initialization) - [Step 6.2: Game Director Initialization](part-6-game-director.md#step-62-game-director-initialization)
- [Step 6.3: Game Director Update Logic](part-6-game-director.md#step-63-game-director-update-logic) - [Step 6.3: Game Director Update Logic](part-6-game-director.md#step-63-game-director-update-logic)
### Part 7: Create Score Manager / UI ### Part 7: Create Score Manager / UI
- [Step 7.1: Create UI Widget Blueprint](part-7-score-manager-ui.md#step-71-create-ui-widget-blueprint) - [Step 7.1: Create UI Widget Blueprint](part-7-score-manager-ui.md#step-71-create-ui-widget-blueprint)
- [Step 7.2: Design HUD Layout](part-7-score-manager-ui.md#step-72-design-hud-layout) - [Step 7.2: Design HUD Layout](part-7-score-manager-ui.md#step-72-design-hud-layout)
- [Step 7.3: Create Score Manager Blueprint](part-7-score-manager-ui.md#step-73-create-score-manager-blueprint) - [Step 7.3: Create Score Manager Blueprint](part-7-score-manager-ui.md#step-73-create-score-manager-blueprint)
@ -62,6 +69,7 @@ This tutorial recreates the Unity "magisterka_1" bullet-hell shooter in Unreal E
- [Step 7.5: Score Manager Functions](part-7-score-manager-ui.md#step-75-score-manager-functions) - [Step 7.5: Score Manager Functions](part-7-score-manager-ui.md#step-75-score-manager-functions)
### Part 8: Create Game Mode and Level ### Part 8: Create Game Mode and Level
- [Step 8.1: Create Custom Game Mode](part-8-game-mode-level.md#step-81-create-custom-game-mode) - [Step 8.1: Create Custom Game Mode](part-8-game-mode-level.md#step-81-create-custom-game-mode)
- [Step 8.2: Configure Project to Use Game Mode](part-8-game-mode-level.md#step-82-configure-project-to-use-game-mode) - [Step 8.2: Configure Project to Use Game Mode](part-8-game-mode-level.md#step-82-configure-project-to-use-game-mode)
- [Step 8.3: Create Game Level](part-8-game-mode-level.md#step-83-create-game-level) - [Step 8.3: Create Game Level](part-8-game-mode-level.md#step-83-create-game-level)
@ -69,6 +77,7 @@ This tutorial recreates the Unity "magisterka_1" bullet-hell shooter in Unreal E
- [Step 8.5: Set Default Level](part-8-game-mode-level.md#step-85-set-default-level) - [Step 8.5: Set Default Level](part-8-game-mode-level.md#step-85-set-default-level)
### Part 9: Final Setup and Testing ### Part 9: Final Setup and Testing
- [Step 9.1: Assign Blueprint References](part-9-final-setup.md#step-91-assign-blueprint-references) - [Step 9.1: Assign Blueprint References](part-9-final-setup.md#step-91-assign-blueprint-references)
- [Step 9.2: Create Final Visuals](part-9-final-setup.md#step-92-create-final-visuals-replace-placeholder) - [Step 9.2: Create Final Visuals](part-9-final-setup.md#step-92-create-final-visuals-replace-placeholder)
- [Step 9.3: Add Background](part-9-final-setup.md#step-93-add-background-optional) - [Step 9.3: Add Background](part-9-final-setup.md#step-93-add-background-optional)
@ -76,6 +85,7 @@ This tutorial recreates the Unity "magisterka_1" bullet-hell shooter in Unreal E
- [Step 9.5: Build Standalone Game](part-9-final-setup.md#step-95-build-standalone-game) - [Step 9.5: Build Standalone Game](part-9-final-setup.md#step-95-build-standalone-game)
### Appendices ### Appendices
- [Appendix A: Complete Variable Reference](appendix-a-variables.md) - [Appendix A: Complete Variable Reference](appendix-a-variables.md)
- [Appendix B: Troubleshooting](appendix-b-troubleshooting.md) - [Appendix B: Troubleshooting](appendix-b-troubleshooting.md)
- [Appendix C: Unity to Unreal Conversion Notes](appendix-c-unity-conversion.md) - [Appendix C: Unity to Unreal Conversion Notes](appendix-c-unity-conversion.md)
@ -94,10 +104,11 @@ This tutorial recreates the Unity "magisterka_1" bullet-hell shooter in Unreal E
## Navigation ## Navigation
Each page includes: Each page includes:
- **← Previous** and **Next →** links at the top and bottom - **← Previous** and **Next →** links at the top and bottom
- Links back to this index - Links back to this index
- Cross-references to related sections - Cross-references to related sections
--- ---
*This tutorial is part of a master's thesis comparing Unity and Unreal Engine.* _This tutorial is part of a master's thesis comparing Unity and Unreal Engine._

View File

@ -6,93 +6,93 @@
## BP_Player Variables ## BP_Player Variables
| Variable Name | Type | Default Value | | Variable Name | Type | Default Value |
|---------------|------|---------------| | -------------- | --------- | ------------- |
| `MoveSpeed` | Float | 750.0 | | `MoveSpeed` | Float | 750.0 |
| `BoundsMin` | Vector 2D | (-850, -450) | | `BoundsMin` | Vector 2D | (-850, -450) |
| `BoundsMax` | Vector 2D | (850, 450) | | `BoundsMax` | Vector 2D | (850, 450) |
| `FireInterval` | Float | 0.08 | | `FireInterval` | Float | 0.08 |
| `FireTimer` | Float | 0.0 | | `FireTimer` | Float | 0.0 |
| `BulletSpeed` | Float | 2200.0 | | `BulletSpeed` | Float | 2200.0 |
| `MaxLives` | Integer | 3 | | `MaxLives` | Integer | 3 |
| `CurrentLives` | Integer | 3 | | `CurrentLives` | Integer | 3 |
| `VolleySize` | Integer | 3 | | `VolleySize` | Integer | 3 |
| `VolleySpread` | Float | 12.0 | | `VolleySpread` | Float | 12.0 |
| `SpecialUsed` | Boolean | false | | `SpecialUsed` | Boolean | false |
| `BulletClass` | Class Ref | BP_Bullet | | `BulletClass` | Class Ref | BP_Bullet |
--- ---
## BP_Bullet Variables ## BP_Bullet Variables
| Variable Name | Type | Default Value | | Variable Name | Type | Default Value |
|---------------|------|---------------| | ------------------- | ------- | ------------- |
| `TravelDirection` | Vector | (0, 1, 0) | | `TravelDirection` | Vector | (0, 1, 0) |
| `TravelSpeed` | Float | 1200.0 | | `TravelSpeed` | Float | 1200.0 |
| `RemainingLifetime` | Float | 4.0 | | `RemainingLifetime` | Float | 4.0 |
| `IsEnemyProjectile` | Boolean | false | | `IsEnemyProjectile` | Boolean | false |
| `Damage` | Integer | 1 | | `Damage` | Integer | 1 |
--- ---
## BP_Enemy Variables ## BP_Enemy Variables
| Variable Name | Type | Default Value | | Variable Name | Type | Default Value |
|---------------|------|---------------| | --------------------- | --------- | ------------- |
| `MaxHealth` | Integer | 12 | | `MaxHealth` | Integer | 12 |
| `CurrentHealth` | Integer | 12 | | `CurrentHealth` | Integer | 12 |
| `ScoreValue` | Integer | 50 | | `ScoreValue` | Integer | 50 |
| `VerticalSpeed` | Float | 220.0 | | `VerticalSpeed` | Float | 220.0 |
| `HorizontalAmplitude` | Float | 250.0 | | `HorizontalAmplitude` | Float | 250.0 |
| `HorizontalFrequency` | Float | 1.8 | | `HorizontalFrequency` | Float | 1.8 |
| `DespawnY` | Float | -750.0 | | `DespawnY` | Float | -750.0 |
| `FireInterval` | Float | 0.35 | | `FireInterval` | Float | 0.35 |
| `BulletsPerBurst` | Integer | 20 | | `BulletsPerBurst` | Integer | 20 |
| `BurstSpread` | Float | 360.0 | | `BurstSpread` | Float | 360.0 |
| `EnemyBulletSpeed` | Float | 1000.0 | | `EnemyBulletSpeed` | Float | 1000.0 |
| `EnemyBulletLifetime` | Float | 6.0 | | `EnemyBulletLifetime` | Float | 6.0 |
| `BaseX` | Float | 0.0 | | `BaseX` | Float | 0.0 |
| `WaveSeed` | Float | 0.0 | | `WaveSeed` | Float | 0.0 |
| `FireTimer` | Float | 0.0 | | `FireTimer` | Float | 0.0 |
| `BulletClass` | Class Ref | BP_Bullet | | `BulletClass` | Class Ref | BP_Bullet |
--- ---
## BP_EnemySpawner Variables ## BP_EnemySpawner Variables
| Variable Name | Type | Default Value | | Variable Name | Type | Default Value |
|---------------|------|---------------| | ------------------------ | ----------- | -------------------- |
| `EnemyClass` | Class Ref | BP_Enemy | | `EnemyClass` | Class Ref | BP_Enemy |
| `SpawnAreaHalfWidth` | Float | 900.0 | | `SpawnAreaHalfWidth` | Float | 900.0 |
| `GameDuration` | Float | 300.0 | | `GameDuration` | Float | 300.0 |
| `MaxSimultaneousEnemies` | Integer | 120 | | `MaxSimultaneousEnemies` | Integer | 120 |
| `ElapsedTime` | Float | 0.0 | | `ElapsedTime` | Float | 0.0 |
| `SpawnTimer` | Float | 0.0 | | `SpawnTimer` | Float | 0.0 |
| `SpawningActive` | Boolean | true | | `SpawningActive` | Boolean | true |
| `SpawnCurve` | Curve Float | SpawnRateCurve asset | | `SpawnCurve` | Curve Float | SpawnRateCurve asset |
--- ---
## BP_GameDirector Variables ## BP_GameDirector Variables
| Variable Name | Type | Default Value | | Variable Name | Type | Default Value |
|---------------|------|---------------| | ------------------ | ---------- | ---------------- |
| `PlayerReference` | Object Ref | (set at runtime) | | `PlayerReference` | Object Ref | (set at runtime) |
| `SpawnerReference` | Object Ref | (set at runtime) | | `SpawnerReference` | Object Ref | (set at runtime) |
| `GameDuration` | Float | 300.0 | | `GameDuration` | Float | 300.0 |
| `ElapsedTime` | Float | 0.0 | | `ElapsedTime` | Float | 0.0 |
| `GameActive` | Boolean | true | | `GameActive` | Boolean | true |
--- ---
## BP_ScoreManager Variables ## BP_ScoreManager Variables
| Variable Name | Type | Default Value | | Variable Name | Type | Default Value |
|---------------|------|---------------| | ---------------- | ---------- | ---------------- |
| `Score` | Integer | 0 | | `Score` | Integer | 0 |
| `CurrentLives` | Integer | 3 | | `CurrentLives` | Integer | 3 |
| `HUDWidget` | Object Ref | (set at runtime) | | `HUDWidget` | Object Ref | (set at runtime) |
| `HUDWidgetClass` | Class Ref | WBP_HUD | | `HUDWidgetClass` | Class Ref | WBP_HUD |
--- ---

View File

@ -143,25 +143,31 @@
## General Debugging Tips ## General Debugging Tips
### Enable Print Statements ### Enable Print Statements
``` ```
Right-click → Print String → Type message Right-click → Print String → Type message
``` ```
Add these throughout your blueprints to track execution flow. Add these throughout your blueprints to track execution flow.
### Check Output Log ### Check Output Log
- **Window → Developer Tools → Output Log** - **Window → Developer Tools → Output Log**
- Shows blueprint errors, warnings, and print statements - Shows blueprint errors, warnings, and print statements
### Use Breakpoints ### Use Breakpoints
- Right-click any blueprint node → **Add Breakpoint** - Right-click any blueprint node → **Add Breakpoint**
- Execution pauses at that point during Play mode - Execution pauses at that point during Play mode
- Step through with F10/F11 - Step through with F10/F11
### Visualize Collisions ### Visualize Collisions
- In viewport: **Show → Collision** - In viewport: **Show → Collision**
- Shows collision shapes in play mode - Shows collision shapes in play mode
### Console Commands ### Console Commands
- Press `~` (tilde) to open console - Press `~` (tilde) to open console
- `stat fps` - Show frame rate - `stat fps` - Show frame rate
- `show collision` - Toggle collision visualization - `show collision` - Toggle collision visualization

View File

@ -6,11 +6,12 @@
## Scale Conversion ## Scale Conversion
| Unity | Unreal | Notes | | Unity | Unreal | Notes |
|-------|--------|-------| | ---------------- | --------------------- | ---------------------------- |
| 1 unit = 1 meter | 1 unit = 1 centimeter | Multiply Unity values by 100 | | 1 unit = 1 meter | 1 unit = 1 centimeter | Multiply Unity values by 100 |
**Example:** **Example:**
- Unity speed: `7.5` → Unreal speed: `750` - Unity speed: `7.5` → Unreal speed: `750`
- Unity position: `(5, 10, 0)` → Unreal position: `(500, 1000, 0)` - Unity position: `(5, 10, 0)` → Unreal position: `(500, 1000, 0)`
@ -18,13 +19,14 @@
## Coordinate System ## Coordinate System
| Axis | Unity | Unreal | | Axis | Unity | Unreal |
|------|-------|--------| | ------- | ----- | ------ |
| Up | Y | Z | | Up | Y | Z |
| Forward | Z | X | | Forward | Z | X |
| Right | X | Y | | Right | X | Y |
**For 2D top-down games:** **For 2D top-down games:**
- Both use X for horizontal - Both use X for horizontal
- Y/Z swap for vertical - Y/Z swap for vertical
@ -35,12 +37,14 @@
### Input ### Input
**Unity:** **Unity:**
```csharp ```csharp
float h = Input.GetAxisRaw("Horizontal"); float h = Input.GetAxisRaw("Horizontal");
float v = Input.GetAxisRaw("Vertical"); float v = Input.GetAxisRaw("Vertical");
``` ```
**Unreal (Enhanced Input):** **Unreal (Enhanced Input):**
1. Create Input Action (IA_Move) with Axis2D type 1. Create Input Action (IA_Move) with Axis2D type
2. Create Input Mapping Context with key bindings 2. Create Input Mapping Context with key bindings
3. Add Mapping Context in BeginPlay 3. Add Mapping Context in BeginPlay
@ -51,11 +55,13 @@ float v = Input.GetAxisRaw("Vertical");
### Instantiate / Spawn ### Instantiate / Spawn
**Unity:** **Unity:**
```csharp ```csharp
Instantiate(prefab, position, rotation); Instantiate(prefab, position, rotation);
``` ```
**Unreal:** **Unreal:**
``` ```
Spawn Actor from Class Spawn Actor from Class
├── Class: YourBlueprintClass ├── Class: YourBlueprintClass
@ -68,11 +74,13 @@ Spawn Actor from Class
### Destroy ### Destroy
**Unity:** **Unity:**
```csharp ```csharp
Destroy(gameObject); Destroy(gameObject);
``` ```
**Unreal:** **Unreal:**
``` ```
Destroy Actor Destroy Actor
└── Target: Self (or reference) └── Target: Self (or reference)
@ -83,11 +91,13 @@ Destroy Actor
### Delta Time ### Delta Time
**Unity:** **Unity:**
```csharp ```csharp
Time.deltaTime Time.deltaTime
``` ```
**Unreal:** **Unreal:**
``` ```
Get World Delta Seconds Get World Delta Seconds
``` ```
@ -97,12 +107,14 @@ Get World Delta Seconds
### Find Objects ### Find Objects
**Unity:** **Unity:**
```csharp ```csharp
FindObjectOfType<PlayerController>(); FindObjectOfType<PlayerController>();
FindObjectsOfType<Enemy>(); FindObjectsOfType<Enemy>();
``` ```
**Unreal:** **Unreal:**
``` ```
Get All Actors of Class Get All Actors of Class
├── Actor Class: BP_Player ├── Actor Class: BP_Player
@ -116,6 +128,7 @@ Then: Get (a ref) → index 0
### Singleton Pattern ### Singleton Pattern
**Unity:** **Unity:**
```csharp ```csharp
public static GameManager Instance { get; private set; } public static GameManager Instance { get; private set; }
@ -125,6 +138,7 @@ void Awake() {
``` ```
**Unreal Options:** **Unreal Options:**
1. **Game Instance** - Persists across levels 1. **Game Instance** - Persists across levels
2. **Subsystem** - Engine-managed singleton 2. **Subsystem** - Engine-managed singleton
3. **Get All Actors of Class** - Find at runtime 3. **Get All Actors of Class** - Find at runtime
@ -134,6 +148,7 @@ void Awake() {
### Coroutines vs Timers ### Coroutines vs Timers
**Unity:** **Unity:**
```csharp ```csharp
StartCoroutine(DelayedAction()); StartCoroutine(DelayedAction());
@ -144,6 +159,7 @@ IEnumerator DelayedAction() {
``` ```
**Unreal:** **Unreal:**
``` ```
Set Timer by Function Name Set Timer by Function Name
├── Function Name: "DelayedAction" ├── Function Name: "DelayedAction"
@ -158,10 +174,12 @@ Or use **Delay** node in blueprints.
### Physics Layers vs Collision Channels ### Physics Layers vs Collision Channels
**Unity:** **Unity:**
- Layer-based collision matrix - Layer-based collision matrix
- `Physics.IgnoreLayerCollision()` - `Physics.IgnoreLayerCollision()`
**Unreal:** **Unreal:**
- Collision Channels (Object Types) - Collision Channels (Object Types)
- Collision Presets - Collision Presets
- Per-component collision settings - Per-component collision settings
@ -171,11 +189,13 @@ Or use **Delay** node in blueprints.
### Tags ### Tags
**Unity:** **Unity:**
```csharp ```csharp
if (other.CompareTag("Enemy")) { } if (other.CompareTag("Enemy")) { }
``` ```
**Unreal:** **Unreal:**
``` ```
Actor Has Tag Actor Has Tag
├── Target: Other Actor ├── Target: Other Actor
@ -188,11 +208,11 @@ Or use **Cast To** for type checking (preferred).
### Vector Math ### Vector Math
| Operation | Unity | Unreal | | Operation | Unity | Unreal |
|-----------|-------|--------| | ------------- | --------------------- | -------------------- |
| Normalize | `vector.normalized` | `Normalize` node | | Normalize | `vector.normalized` | `Normalize` node |
| Magnitude | `vector.magnitude` | `Vector Length` node | | Magnitude | `vector.magnitude` | `Vector Length` node |
| Dot Product | `Vector3.Dot(a, b)` | `Dot Product` node | | Dot Product | `Vector3.Dot(a, b)` | `Dot Product` node |
| Cross Product | `Vector3.Cross(a, b)` | `Cross Product` node | | Cross Product | `Vector3.Cross(a, b)` | `Cross Product` node |
--- ---
@ -200,12 +220,14 @@ Or use **Cast To** for type checking (preferred).
### Random ### Random
**Unity:** **Unity:**
```csharp ```csharp
Random.Range(0f, 1f); Random.Range(0f, 1f);
Random.Range(0, 10); // int, exclusive max Random.Range(0, 10); // int, exclusive max
``` ```
**Unreal:** **Unreal:**
``` ```
Random Float in Range Random Float in Range
├── Min: 0.0 ├── Min: 0.0
@ -223,12 +245,14 @@ Random Integer in Range
### Debug ### Debug
**Unity:** **Unity:**
```csharp ```csharp
Debug.Log("Message"); Debug.Log("Message");
Debug.DrawLine(start, end, Color.red); Debug.DrawLine(start, end, Color.red);
``` ```
**Unreal:** **Unreal:**
``` ```
Print String Print String
└── In String: "Message" └── In String: "Message"
@ -243,16 +267,16 @@ Draw Debug Line
## Quick Reference Table ## Quick Reference Table
| Concept | Unity | Unreal | | Concept | Unity | Unreal |
|---------|-------|--------| | --------- | ---------------- | --------------------- |
| Script | C# MonoBehaviour | Blueprint / C++ Actor | | Script | C# MonoBehaviour | Blueprint / C++ Actor |
| Prefab | .prefab asset | Blueprint Class | | Prefab | .prefab asset | Blueprint Class |
| Scene | .unity scene | Level (.umap) | | Scene | .unity scene | Level (.umap) |
| Inspector | Inspector window | Details panel | | Inspector | Inspector window | Details panel |
| Hierarchy | Hierarchy window | Outliner | | Hierarchy | Hierarchy window | Outliner |
| Project | Project window | Content Browser | | Project | Project window | Content Browser |
| Console | Console window | Output Log | | Console | Console window | Output Log |
| Play | Play button | Play (Alt+P) | | Play | Play button | Play (Alt+P) |
--- ---

View File

@ -0,0 +1,155 @@
#!/bin/bash
# Markdown Linter and Formatter Script
# Usage: ./lint-markdown.sh [--fix]
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
FIX_MODE=false
# Parse arguments
if [[ "$1" == "--fix" ]]; then
FIX_MODE=true
fi
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo "========================================="
echo "Markdown Linter and Formatter"
echo "========================================="
# Check for required tools
check_tool() {
if ! command -v "$1" &> /dev/null; then
echo -e "${YELLOW}Warning: $1 not found. Installing...${NC}"
return 1
fi
return 0
}
# Install markdownlint-cli if not present
if ! check_tool "markdownlint"; then
echo "Installing markdownlint-cli..."
npm install -g markdownlint-cli
fi
# Install prettier if not present
if ! check_tool "prettier"; then
echo "Installing prettier..."
npm install -g prettier
fi
# Create markdownlint config if it doesn't exist
CONFIG_FILE="$SCRIPT_DIR/.markdownlint.json"
if [[ ! -f "$CONFIG_FILE" ]]; then
echo "Creating markdownlint configuration..."
cat > "$CONFIG_FILE" << 'EOF'
{
"default": true,
"MD013": {
"line_length": 300,
"code_blocks": false,
"tables": false
},
"MD024": {
"siblings_only": true
},
"MD033": {
"allowed_elements": ["br", "details", "summary", "kbd"]
},
"MD041": false,
"MD036": false,
"MD040": false,
"MD046": false
}
EOF
fi
# Create prettier config if it doesn't exist
PRETTIER_CONFIG="$SCRIPT_DIR/.prettierrc"
if [[ ! -f "$PRETTIER_CONFIG" ]]; then
echo "Creating prettier configuration..."
cat > "$PRETTIER_CONFIG" << 'EOF'
{
"proseWrap": "preserve",
"tabWidth": 2,
"useTabs": false,
"printWidth": 300
}
EOF
fi
echo ""
echo "========================================="
echo "Running Linters on Markdown Files"
echo "========================================="
# Find all markdown files
MD_FILES=$(find "$SCRIPT_DIR" -maxdepth 1 -name "*.md" -type f | sort)
TOTAL_ISSUES=0
if [[ "$FIX_MODE" == true ]]; then
echo -e "${GREEN}Running in FIX mode - will auto-fix issues${NC}"
echo ""
# Run prettier to format
echo "Step 1: Running Prettier (formatting)..."
for file in $MD_FILES; do
echo " Formatting: $(basename "$file")"
prettier --write "$file" 2>/dev/null || true
done
echo ""
echo "Step 2: Running markdownlint with --fix..."
for file in $MD_FILES; do
echo " Linting: $(basename "$file")"
markdownlint --fix --config "$CONFIG_FILE" "$file" 2>&1 || true
done
echo ""
echo "Step 3: Final check for remaining issues..."
for file in $MD_FILES; do
OUTPUT=$(markdownlint --config "$CONFIG_FILE" "$file" 2>&1 || true)
if [[ -n "$OUTPUT" ]]; then
echo -e "${YELLOW}$(basename "$file"):${NC}"
echo "$OUTPUT"
TOTAL_ISSUES=$((TOTAL_ISSUES + $(echo "$OUTPUT" | wc -l)))
fi
done
else
echo -e "${YELLOW}Running in CHECK mode - use --fix to auto-fix${NC}"
echo ""
for file in $MD_FILES; do
echo "Checking: $(basename "$file")"
OUTPUT=$(markdownlint --config "$CONFIG_FILE" "$file" 2>&1 || true)
if [[ -n "$OUTPUT" ]]; then
echo -e "${RED}Issues found:${NC}"
echo "$OUTPUT"
TOTAL_ISSUES=$((TOTAL_ISSUES + $(echo "$OUTPUT" | wc -l)))
else
echo -e "${GREEN} ✓ No issues${NC}"
fi
echo ""
done
fi
echo ""
echo "========================================="
echo "Summary"
echo "========================================="
if [[ $TOTAL_ISSUES -eq 0 ]]; then
echo -e "${GREEN}✓ All markdown files are clean!${NC}"
else
echo -e "${YELLOW}Found $TOTAL_ISSUES remaining issue(s)${NC}"
echo "Some issues may require manual fixing."
fi
echo ""
echo "Done!"

View File

@ -14,7 +14,6 @@
5. In the "Unreal Project Browser" window that appears: 5. In the "Unreal Project Browser" window that appears:
- At the top, select "Games" category (should be selected by default) - At the top, select "Games" category (should be selected by default)
- Click "Blank" template (empty square icon) - Click "Blank" template (empty square icon)
6. On the right side panel, configure: 6. On the right side panel, configure:
- **Project Defaults:** Blueprint (not C++) - **Project Defaults:** Blueprint (not C++)
- **Target Platform:** Desktop - **Target Platform:** Desktop
@ -25,17 +24,18 @@
7. At the bottom: 7. At the bottom:
- Choose folder location where you want to save - Choose folder location where you want to save
- Name the project: `BulletHellGame` - Name the project: `BulletHellGame`
8. Click "Create" button (bottom right, yellow) 8. Click "Create" button (bottom right, yellow)
### Expected Result ### Expected Result
Unreal Editor opens with an empty level. You should see: Unreal Editor opens with an empty level. You should see:
- Main 3D viewport in the center - Main 3D viewport in the center
- Outliner panel on the right (showing "Untitled" level) - Outliner panel on the right (showing "Untitled" level)
- Details panel on the right side - Details panel on the right side
> **NOTE:** The Content Drawer is NOT open by default. To open it: > **NOTE:** The Content Drawer is NOT open by default. To open it:
>
> - Click "Content Drawer" button at the bottom of the screen, OR > - Click "Content Drawer" button at the bottom of the screen, OR
> - Press `Ctrl+Space`, OR > - Press `Ctrl+Space`, OR
> - Go to Window → Content Drawer > - Go to Window → Content Drawer
@ -54,7 +54,6 @@ Since bullet-hell games are typically 2D, we'll set up a top-down orthographic v
- Go to menu bar (the top bar): **Edit** (Second from left, next to "File" and "Window") → **Project Settings** - Go to menu bar (the top bar): **Edit** (Second from left, next to "File" and "Window") → **Project Settings**
- In the left sidebar, search for "Maps & Modes" (Project → 4th from the top, next to GameplayTags and Movies) - In the left sidebar, search for "Maps & Modes" (Project → 4th from the top, next to GameplayTags and Movies)
- Click on "Maps & Modes" - Click on "Maps & Modes"
5. Under "Default Modes": 5. Under "Default Modes":
- Find "Default GameMode" dropdown - Find "Default GameMode" dropdown
- We'll create our own later, leave it for now - We'll create our own later, leave it for now

View File

@ -21,6 +21,7 @@
### Expected Result ### Expected Result
A new tab opens showing the Blueprint Editor with: A new tab opens showing the Blueprint Editor with:
- Components panel on the left - Components panel on the left
- Viewport in the center - Viewport in the center
- Details panel on the right - Details panel on the right
@ -39,6 +40,7 @@ A new tab opens showing the Blueprint Editor with:
- Rename it to `PlayerSprite` (click on it, then press F2) - Rename it to `PlayerSprite` (click on it, then press F2)
> **NOTE:** If Paper Sprite is not available: > **NOTE:** If Paper Sprite is not available:
>
> - Go to Edit → Plugins (from main menu bar) > - Go to Edit → Plugins (from main menu bar)
> - Search for "Paper2D" > - Search for "Paper2D"
> - Make sure "Paper2D" plugin is ENABLED > - Make sure "Paper2D" plugin is ENABLED
@ -81,7 +83,7 @@ A new tab opens showing the Blueprint Editor with:
- This should already be ENABLED by default - This should already be ENABLED by default
- If not, check/enable it - this tells Unreal to use THIS camera when playing - If not, check/enable it - this tells Unreal to use THIS camera when playing
- This camera will follow the player, making testing easy - This camera will follow the player, making testing easy
> **NOTE:** This temporary camera ensures you can see the player during testing. > **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. > In [Part 8](part-8-game-mode-level.md), we'll set up a proper fixed camera for the final game.
@ -104,7 +106,7 @@ DefaultSceneRoot
- Find "Auto Possess Player" dropdown (currently set to "Disabled") - Find "Auto Possess Player" dropdown (currently set to "Disabled")
- Change it to **"Player 0"** - Change it to **"Player 0"**
- This makes the engine automatically possess this pawn when playing, which activates the PlayerCamera - 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 > **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. > in [Part 8](part-8-game-mode-level.md), which handles possession automatically.
@ -112,7 +114,7 @@ DefaultSceneRoot
## Step 2.3: Create Player Variables ## Step 2.3: Create Player Variables
*(Continue in the same BP_Player Blueprint Editor tab that was opened in Step 2.1)* _(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) 1. In the Blueprint Editor, look at the left panel (below the Components panel)
2. Find "My Blueprint" section 2. Find "My Blueprint" section
@ -120,27 +122,27 @@ DefaultSceneRoot
4. Create these variables one by one (click +, then set properties in Details): 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 > **NOTE:** Variables are NOT added to the Components hierarchy. They appear in a
> separate "Variables" list within the "My Blueprint" panel. Variables store > separate "Variables" list within the "My Blueprint" panel. Variables store
> data values, while Components are physical/visual parts of the actor. > data values, while Components are physical/visual parts of the actor.
| # | Variable Name | Type | Default Value | | # | Variable Name | Type | Default Value |
|---|---------------|------|---------------| | --- | -------------- | ----------------- | ----------------- |
| 1 | `MoveSpeed` | Float | 750.0 | | 1 | `MoveSpeed` | Float | 750.0 |
| 2 | `BoundsMin` | Vector 2D | X=-850, Y=-450 | | 2 | `BoundsMin` | Vector 2D | X=-850, Y=-450 |
| 3 | `BoundsMax` | Vector 2D | X=850, Y=450 | | 3 | `BoundsMax` | Vector 2D | X=850, Y=450 |
| 4 | `FireInterval` | Float | 0.08 | | 4 | `FireInterval` | Float | 0.08 |
| 5 | `FireTimer` | Float | 0.0 | | 5 | `FireTimer` | Float | 0.0 |
| 6 | `BulletSpeed` | Float | 2200.0 | | 6 | `BulletSpeed` | Float | 2200.0 |
| 7 | `MaxLives` | Integer | 3 | | 7 | `MaxLives` | Integer | 3 |
| 8 | `CurrentLives` | Integer | 3 | | 8 | `CurrentLives` | Integer | 3 |
| 9 | `VolleySize` | Integer | 3 | | 9 | `VolleySize` | Integer | 3 |
| 10 | `VolleySpread` | Float | 12.0 | | 10 | `VolleySpread` | Float | 12.0 |
| 11 | `SpecialUsed` | Boolean | false (unchecked) | | 11 | `SpecialUsed` | Boolean | false (unchecked) |
| 12 | `BulletClass` | Class Reference* | (set later) | | 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.
> \*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. > **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) 5. Click **"Compile"** button (top left, blue checkmark icon)
@ -149,6 +151,7 @@ DefaultSceneRoot
### Expected Result ### Expected Result
My Blueprint panel shows: My Blueprint panel shows:
- All 12 variables you created listed under "Variables" - 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 - A "Components" category with 4 component references (DefaultSceneRoot, PlayerSprite, PlayerCollision, Arrow) - these are automatically created from the components you added in Step 2.2
@ -200,38 +203,38 @@ Before creating any input assets, you **MUST** configure the project to use Enha
2. Click **"+"** next to "Mappings" to add IA_Move: 2. Click **"+"** next to "Mappings" to add IA_Move:
- Select "IA_Move" from dropdown - Select "IA_Move" from dropdown
- Click "+" under IA_Move to add key bindings: - Click "+" under IA_Move to add key bindings:
> **NOTE:** In Unreal's top-down view, X axis = up/down, Y axis = left/right > **NOTE:** In Unreal's top-down view, X axis = up/down, Y axis = left/right
**For W key (move UP on screen):** **For W key (move UP on screen):**
- Click "+", select "W" - Click "+", select "W"
- No modifiers needed (outputs X positive = up) - No modifiers needed (outputs X positive = up)
- TOTAL MODIFIERS FOR W: **0** - TOTAL MODIFIERS FOR W: **0**
**For S key (move DOWN on screen):** **For S key (move DOWN on screen):**
- Click "+", select "S" - Click "+", select "S"
- Click "+" next to "Modifiers" → Add "Negate" - Click "+" next to "Modifiers" → Add "Negate"
- TOTAL MODIFIERS FOR S: **1** (Negate only) - TOTAL MODIFIERS FOR S: **1** (Negate only)
**For A key (move LEFT on screen):** **For A key (move LEFT on screen):**
- Click "+", select "A" - Click "+", select "A"
- Click "+" next to "Modifiers" → Add "Swizzle Input Axis Values" → Order: "YXZ" - Click "+" next to "Modifiers" → Add "Swizzle Input Axis Values" → Order: "YXZ"
- Click "+" next to "Modifiers" AGAIN → Add "Negate" - Click "+" next to "Modifiers" AGAIN → Add "Negate"
- TOTAL MODIFIERS FOR A: **2** (Swizzle AND Negate) - TOTAL MODIFIERS FOR A: **2** (Swizzle AND Negate)
**For D key (move RIGHT on screen):** **For D key (move RIGHT on screen):**
- Click "+", select "D" - Click "+", select "D"
- Click "+" next to "Modifiers" → Add "Swizzle Input Axis Values" → Order: "YXZ" - Click "+" next to "Modifiers" → Add "Swizzle Input Axis Values" → Order: "YXZ"
- TOTAL MODIFIERS FOR D: **1** (Swizzle only) - TOTAL MODIFIERS FOR D: **1** (Swizzle only)
**Summary Table - verify your IMC_Default matches this:** **Summary Table - verify your IMC_Default matches this:**
| Key | Modifiers | Output Vector | | Key | Modifiers | Output Vector |
|-----|-----------|---------------| | --- | ---------------------------------------- | -------------- |
| W | (none) | (1, 0) = UP | | W | (none) | (1, 0) = UP |
| S | Negate | (-1, 0) = DOWN | | S | Negate | (-1, 0) = DOWN |
| A | Swizzle Input Axis Values (YXZ) + Negate | (0, -1) = LEFT | | A | Swizzle Input Axis Values (YXZ) + Negate | (0, -1) = LEFT |
| D | Swizzle Input Axis Values (YXZ) | (0, 1) = RIGHT | | D | Swizzle Input Axis Values (YXZ) | (0, 1) = RIGHT |
After adding all keys, press `Ctrl+S` to SAVE IMC_Default! After adding all keys, press `Ctrl+S` to SAVE IMC_Default!
@ -254,8 +257,9 @@ Before creating any input assets, you **MUST** configure the project to use Enha
3. Click on **"Event Graph"** tab (above the main view, third tab from left, next to Construction Script) 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): You should see three default events (red nodes):
- Event BeginPlay - Event BeginPlay
- Event ActorBeginOverlap - Event ActorBeginOverlap
- Event Tick - Event Tick
### 3. ADD MAPPING CONTEXT IN BEGINPLAY ### 3. ADD MAPPING CONTEXT IN BEGINPLAY
@ -267,7 +271,7 @@ From "Event BeginPlay" node:
- It has a "Player Index" input (default 0) and "Return Value" output - 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` 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` 3. From that blue output, drag and search `Add Mapping Context`
4. For "Mapping Context" input: 4. For "Mapping Context" input:
- Click dropdown and select `IMC_Default` - Click dropdown and select `IMC_Default`
- Or drag IMC_Default from Content Drawer - Or drag IMC_Default from Content Drawer
5. Set "Priority" to `0` 5. Set "Priority" to `0`
@ -276,11 +280,10 @@ From "Event BeginPlay" node:
- The WHITE TRIANGLE pins are different from the BLUE CIRCLE pins! - The WHITE TRIANGLE pins are different from the BLUE CIRCLE pins!
- Blue circles = DATA (what values to use) - Blue circles = DATA (what values to use)
- White triangles = EXECUTION (when to run) - White triangles = EXECUTION (when to run)
- On "Event BeginPlay", find the WHITE TRIANGLE on the RIGHT side - On "Event BeginPlay", find the WHITE TRIANGLE on the RIGHT side
- On "Add Mapping Context", find the WHITE TRIANGLE on the LEFT 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 - Click and DRAG from BeginPlay's white triangle to Add Mapping Context's white triangle
**WITHOUT THIS WHITE WIRE, THE CODE NEVER RUNS!** **WITHOUT THIS WHITE WIRE, THE CODE NEVER RUNS!**
Your graph should look like this: Your graph should look like this:
@ -303,6 +306,7 @@ Your graph should look like this:
``` ```
**VERIFY:** You must have BOTH: **VERIFY:** You must have BOTH:
- Blue data wires connecting the nodes (passes the subsystem reference) - Blue data wires connecting the nodes (passes the subsystem reference)
- White execution wire from Event BeginPlay to Add Mapping Context (makes it run) - White execution wire from Event BeginPlay to Add Mapping Context (makes it run)
@ -310,19 +314,21 @@ Your graph should look like this:
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. 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): #### a) Create debug variables (these are ONLY for debugging)
- In My Blueprint panel → Variables → click "+" - In My Blueprint panel → Variables → click "+"
- Name: `DEBUG_LastMoveInput` - Name: `DEBUG_LastMoveInput`
- Type: Vector 2D - Type: Vector 2D
- Default Value: (0, 0) - Default Value: (0, 0)
- Compile to save - Compile to save
#### b) Create a SEPARATE debug event using a Custom Event: #### b) Create a SEPARATE debug event using a Custom Event
- Right-click in empty space → search `Custom Event` → add it - Right-click in empty space → search `Custom Event` → add it
- Name it `DEBUG_PrintInput` - Name it `DEBUG_PrintInput`
- This keeps debug logic completely isolated - This keeps debug logic completely isolated
#### c) Build the debug logic from DEBUG_PrintInput: #### c) Build the debug logic from DEBUG_PrintInput
1. **Get the current input value:** 1. **Get the current input value:**
- Right-click → "Get Player Controller" - Right-click → "Get Player Controller"
@ -342,7 +348,8 @@ Before creating movement, let's verify input is working with a separate debug se
- "Set DEBUG_LastMoveInput" = IA_Move value - "Set DEBUG_LastMoveInput" = IA_Move value
- Then → "Print String" with IA_Move value - Then → "Print String" with IA_Move value
#### d) Call the debug event from Event Tick: #### d) Call the debug event from Event Tick
- From "Event Tick" WHITE pin → search `DEBUG_PrintInput` - From "Event Tick" WHITE pin → search `DEBUG_PrintInput`
- This adds a node that calls your custom event - This adds a node that calls your custom event
@ -374,18 +381,22 @@ AREA 2 - Debug logic (completely separate):
``` ```
#### e) Compile and Save #### e) Compile and Save
#### f) Drag BP_Player into level, Press Play (`Alt+P`) #### f) Drag BP_Player into level, Press Play (`Alt+P`)
#### g) IMPORTANT: Click inside the game viewport to give it keyboard focus!
#### g) IMPORTANT: Click inside the game viewport to give it keyboard focus
#### h) Press WASD keys and look at top-left of screen #### h) Press WASD keys and look at top-left of screen
**Expected:** Message appears ONLY when you press or release a key: **Expected:** Message appears ONLY when you press or release a key:
- Press W: prints "X=1.0 Y=0.0" - Press W: prints "X=1.0 Y=0.0"
- Release W: prints "X=0.0 Y=0.0" - Release W: prints "X=0.0 Y=0.0"
- Log is NOT spammed every frame - 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. **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 Steps (if you see "X=0.0 Y=0.0" always)
<details> <details>
<summary><b>DEBUGGING STEP 1</b> - Verify the pawn is possessed</summary> <summary><b>DEBUGGING STEP 1</b> - Verify the pawn is possessed</summary>
@ -394,6 +405,7 @@ AREA 2 - Debug logic (completely separate):
- Find BP_Player in the list - Find BP_Player in the list
- If it shows a small controller icon next to it, it's possessed - 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 - If NOT possessed: Check that "Auto Possess Player" = "Player 0" in BP_Player
</details> </details>
<details> <details>
@ -403,6 +415,7 @@ AREA 2 - Debug logic (completely separate):
- The viewport must have focus to receive keyboard input - The viewport must have focus to receive keyboard input
- Try pressing `Shift+F1` to release mouse, then click viewport again - Try pressing `Shift+F1` to release mouse, then click viewport again
- Then press WASD again - Then press WASD again
</details> </details>
<details> <details>
@ -413,6 +426,7 @@ AREA 2 - Debug logic (completely separate):
- Type "Tick is running" in the In String field (just plain text) - 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 - 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 - If you DON'T see this: The blueprint isn't running at all
</details> </details>
<details> <details>
@ -423,6 +437,7 @@ AREA 2 - Debug logic (completely separate):
- Connect the white wire: BeginPlay → Add Mapping Context → Print String - Connect the white wire: BeginPlay → Add Mapping Context → Print String
- Play the game - you should see "BeginPlay executed" once at start - Play the game - you should see "BeginPlay executed" once at start
- If you DON'T see this: BeginPlay isn't running (pawn not spawned?) - If you DON'T see this: BeginPlay isn't running (pawn not spawned?)
</details> </details>
<details> <details>
@ -432,6 +447,7 @@ AREA 2 - Debug logic (completely separate):
- Delete ALL of them - Delete ALL of them
- Drag in exactly ONE fresh BP_Player from Content Drawer - Drag in exactly ONE fresh BP_Player from Content Drawer
- Try again - Try again
</details> </details>
<details> <details>
@ -443,6 +459,7 @@ AREA 2 - Debug logic (completely separate):
- In Event Graph: Event Tick → Print String (connected to Get IA_Move) - In Event Graph: Event Tick → Print String (connected to Get IA_Move)
- Drag this minimal test blueprint into level - Drag this minimal test blueprint into level
- If THIS works, something is wrong with your BP_Player - If THIS works, something is wrong with your BP_Player
</details> </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 WORKS:** Continue to step 5. You can optionally delete the DEBUG_PrintInput call from Event Tick to stop the debug prints.
@ -452,7 +469,8 @@ AREA 2 - Debug logic (completely separate):
> **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). > **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: #### a) INSERT A SEQUENCE NODE between Event Tick and debug
- Delete the wire from Event Tick to DEBUG_PrintInput - Delete the wire from Event Tick to DEBUG_PrintInput
- Right-click → search `Sequence` → add it - Right-click → search `Sequence` → add it
- Connect Event Tick → Sequence (input) - Connect Event Tick → Sequence (input)
@ -468,92 +486,110 @@ Event Tick → Sequence ─┬─ Then 0 → DEBUG_PrintInput
If you skipped debug, just connect Event Tick directly to movement. If you skipped debug, just connect Event Tick directly to movement.
#### b) Starting point for movement: #### b) Starting point for movement
- If using Sequence: drag from "Then 1" output - If using Sequence: drag from "Then 1" output
- If no debug: drag from Event Tick directly - If no debug: drag from Event Tick directly
#### c) Right-click → search `Get Player Controller` and add it #### c) Right-click → search `Get Player Controller` and add it
(This is a NEW Get Player Controller - separate from the one in debug) (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` #### d) From Player Controller output, drag → `Get Enhanced Input Local Player Subsystem`
#### e) From subsystem output, drag and right-click → search `IA_Move` #### e) From subsystem output, drag and right-click → search `IA_Move`
- Look under "Input" → "Enhanced Action Values" → select "IA_Move" - Look under "Input" → "Enhanced Action Values" → select "IA_Move"
- This node returns the current value of the IA_Move input action - 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). #### 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, 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 OR drag from output and search "To Vector 2D" to convert it
#### g) Right-click → `Make Vector` #### g) Right-click → `Make Vector`
- Connect the X from movement input to X - Connect the X from movement input to X
- Connect the Y from movement input to Y - Connect the Y from movement input to Y
- Set Z to 0 - Set Z to 0
#### h) Right-click → `Normalize` #### h) Right-click → `Normalize`
- Connect the vector output to Normalize input - Connect the vector output to Normalize input
#### i) Right-click → `Get World Delta Seconds` #### i) Right-click → `Get World Delta Seconds`
#### j) Right-click → `Get MoveSpeed` (your variable) #### j) Right-click → `Get MoveSpeed` (your variable)
#### k) Right-click → Multiply (float * float) #### k) Right-click → Multiply (float \* float)
- Connect Delta Seconds output to first input - Connect Delta Seconds output to first input
- Connect MoveSpeed output to second input - Connect MoveSpeed output to second input
#### l) Right-click → Multiply (vector * float) #### l) Right-click → Multiply (vector \* float)
- Connect Normalized vector (from step h) to vector input - Connect Normalized vector (from step h) to vector input
- Connect (DeltaSeconds * MoveSpeed) result (from step k) to float input - Connect (DeltaSeconds \* MoveSpeed) result (from step k) to float input
- This output is the "movement delta" - how far to move this frame - This output is the "movement delta" - how far to move this frame
#### m) Right-click → `Get Actor Location` #### m) Right-click → `Get Actor Location`
#### n) Right-click → Add (vector + vector) #### n) Right-click → Add (vector + vector)
- Connect current location (from step m) to first input - Connect current location (from step m) to first input
- Connect movement delta (from step l) to second input - Connect movement delta (from step l) to second input
#### o) CLAMP X COORDINATE: #### o) CLAMP X COORDINATE
First, break apart the new position vector from step n: 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" - 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 - This splits the vector into three separate pins: X, Y, Z
Now clamp the X value: Now clamp the X value:
- Right-click in empty space → search `Clamp (float)` → add it - Right-click in empty space → search `Clamp (float)` → add it
- Connect the "X" output (from the split Add node) to "Value" input of Clamp - Connect the "X" output (from the split Add node) to "Value" input of Clamp
Get the min bound: Get the min bound:
- Right-click → search `Get BoundsMin` (your variable) → add it - Right-click → search `Get BoundsMin` (your variable) → add it
- Right-click on BoundsMin output pin → "Split Struct Pin" (splits into X, Y) - Right-click on BoundsMin output pin → "Split Struct Pin" (splits into X, Y)
- Connect BoundsMin's "X" to Clamp's "Min" input - Connect BoundsMin's "X" to Clamp's "Min" input
Get the max bound: Get the max bound:
- Right-click → search `Get BoundsMax` (your variable) → add it - Right-click → search `Get BoundsMax` (your variable) → add it
- Right-click on BoundsMax output pin → "Split Struct Pin" - Right-click on BoundsMax output pin → "Split Struct Pin"
- Connect BoundsMax's "X" to Clamp's "Max" input - Connect BoundsMax's "X" to Clamp's "Max" input
The Clamp node now outputs the X position clamped within bounds. The Clamp node now outputs the X position clamped within bounds.
#### p) CLAMP Y COORDINATE: #### p) CLAMP Y COORDINATE
- Right-click → add another `Clamp (float)` node - Right-click → add another `Clamp (float)` node
- Connect the "Y" output (from the split Add node in step o) to "Value" - Connect the "Y" output (from the split Add node in step o) to "Value"
- Connect BoundsMin's "Y" (already split) to "Min" - Connect BoundsMin's "Y" (already split) to "Min"
- Connect BoundsMax's "Y" (already split) to "Max" - Connect BoundsMax's "Y" (already split) to "Max"
#### q) Right-click → `Make Vector` #### q) Right-click → `Make Vector`
- Connect clamped X - Connect clamped X
- Connect clamped Y - Connect clamped Y
- Set Z to 0 - Set Z to 0
#### r) Right-click → `Set Actor Location` #### r) Right-click → `Set Actor Location`
- Connect the clamped vector to "New Location" - Connect the clamped vector to "New Location"
#### s) CRITICAL - Connect the execution wire (white wire): #### s) CRITICAL - Connect the execution wire (white wire)
- If using Sequence (from step a): drag from "Then 1" to Set Actor Location - 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 - If no debug: drag from Event Tick to Set Actor Location
- Without this execution wire, the movement code NEVER runs! - Without this execution wire, the movement code NEVER runs!
**Final Structure (with debug - using Sequence node):** **Final Structure (with debug - using Sequence node):**
``` ```
Event Tick → Sequence ─┬─ Then 0 → DEBUG_PrintInput Event Tick → Sequence ─┬─ Then 0 → DEBUG_PrintInput
@ -561,6 +597,7 @@ Event Tick → Sequence ─┬─ Then 0 → DEBUG_PrintInput
``` ```
**Final Structure (without debug):** **Final Structure (without debug):**
``` ```
Event Tick → [movement logic] → Set Actor Location Event Tick → [movement logic] → Set Actor Location
``` ```
@ -569,18 +606,21 @@ Event Tick → [movement logic] → Set Actor Location
### 6. Click Compile and Save ### 6. Click Compile and Save
### Expected Result after Compile: ### Expected Result after Compile
- Compile button shows GREEN checkmark (no errors) - Compile button shows GREEN checkmark (no errors)
- No warnings about unconnected pins - No warnings about unconnected pins
### How to Test at This Stage: ### How to Test at This Stage
1. Open any level (or the default "Untitled" level) 1. Open any level (or the default "Untitled" level)
2. From Content Drawer, drag BP_Player into the viewport 2. From Content Drawer, drag BP_Player into the viewport
3. Press Play (`Alt+P`) 3. Press Play (`Alt+P`)
4. Click inside the game viewport to give it keyboard focus 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 5. You should see the cube (TempVisual added in Step 2.2) and control it with WASD
### Expected Result when tested: ### Expected Result when tested
- Player pawn moves smoothly when pressing WASD keys - Player pawn moves smoothly when pressing WASD keys
- Movement is frame-rate independent (consistent speed) - Movement is frame-rate independent (consistent speed)
- Player cannot move outside the screen bounds (stops at edges) - Player cannot move outside the screen bounds (stops at edges)
@ -588,7 +628,7 @@ Event Tick → [movement logic] → Set Actor Location
> **NOTE:** Full game testing will be possible after completing [Part 8 (Level Setup)](part-8-game-mode-level.md). > **NOTE:** Full game testing will be possible after completing [Part 8 (Level Setup)](part-8-game-mode-level.md).
### Visual Diagram of Movement Nodes: ### Visual Diagram of Movement Nodes
``` ```
┌─────────────┐ ┌──────────┐ ┌─────────────┐ ┌──────────┐
@ -597,7 +637,7 @@ Event Tick → [movement logic] → Set Actor Location
└─ Then 1 ──► Movement logic ──► Set Actor Location └─ Then 1 ──► Movement logic ──► Set Actor Location
(If no debug, skip Sequence and connect Event Tick directly to movement) (If no debug, skip Sequence and connect Event Tick directly to movement)
┌──────────────────────────────────┐ ┌────────────────────┐ ┌──────────────────────────────────┐ ┌────────────────────┐
│ Get Enhanced Input Subsystem │ │ Set Actor Location │ │ Get Enhanced Input Subsystem │ │ Set Actor Location │
│ → Get Action Value (IA_Move) │ └────────────────────┘ │ → Get Action Value (IA_Move) │ └────────────────────┘
@ -624,18 +664,20 @@ Event Tick → [movement logic] → Set Actor Location
2. After the movement logic, add firing check using Enhanced Input: 2. After the movement logic, add firing check using Enhanced Input:
#### a) Right-click → search `IA_Fire` ### a) Right-click → search `IA_Fire`
- Look under "Input" → "Enhanced Action Events" category (diamond ◇ icons) - Look under "Input" → "Enhanced Action Events" category (diamond ◇ icons)
- Select "IA_Fire" (the one with diamond icon, NOT the square icon) - 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 - This creates an EVENT node (red, like Event Tick) that fires when the button is pressed
> **NOTE:** > **NOTE:**
>
> - "Enhanced Action Events" (diamond ◇) = triggers when button pressed > - "Enhanced Action Events" (diamond ◇) = triggers when button pressed
> - "Enhanced Action Values" (square □) = reads current value continuously > - "Enhanced Action Values" (square □) = reads current value continuously
> >
> For firing, we want the EVENT. > For firing, we want the EVENT.
#### b) From the "Triggered" execution pin, build the fire rate limiter: #### b) From the "Triggered" execution pin, build the fire rate limiter
1. **Get current timer value:** 1. **Get current timer value:**
- Right-click → `Get FireTimer` (your variable) - Right-click → `Get FireTimer` (your variable)
@ -701,7 +743,8 @@ Leave the execution wire open after the "Set FireTimer = FireInterval" node. We'
### 4. Compile and Save ### 4. Compile and Save
### Expected Result after Compile: ### Expected Result after Compile
- Compile button shows GREEN checkmark - Compile button shows GREEN checkmark
- Fire rate limiter logic works (Print String spams when holding Z) - 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 - 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
@ -717,17 +760,18 @@ Leave the execution wire open after the "Set FireTimer = FireInterval" node. We'
--- ---
### 1. CREATE "HandleDeath" FUNCTION (Placeholder): ### 1. CREATE "HandleDeath" FUNCTION (Placeholder)
We create this FIRST because `TakeHit` will call it. For now, it's a placeholder. We create this FIRST because `TakeHit` will call it. For now, it's a placeholder.
#### a) Create the function: #### a) Create the function
1. In My Blueprint panel (left side), find "Functions" section 1. In My Blueprint panel (left side), find "Functions" section
2. Click the **"+"** button next to "Functions" 2. Click the **"+"** button next to "Functions"
3. Name it `HandleDeath` 3. Name it `HandleDeath`
4. Double-click to open the function graph 4. Double-click to open the function graph
#### b) Add placeholder logic (will be completed in Part 6): #### b) Add placeholder logic (will be completed in Part 6)
1. The function graph opens with a purple "HandleDeath" entry node 1. The function graph opens with a purple "HandleDeath" entry node
@ -742,6 +786,7 @@ We create this FIRST because `TakeHit` will call it. For now, it's a placeholder
- Position it near the end of the function - Position it near the end of the function
4. **Connect execution:** 4. **Connect execution:**
``` ```
HandleDeath (entry) ──► Set Actor Hidden in Game HandleDeath (entry) ──► Set Actor Hidden in Game
@ -755,16 +800,18 @@ We create this FIRST because `TakeHit` will call it. For now, it's a placeholder
--- ---
### 2. CREATE "TakeHit" FUNCTION: ### 2. CREATE "TakeHit" FUNCTION
Now we can create TakeHit, which calls HandleDeath. Now we can create TakeHit, which calls HandleDeath.
#### a) Create the function: #### a) Create the function
1. In My Blueprint panel → Functions → click **"+"** 1. In My Blueprint panel → Functions → click **"+"**
2. Name it `TakeHit` 2. Name it `TakeHit`
3. Double-click to open the function graph 3. Double-click to open the function graph
#### b) Add input parameter: #### b) Add input parameter
1. With the function graph open, look at the Details panel (right side) 1. With the function graph open, look at the Details panel (right side)
2. Find "Inputs" section 2. Find "Inputs" section
3. Click **"+"** to add a new input 3. Click **"+"** to add a new input
@ -772,19 +819,22 @@ Now we can create TakeHit, which calls HandleDeath.
5. Type: `Integer` 5. Type: `Integer`
6. The purple entry node now shows a "Damage" output pin 6. The purple entry node now shows a "Damage" output pin
#### c) Build the function logic: #### c) Build the function logic
**Step 1 - Get current lives:** **Step 1 - Get current lives:**
- Right-click in empty space → search `Get CurrentLives` → add it - Right-click in empty space → search `Get CurrentLives` → add it
- This reads the CurrentLives variable value - This reads the CurrentLives variable value
**Step 2 - Subtract damage from lives:** **Step 2 - Subtract damage from lives:**
- Right-click → search `integer - integer` or `Subtract (int)` → add it - Right-click → search `integer - integer` or `Subtract (int)` → add it
- Connect `CurrentLives` (from Step 1) to the TOP input (A) - Connect `CurrentLives` (from Step 1) to the TOP input (A)
- Connect `Damage` (from the purple entry node) to the BOTTOM input (B) - Connect `Damage` (from the purple entry node) to the BOTTOM input (B)
- Result = CurrentLives minus Damage - Result = CurrentLives minus Damage
**Step 3 - Clamp the result (prevent negative lives):** **Step 3 - Clamp the result (prevent negative lives):**
- Right-click → search `Clamp (int)` → add the "Clamp (integer)" node - Right-click → search `Clamp (int)` → add the "Clamp (integer)" node
- Connect the Subtract result (from Step 2) to the "Value" input - Connect the Subtract result (from Step 2) to the "Value" input
- Set "Min" to `0` (type directly in the field) - Set "Min" to `0` (type directly in the field)
@ -792,11 +842,13 @@ Now we can create TakeHit, which calls HandleDeath.
- The output is the clamped value (0 or higher) - The output is the clamped value (0 or higher)
**Step 4 - Store the new lives value:** **Step 4 - Store the new lives value:**
- Right-click → search `Set CurrentLives` → add it - Right-click → search `Set CurrentLives` → add it
- Connect the Clamp output (from Step 3) to the input - Connect the Clamp output (from Step 3) to the input
- **IMPORTANT:** Connect execution wire from entry node → Set CurrentLives - **IMPORTANT:** Connect execution wire from entry node → Set CurrentLives
**Step 5 - Check if player should die:** **Step 5 - Check if player should die:**
- From `Set CurrentLives`, drag execution → search `Branch` → add it - From `Set CurrentLives`, drag execution → search `Branch` → add it
- Right-click → search `<= (integer)` (less than or equal) → 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 - Connect `CurrentLives` (drag from Set node's output, or add new Get) to TOP input
@ -804,13 +856,15 @@ Now we can create TakeHit, which calls HandleDeath.
- Connect the `<=` result (boolean) to Branch's "Condition" input - Connect the `<=` result (boolean) to Branch's "Condition" input
**Step 6 - On TRUE (player is dead):** **Step 6 - On TRUE (player is dead):**
- From Branch's "True" execution pin, drag → search `HandleDeath` → add it - From Branch's "True" execution pin, drag → search `HandleDeath` → add it
- This calls the function we created in step 1 - This calls the function we created in step 1
**Step 7 - On FALSE (player still alive):** **Step 7 - On FALSE (player still alive):**
- Leave the "False" pin unconnected (do nothing, player survives) - Leave the "False" pin unconnected (do nothing, player survives)
#### d) Visual diagram of TakeHit function: #### d) Visual diagram of TakeHit function
``` ```
┌────────────────────────┐ ┌────────────────────────┐
@ -840,7 +894,7 @@ Now we can create TakeHit, which calls HandleDeath.
--- ---
### 3. SPECIAL ABILITY (Screen Clear) - Input Setup Only: ### 3. SPECIAL ABILITY (Screen Clear) - Input Setup Only
> **IMPORTANT - Dependency Note:** > **IMPORTANT - Dependency Note:**
> The special ability needs to destroy BP_Enemy and BP_Bullet actors, which don't exist yet. > The special ability needs to destroy BP_Enemy and BP_Bullet actors, which don't exist yet.
@ -849,21 +903,24 @@ Now we can create TakeHit, which calls HandleDeath.
Using Enhanced Input (IA_Special was already set up with X and Right Mouse in Step 2.4): Using Enhanced Input (IA_Special was already set up with X and Right Mouse in Step 2.4):
#### a) Create the event node: #### a) Create the event node
1. Go back to the **Event Graph** tab (not inside a function) 1. Go back to the **Event Graph** tab (not inside a function)
2. Right-click in empty space → search `EnhancedInputAction IA_Special` 2. Right-click in empty space → search `EnhancedInputAction IA_Special`
- Look under "Input" → "Enhanced Action Events" category - Look under "Input" → "Enhanced Action Events" category
- Select "IA_Special" (the EVENT with diamond ◇ icon) - Select "IA_Special" (the EVENT with diamond ◇ icon)
3. This creates a red event node that fires when X or Right Mouse is pressed 3. This creates a red event node that fires when X or Right Mouse is pressed
#### b) Check if special was already used: #### b) Check if special was already used
1. From the "Triggered" execution pin, drag → search `Branch` → add it 1. From the "Triggered" execution pin, drag → search `Branch` → add it
2. Right-click → search `Get SpecialUsed` → add it 2. Right-click → search `Get SpecialUsed` → add it
3. Right-click → search `NOT Boolean` → add it 3. Right-click → search `NOT Boolean` → add it
4. Connect SpecialUsed → NOT → Branch Condition 4. Connect SpecialUsed → NOT → Branch Condition
- We branch on "NOT SpecialUsed" (true = hasn't been used yet) - We branch on "NOT SpecialUsed" (true = hasn't been used yet)
#### c) On TRUE (special available) - placeholder: #### c) On TRUE (special available) - placeholder
1. From Branch's "True" pin, drag → search `Set SpecialUsed` → add it 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) 2. Check the box to set it to TRUE (marks ability as used)
@ -877,10 +934,11 @@ Using Enhanced Input (IA_Special was already set up with X and Right Mouse in St
- Type: "TODO: Add enemy/bullet destruction (Part 4)" - Type: "TODO: Add enemy/bullet destruction (Part 4)"
- Position it after the Print String - Position it after the Print String
#### d) On FALSE (special already used): #### d) On FALSE (special already used)
- Leave unconnected (nothing happens on second press) - Leave unconnected (nothing happens on second press)
#### e) Visual diagram (placeholder version): #### e) Visual diagram (placeholder version)
``` ```
┌─────────────────────────────┐ ┌─────────────────────────────┐
@ -912,7 +970,8 @@ Using Enhanced Input (IA_Special was already set up with X and Right Mouse in St
#### f) Compile and Save #### f) Compile and Save
#### g) Test the placeholder: #### g) Test the placeholder
1. Press Play 1. Press Play
2. Press X key (or Right Mouse Button) 2. Press X key (or Right Mouse Button)
3. You should see "SPECIAL ABILITY ACTIVATED!" appear once 3. You should see "SPECIAL ABILITY ACTIVATED!" appear once
@ -924,21 +983,24 @@ Using Enhanced Input (IA_Special was already set up with X and Right Mouse in St
### 4. Compile and Save ### 4. Compile and Save
### Expected Result after Compile: ### Expected Result after Compile
- Compile button shows GREEN checkmark - Compile button shows GREEN checkmark
- "TakeHit" and "HandleDeath" functions appear under Functions in My Blueprint panel - "TakeHit" and "HandleDeath" functions appear under Functions in My Blueprint panel
- Event Graph shows IA_Special event with branching logic - Event Graph shows IA_Special event with branching logic
### Expected Result in Play mode (at this stage): ### Expected Result in Play mode (at this stage)
- Press X key: "SPECIAL ABILITY ACTIVATED!" appears once in top-left - Press X key: "SPECIAL ABILITY ACTIVATED!" appears once in top-left
- Press X again: Nothing happens (SpecialUsed is now true) - Press X again: Nothing happens (SpecialUsed is now true)
- TakeHit and HandleDeath functions exist but cannot be tested yet (nothing calls them) - TakeHit and HandleDeath functions exist but cannot be tested yet (nothing calls them)
> **NOTE:** Full testing of damage/death systems requires: > **NOTE:** Full testing of damage/death systems requires:
>
> - BP_Bullet collision (Part 3) to call TakeHit > - BP_Bullet collision (Part 3) to call TakeHit
> - BP_GameDirector (Part 6) for HandleDeath to notify > - BP_GameDirector (Part 6) for HandleDeath to notify
> - BP_Enemy (Part 4) for special ability to destroy > - 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. > **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.
--- ---

View File

@ -23,20 +23,22 @@
6. **Create Variables:** 6. **Create Variables:**
| Variable Name | Type | Default Value | | Variable Name | Type | Default Value |
|---------------|------|---------------| | ------------------- | ------- | ------------- |
| `TravelDirection` | Vector | (0, 1, 0) | | `TravelDirection` | Vector | (0, 1, 0) |
| `TravelSpeed` | Float | 1200.0 | | `TravelSpeed` | Float | 1200.0 |
| `RemainingLifetime` | Float | 4.0 | | `RemainingLifetime` | Float | 4.0 |
| `IsEnemyProjectile` | Boolean | false | | `IsEnemyProjectile` | Boolean | false |
| `Damage` | Integer | 1 | | `Damage` | Integer | 1 |
### Expected Result after Compile
### Expected Result after Compile:
- Compile button shows GREEN checkmark - Compile button shows GREEN checkmark
- Components panel shows: DefaultSceneRoot → BulletSprite, BulletCollision, TempVisual - Components panel shows: DefaultSceneRoot → BulletSprite, BulletCollision, TempVisual
- Variables panel shows all 5 variables with correct types - Variables panel shows all 5 variables with correct types
### Expected Result in Viewport (Blueprint Editor): ### Expected Result in Viewport (Blueprint Editor)
- Small cube visible (the TempVisual placeholder) - Small cube visible (the TempVisual placeholder)
- Sphere collision visible (radius 8) - Sphere collision visible (radius 8)
@ -46,7 +48,7 @@
1. In Event Graph, from **Event Tick:** 1. In Event Graph, from **Event Tick:**
#### a) Calculate movement: ### a) Calculate movement
1. Right-click → `Get TravelDirection` 1. Right-click → `Get TravelDirection`
2. Right-click → `Get TravelSpeed` 2. Right-click → `Get TravelSpeed`
@ -70,7 +72,7 @@
- Connect the Add result to "New Location" - Connect the Add result to "New Location"
- Connect execution wire from Event Tick to Set Actor Location - Connect execution wire from Event Tick to Set Actor Location
#### b) Check lifetime and destroy when expired: #### b) Check lifetime and destroy when expired
9. Right-click → `Get RemainingLifetime` 9. Right-click → `Get RemainingLifetime`
@ -115,30 +117,32 @@ Event Tick ──► Set Actor Location ──► Set RemainingLifetime ──
Destroy Actor (nothing) Destroy Actor (nothing)
``` ```
### 2. CREATE "Initialize" FUNCTION: ### 2. CREATE "Initialize" FUNCTION
#### a) Create the function
#### a) Create the function:
1. In "My Blueprint" panel (left side), find "Functions" section 1. In "My Blueprint" panel (left side), find "Functions" section
2. Click the **"+"** button next to Functions 2. Click the **"+"** button next to Functions
3. Name the new function `Initialize` 3. Name the new function `Initialize`
4. Double-click to open the function graph 4. Double-click to open the function graph
#### b) Add input parameters: #### b) Add input parameters
1. In the function graph, you should see a purple "Initialize" entry node 1. In the function graph, you should see a purple "Initialize" entry node
2. With the entry node selected, look at the Details panel (right side) 2. With the entry node selected, look at the Details panel (right side)
3. Find "Inputs" section and click "+" to add parameters: 3. Find "Inputs" section and click "+" to add parameters:
| Parameter | Type | Default | | Parameter | Type | Default |
|-----------|------|---------| | ------------- | ------- | ------- |
| `Direction` | Vector | - | | `Direction` | Vector | - |
| `Speed` | Float | - | | `Speed` | Float | - |
| `bIsEnemy` | Boolean | - | | `bIsEnemy` | Boolean | - |
| `Lifetime` | Float | - | | `Lifetime` | Float | - |
| `DamageValue` | Integer | 1 | | `DamageValue` | Integer | 1 |
The entry node should now show 5 input pins. The entry node should now show 5 input pins.
#### c) Build the function logic: #### c) Build the function logic
1. **Normalize and set direction:** 1. **Normalize and set direction:**
- Drag from **Direction** parameter (yellow pin) → search `Normalize` → add it - Drag from **Direction** parameter (yellow pin) → search `Normalize` → add it
@ -161,7 +165,7 @@ The entry node should now show 5 input pins.
- Right-click → `Set Damage` - Right-click → `Set Damage`
- Drag from **DamageValue** parameter → Set Damage input - Drag from **DamageValue** parameter → Set Damage input
#### d) CRITICAL - Connect execution wires: #### d) CRITICAL - Connect execution wires
> **IMPORTANT:** Without execution wires, the SET nodes will NEVER run! You must chain them together. > **IMPORTANT:** Without execution wires, the SET nodes will NEVER run! You must chain them together.
@ -192,7 +196,8 @@ The entry node should now show 5 input pins.
### 3. Compile and Save ### 3. Compile and Save
### Expected Result after Compile: ### Expected Result after Compile
- Compile button shows GREEN checkmark - Compile button shows GREEN checkmark
- "Initialize" function appears under Functions with 5 input parameters - "Initialize" function appears under Functions with 5 input parameters
@ -202,7 +207,7 @@ The entry node should now show 5 input pins.
## Step 3.3: Bullet Collision Logic ## Step 3.3: Bullet Collision Logic
### 1. ADD THE OVERLAP EVENT: ### 1. ADD THE OVERLAP EVENT
1. In the Components panel (top-left), click on **"BulletCollision"** to select it 1. In the Components panel (top-left), click on **"BulletCollision"** to select it
@ -214,7 +219,7 @@ The entry node should now show 5 input pins.
3. The Event Graph now shows a red node: **"On Component Begin Overlap (BulletCollision)"** 3. The Event Graph now shows a red node: **"On Component Begin Overlap (BulletCollision)"**
- This fires whenever another actor overlaps with the bullet's collision sphere - This fires whenever another actor overlaps with the bullet's collision sphere
### 2. CHECK IF THIS IS AN ENEMY PROJECTILE: ### 2. CHECK IF THIS IS AN ENEMY PROJECTILE
4. Right-click → `Get IsEnemyProjectile` 4. Right-click → `Get IsEnemyProjectile`
- This gets your boolean variable - This gets your boolean variable
@ -224,7 +229,7 @@ The entry node should now show 5 input pins.
- TRUE = this is an enemy bullet (should damage player) - TRUE = this is an enemy bullet (should damage player)
- FALSE = this is a player bullet (should damage enemies) - FALSE = this is a player bullet (should damage enemies)
### 3. TRUE BRANCH - Enemy Bullet Hits Player: ### 3. TRUE BRANCH - Enemy Bullet Hits Player
6. From the **"On Component Begin Overlap"** node, look for the **"Other Actor"** output pin 6. From the **"On Component Begin Overlap"** node, look for the **"Other Actor"** output pin
- This is the actor that overlapped with the bullet - This is the actor that overlapped with the bullet
@ -254,7 +259,7 @@ The entry node should now show 5 input pins.
12. Connect execution wire: 12. Connect execution wire:
- Cast to BP_Player → TakeHit → Destroy Actor - Cast to BP_Player → TakeHit → Destroy Actor
### 4. FALSE BRANCH - Player Bullet Hits Enemy (PLACEHOLDER): ### 4. FALSE BRANCH - Player Bullet Hits Enemy (PLACEHOLDER)
> **NOTE:** BP_Enemy doesn't exist yet, so we'll add a placeholder. You'll complete this logic in [Part 4, Step 4.7](part-4-create-enemy.md#step-47-complete-bullet-collision-logic-bp_bullet). > **NOTE:** BP_Enemy doesn't exist yet, so we'll add a placeholder. You'll complete this logic in [Part 4, Step 4.7](part-4-create-enemy.md#step-47-complete-bullet-collision-logic-bp_bullet).
@ -293,7 +298,8 @@ The entry node should now show 5 input pins.
### 4. Compile and Save ### 4. Compile and Save
### Expected Result after Compile: ### Expected Result after Compile
- Compile button shows GREEN checkmark - Compile button shows GREEN checkmark
- Event Graph shows "On Component Begin Overlap" event connected to Branch - Event Graph shows "On Component Begin Overlap" event connected to Branch
@ -305,11 +311,12 @@ The entry node should now show 5 input pins.
Now that BP_Bullet exists, we can complete the player's firing functions. Now that BP_Bullet exists, we can complete the player's firing functions.
### 1. Open BP_Player Blueprint: ### 1. Open BP_Player Blueprint
- In Content Drawer, navigate to Content → Blueprints - In Content Drawer, navigate to Content → Blueprints
- Double-click "BP_Player" to open the Blueprint Editor - Double-click "BP_Player" to open the Blueprint Editor
### 2. SET THE BULLET CLASS VARIABLE: ### 2. SET THE BULLET CLASS VARIABLE
1. In My Blueprint panel, find the `BulletClass` variable 1. In My Blueprint panel, find the `BulletClass` variable
2. Click on it to select it 2. Click on it to select it
@ -317,25 +324,28 @@ Now that BP_Bullet exists, we can complete the player's firing functions.
4. Click the dropdown and select `BP_Bullet` 4. Click the dropdown and select `BP_Bullet`
5. Compile to save the change 5. Compile to save the change
### 3. CREATE SPAWN BULLET FUNCTION: ### 3. CREATE SPAWN BULLET FUNCTION
#### a) In "My Blueprint" panel, under "Functions", click "+" #### a) In "My Blueprint" panel, under "Functions", click "+"
#### b) Name the function `SpawnBullet` #### b) Name the function `SpawnBullet`
#### c) Double-click to open function graph #### c) Double-click to open function graph
#### d) Add input parameters to the function: #### d) Add input parameters to the function
- In the function graph, look at the purple "SpawnBullet" entry node - In the function graph, look at the purple "SpawnBullet" entry node
- In Details panel (right side), find "Inputs" section - In Details panel (right side), find "Inputs" section
- Click "+" to add a new input parameter - Click "+" to add a new input parameter
| Parameter | Type | | Parameter | Type |
|-----------|------| | --------------- | ------ |
| `SpawnLocation` | Vector | | `SpawnLocation` | Vector |
| `Direction` | Vector | | `Direction` | Vector |
The entry node should now show two input pins: SpawnLocation and Direction The entry node should now show two input pins: SpawnLocation and Direction
#### e) Build the spawning logic: #### e) Build the spawning logic
1. Right-click → search `Spawn Actor from Class` → add it 1. Right-click → search `Spawn Actor from Class` → add it
@ -352,7 +362,7 @@ The entry node should now show two input pins: SpawnLocation and Direction
4. Connect execution wire: 4. Connect execution wire:
- Drag from SpawnBullet entry node (white triangle) → SpawnActor (white triangle) - Drag from SpawnBullet entry node (white triangle) → SpawnActor (white triangle)
#### f) Initialize the spawned bullet: #### f) Initialize the spawned bullet
5. Add the Cast node: 5. Add the Cast node:
- From SpawnActor's **"Return Value"** output (blue pin on right side), drag → search `Cast to BP_Bullet` - From SpawnActor's **"Return Value"** output (blue pin on right side), drag → search `Cast to BP_Bullet`
@ -408,13 +418,15 @@ The entry node should now show two input pins: SpawnLocation and Direction
#### g) Compile (should have no errors now) #### g) Compile (should have no errors now)
### 4. CREATE FIRE VOLLEY FUNCTION: ### 4. CREATE FIRE VOLLEY FUNCTION
#### a) In "My Blueprint" panel, under "Functions", click "+" #### a) In "My Blueprint" panel, under "Functions", click "+"
#### b) Name the function `FireVolley` #### b) Name the function `FireVolley`
#### c) Double-click to open function graph #### c) Double-click to open function graph
#### d) Inside FireVolley function graph, build this logic step by step: #### d) Inside FireVolley function graph, build this logic step by step
--- ---
@ -477,73 +489,68 @@ The entry node should now show two input pins: SpawnLocation and Direction
9. From Branch FALSE pin, we need to loop through each bullet. 9. From Branch FALSE pin, we need to loop through each bullet.
First, calculate the starting angle: First, calculate the starting angle:
The spread is CENTERED on "straight up". For 3 bullets with 12° spread: The spread is CENTERED on "straight up". For 3 bullets with 12° spread:
- Bullet 0: -12° (left of center) - Bullet 0: -12° (left of center)
- Bullet 1: 0° (center, straight up) - Bullet 1: 0° (center, straight up)
- Bullet 2: +12° (right of center) - Bullet 2: +12° (right of center)
**Formula:** `StartAngle = -(VolleySpread * (VolleySize - 1)) / 2` **Formula:** `StartAngle = -(VolleySpread * (VolleySize - 1)) / 2`
10. **Calculate StartAngle:** 10. **Calculate StartAngle:**
- Right-click → `Get VolleySize` - Right-click → `Get VolleySize`
- Right-click → `Subtract (Integer)` → connect VolleySize, type `1` - Right-click → `Subtract (Integer)` → connect VolleySize, type `1`
- Result: (VolleySize - 1) = 2 for default - Result: (VolleySize - 1) = 2 for default
- Right-click → `Get VolleySpread` - Right-click → `Get VolleySpread`
- Right-click → `Multiply (float)` → connect VolleySpread and (VolleySize-1) - Right-click → `Multiply (float)` → connect VolleySpread and (VolleySize-1)
- NOTE: The integer will auto-convert to float - NOTE: The integer will auto-convert to float
- Result: VolleySpread * (VolleySize - 1) = 12 * 2 = 24 - Result: VolleySpread _(VolleySize - 1) = 12_ 2 = 24
- Right-click → `Divide (float)` → connect the multiply result, type `2` - Right-click → `Divide (float)` → connect the multiply result, type `2`
- Result: 24 / 2 = 12 - Result: 24 / 2 = 12
- Right-click → `Negate (float)` or "Multiply by -1" - Right-click → `Negate (float)` or "Multiply by -1"
- Result: -12 (this is StartAngle) - Result: -12 (this is StartAngle)
- Right-click → `Set` → create a LOCAL variable "StartAngle" (Float) - Right-click → `Set` → create a LOCAL variable "StartAngle" (Float)
- OR just keep the wire connected (we'll use it in the loop) - OR just keep the wire connected (we'll use it in the loop)
11. **Set up the FOR loop:** 11. **Set up the FOR loop:**
- From Branch FALSE pin, drag → right-click → search `For Loop` - From Branch FALSE pin, drag → right-click → search `For Loop`
- "First Index": type `0` - "First Index": type `0`
- "Last Index": connect (VolleySize - 1) - "Last Index": connect (VolleySize - 1)
- You already calculated this in step 10, reuse it or calculate again: - You already calculated this in step 10, reuse it or calculate again:
- Get VolleySize → Subtract 1 → connect to Last Index - Get VolleySize → Subtract 1 → connect to Last Index
The loop will run with Index = 0, 1, 2 for VolleySize=3 The loop will run with Index = 0, 1, 2 for VolleySize=3
12. **Inside the loop (from "Loop Body" execution pin):** 12. **Inside the loop (from "Loop Body" execution pin):**
Calculate angle for THIS bullet: Calculate angle for THIS bullet:
- Right-click → `Multiply (float)` - Right-click → `Multiply (float)`
- Connect loop "Index" to first input (auto-converts int to float) - Connect loop "Index" to first input (auto-converts int to float)
- Connect VolleySpread to second input - Connect VolleySpread to second input
- Result: Index * VolleySpread (0, 12, 24 for indices 0, 1, 2) - Result: Index \* VolleySpread (0, 12, 24 for indices 0, 1, 2)
- Right-click → `Add (float)` - Right-click → `Add (float)`
- Connect StartAngle (the -12 from step 10) to first input - Connect StartAngle (the -12 from step 10) to first input
- Connect (Index * VolleySpread) to second input - Connect (Index \* VolleySpread) to second input
- Result: StartAngle + (Index * VolleySpread) = -12, 0, +12 degrees - Result: StartAngle + (Index \* VolleySpread) = -12, 0, +12 degrees
This is the angle in DEGREES. Store or continue with this value. This is the angle in DEGREES. Store or continue with this value.
13. **Convert angle from degrees to a direction vector:** 13. **Convert angle from degrees to a direction vector:**
In Unreal's top-down view (looking down Z axis): In Unreal's top-down view (looking down Z axis):
- X axis = up/down on screen (vertical) - X axis = up/down on screen (vertical)
- Y axis = left/right on screen (horizontal) - Y axis = left/right on screen (horizontal)
- Angle 0° = straight up = direction (1, 0, 0) - Angle 0° = straight up = direction (1, 0, 0)
- Angle 90° = right = direction (0, 1, 0) - Angle 90° = right = direction (0, 1, 0)
``` ```
Direction.X = Cos(angle) // Cos for vertical (forward/up) Direction.X = Cos(angle) // Cos for vertical (forward/up)
Direction.Y = Sin(angle) // Sin for horizontal offset Direction.Y = Sin(angle) // Sin for horizontal offset
Direction.Z = 0 Direction.Z = 0
``` ```
BUT Unreal's Sin/Cos use RADIANS, not degrees! BUT Unreal's Sin/Cos use RADIANS, not degrees!
**a) Convert degrees to radians:** **a) Convert degrees to radians:**
- Right-click → search `Degrees To Radians` → add it - Right-click → search `Degrees To Radians` → add it
- Connect your angle (from step 12) to the input - Connect your angle (from step 12) to the input
@ -552,14 +559,14 @@ The entry node should now show two input pins: SpawnLocation and Direction
**b) Calculate X component (Cos - forward/up direction):** **b) Calculate X component (Cos - forward/up direction):**
- Right-click → `Cos (Radians)` → add it - Right-click → `Cos (Radians)` → add it
- Connect the Degrees To Radians output to Cos input - Connect the Degrees To Radians output to Cos input
**c) Calculate Y component (Sin - horizontal offset):** **c) Calculate Y component (Sin - horizontal offset):**
- Right-click → `Sin (Radians)` → add it - Right-click → `Sin (Radians)` → add it
- To connect the SAME radians value to Sin (without losing the Cos connection): - To connect the SAME radians value to Sin (without losing the Cos connection):
- **Option 1:** Ctrl+drag from "Degrees To Radians" output to Sin input (creates second wire) - **Option 1:** Ctrl+drag from "Degrees To Radians" output to Sin input (creates second wire)
- **Option 2:** Drag directly from "Degrees To Radians" output again - UE5 allows multiple wires from one output pin - **Option 2:** Drag directly from "Degrees To Radians" output again - UE5 allows multiple wires from one output pin
- Both Cos and Sin should now be connected to the same radians value - Both Cos and Sin should now be connected to the same radians value
**d) Make the direction vector:** **d) Make the direction vector:**
- Right-click → `Make Vector` - Right-click → `Make Vector`
- Connect Cos result to X (forward/up direction) - Connect Cos result to X (forward/up direction)
@ -606,12 +613,13 @@ The entry node should now show two input pins: SpawnLocation and Direction
``` ```
**Test Values (VolleySize=3, VolleySpread=12):** **Test Values (VolleySize=3, VolleySpread=12):**
- StartAngle = -(12 * 2) / 2 = -12°
- Bullet 0: -12 + (0 * 12) = -12° → slightly left
- Bullet 1: -12 + (1 * 12) = 0° → straight up
- Bullet 2: -12 + (2 * 12) = +12° → slightly right
### 5. CONNECT FIREVOLLEY TO THE FIRE RATE LIMITER: - StartAngle = -(12 \* 2) / 2 = -12°
- Bullet 0: -12 + (0 \* 12) = -12° → slightly left
- Bullet 1: -12 + (1 \* 12) = 0° → straight up
- Bullet 2: -12 + (2 \* 12) = +12° → slightly right
### 5. CONNECT FIREVOLLEY TO THE FIRE RATE LIMITER
1. Go back to the Event Graph tab 1. Go back to the Event Graph tab
2. Find your IA_Fire logic from [Step 2.5](part-2-create-player.md#step-25-create-player-firing-logic) 2. Find your IA_Fire logic from [Step 2.5](part-2-create-player.md#step-25-create-player-firing-logic)
@ -629,11 +637,13 @@ The entry node should now show two input pins: SpawnLocation and Direction
### 6. Compile and Save ### 6. Compile and Save
### Expected Result after Compile: ### Expected Result after Compile
- Compile button shows GREEN checkmark - Compile button shows GREEN checkmark
- "FireVolley" and "SpawnBullet" functions appear under Functions in My Blueprint panel - "FireVolley" and "SpawnBullet" functions appear under Functions in My Blueprint panel
### Expected Result in Play mode: ### Expected Result in Play mode
- Pressing Z or Left Mouse Button spawns 3 bullets in a spread pattern - Pressing Z or Left Mouse Button spawns 3 bullets in a spread pattern
- Bullets travel upward from player position - Bullets travel upward from player position
- Rapid fire when holding the button (every 0.08 seconds) - Rapid fire when holding the button (every 0.08 seconds)

View File

@ -22,31 +22,33 @@
6. **Create Variables:** 6. **Create Variables:**
| Variable Name | Type | Default Value | | Variable Name | Type | Default Value |
|---------------|------|---------------| | --------------------- | ------------------------ | ------------- |
| `MaxHealth` | Integer | 12 | | `MaxHealth` | Integer | 12 |
| `CurrentHealth` | Integer | 12 | | `CurrentHealth` | Integer | 12 |
| `ScoreValue` | Integer | 50 | | `ScoreValue` | Integer | 50 |
| `VerticalSpeed` | Float | 220.0 | | `VerticalSpeed` | Float | 220.0 |
| `HorizontalAmplitude` | Float | 250.0 | | `HorizontalAmplitude` | Float | 250.0 |
| `HorizontalFrequency` | Float | 1.8 | | `HorizontalFrequency` | Float | 1.8 |
| `DespawnY` | Float | -750.0 | | `DespawnY` | Float | -750.0 |
| `FireInterval` | Float | 0.35 | | `FireInterval` | Float | 0.35 |
| `BulletsPerBurst` | Integer | 20 | | `BulletsPerBurst` | Integer | 20 |
| `BurstSpread` | Float | 360.0 | | `BurstSpread` | Float | 360.0 |
| `EnemyBulletSpeed` | Float | 1000.0 | | `EnemyBulletSpeed` | Float | 1000.0 |
| `EnemyBulletLifetime` | Float | 6.0 | | `EnemyBulletLifetime` | Float | 6.0 |
| `BaseX` | Float | 0.0 | | `BaseX` | Float | 0.0 |
| `WaveSeed` | Float | 0.0 | | `WaveSeed` | Float | 0.0 |
| `FireTimer` | Float | 0.0 | | `FireTimer` | Float | 0.0 |
| `BulletClass` | Class Reference to Actor | (set later) | | `BulletClass` | Class Reference to Actor | (set later) |
### Expected Result after Compile
### Expected Result after Compile:
- Compile button shows GREEN checkmark - Compile button shows GREEN checkmark
- Components panel shows: DefaultSceneRoot → EnemySprite, EnemyCollision, TempVisual - Components panel shows: DefaultSceneRoot → EnemySprite, EnemyCollision, TempVisual
- Variables panel shows all 16 variables with correct types and defaults - Variables panel shows all 16 variables with correct types and defaults
### Expected Result in Viewport (Blueprint Editor): ### Expected Result in Viewport (Blueprint Editor)
- Cube visible (the TempVisual placeholder, larger than player) - Cube visible (the TempVisual placeholder, larger than player)
- Box collision visible (30x30x10) - Box collision visible (30x30x10)
@ -56,20 +58,23 @@
1. In Event Graph, from **"Event BeginPlay":** 1. In Event Graph, from **"Event BeginPlay":**
#### a) Set CurrentHealth = MaxHealth ### a) Set CurrentHealth = MaxHealth
#### b) Get Actor Location → Break Vector → Set BaseX = X value ### b) Get Actor Location → Break Vector → Set BaseX = X value
### c) Random Float in Range (0, 6.28) → Set WaveSeed
#### c) Random Float in Range (0, 6.28) → Set WaveSeed
(6.28 ≈ 2π for wave randomization) (6.28 ≈ 2π for wave randomization)
#### d) Random Float in Range (0, FireInterval) → Set FireTimer #### d) Random Float in Range (0, FireInterval) → Set FireTimer
### Expected Result after Compile: ### Expected Result after Compile
- Compile button shows GREEN checkmark - Compile button shows GREEN checkmark
- BeginPlay event connected to variable setters - BeginPlay event connected to variable setters
### Expected Result in Play mode: ### Expected Result in Play mode
- Each enemy spawns with randomized WaveSeed (0 to 6.28) - Each enemy spawns with randomized WaveSeed (0 to 6.28)
- Each enemy starts with different FireTimer offset - Each enemy starts with different FireTimer offset
- This creates varied, non-synchronized enemy behavior - This creates varied, non-synchronized enemy behavior
@ -80,11 +85,13 @@
1. In Event Graph, from **"Event Tick":** 1. In Event Graph, from **"Event Tick":**
#### a) VERTICAL MOVEMENT: ### a) VERTICAL MOVEMENT
- Get Actor Location → Break into X, Y, Z
- Subtract (VerticalSpeed * DeltaSeconds) from Y - Get Actor Location → Break into X, Y, Z
- Subtract (VerticalSpeed \* DeltaSeconds) from Y
### b) HORIZONTAL SINE WAVE
#### b) HORIZONTAL SINE WAVE:
- Get Game Time in Seconds - Get Game Time in Seconds
- Add WaveSeed - Add WaveSeed
- Multiply by HorizontalFrequency - Multiply by HorizontalFrequency
@ -95,7 +102,8 @@
#### c) Set Actor Location with new X, Y (Z stays 0) #### c) Set Actor Location with new X, Y (Z stays 0)
#### d) DESPAWN CHECK: #### d) DESPAWN CHECK
- If Y < DespawnY: Destroy Actor - If Y < DespawnY: Destroy Actor
- If Abs(X) > 1400: Destroy Actor - If Abs(X) > 1400: Destroy Actor
@ -115,11 +123,13 @@
└──────────────────────────────────────────────┘ └──────────────────────────────────────────────┘
``` ```
### Expected Result after Compile: ### Expected Result after Compile
- Compile button shows GREEN checkmark - Compile button shows GREEN checkmark
- Event Tick connected to movement and despawn logic - Event Tick connected to movement and despawn logic
### Expected Result in Play mode: ### Expected Result in Play mode
- Enemies drift downward at VerticalSpeed (220 units/sec) - Enemies drift downward at VerticalSpeed (220 units/sec)
- Enemies oscillate horizontally in sine wave pattern - Enemies oscillate horizontally in sine wave pattern
- Each enemy has different horizontal phase (due to WaveSeed) - Each enemy has different horizontal phase (due to WaveSeed)
@ -129,28 +139,31 @@
## Step 4.4: Enemy Firing Logic ## Step 4.4: Enemy Firing Logic
### 1. Continue in Event Tick (after movement): ### 1. Continue in Event Tick (after movement)
#### a) Decrease FireTimer by DeltaSeconds #### a) Decrease FireTimer by DeltaSeconds
#### b) Branch: if FireTimer <= 0: #### b) Branch: if FireTimer <= 0
- Reset FireTimer to FireInterval - Reset FireTimer to FireInterval
- Call "FireBurst" function - Call "FireBurst" function
### 2. CREATE "FireBurst" FUNCTION: ### 2. CREATE "FireBurst" FUNCTION
#### a) Add function "FireBurst" #### a) Add function "FireBurst"
#### b) Inside: #### b) Inside
- Get BulletsPerBurst - Get BulletsPerBurst
- Calculate angle step: `360 / BulletsPerBurst` (for full circle) - Calculate angle step: `360 / BulletsPerBurst` (for full circle)
- OR: `BurstSpread / (BulletsPerBurst - 1)` (for partial arc) - OR: `BurstSpread / (BulletsPerBurst - 1)` (for partial arc)
#### c) For Loop from 0 to BulletsPerBurst - 1: #### c) For Loop from 0 to BulletsPerBurst - 1
- Calculate angle: `i * AngleStep` - Calculate angle: `i * AngleStep`
- Convert to direction vector: - Convert to direction vector:
- X = Sin(angle in radians) - X = Sin(angle in radians)
- Y = -Cos(angle in radians) ← negative because enemies fire DOWN - Y = -Cos(angle in radians) ← negative because enemies fire DOWN
- Spawn bullet: - Spawn bullet:
- Spawn Actor from Class (BP_Bullet) - Spawn Actor from Class (BP_Bullet)
- Get spawned bullet, call Initialize: - Get spawned bullet, call Initialize:
@ -171,11 +184,13 @@
``` ```
### Expected Result after Compile: ### Expected Result after Compile
- Compile button shows GREEN checkmark - Compile button shows GREEN checkmark
- "FireBurst" function appears under Functions - "FireBurst" function appears under Functions
### Expected Result in Play mode: ### Expected Result in Play mode
- Every 0.35 seconds (FireInterval), enemy fires bullet burst - Every 0.35 seconds (FireInterval), enemy fires bullet burst
- 20 bullets spawn in a full 360° circle pattern - 20 bullets spawn in a full 360° circle pattern
- Bullets travel outward from enemy position - Bullets travel outward from enemy position
@ -185,37 +200,42 @@
## Step 4.5: Enemy Damage and Death ## Step 4.5: Enemy Damage and Death
### 1. CREATE "ApplyDamage" FUNCTION: ### 1. CREATE "ApplyDamage" FUNCTION
#### a) Add input: `DamageAmount` (Integer) #### a) Add input: `DamageAmount` (Integer)
#### b) Inside: #### b) Inside
- Subtract DamageAmount from CurrentHealth - Subtract DamageAmount from CurrentHealth
- If CurrentHealth <= 0: - If CurrentHealth <= 0:
- Call HandleDeath - Call HandleDeath
### 2. CREATE "HandleDeath" FUNCTION: ### 2. CREATE "HandleDeath" FUNCTION
#### a) Inside
#### a) Inside:
- Get reference to ScoreManager (we'll create in [Part 7](part-7-score-manager-ui.md)) - Get reference to ScoreManager (we'll create in [Part 7](part-7-score-manager-ui.md))
- Call AddScore, passing ScoreValue - Call AddScore, passing ScoreValue
- Spawn death effect (optional) - Spawn death effect (optional)
- Destroy Actor - Destroy Actor
### 3. COLLISION WITH PLAYER: ### 3. COLLISION WITH PLAYER
#### a) On the EnemyCollision component overlap event
#### a) On the EnemyCollision component overlap event:
- Cast Other Actor to BP_Player - Cast Other Actor to BP_Player
- If successful: - If successful:
- Call TakeHit(1) on player - Call TakeHit(1) on player
- Call HandleDeath() on self - Call HandleDeath() on self
### Expected Result after Compile: ### Expected Result after Compile
- Compile button shows GREEN checkmark - Compile button shows GREEN checkmark
- "ApplyDamage" and "HandleDeath" functions appear under Functions - "ApplyDamage" and "HandleDeath" functions appear under Functions
- EnemyCollision has overlap event in Event Graph - EnemyCollision has overlap event in Event Graph
### Expected Result in Play mode: ### Expected Result in Play mode
- Player bullets hitting enemy: Enemy health decreases - Player bullets hitting enemy: Enemy health decreases
- After 12 hits (MaxHealth): Enemy disappears, score increases by 50 - After 12 hits (MaxHealth): Enemy disappears, score increases by 50
- Player colliding with enemy: Player takes 1 damage, enemy dies - Player colliding with enemy: Player takes 1 damage, enemy dies
@ -226,47 +246,54 @@
Now that BP_Enemy exists (and BP_Bullet from Part 3), we can complete the special ability that was set up as a placeholder in [Part 2, Step 2.6](part-2-create-player.md#step-26-create-player-damage-and-special-ability). Now that BP_Enemy exists (and BP_Bullet from Part 3), we can complete the special ability that was set up as a placeholder in [Part 2, Step 2.6](part-2-create-player.md#step-26-create-player-damage-and-special-ability).
### 1. Open BP_Player Blueprint: ### 1. Open BP_Player Blueprint
1. Content Browser → Blueprints → double-click `BP_Player` 1. Content Browser → Blueprints → double-click `BP_Player`
2. Go to **Event Graph** tab 2. Go to **Event Graph** tab
3. Find the `EnhancedInputAction IA_Special` event node (created in Step 2.6) 3. Find the `EnhancedInputAction IA_Special` event node (created in Step 2.6)
4. Locate the `Print String "SPECIAL ABILITY ACTIVATED!"` node 4. Locate the `Print String "SPECIAL ABILITY ACTIVATED!"` node
### 2. Replace Print String with destruction logic: ### 2. Replace Print String with destruction logic
#### a) Delete the placeholder
#### a) Delete the placeholder:
- Select the `Print String` node - Select the `Print String` node
- Press Delete - Press Delete
- (Keep the TODO comment if you want, or delete it too) - (Keep the TODO comment if you want, or delete it too)
#### b) Destroy all enemies: #### b) Destroy all enemies
1. From `Set SpecialUsed` output execution pin, drag → search `Get All Actors of Class` → add it 1. From `Set SpecialUsed` output execution pin, drag → search `Get All Actors of Class` → add it
2. Click the "Actor Class" dropdown → search and select `BP_Enemy` 2. Click the "Actor Class" dropdown → search and select `BP_Enemy`
3. The output "Out Actors" is an array of all enemies currently in the level 3. The output "Out Actors" is an array of all enemies currently in the level
#### c) Loop through enemies and destroy them: #### c) Loop through enemies and destroy them
1. From `Get All Actors of Class`, drag execution → search `For Each Loop` → add it 1. From `Get All Actors of Class`, drag execution → search `For Each Loop` → add it
2. Connect the "Out Actors" array (blue pin) to the loop's "Array" input (blue pin) 2. Connect the "Out Actors" array (blue pin) to the loop's "Array" input (blue pin)
3. From "Loop Body" execution pin, drag → search `Destroy Actor` → add it 3. From "Loop Body" execution pin, drag → search `Destroy Actor` → add it
4. Connect "Array Element" (blue pin - the current enemy in the loop) to Destroy Actor's "Target" input 4. Connect "Array Element" (blue pin - the current enemy in the loop) to Destroy Actor's "Target" input
#### d) Now destroy enemy bullets (after enemies are done): #### d) Now destroy enemy bullets (after enemies are done)
1. From `For Each Loop`'s **"Completed"** execution pin (NOT "Loop Body"), drag → search `Get All Actors of Class` → add it 1. From `For Each Loop`'s **"Completed"** execution pin (NOT "Loop Body"), drag → search `Get All Actors of Class` → add it
2. Click "Actor Class" dropdown → select `BP_Bullet` 2. Click "Actor Class" dropdown → select `BP_Bullet`
3. From this Get All Actors, drag execution → search `For Each Loop` → add another loop 3. From this Get All Actors, drag execution → search `For Each Loop` → add another loop
#### e) Filter to only destroy ENEMY bullets (keep player bullets): #### e) Filter to only destroy ENEMY bullets (keep player bullets)
1. From the second loop's "Loop Body" pin, drag → search `Branch` → add it 1. From the second loop's "Loop Body" pin, drag → search `Branch` → add it
2. From "Array Element" (the current bullet), drag → search `Get IsEnemyProjectile` → add it 2. From "Array Element" (the current bullet), drag → search `Get IsEnemyProjectile` → add it
- This reads the IsEnemyProjectile variable from BP_Bullet - This reads the IsEnemyProjectile variable from BP_Bullet
3. Connect `IsEnemyProjectile` output (boolean) to Branch's "Condition" input 3. Connect `IsEnemyProjectile` output (boolean) to Branch's "Condition" input
#### f) Destroy only if it's an enemy bullet: #### f) Destroy only if it's an enemy bullet
1. From Branch's **"True"** pin, drag → search `Destroy Actor` → add it 1. From Branch's **"True"** pin, drag → search `Destroy Actor` → add it
2. Connect "Array Element" (the bullet) to Destroy Actor's "Target" 2. Connect "Array Element" (the bullet) to Destroy Actor's "Target"
3. Leave Branch's "False" pin unconnected (player bullets are preserved) 3. Leave Branch's "False" pin unconnected (player bullets are preserved)
### 3. Visual diagram of completed special ability: ### 3. Visual diagram of completed special ability
``` ```
┌─────────────────────────────┐ ┌─────────────────────────────┐
@ -332,11 +359,13 @@ Now that BP_Enemy exists (and BP_Bullet from Part 3), we can complete the specia
### 4. Compile and Save ### 4. Compile and Save
### Expected Result after Compile: ### Expected Result after Compile
- Compile button shows GREEN checkmark - Compile button shows GREEN checkmark
- No warnings about missing classes (BP_Enemy and BP_Bullet now exist) - No warnings about missing classes (BP_Enemy and BP_Bullet now exist)
### Expected Result in Play mode: ### Expected Result in Play mode
- Press X (or Right Mouse Button) once: - Press X (or Right Mouse Button) once:
- ALL enemies on screen instantly disappear - ALL enemies on screen instantly disappear
- ALL enemy bullets (red) instantly disappear - ALL enemy bullets (red) instantly disappear
@ -350,19 +379,22 @@ Now that BP_Enemy exists (and BP_Bullet from Part 3), we can complete the specia
Now that BP_Enemy exists (and the `ApplyDamage` function from Step 4.3), we can complete the collision logic that was set up as a placeholder in [Part 3, Step 3.3](part-3-create-bullet.md#step-33-bullet-collision-logic). Now that BP_Enemy exists (and the `ApplyDamage` function from Step 4.3), we can complete the collision logic that was set up as a placeholder in [Part 3, Step 3.3](part-3-create-bullet.md#step-33-bullet-collision-logic).
### 1. Open BP_Bullet Blueprint: ### 1. Open BP_Bullet Blueprint
1. Content Browser → Blueprints → double-click `BP_Bullet` 1. Content Browser → Blueprints → double-click `BP_Bullet`
2. Go to **Event Graph** tab 2. Go to **Event Graph** tab
3. Find the `On Component Begin Overlap (BulletCollision)` event node 3. Find the `On Component Begin Overlap (BulletCollision)` event node
4. Locate the `Print String "TODO: Damage enemy"` node on the FALSE branch 4. Locate the `Print String "TODO: Damage enemy"` node on the FALSE branch
### 2. Replace Print String with enemy damage logic: ### 2. Replace Print String with enemy damage logic
#### a) Delete the placeholder
#### a) Delete the placeholder:
- Select the `Print String` node - Select the `Print String` node
- Press Delete - Press Delete
#### b) Add Cast to BP_Enemy: #### b) Add Cast to BP_Enemy
1. Right-click → search `Cast to BP_Enemy` → add it 1. Right-click → search `Cast to BP_Enemy` → add it
2. Connect execution wire: 2. Connect execution wire:
@ -372,7 +404,8 @@ Now that BP_Enemy exists (and the `ApplyDamage` function from Step 4.3), we can
- From the **"On Component Begin Overlap"** node, drag from **"Other Actor"** to the Cast's "Object" input - From the **"On Component Begin Overlap"** node, drag from **"Other Actor"** to the Cast's "Object" input
- (You can Ctrl+drag to create a second wire without removing the existing one to BP_Player) - (You can Ctrl+drag to create a second wire without removing the existing one to BP_Player)
#### c) Call ApplyDamage on the enemy: #### c) Call ApplyDamage on the enemy
4. From "Cast Succeeded" on the BP_Enemy cast: 4. From "Cast Succeeded" on the BP_Enemy cast:
- From the cast's **"As BP Enemy"** output pin, drag → search `ApplyDamage` - From the cast's **"As BP Enemy"** output pin, drag → search `ApplyDamage`
- This calls the ApplyDamage function you created in Step 4.3 - This calls the ApplyDamage function you created in Step 4.3
@ -381,7 +414,8 @@ Now that BP_Enemy exists (and the `ApplyDamage` function from Step 4.3), we can
- Right-click → `Get Damage` (the bullet's damage variable) - Right-click → `Get Damage` (the bullet's damage variable)
- Connect to ApplyDamage's "DamageAmount" input - Connect to ApplyDamage's "DamageAmount" input
#### d) Destroy the bullet after damaging: #### d) Destroy the bullet after damaging
6. From ApplyDamage, drag execution → `Destroy Actor` 6. From ApplyDamage, drag execution → `Destroy Actor`
- Leave "Target" as "Self" (destroys this bullet) - Leave "Target" as "Self" (destroys this bullet)
@ -412,11 +446,13 @@ Now that BP_Enemy exists (and the `ApplyDamage` function from Step 4.3), we can
### 3. Compile and Save ### 3. Compile and Save
### Expected Result after Compile: ### Expected Result after Compile
- Compile button shows GREEN checkmark - Compile button shows GREEN checkmark
- No warnings - both BP_Player and BP_Enemy casts are valid now - No warnings - both BP_Player and BP_Enemy casts are valid now
### Expected Result in Play mode: ### Expected Result in Play mode
- Player bullets (IsEnemyProjectile=false) hitting enemies: - Player bullets (IsEnemyProjectile=false) hitting enemies:
- Enemy takes damage, bullet disappears - Enemy takes damage, bullet disappears
- After enough hits, enemy dies and awards score - After enough hits, enemy dies and awards score

View File

@ -13,17 +13,18 @@
5. **Create Variables:** 5. **Create Variables:**
| Variable Name | Type | Default Value | | Variable Name | Type | Default Value |
|---------------|------|---------------| | ------------------------ | --------------- | ------------------------- |
| `EnemyClass` | Class Reference | (will reference BP_Enemy) | | `EnemyClass` | Class Reference | (will reference BP_Enemy) |
| `SpawnAreaHalfWidth` | Float | 900.0 | | `SpawnAreaHalfWidth` | Float | 900.0 |
| `GameDuration` | Float | 300.0 (5 minutes) | | `GameDuration` | Float | 300.0 (5 minutes) |
| `MaxSimultaneousEnemies` | Integer | 120 | | `MaxSimultaneousEnemies` | Integer | 120 |
| `ElapsedTime` | Float | 0.0 | | `ElapsedTime` | Float | 0.0 |
| `SpawnTimer` | Float | 0.0 | | `SpawnTimer` | Float | 0.0 |
| `SpawningActive` | Boolean | true | | `SpawningActive` | Boolean | true |
### Expected Result after Compile
### Expected Result after Compile:
- Compile button shows GREEN checkmark - Compile button shows GREEN checkmark
- Variables panel shows all 7 variables with correct types - Variables panel shows all 7 variables with correct types
- No components needed (spawner is invisible logic actor) - No components needed (spawner is invisible logic actor)
@ -32,37 +33,40 @@
## Step 5.2: Spawn Rate Curve ## Step 5.2: Spawn Rate Curve
### 1. Create Variable: ### 1. Create Variable
- `SpawnCurve` (Curve Float) - `SpawnCurve` (Curve Float)
### 2. To create the curve asset: ### 2. To create the curve asset
1. In Content Browser, right-click → **Miscellaneous → Curve** 1. In Content Browser, right-click → **Miscellaneous → Curve**
2. Select "CurveFloat" 2. Select "CurveFloat"
3. Name it `SpawnRateCurve` 3. Name it `SpawnRateCurve`
4. Double-click to open Curve Editor 4. Double-click to open Curve Editor
### 3. In Curve Editor: ### 3. In Curve Editor
- Right-click on the curve → Add Key - Right-click on the curve → Add Key
- Create these keyframes: - Create these keyframes:
| Time | Value | Description | | Time | Value | Description |
|------|-------|-------------| | ---- | ----- | -------------------- |
| 0.0 | 0.4 | slow spawn at start | | 0.0 | 0.4 | slow spawn at start |
| 0.5 | 2.0 | medium spawn halfway | | 0.5 | 2.0 | medium spawn halfway |
| 1.0 | 4.5 | fast spawn at end | | 1.0 | 4.5 | fast spawn at end |
- The X axis is normalized time (0-1) - The X axis is normalized time (0-1)
- The Y axis is spawns per second - The Y axis is spawns per second
### 4. In BP_EnemySpawner, set SpawnCurve default to this curve asset ### 4. In BP_EnemySpawner, set SpawnCurve default to this curve asset
### Expected Result in Curve Editor: ### Expected Result in Curve Editor
- Curve line visible starting at (0, 0.4), rising through (0.5, 2.0), ending at (1.0, 4.5) - Curve line visible starting at (0, 0.4), rising through (0.5, 2.0), ending at (1.0, 4.5)
- Smooth interpolation between keyframes - Smooth interpolation between keyframes
### Expected Result in Play mode: ### Expected Result in Play mode
- Game start: ~0.4 enemies spawn per second (slow) - Game start: ~0.4 enemies spawn per second (slow)
- At 2.5 minutes: ~2 enemies spawn per second (medium) - At 2.5 minutes: ~2 enemies spawn per second (medium)
- At 5 minutes: ~4.5 enemies spawn per second (intense) - At 5 minutes: ~4.5 enemies spawn per second (intense)
@ -71,67 +75,81 @@
## Step 5.3: Spawning Logic ## Step 5.3: Spawning Logic
### 1. In Event Graph, from Event Tick: ### 1. In Event Graph, from Event Tick
#### a) Check if SpawningActive is true #### a) Check if SpawningActive is true
- If false, do nothing - If false, do nothing
#### b) Update ElapsedTime: #### b) Update ElapsedTime
- Add DeltaSeconds to ElapsedTime - Add DeltaSeconds to ElapsedTime
#### c) Calculate normalized time: #### c) Calculate normalized time
- Divide ElapsedTime by GameDuration - Divide ElapsedTime by GameDuration
- Clamp between 0 and 1 - Clamp between 0 and 1
#### d) Get spawn rate from curve: #### d) Get spawn rate from curve
- Get SpawnCurve - Get SpawnCurve
- Call `Get Float Value` with normalized time - Call `Get Float Value` with normalized time
- This returns spawns per second - This returns spawns per second
#### e) Update SpawnTimer: #### e) Update SpawnTimer
- Subtract DeltaSeconds from SpawnTimer - Subtract DeltaSeconds from SpawnTimer
- If SpawnTimer <= 0: - If SpawnTimer <= 0:
- Reset SpawnTimer to `(1.0 / SpawnsPerSecond)` - Reset SpawnTimer to `(1.0 / SpawnsPerSecond)`
- Call SpawnWave function - Call SpawnWave function
### 2. CREATE "SpawnWave" FUNCTION: ### 2. CREATE "SpawnWave" FUNCTION
#### a) Inside
#### a) Inside:
- Calculate burst size based on time: - Calculate burst size based on time:
``` ```
BaseCount = 1 + (NormalizedTime * 6) BaseCount = 1 + (NormalizedTime * 6)
BurstSize = BaseCount + Random(0, 2) BurstSize = BaseCount + Random(0, 2)
Clamp between 1 and 12 Clamp between 1 and 12
``` ```
#### b) For Loop from 0 to BurstSize - 1: #### b) For Loop from 0 to BurstSize - 1
- Call SpawnEnemy function - Call SpawnEnemy function
### 3. CREATE "SpawnEnemy" FUNCTION: ### 3. CREATE "SpawnEnemy" FUNCTION
#### a) Inside
#### a) Inside:
- Check: Get All Actors of Class (BP_Enemy) - Check: Get All Actors of Class (BP_Enemy)
- Get array length - Get array length
- If >= MaxSimultaneousEnemies: Return (don't spawn) - If >= MaxSimultaneousEnemies: Return (don't spawn)
#### b) Calculate spawn position: #### b) Calculate spawn position
- X = Random Float in Range (-SpawnAreaHalfWidth, SpawnAreaHalfWidth) - X = Random Float in Range (-SpawnAreaHalfWidth, SpawnAreaHalfWidth)
- Y = Get this actor's Y position (top of screen) - Y = Get this actor's Y position (top of screen)
- Z = 0 - Z = 0
#### c) Spawn Actor from Class: #### c) Spawn Actor from Class
- Class: EnemyClass - Class: EnemyClass
- Location: calculated position - Location: calculated position
- Rotation: (0, 0, 0) - Rotation: (0, 0, 0)
### 4. CREATE "StopSpawning" FUNCTION: ### 4. CREATE "StopSpawning" FUNCTION
- Set SpawningActive = false - Set SpawningActive = false
### Expected Result after Compile: ### Expected Result after Compile
- Compile button shows GREEN checkmark - Compile button shows GREEN checkmark
- "SpawnWave", "SpawnEnemy", "StopSpawning" functions appear under Functions - "SpawnWave", "SpawnEnemy", "StopSpawning" functions appear under Functions
### Expected Result in Play mode: ### Expected Result in Play mode
- Enemies spawn at top of screen (Y=500) at random X positions - Enemies spawn at top of screen (Y=500) at random X positions
- Spawn rate increases over time following the curve - Spawn rate increases over time following the curve
- Maximum 120 enemies on screen at once - Maximum 120 enemies on screen at once

View File

@ -13,15 +13,16 @@
5. **Create Variables:** 5. **Create Variables:**
| Variable Name | Type | Default Value | | Variable Name | Type | Default Value |
|---------------|------|---------------| | ------------------ | ----------------------------------- | ------------- |
| `PlayerReference` | Object Reference to BP_Player | - | | `PlayerReference` | Object Reference to BP_Player | - |
| `SpawnerReference` | Object Reference to BP_EnemySpawner | - | | `SpawnerReference` | Object Reference to BP_EnemySpawner | - |
| `GameDuration` | Float | 300.0 | | `GameDuration` | Float | 300.0 |
| `ElapsedTime` | Float | 0.0 | | `ElapsedTime` | Float | 0.0 |
| `GameActive` | Boolean | true | | `GameActive` | Boolean | true |
### Expected Result after Compile
### Expected Result after Compile:
- Compile button shows GREEN checkmark - Compile button shows GREEN checkmark
- Variables panel shows all 5 variables with correct types - Variables panel shows all 5 variables with correct types
- No components needed (director is invisible logic actor) - No components needed (director is invisible logic actor)
@ -30,27 +31,32 @@
## Step 6.2: Game Director Initialization ## Step 6.2: Game Director Initialization
### 1. From Event BeginPlay: ### 1. From Event BeginPlay
#### a) Find player in scene
#### a) Find player in scene:
- Get All Actors of Class → BP_Player - Get All Actors of Class → BP_Player
- Get first element (index 0) - Get first element (index 0)
- Set PlayerReference - Set PlayerReference
#### b) Find spawner in scene: #### b) Find spawner in scene
- Get All Actors of Class → BP_EnemySpawner - Get All Actors of Class → BP_EnemySpawner
- Get first element - Get first element
- Set SpawnerReference - Set SpawnerReference
#### c) Initialize ScoreManager: #### c) Initialize ScoreManager
- Get ScoreManager reference - Get ScoreManager reference
- Call RegisterGameStart with initial lives and duration - Call RegisterGameStart with initial lives and duration
### Expected Result after Compile: ### Expected Result after Compile
- Compile button shows GREEN checkmark - Compile button shows GREEN checkmark
- BeginPlay event connected to "Get All Actors of Class" nodes - BeginPlay event connected to "Get All Actors of Class" nodes
### Expected Result in Play mode: ### Expected Result in Play mode
- Game Director automatically finds Player, Spawner, and ScoreManager - Game Director automatically finds Player, Spawner, and ScoreManager
- UI initializes with correct starting values (Lives: 3, Time: 05:00) - UI initializes with correct starting values (Lives: 3, Time: 05:00)
@ -58,41 +64,49 @@
## Step 6.3: Game Director Update Logic ## Step 6.3: Game Director Update Logic
### 1. From Event Tick: ### 1. From Event Tick
#### a) Check if GameActive #### a) Check if GameActive
- If false, skip everything - If false, skip everything
#### b) Update elapsed time: #### b) Update elapsed time
- Add DeltaSeconds to ElapsedTime - Add DeltaSeconds to ElapsedTime
#### c) Calculate remaining time: #### c) Calculate remaining time
- Subtract ElapsedTime from GameDuration - Subtract ElapsedTime from GameDuration
- Max with 0 (don't go negative) - Max with 0 (don't go negative)
#### d) Update UI timer: #### d) Update UI timer
- Get ScoreManager - Get ScoreManager
- Call UpdateTimer with remaining time - Call UpdateTimer with remaining time
#### e) Check for victory: #### e) Check for victory
- If ElapsedTime >= GameDuration: - If ElapsedTime >= GameDuration:
- Set GameActive = false - Set GameActive = false
- Get SpawnerReference → Call StopSpawning - Get SpawnerReference → Call StopSpawning
- Get ScoreManager → Call HandleGameClear - Get ScoreManager → Call HandleGameClear
### 2. CREATE "HandlePlayerDeath" FUNCTION: ### 2. CREATE "HandlePlayerDeath" FUNCTION
#### a) Inside
#### a) Inside:
- Set GameActive = false - Set GameActive = false
- Get SpawnerReference → Call StopSpawning - Get SpawnerReference → Call StopSpawning
- Get ScoreManager → Call HandleGameOver - Get ScoreManager → Call HandleGameOver
### Expected Result after Compile: ### Expected Result after Compile
- Compile button shows GREEN checkmark - Compile button shows GREEN checkmark
- "HandlePlayerDeath" function appears under Functions - "HandlePlayerDeath" function appears under Functions
- Event Tick connected to timer update and victory check - Event Tick connected to timer update and victory check
### Expected Result in Play mode: ### Expected Result in Play mode
- Timer counts down from 05:00 to 00:00 - Timer counts down from 05:00 to 00:00
- At 00:00: "Mission Complete" appears, enemies stop spawning - At 00:00: "Mission Complete" appears, enemies stop spawning
- When player dies: "Game Over" appears, enemies stop spawning - When player dies: "Game Over" appears, enemies stop spawning

View File

@ -11,8 +11,10 @@
3. Name: `WBP_HUD` 3. Name: `WBP_HUD`
4. Double-click to open Widget Designer 4. Double-click to open Widget Designer
### Expected Result: ### Expected Result
Widget Designer opens with: Widget Designer opens with:
- Hierarchy panel on left - Hierarchy panel on left
- Canvas preview in center - Canvas preview in center
- Details panel on right - Details panel on right
@ -22,9 +24,10 @@ Widget Designer opens with:
## Step 7.2: Design HUD Layout ## Step 7.2: Design HUD Layout
### 1. In the Palette panel (left side), search for "Canvas Panel" ### 1. In the Palette panel (left side), search for "Canvas Panel"
- Drag Canvas Panel to the Hierarchy (if not already there) - Drag Canvas Panel to the Hierarchy (if not already there)
### 2. Add Score Text: ### 2. Add Score Text
1. In Palette, search for "Text" 1. In Palette, search for "Text"
2. Drag "Text" widget onto Canvas Panel in hierarchy 2. Drag "Text" widget onto Canvas Panel in hierarchy
@ -41,7 +44,7 @@ Widget Designer opens with:
- Font Size: `24` - Font Size: `24`
- Color: White - Color: White
### 3. Add Lives Text: ### 3. Add Lives Text
1. Drag another "Text" widget 1. Drag another "Text" widget
2. Rename to `LivesText` 2. Rename to `LivesText`
@ -49,7 +52,7 @@ Widget Designer opens with:
4. Text: `Lives: 3` 4. Text: `Lives: 3`
5. Same font settings as Score 5. Same font settings as Score
### 4. Add Timer Text: ### 4. Add Timer Text
1. Drag another "Text" widget 1. Drag another "Text" widget
2. Rename to `TimerText` 2. Rename to `TimerText`
@ -59,9 +62,10 @@ Widget Designer opens with:
### 5. Click "Compile" and "Save" (top buttons) ### 5. Click "Compile" and "Save" (top buttons)
### Expected Result: ### Expected Result
Preview shows: Preview shows:
``` ```
┌────────────────────────────────────┐ ┌────────────────────────────────────┐
│ Score: 0 │ │ Score: 0 │
@ -83,14 +87,15 @@ Preview shows:
5. **Create Variables:** 5. **Create Variables:**
| Variable Name | Type | Default Value | | Variable Name | Type | Default Value |
|---------------|------|---------------| | ---------------- | --------------------------- | ---------------- |
| `Score` | Integer | 0 | | `Score` | Integer | 0 |
| `CurrentLives` | Integer | 3 | | `CurrentLives` | Integer | 3 |
| `HUDWidget` | Object Reference to WBP_HUD | - | | `HUDWidget` | Object Reference to WBP_HUD | - |
| `HUDWidgetClass` | Class Reference | (set to WBP_HUD) | | `HUDWidgetClass` | Class Reference | (set to WBP_HUD) |
### Expected Result after Compile
### Expected Result after Compile:
- Compile button shows GREEN checkmark - Compile button shows GREEN checkmark
- Variables panel shows all 4 variables with correct types - Variables panel shows all 4 variables with correct types
@ -98,25 +103,30 @@ Preview shows:
## Step 7.4: Score Manager Initialization ## Step 7.4: Score Manager Initialization
### 1. From Event BeginPlay: ### 1. From Event BeginPlay
#### a) Create HUD Widget
#### a) Create HUD Widget:
- Right-click → `Create Widget` - Right-click → `Create Widget`
- Class: Select WBP_HUD (or use HUDWidgetClass variable) - Class: Select WBP_HUD (or use HUDWidgetClass variable)
- Owning Player: Get Player Controller (index 0) - Owning Player: Get Player Controller (index 0)
#### b) Store widget reference: #### b) Store widget reference
- Set HUDWidget to the created widget - Set HUDWidget to the created widget
#### c) Add to viewport: #### c) Add to viewport
- Right-click → `Add to Viewport` - Right-click → `Add to Viewport`
- Connect widget reference as target - Connect widget reference as target
### Expected Result after Compile: ### Expected Result after Compile
- Compile button shows GREEN checkmark - Compile button shows GREEN checkmark
- BeginPlay event connected to Create Widget → Add to Viewport - BeginPlay event connected to Create Widget → Add to Viewport
### Expected Result in Play mode: ### Expected Result in Play mode
- HUD appears immediately when game starts - HUD appears immediately when game starts
- HUD displays in top-left corner of screen - HUD displays in top-left corner of screen
- Text is visible and readable (white on game background) - Text is visible and readable (white on game background)
@ -125,22 +135,25 @@ Preview shows:
## Step 7.5: Score Manager Functions ## Step 7.5: Score Manager Functions
### 1. CREATE "RegisterGameStart" FUNCTION: ### 1. CREATE "RegisterGameStart" FUNCTION
**Inputs:**
**Inputs:**
- `InitialLives` (Integer) - `InitialLives` (Integer)
- `Duration` (Float) - `Duration` (Float)
**Inside:** **Inside:**
- Set Score = 0 - Set Score = 0
- Set CurrentLives = InitialLives - Set CurrentLives = InitialLives
- Update all UI labels - Update all UI labels
### 2. CREATE "AddScore" FUNCTION: ### 2. CREATE "AddScore" FUNCTION
**Input:** `Amount` (Integer) **Input:** `Amount` (Integer)
**Inside:** **Inside:**
- Add Amount to Score - Add Amount to Score
- Update Score label in HUD: - Update Score label in HUD:
- Get HUDWidget - Get HUDWidget
@ -148,42 +161,50 @@ Preview shows:
- Get "ScoreText" widget - Get "ScoreText" widget
- Set Text to "Score: " + Score - Set Text to "Score: " + Score
### 3. CREATE "SetLives" FUNCTION: ### 3. CREATE "SetLives" FUNCTION
**Input:** `Lives` (Integer) **Input:** `Lives` (Integer)
**Inside:** **Inside:**
- Set CurrentLives = Lives - Set CurrentLives = Lives
- Update Lives label in HUD - Update Lives label in HUD
### 4. CREATE "UpdateTimer" FUNCTION: ### 4. CREATE "UpdateTimer" FUNCTION
**Input:** `TimeRemaining` (Float) **Input:** `TimeRemaining` (Float)
**Inside:** **Inside:**
- Convert to minutes:seconds format: - Convert to minutes:seconds format:
``` ```
Minutes = Floor(TimeRemaining / 60) Minutes = Floor(TimeRemaining / 60)
Seconds = Floor(TimeRemaining mod 60) Seconds = Floor(TimeRemaining mod 60)
``` ```
- Format string: "Time: MM:SS" - Format string: "Time: MM:SS"
- Update Timer label - Update Timer label
### 5. CREATE "HandleGameOver" FUNCTION: ### 5. CREATE "HandleGameOver" FUNCTION
**Inside:** **Inside:**
- Set Timer text to "Game Over" - Set Timer text to "Game Over"
### 6. CREATE "HandleGameClear" FUNCTION: ### 6. CREATE "HandleGameClear" FUNCTION
**Inside:** **Inside:**
- Set Timer text to "Mission Complete" - Set Timer text to "Mission Complete"
### Expected Result after Compile: ### Expected Result after Compile
- Compile button shows GREEN checkmark - Compile button shows GREEN checkmark
- All 6 functions appear under Functions panel - All 6 functions appear under Functions panel
### Expected Result in Play mode: ### Expected Result in Play mode
- Score updates instantly when enemies are killed (+50 each) - Score updates instantly when enemies are killed (+50 each)
- Lives display updates when player is hit - Lives display updates when player is hit
- Timer counts down in MM:SS format - Timer counts down in MM:SS format

View File

@ -19,7 +19,8 @@
- Default Pawn Class: Select `BP_Player` - Default Pawn Class: Select `BP_Player`
- Player Controller Class: Keep default - Player Controller Class: Keep default
### Expected Result after Compile: ### Expected Result after Compile
- Compile button shows GREEN checkmark - Compile button shows GREEN checkmark
- Details panel shows "Default Pawn Class" set to BP_Player - Details panel shows "Default Pawn Class" set to BP_Player
@ -33,7 +34,8 @@
- Default GameMode: Select `BP_BulletHellGameMode` - Default GameMode: Select `BP_BulletHellGameMode`
4. Close Project Settings 4. Close Project Settings
### Expected Result: ### Expected Result
- Project Settings shows BP_BulletHellGameMode as Default GameMode - Project Settings shows BP_BulletHellGameMode as Default GameMode
- This means the game will automatically spawn BP_Player when Play is pressed - This means the game will automatically spawn BP_Player when Play is pressed
@ -48,7 +50,8 @@
5. Name: `BulletHellLevel` 5. Name: `BulletHellLevel`
6. Click Save 6. Click Save
### Expected Result: ### Expected Result
- New level file "BulletHellLevel" appears in Content folder - New level file "BulletHellLevel" appears in Content folder
- Level is completely empty (black viewport) - Level is completely empty (black viewport)
- Outliner shows only default actors (if any) - Outliner shows only default actors (if any)
@ -59,7 +62,7 @@
In the level (main viewport), we need to add game actors: In the level (main viewport), we need to add game actors:
### 1. ADD CAMERA: ### 1. ADD CAMERA
1. In Place Actors panel (left side, or Window → Place Actors) 1. In Place Actors panel (left side, or Window → Place Actors)
2. Search for "Camera Actor" 2. Search for "Camera Actor"
@ -73,44 +76,47 @@ In the level (main viewport), we need to add game actors:
- Check the box, set Player Index to `0` - Check the box, set Player Index to `0`
**For Orthographic View:** **For Orthographic View:**
- Click on Camera component - Click on Camera component
- In Details, set "Projection Mode" to `Orthographic` - In Details, set "Projection Mode" to `Orthographic`
- Set "Ortho Width" to `1920` (or your screen width) - Set "Ortho Width" to `1920` (or your screen width)
### 2. ADD PLAYER: ### 2. ADD PLAYER
1. From Content Browser, drag `BP_Player` into level 1. From Content Browser, drag `BP_Player` into level
2. Position: `X=0, Y=-300, Z=0` (bottom center) 2. Position: `X=0, Y=-300, Z=0` (bottom center)
### 3. ADD ENEMY SPAWNER: ### 3. ADD ENEMY SPAWNER
1. Drag `BP_EnemySpawner` into level 1. Drag `BP_EnemySpawner` into level
2. Position: `X=0, Y=500, Z=0` (top of screen) 2. Position: `X=0, Y=500, Z=0` (top of screen)
3. In Details panel, set EnemyClass to `BP_Enemy` 3. In Details panel, set EnemyClass to `BP_Enemy`
### 4. ADD GAME DIRECTOR: ### 4. ADD GAME DIRECTOR
1. Drag `BP_GameDirector` into level 1. Drag `BP_GameDirector` into level
2. Position doesn't matter (it's invisible) 2. Position doesn't matter (it's invisible)
### 5. ADD SCORE MANAGER: ### 5. ADD SCORE MANAGER
1. Drag `BP_ScoreManager` into level 1. Drag `BP_ScoreManager` into level
2. Position doesn't matter 2. Position doesn't matter
### 6. Save the level (`Ctrl+S`) ### 6. Save the level (`Ctrl+S`)
### Expected Result in Level Viewport: ### Expected Result in Level Viewport
- Camera actor visible at top of scene (Z=1000) - Camera actor visible at top of scene (Z=1000)
- BP_Player visible at bottom center (Y=-300) - BP_Player visible at bottom center (Y=-300)
- BP_EnemySpawner visible at top (Y=500) - BP_EnemySpawner visible at top (Y=500)
- BP_GameDirector and BP_ScoreManager in Outliner (invisible actors) - BP_GameDirector and BP_ScoreManager in Outliner (invisible actors)
### Expected Result in Outliner: ### Expected Result in Outliner
``` ```
- CameraActor - CameraActor
- BP_Player - BP_Player
- BP_EnemySpawner - BP_EnemySpawner
- BP_GameDirector - BP_GameDirector
- BP_ScoreManager - BP_ScoreManager
``` ```
@ -125,7 +131,8 @@ In the level (main viewport), we need to add game actors:
- Editor Startup Map: Select `BulletHellLevel` - Editor Startup Map: Select `BulletHellLevel`
- Game Default Map: Select `BulletHellLevel` - Game Default Map: Select `BulletHellLevel`
### Expected Result: ### Expected Result
- Project Settings shows BulletHellLevel as both startup and default map - Project Settings shows BulletHellLevel as both startup and default map
- Launching the game (standalone or in editor) loads this level automatically - Launching the game (standalone or in editor) loads this level automatically

View File

@ -6,26 +6,31 @@
## Step 9.1: Assign Blueprint References ## Step 9.1: Assign Blueprint References
### 1. Open BP_Player: ### 1. Open BP_Player
- In Details panel (with blueprint open) - In Details panel (with blueprint open)
- Set BulletClass: `BP_Bullet` - Set BulletClass: `BP_Bullet`
### 2. Open BP_EnemySpawner: ### 2. Open BP_EnemySpawner
- Set EnemyClass: `BP_Enemy` - Set EnemyClass: `BP_Enemy`
### 3. Open BP_Enemy: ### 3. Open BP_Enemy
- Set BulletClass: `BP_Bullet` - Set BulletClass: `BP_Bullet`
### 4. Compile and Save all blueprints ### 4. Compile and Save all blueprints
### Expected Result after Compile (all blueprints): ### Expected Result after Compile (all blueprints)
- All blueprints compile with GREEN checkmark - All blueprints compile with GREEN checkmark
- No "None" or missing references in variable defaults - No "None" or missing references in variable defaults
- BP_Player.BulletClass → BP_Bullet - BP_Player.BulletClass → BP_Bullet
- BP_Enemy.BulletClass → BP_Bullet - BP_Enemy.BulletClass → BP_Bullet
- BP_EnemySpawner.EnemyClass → BP_Enemy - BP_EnemySpawner.EnemyClass → BP_Enemy
### Expected Result in Play mode: ### Expected Result in Play mode
- Player can shoot bullets (BP_Bullet spawns) - Player can shoot bullets (BP_Bullet spawns)
- Enemies spawn and shoot bullets - Enemies spawn and shoot bullets
- All collision/damage systems functional - All collision/damage systems functional
@ -36,7 +41,7 @@
Now replace the temporary cube visuals with proper colored materials: Now replace the temporary cube visuals with proper colored materials:
### 1. REMOVE TEMPORARY COMPONENTS: ### 1. REMOVE TEMPORARY COMPONENTS
1. Open BP_Player blueprint 1. Open BP_Player blueprint
2. In Components panel, select "TempVisual" (the cube added in [Step 2.2](part-2-create-player.md#step-22-add-player-visual-components)) 2. In Components panel, select "TempVisual" (the cube added in [Step 2.2](part-2-create-player.md#step-22-add-player-visual-components))
@ -46,7 +51,7 @@ Now replace the temporary cube visuals with proper colored materials:
### 2. Content Browser → Materials folder ### 2. Content Browser → Materials folder
### 3. PLAYER MATERIAL: ### 3. PLAYER MATERIAL
1. Right-click → Material 1. Right-click → Material
2. Name: `M_Player` 2. Name: `M_Player`
@ -56,19 +61,21 @@ Now replace the temporary cube visuals with proper colored materials:
6. Connect to Base Color 6. Connect to Base Color
7. Save and Close 7. Save and Close
### 4. BULLET MATERIALS: ### 4. BULLET MATERIALS
- Create `M_PlayerBullet` - Yellow `(1, 1, 0)` - Create `M_PlayerBullet` - Yellow `(1, 1, 0)`
- Create `M_EnemyBullet` - Red `(1, 0, 0)` - Create `M_EnemyBullet` - Red `(1, 0, 0)`
### 5. ENEMY MATERIAL: ### 5. ENEMY MATERIAL
- Create `M_Enemy` - Magenta `(1, 0, 1)` - Create `M_Enemy` - Magenta `(1, 0, 1)`
### 6. Apply materials to sprite components in each Blueprint ### 6. Apply materials to sprite components in each Blueprint
(Or use Sprite assets if you have 2D images) (Or use Sprite assets if you have 2D images)
### Expected Result in Play mode: ### Expected Result in Play mode
- Player visible as blue shape - Player visible as blue shape
- Player bullets visible as yellow shapes - Player bullets visible as yellow shapes
- Enemy bullets visible as red shapes - Enemy bullets visible as red shapes
@ -79,15 +86,18 @@ Now replace the temporary cube visuals with proper colored materials:
## Step 9.3: Add Background (Optional) ## Step 9.3: Add Background (Optional)
### 1. In level, add a Plane mesh: ### 1. In level, add a Plane mesh
- Place Actors → Basic → Plane - Place Actors → Basic → Plane
- Scale: `X=20, Y=30, Z=1` - Scale: `X=20, Y=30, Z=1`
- Position: `X=0, Y=0, Z=-100` (behind everything) - Position: `X=0, Y=0, Z=-100` (behind everything)
### 2. Create dark space material: ### 2. Create dark space material
- `M_Background` - Dark blue/black - `M_Background` - Dark blue/black
### Expected Result in Play mode: ### Expected Result in Play mode
- Dark background visible behind all game elements - Dark background visible behind all game elements
- Game elements (player, enemies, bullets) clearly visible against background - Game elements (player, enemies, bullets) clearly visible against background
- Background doesn't interfere with gameplay (Z=-100, behind everything) - Background doesn't interfere with gameplay (Z=-100, behind everything)
@ -97,31 +107,33 @@ Now replace the temporary cube visuals with proper colored materials:
## Step 9.4: Test the Game ## Step 9.4: Test the Game
### 1. Click "Play" button (green arrow in main toolbar) ### 1. Click "Play" button (green arrow in main toolbar)
OR press `Alt+P` OR press `Alt+P`
### 2. TEST CHECKLIST: ### 2. TEST CHECKLIST
| # | Test | Pass? | | # | Test | Pass? |
|---|------|-------| | --- | --------------------------------------------- | ----- |
| 1 | Player moves with WASD or Arrow keys | ☐ | | 1 | Player moves with WASD or Arrow keys | ☐ |
| 2 | Player stays within screen bounds | ☐ | | 2 | Player stays within screen bounds | ☐ |
| 3 | Player shoots with Z key or Left Mouse | ☐ | | 3 | Player shoots with Z key or Left Mouse | ☐ |
| 4 | Bullets travel upward | ☐ | | 4 | Bullets travel upward | ☐ |
| 5 | Enemies spawn at top of screen | ☐ | | 5 | Enemies spawn at top of screen | ☐ |
| 6 | Enemies move down with wavy motion | ☐ | | 6 | Enemies move down with wavy motion | ☐ |
| 7 | Enemies shoot radial bullet patterns | ☐ | | 7 | Enemies shoot radial bullet patterns | ☐ |
| 8 | Player bullets damage enemies | ☐ | | 8 | Player bullets damage enemies | ☐ |
| 9 | Enemy bullets damage player | ☐ | | 9 | Enemy bullets damage player | ☐ |
| 10 | Score increases when enemies die | ☐ | | 10 | Score increases when enemies die | ☐ |
| 11 | Lives decrease when player is hit | ☐ | | 11 | Lives decrease when player is hit | ☐ |
| 12 | Timer counts down from 5:00 | ☐ | | 12 | Timer counts down from 5:00 | ☐ |
| 13 | Game shows "Game Over" when lives = 0 | ☐ | | 13 | Game shows "Game Over" when lives = 0 | ☐ |
| 14 | Game shows "Mission Complete" after 5 minutes | ☐ | | 14 | Game shows "Mission Complete" after 5 minutes | ☐ |
| 15 | Special ability (X key) clears screen | ☐ | | 15 | Special ability (X key) clears screen | ☐ |
### 3. To stop playing: Press `ESC` or click "Stop" button ### 3. To stop playing: Press `ESC` or click "Stop" button
### Expected Result - Complete Game Test: ### Expected Result - Complete Game Test
- All checklist items above should pass - All checklist items above should pass
- Frame rate stable (60+ FPS recommended) - Frame rate stable (60+ FPS recommended)
- No crashes or Blueprint errors in Output Log - No crashes or Blueprint errors in Output Log
@ -137,7 +149,8 @@ OR press `Alt+P`
4. Navigate to output folder → WindowsNoEditor → [ProjectName].exe 4. Navigate to output folder → WindowsNoEditor → [ProjectName].exe
5. Run the executable to play standalone 5. Run the executable to play standalone
### Expected Result: ### Expected Result
- Build completes without errors (check Output Log) - Build completes without errors (check Output Log)
- Executable file created in output folder - Executable file created in output folder
- Running .exe launches the game in fullscreen - Running .exe launches the game in fullscreen
@ -146,11 +159,12 @@ OR press `Alt+P`
--- ---
## 🎉 Congratulations! ## 🎉 Congratulations
You have completed the Unreal Engine Bullet Hell tutorial! You have completed the Unreal Engine Bullet Hell tutorial!
Your game includes: Your game includes:
- ✅ Player with 3 lives, WASD movement, Z/mouse shooting - ✅ Player with 3 lives, WASD movement, Z/mouse shooting
- ✅ Volley shooting (3 bullets in spread pattern) - ✅ Volley shooting (3 bullets in spread pattern)
- ✅ Screen-clear special ability (X key, one use) - ✅ Screen-clear special ability (X key, one use)
@ -163,7 +177,7 @@ Your game includes:
--- ---
### Additional Resources: ### Additional Resources
- [Appendix A: Complete Variable Reference](appendix-a-variables.md) - [Appendix A: Complete Variable Reference](appendix-a-variables.md)
- [Appendix B: Troubleshooting](appendix-b-troubleshooting.md) - [Appendix B: Troubleshooting](appendix-b-troubleshooting.md)