From 472f6dc19dac9e4f9cebbd50c284fa038b8b00b5 Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Mon, 5 Jan 2026 18:43:37 +0100 Subject: [PATCH] chore: markdown linter and fixes --- games/unreal/tutorial/.markdownlint.json | 18 ++ games/unreal/tutorial/.prettierrc | 6 + games/unreal/tutorial/README.md | 13 +- games/unreal/tutorial/appendix-a-variables.md | 122 +++++----- .../tutorial/appendix-b-troubleshooting.md | 6 + .../tutorial/appendix-c-unity-conversion.md | 68 ++++-- games/unreal/tutorial/lint-markdown.sh | 155 ++++++++++++ games/unreal/tutorial/part-1-project-setup.md | 5 +- games/unreal/tutorial/part-2-create-player.md | 222 +++++++++++------- games/unreal/tutorial/part-3-create-bullet.md | 148 ++++++------ games/unreal/tutorial/part-4-create-enemy.md | 170 ++++++++------ .../unreal/tutorial/part-5-create-spawner.md | 88 ++++--- games/unreal/tutorial/part-6-game-director.md | 60 +++-- .../tutorial/part-7-score-manager-ui.md | 75 +++--- .../unreal/tutorial/part-8-game-mode-level.md | 31 ++- games/unreal/tutorial/part-9-final-setup.md | 86 ++++--- 16 files changed, 837 insertions(+), 436 deletions(-) create mode 100644 games/unreal/tutorial/.markdownlint.json create mode 100644 games/unreal/tutorial/.prettierrc create mode 100755 games/unreal/tutorial/lint-markdown.sh diff --git a/games/unreal/tutorial/.markdownlint.json b/games/unreal/tutorial/.markdownlint.json new file mode 100644 index 0000000..8d446c5 --- /dev/null +++ b/games/unreal/tutorial/.markdownlint.json @@ -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 +} diff --git a/games/unreal/tutorial/.prettierrc b/games/unreal/tutorial/.prettierrc new file mode 100644 index 0000000..ea0cffe --- /dev/null +++ b/games/unreal/tutorial/.prettierrc @@ -0,0 +1,6 @@ +{ + "proseWrap": "preserve", + "tabWidth": 2, + "useTabs": false, + "printWidth": 300 +} diff --git a/games/unreal/tutorial/README.md b/games/unreal/tutorial/README.md index e60a339..f115669 100644 --- a/games/unreal/tutorial/README.md +++ b/games/unreal/tutorial/README.md @@ -17,11 +17,13 @@ This tutorial recreates the Unity "magisterka_1" bullet-hell shooter in Unreal E ## Table of Contents ### Part 1: Project Setup + - [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.3: Create Folder Structure](part-1-project-setup.md#step-13-create-folder-structure) ### Part 2: Create the Player + - [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.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) ### Part 3: Create the Bullet + - [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.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) ### Part 4: Create the Enemy + - [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.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) ### Part 5: Create Enemy Spawner + - [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.3: Spawning Logic](part-5-create-spawner.md#step-53-spawning-logic) ### 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.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) ### 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.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) @@ -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) ### 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.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) @@ -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) ### Part 9: Final Setup and Testing + - [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.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) ### Appendices + - [Appendix A: Complete Variable Reference](appendix-a-variables.md) - [Appendix B: Troubleshooting](appendix-b-troubleshooting.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 Each page includes: + - **← Previous** and **Next →** links at the top and bottom - Links back to this index - 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._ diff --git a/games/unreal/tutorial/appendix-a-variables.md b/games/unreal/tutorial/appendix-a-variables.md index 4e0e563..9a475c6 100644 --- a/games/unreal/tutorial/appendix-a-variables.md +++ b/games/unreal/tutorial/appendix-a-variables.md @@ -6,93 +6,93 @@ ## BP_Player Variables -| Variable Name | Type | Default Value | -|---------------|------|---------------| -| `MoveSpeed` | Float | 750.0 | -| `BoundsMin` | Vector 2D | (-850, -450) | -| `BoundsMax` | Vector 2D | (850, 450) | -| `FireInterval` | Float | 0.08 | -| `FireTimer` | Float | 0.0 | -| `BulletSpeed` | Float | 2200.0 | -| `MaxLives` | Integer | 3 | -| `CurrentLives` | Integer | 3 | -| `VolleySize` | Integer | 3 | -| `VolleySpread` | Float | 12.0 | -| `SpecialUsed` | Boolean | false | -| `BulletClass` | Class Ref | BP_Bullet | +| Variable Name | Type | Default Value | +| -------------- | --------- | ------------- | +| `MoveSpeed` | Float | 750.0 | +| `BoundsMin` | Vector 2D | (-850, -450) | +| `BoundsMax` | Vector 2D | (850, 450) | +| `FireInterval` | Float | 0.08 | +| `FireTimer` | Float | 0.0 | +| `BulletSpeed` | Float | 2200.0 | +| `MaxLives` | Integer | 3 | +| `CurrentLives` | Integer | 3 | +| `VolleySize` | Integer | 3 | +| `VolleySpread` | Float | 12.0 | +| `SpecialUsed` | Boolean | false | +| `BulletClass` | Class Ref | BP_Bullet | --- ## BP_Bullet Variables -| Variable Name | Type | Default Value | -|---------------|------|---------------| -| `TravelDirection` | Vector | (0, 1, 0) | -| `TravelSpeed` | Float | 1200.0 | -| `RemainingLifetime` | Float | 4.0 | -| `IsEnemyProjectile` | Boolean | false | -| `Damage` | Integer | 1 | +| Variable Name | Type | Default Value | +| ------------------- | ------- | ------------- | +| `TravelDirection` | Vector | (0, 1, 0) | +| `TravelSpeed` | Float | 1200.0 | +| `RemainingLifetime` | Float | 4.0 | +| `IsEnemyProjectile` | Boolean | false | +| `Damage` | Integer | 1 | --- ## BP_Enemy Variables -| Variable Name | Type | Default Value | -|---------------|------|---------------| -| `MaxHealth` | Integer | 12 | -| `CurrentHealth` | Integer | 12 | -| `ScoreValue` | Integer | 50 | -| `VerticalSpeed` | Float | 220.0 | -| `HorizontalAmplitude` | Float | 250.0 | -| `HorizontalFrequency` | Float | 1.8 | -| `DespawnY` | Float | -750.0 | -| `FireInterval` | Float | 0.35 | -| `BulletsPerBurst` | Integer | 20 | -| `BurstSpread` | Float | 360.0 | -| `EnemyBulletSpeed` | Float | 1000.0 | -| `EnemyBulletLifetime` | Float | 6.0 | -| `BaseX` | Float | 0.0 | -| `WaveSeed` | Float | 0.0 | -| `FireTimer` | Float | 0.0 | -| `BulletClass` | Class Ref | BP_Bullet | +| Variable Name | Type | Default Value | +| --------------------- | --------- | ------------- | +| `MaxHealth` | Integer | 12 | +| `CurrentHealth` | Integer | 12 | +| `ScoreValue` | Integer | 50 | +| `VerticalSpeed` | Float | 220.0 | +| `HorizontalAmplitude` | Float | 250.0 | +| `HorizontalFrequency` | Float | 1.8 | +| `DespawnY` | Float | -750.0 | +| `FireInterval` | Float | 0.35 | +| `BulletsPerBurst` | Integer | 20 | +| `BurstSpread` | Float | 360.0 | +| `EnemyBulletSpeed` | Float | 1000.0 | +| `EnemyBulletLifetime` | Float | 6.0 | +| `BaseX` | Float | 0.0 | +| `WaveSeed` | Float | 0.0 | +| `FireTimer` | Float | 0.0 | +| `BulletClass` | Class Ref | BP_Bullet | --- ## BP_EnemySpawner Variables -| Variable Name | Type | Default Value | -|---------------|------|---------------| -| `EnemyClass` | Class Ref | BP_Enemy | -| `SpawnAreaHalfWidth` | Float | 900.0 | -| `GameDuration` | Float | 300.0 | -| `MaxSimultaneousEnemies` | Integer | 120 | -| `ElapsedTime` | Float | 0.0 | -| `SpawnTimer` | Float | 0.0 | -| `SpawningActive` | Boolean | true | -| `SpawnCurve` | Curve Float | SpawnRateCurve asset | +| Variable Name | Type | Default Value | +| ------------------------ | ----------- | -------------------- | +| `EnemyClass` | Class Ref | BP_Enemy | +| `SpawnAreaHalfWidth` | Float | 900.0 | +| `GameDuration` | Float | 300.0 | +| `MaxSimultaneousEnemies` | Integer | 120 | +| `ElapsedTime` | Float | 0.0 | +| `SpawnTimer` | Float | 0.0 | +| `SpawningActive` | Boolean | true | +| `SpawnCurve` | Curve Float | SpawnRateCurve asset | --- ## BP_GameDirector Variables -| Variable Name | Type | Default Value | -|---------------|------|---------------| -| `PlayerReference` | Object Ref | (set at runtime) | +| Variable Name | Type | Default Value | +| ------------------ | ---------- | ---------------- | +| `PlayerReference` | Object Ref | (set at runtime) | | `SpawnerReference` | Object Ref | (set at runtime) | -| `GameDuration` | Float | 300.0 | -| `ElapsedTime` | Float | 0.0 | -| `GameActive` | Boolean | true | +| `GameDuration` | Float | 300.0 | +| `ElapsedTime` | Float | 0.0 | +| `GameActive` | Boolean | true | --- ## BP_ScoreManager Variables -| Variable Name | Type | Default Value | -|---------------|------|---------------| -| `Score` | Integer | 0 | -| `CurrentLives` | Integer | 3 | -| `HUDWidget` | Object Ref | (set at runtime) | -| `HUDWidgetClass` | Class Ref | WBP_HUD | +| Variable Name | Type | Default Value | +| ---------------- | ---------- | ---------------- | +| `Score` | Integer | 0 | +| `CurrentLives` | Integer | 3 | +| `HUDWidget` | Object Ref | (set at runtime) | +| `HUDWidgetClass` | Class Ref | WBP_HUD | --- diff --git a/games/unreal/tutorial/appendix-b-troubleshooting.md b/games/unreal/tutorial/appendix-b-troubleshooting.md index 857c80a..57c9671 100644 --- a/games/unreal/tutorial/appendix-b-troubleshooting.md +++ b/games/unreal/tutorial/appendix-b-troubleshooting.md @@ -143,25 +143,31 @@ ## General Debugging Tips ### Enable Print Statements + ``` Right-click → Print String → Type message ``` + Add these throughout your blueprints to track execution flow. ### Check Output Log + - **Window → Developer Tools → Output Log** - Shows blueprint errors, warnings, and print statements ### Use Breakpoints + - Right-click any blueprint node → **Add Breakpoint** - Execution pauses at that point during Play mode - Step through with F10/F11 ### Visualize Collisions + - In viewport: **Show → Collision** - Shows collision shapes in play mode ### Console Commands + - Press `~` (tilde) to open console - `stat fps` - Show frame rate - `show collision` - Toggle collision visualization diff --git a/games/unreal/tutorial/appendix-c-unity-conversion.md b/games/unreal/tutorial/appendix-c-unity-conversion.md index e33cf81..68c8445 100644 --- a/games/unreal/tutorial/appendix-c-unity-conversion.md +++ b/games/unreal/tutorial/appendix-c-unity-conversion.md @@ -6,11 +6,12 @@ ## Scale Conversion -| Unity | Unreal | Notes | -|-------|--------|-------| +| Unity | Unreal | Notes | +| ---------------- | --------------------- | ---------------------------- | | 1 unit = 1 meter | 1 unit = 1 centimeter | Multiply Unity values by 100 | **Example:** + - Unity speed: `7.5` → Unreal speed: `750` - Unity position: `(5, 10, 0)` → Unreal position: `(500, 1000, 0)` @@ -18,13 +19,14 @@ ## Coordinate System -| Axis | Unity | Unreal | -|------|-------|--------| -| Up | Y | Z | -| Forward | Z | X | -| Right | X | Y | +| Axis | Unity | Unreal | +| ------- | ----- | ------ | +| Up | Y | Z | +| Forward | Z | X | +| Right | X | Y | **For 2D top-down games:** + - Both use X for horizontal - Y/Z swap for vertical @@ -35,12 +37,14 @@ ### Input **Unity:** + ```csharp float h = Input.GetAxisRaw("Horizontal"); float v = Input.GetAxisRaw("Vertical"); ``` **Unreal (Enhanced Input):** + 1. Create Input Action (IA_Move) with Axis2D type 2. Create Input Mapping Context with key bindings 3. Add Mapping Context in BeginPlay @@ -51,11 +55,13 @@ float v = Input.GetAxisRaw("Vertical"); ### Instantiate / Spawn **Unity:** + ```csharp Instantiate(prefab, position, rotation); ``` **Unreal:** + ``` Spawn Actor from Class ├── Class: YourBlueprintClass @@ -68,11 +74,13 @@ Spawn Actor from Class ### Destroy **Unity:** + ```csharp Destroy(gameObject); ``` **Unreal:** + ``` Destroy Actor └── Target: Self (or reference) @@ -83,11 +91,13 @@ Destroy Actor ### Delta Time **Unity:** + ```csharp Time.deltaTime ``` **Unreal:** + ``` Get World Delta Seconds ``` @@ -97,12 +107,14 @@ Get World Delta Seconds ### Find Objects **Unity:** + ```csharp FindObjectOfType(); FindObjectsOfType(); ``` **Unreal:** + ``` Get All Actors of Class ├── Actor Class: BP_Player @@ -116,6 +128,7 @@ Then: Get (a ref) → index 0 ### Singleton Pattern **Unity:** + ```csharp public static GameManager Instance { get; private set; } @@ -125,6 +138,7 @@ void Awake() { ``` **Unreal Options:** + 1. **Game Instance** - Persists across levels 2. **Subsystem** - Engine-managed singleton 3. **Get All Actors of Class** - Find at runtime @@ -134,6 +148,7 @@ void Awake() { ### Coroutines vs Timers **Unity:** + ```csharp StartCoroutine(DelayedAction()); @@ -144,6 +159,7 @@ IEnumerator DelayedAction() { ``` **Unreal:** + ``` Set Timer by Function Name ├── Function Name: "DelayedAction" @@ -158,10 +174,12 @@ Or use **Delay** node in blueprints. ### Physics Layers vs Collision Channels **Unity:** + - Layer-based collision matrix - `Physics.IgnoreLayerCollision()` **Unreal:** + - Collision Channels (Object Types) - Collision Presets - Per-component collision settings @@ -171,11 +189,13 @@ Or use **Delay** node in blueprints. ### Tags **Unity:** + ```csharp if (other.CompareTag("Enemy")) { } ``` **Unreal:** + ``` Actor Has Tag ├── Target: Other Actor @@ -188,11 +208,11 @@ Or use **Cast To** for type checking (preferred). ### Vector Math -| Operation | Unity | Unreal | -|-----------|-------|--------| -| Normalize | `vector.normalized` | `Normalize` node | -| Magnitude | `vector.magnitude` | `Vector Length` node | -| Dot Product | `Vector3.Dot(a, b)` | `Dot Product` node | +| Operation | Unity | Unreal | +| ------------- | --------------------- | -------------------- | +| Normalize | `vector.normalized` | `Normalize` node | +| Magnitude | `vector.magnitude` | `Vector Length` node | +| Dot Product | `Vector3.Dot(a, b)` | `Dot Product` node | | Cross Product | `Vector3.Cross(a, b)` | `Cross Product` node | --- @@ -200,12 +220,14 @@ Or use **Cast To** for type checking (preferred). ### Random **Unity:** + ```csharp Random.Range(0f, 1f); Random.Range(0, 10); // int, exclusive max ``` **Unreal:** + ``` Random Float in Range ├── Min: 0.0 @@ -223,12 +245,14 @@ Random Integer in Range ### Debug **Unity:** + ```csharp Debug.Log("Message"); Debug.DrawLine(start, end, Color.red); ``` **Unreal:** + ``` Print String └── In String: "Message" @@ -243,16 +267,16 @@ Draw Debug Line ## Quick Reference Table -| Concept | Unity | Unreal | -|---------|-------|--------| -| Script | C# MonoBehaviour | Blueprint / C++ Actor | -| Prefab | .prefab asset | Blueprint Class | -| Scene | .unity scene | Level (.umap) | -| Inspector | Inspector window | Details panel | -| Hierarchy | Hierarchy window | Outliner | -| Project | Project window | Content Browser | -| Console | Console window | Output Log | -| Play | Play button | Play (Alt+P) | +| Concept | Unity | Unreal | +| --------- | ---------------- | --------------------- | +| Script | C# MonoBehaviour | Blueprint / C++ Actor | +| Prefab | .prefab asset | Blueprint Class | +| Scene | .unity scene | Level (.umap) | +| Inspector | Inspector window | Details panel | +| Hierarchy | Hierarchy window | Outliner | +| Project | Project window | Content Browser | +| Console | Console window | Output Log | +| Play | Play button | Play (Alt+P) | --- diff --git a/games/unreal/tutorial/lint-markdown.sh b/games/unreal/tutorial/lint-markdown.sh new file mode 100755 index 0000000..15d7995 --- /dev/null +++ b/games/unreal/tutorial/lint-markdown.sh @@ -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!" diff --git a/games/unreal/tutorial/part-1-project-setup.md b/games/unreal/tutorial/part-1-project-setup.md index 407ac85..02379d0 100644 --- a/games/unreal/tutorial/part-1-project-setup.md +++ b/games/unreal/tutorial/part-1-project-setup.md @@ -14,7 +14,6 @@ 5. In the "Unreal Project Browser" window that appears: - At the top, select "Games" category (should be selected by default) - Click "Blank" template (empty square icon) - 6. On the right side panel, configure: - **Project Defaults:** Blueprint (not C++) - **Target Platform:** Desktop @@ -25,17 +24,18 @@ 7. At the bottom: - Choose folder location where you want to save - Name the project: `BulletHellGame` - 8. Click "Create" button (bottom right, yellow) ### Expected Result Unreal Editor opens with an empty level. You should see: + - Main 3D viewport in the center - Outliner panel on the right (showing "Untitled" level) - Details panel on the right side > **NOTE:** The Content Drawer is NOT open by default. To open it: +> > - Click "Content Drawer" button at the bottom of the screen, OR > - Press `Ctrl+Space`, OR > - 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** - In the left sidebar, search for "Maps & Modes" (Project → 4th from the top, next to GameplayTags and Movies) - Click on "Maps & Modes" - 5. Under "Default Modes": - Find "Default GameMode" dropdown - We'll create our own later, leave it for now diff --git a/games/unreal/tutorial/part-2-create-player.md b/games/unreal/tutorial/part-2-create-player.md index ffeb932..a02ea5d 100644 --- a/games/unreal/tutorial/part-2-create-player.md +++ b/games/unreal/tutorial/part-2-create-player.md @@ -21,6 +21,7 @@ ### Expected Result A new tab opens showing the Blueprint Editor with: + - Components panel on the left - Viewport in the center - Details panel on the right @@ -39,6 +40,7 @@ A new tab opens showing the Blueprint Editor with: - Rename it to `PlayerSprite` (click on it, then press F2) > **NOTE:** If Paper Sprite is not available: + > > - Go to Edit → Plugins (from main menu bar) > - Search for "Paper2D" > - Make sure "Paper2D" plugin is ENABLED @@ -81,7 +83,7 @@ A new tab opens showing the Blueprint Editor with: - This should already be ENABLED by default - If not, check/enable it - this tells Unreal to use THIS camera when playing - This camera will follow the player, making testing easy - + > **NOTE:** This temporary camera ensures you can see the player during testing. > In [Part 8](part-8-game-mode-level.md), we'll set up a proper fixed camera for the final game. @@ -104,7 +106,7 @@ DefaultSceneRoot - Find "Auto Possess Player" dropdown (currently set to "Disabled") - Change it to **"Player 0"** - This makes the engine automatically possess this pawn when playing, which activates the PlayerCamera - + > **NOTE:** This setting will be overridden later when we set up the GameMode > in [Part 8](part-8-game-mode-level.md), which handles possession automatically. @@ -112,7 +114,7 @@ DefaultSceneRoot ## 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) 2. Find "My Blueprint" section @@ -120,27 +122,27 @@ DefaultSceneRoot 4. Create these variables one by one (click +, then set properties in Details): - > **NOTE:** Variables are NOT added to the Components hierarchy. They appear in a - > separate "Variables" list within the "My Blueprint" panel. Variables store + > **NOTE:** Variables are NOT added to the Components hierarchy. They appear in a + > separate "Variables" list within the "My Blueprint" panel. Variables store > data values, while Components are physical/visual parts of the actor. -| # | Variable Name | Type | Default Value | -|---|---------------|------|---------------| -| 1 | `MoveSpeed` | Float | 750.0 | -| 2 | `BoundsMin` | Vector 2D | X=-850, Y=-450 | -| 3 | `BoundsMax` | Vector 2D | X=850, Y=450 | -| 4 | `FireInterval` | Float | 0.08 | -| 5 | `FireTimer` | Float | 0.0 | -| 6 | `BulletSpeed` | Float | 2200.0 | -| 7 | `MaxLives` | Integer | 3 | -| 8 | `CurrentLives` | Integer | 3 | -| 9 | `VolleySize` | Integer | 3 | -| 10 | `VolleySpread` | Float | 12.0 | -| 11 | `SpecialUsed` | Boolean | false (unchecked) | -| 12 | `BulletClass` | Class Reference* | (set later) | - -> *For BulletClass: In the type dropdown, go to "Object Types" category → "Actor" → select "Class Reference" (the subcategory under Actor). This will hold reference to bullet blueprint class for spawning. +| # | Variable Name | Type | Default Value | +| --- | -------------- | ----------------- | ----------------- | +| 1 | `MoveSpeed` | Float | 750.0 | +| 2 | `BoundsMin` | Vector 2D | X=-850, Y=-450 | +| 3 | `BoundsMax` | Vector 2D | X=850, Y=450 | +| 4 | `FireInterval` | Float | 0.08 | +| 5 | `FireTimer` | Float | 0.0 | +| 6 | `BulletSpeed` | Float | 2200.0 | +| 7 | `MaxLives` | Integer | 3 | +| 8 | `CurrentLives` | Integer | 3 | +| 9 | `VolleySize` | Integer | 3 | +| 10 | `VolleySpread` | Float | 12.0 | +| 11 | `SpecialUsed` | Boolean | false (unchecked) | +| 12 | `BulletClass` | Class Reference\* | (set later) | +> \*For BulletClass: In the type dropdown, go to "Object Types" category → "Actor" → select "Class Reference" (the subcategory under Actor). This will hold reference to bullet blueprint class for spawning. +> > **TIP:** For Variable 1 (MoveSpeed), click the "eye" icon to make it public/editable. Compile the blueprint (Click on "Compile" third option from left or simply `Ctrl + Alt`) before setting the default value. 5. Click **"Compile"** button (top left, blue checkmark icon) @@ -149,6 +151,7 @@ DefaultSceneRoot ### Expected Result My Blueprint panel shows: + - All 12 variables you created listed under "Variables" - A "Components" category with 4 component references (DefaultSceneRoot, PlayerSprite, PlayerCollision, Arrow) - these are automatically created from the components you added in Step 2.2 @@ -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: - Select "IA_Move" from dropdown - Click "+" under IA_Move to add key bindings: - + > **NOTE:** In Unreal's top-down view, X axis = up/down, Y axis = left/right **For W key (move UP on screen):** - Click "+", select "W" - No modifiers needed (outputs X positive = up) - TOTAL MODIFIERS FOR W: **0** - + **For S key (move DOWN on screen):** - Click "+", select "S" - Click "+" next to "Modifiers" → Add "Negate" - TOTAL MODIFIERS FOR S: **1** (Negate only) - + **For A key (move LEFT on screen):** - Click "+", select "A" - Click "+" next to "Modifiers" → Add "Swizzle Input Axis Values" → Order: "YXZ" - Click "+" next to "Modifiers" AGAIN → Add "Negate" - TOTAL MODIFIERS FOR A: **2** (Swizzle AND Negate) - + **For D key (move RIGHT on screen):** - Click "+", select "D" - Click "+" next to "Modifiers" → Add "Swizzle Input Axis Values" → Order: "YXZ" - TOTAL MODIFIERS FOR D: **1** (Swizzle only) **Summary Table - verify your IMC_Default matches this:** - - | Key | Modifiers | Output Vector | - |-----|-----------|---------------| - | W | (none) | (1, 0) = UP | - | S | Negate | (-1, 0) = DOWN | - | A | Swizzle Input Axis Values (YXZ) + Negate | (0, -1) = LEFT | - | D | Swizzle Input Axis Values (YXZ) | (0, 1) = RIGHT | + + | Key | Modifiers | Output Vector | + | --- | ---------------------------------------- | -------------- | + | W | (none) | (1, 0) = UP | + | S | Negate | (-1, 0) = DOWN | + | A | Swizzle Input Axis Values (YXZ) + Negate | (0, -1) = LEFT | + | D | Swizzle Input Axis Values (YXZ) | (0, 1) = RIGHT | After adding all keys, press `Ctrl+S` to SAVE IMC_Default! @@ -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) You should see three default events (red nodes): + - Event BeginPlay -- Event ActorBeginOverlap +- Event ActorBeginOverlap - Event Tick ### 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 2. From the blue "Return Value" output, drag and search `Get Enhanced Input Local Player Subsystem` 3. From that blue output, drag and search `Add Mapping Context` -4. For "Mapping Context" input: +4. For "Mapping Context" input: - Click dropdown and select `IMC_Default` - Or drag IMC_Default from Content Drawer 5. Set "Priority" to `0` @@ -276,11 +280,10 @@ From "Event BeginPlay" node: - The WHITE TRIANGLE pins are different from the BLUE CIRCLE pins! - Blue circles = DATA (what values to use) - White triangles = EXECUTION (when to run) - - On "Event BeginPlay", find the WHITE TRIANGLE on the RIGHT side - On "Add Mapping Context", find the WHITE TRIANGLE on the LEFT side - Click and DRAG from BeginPlay's white triangle to Add Mapping Context's white triangle - + **WITHOUT THIS WHITE WIRE, THE CODE NEVER RUNS!** Your graph should look like this: @@ -303,6 +306,7 @@ Your graph should look like this: ``` **VERIFY:** You must have BOTH: + - Blue data wires connecting the nodes (passes the subsystem reference) - White execution wire from Event BeginPlay to Add Mapping Context (makes it run) @@ -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. -#### a) Create debug variables (these are ONLY for debugging): +#### a) Create debug variables (these are ONLY for debugging) + - In My Blueprint panel → Variables → click "+" - Name: `DEBUG_LastMoveInput` - Type: Vector 2D - Default Value: (0, 0) - Compile to save -#### b) Create a SEPARATE debug event using a Custom Event: +#### b) Create a SEPARATE debug event using a Custom Event + - Right-click in empty space → search `Custom Event` → add it - Name it `DEBUG_PrintInput` - This keeps debug logic completely isolated -#### c) Build the debug logic from DEBUG_PrintInput: +#### c) Build the debug logic from DEBUG_PrintInput 1. **Get the current input value:** - 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 - 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` - This adds a node that calls your custom event @@ -374,18 +381,22 @@ AREA 2 - Debug logic (completely separate): ``` #### e) Compile and Save + #### f) Drag BP_Player into level, Press Play (`Alt+P`) -#### g) IMPORTANT: Click inside the game viewport to give it keyboard focus! + +#### g) IMPORTANT: Click inside the game viewport to give it keyboard focus + #### h) Press WASD keys and look at top-left of screen **Expected:** Message appears ONLY when you press or release a key: + - Press W: prints "X=1.0 Y=0.0" - Release W: prints "X=0.0 Y=0.0" - Log is NOT spammed every frame **TO DISABLE DEBUG LATER:** Simply delete the "DEBUG_PrintInput" call node from Event Tick. The Custom Event and its logic can stay (unused) or be deleted entirely - movement will be unaffected either way. -#### Debugging Steps (if you see "X=0.0 Y=0.0" always): +#### Debugging Steps (if you see "X=0.0 Y=0.0" always)
DEBUGGING STEP 1 - Verify the pawn is possessed @@ -394,6 +405,7 @@ AREA 2 - Debug logic (completely separate): - Find BP_Player in the list - If it shows a small controller icon next to it, it's possessed - If NOT possessed: Check that "Auto Possess Player" = "Player 0" in BP_Player +
@@ -403,6 +415,7 @@ AREA 2 - Debug logic (completely separate): - The viewport must have focus to receive keyboard input - Try pressing `Shift+F1` to release mouse, then click viewport again - Then press WASD again +
@@ -413,6 +426,7 @@ AREA 2 - Debug logic (completely separate): - Type "Tick is running" in the In String field (just plain text) - Play the game - you should see "Tick is running" spam in top-left - If you DON'T see this: The blueprint isn't running at all +
@@ -423,6 +437,7 @@ AREA 2 - Debug logic (completely separate): - Connect the white wire: BeginPlay → Add Mapping Context → Print String - Play the game - you should see "BeginPlay executed" once at start - If you DON'T see this: BeginPlay isn't running (pawn not spawned?) +
@@ -432,6 +447,7 @@ AREA 2 - Debug logic (completely separate): - Delete ALL of them - Drag in exactly ONE fresh BP_Player from Content Drawer - Try again +
@@ -443,6 +459,7 @@ AREA 2 - Debug logic (completely separate): - In Event Graph: Event Tick → Print String (connected to Get IA_Move) - Drag this minimal test blueprint into level - If THIS works, something is wrong with your BP_Player +
**IF THIS TEST WORKS:** Continue to step 5. You can optionally delete the DEBUG_PrintInput call from Event Tick to stop the debug prints. @@ -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). -#### 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 - Right-click → search `Sequence` → add it - 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. -#### b) Starting point for movement: +#### b) Starting point for movement + - If using Sequence: drag from "Then 1" output - If no debug: drag from Event Tick directly #### c) Right-click → search `Get Player Controller` and add it + (This is a NEW Get Player Controller - separate from the one in debug) #### d) From Player Controller output, drag → `Get Enhanced Input Local Player Subsystem` #### e) From subsystem output, drag and right-click → search `IA_Move` + - Look under "Input" → "Enhanced Action Values" → select "IA_Move" - This node returns the current value of the IA_Move input action -#### f) The output is an Input Action Value (Vector2D since we set IA_Move to Axis2D). +#### f) The output is an Input Action Value (Vector2D since we set IA_Move to Axis2D) + Right-click the output pin → "Split Struct Pin" to get X and Y components, OR drag from output and search "To Vector 2D" to convert it #### g) Right-click → `Make Vector` + - Connect the X from movement input to X -- Connect the Y from movement input to Y +- Connect the Y from movement input to Y - Set Z to 0 #### h) Right-click → `Normalize` + - Connect the vector output to Normalize input #### i) Right-click → `Get World Delta Seconds` #### j) Right-click → `Get MoveSpeed` (your variable) -#### k) Right-click → Multiply (float * float) +#### k) Right-click → Multiply (float \* float) + - Connect Delta Seconds output to first input - Connect MoveSpeed output to second input -#### l) Right-click → Multiply (vector * float) +#### l) Right-click → Multiply (vector \* float) + - Connect Normalized vector (from step h) to vector input -- Connect (DeltaSeconds * MoveSpeed) result (from step k) to float input +- Connect (DeltaSeconds \* MoveSpeed) result (from step k) to float input - This output is the "movement delta" - how far to move this frame #### m) Right-click → `Get Actor Location` #### n) Right-click → Add (vector + vector) + - Connect current location (from step m) to first input - Connect movement delta (from step l) to second input -#### o) CLAMP X COORDINATE: +#### o) CLAMP X COORDINATE First, break apart the new position vector from step n: + - Right-click on the output pin of the Add node (from step n) → "Split Struct Pin" - This splits the vector into three separate pins: X, Y, Z Now clamp the X value: + - Right-click in empty space → search `Clamp (float)` → add it - Connect the "X" output (from the split Add node) to "Value" input of Clamp Get the min bound: + - Right-click → search `Get BoundsMin` (your variable) → add it - Right-click on BoundsMin output pin → "Split Struct Pin" (splits into X, Y) - Connect BoundsMin's "X" to Clamp's "Min" input Get the max bound: + - Right-click → search `Get BoundsMax` (your variable) → add it - Right-click on BoundsMax output pin → "Split Struct Pin" - Connect BoundsMax's "X" to Clamp's "Max" input The Clamp node now outputs the X position clamped within bounds. -#### p) CLAMP Y COORDINATE: +#### p) CLAMP Y COORDINATE + - Right-click → add another `Clamp (float)` node - Connect the "Y" output (from the split Add node in step o) to "Value" - Connect BoundsMin's "Y" (already split) to "Min" - Connect BoundsMax's "Y" (already split) to "Max" #### q) Right-click → `Make Vector` + - Connect clamped X - Connect clamped Y - Set Z to 0 #### r) Right-click → `Set Actor Location` + - Connect the clamped vector to "New Location" -#### s) CRITICAL - Connect the execution wire (white wire): +#### s) CRITICAL - Connect the execution wire (white wire) + - If using Sequence (from step a): drag from "Then 1" to Set Actor Location - If no debug: drag from Event Tick to Set Actor Location - Without this execution wire, the movement code NEVER runs! **Final Structure (with debug - using Sequence node):** + ``` Event Tick → Sequence ─┬─ Then 0 → DEBUG_PrintInput │ @@ -561,6 +597,7 @@ Event Tick → Sequence ─┬─ Then 0 → DEBUG_PrintInput ``` **Final Structure (without debug):** + ``` Event Tick → [movement logic] → Set Actor Location ``` @@ -569,18 +606,21 @@ Event Tick → [movement logic] → Set Actor Location ### 6. Click Compile and Save -### Expected Result after Compile: +### Expected Result after Compile + - Compile button shows GREEN checkmark (no errors) - 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) 2. From Content Drawer, drag BP_Player into the viewport 3. Press Play (`Alt+P`) 4. Click inside the game viewport to give it keyboard focus 5. You should see the cube (TempVisual added in Step 2.2) and control it with WASD -### Expected Result when tested: +### Expected Result when tested + - Player pawn moves smoothly when pressing WASD keys - Movement is frame-rate independent (consistent speed) - Player cannot move outside the screen bounds (stops at edges) @@ -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). -### 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 (If no debug, skip Sequence and connect Event Tick directly to movement) - + ┌──────────────────────────────────┐ ┌────────────────────┐ │ Get Enhanced Input Subsystem │ │ Set Actor Location │ │ → 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: -#### a) Right-click → search `IA_Fire` +### a) Right-click → search `IA_Fire` + - Look under "Input" → "Enhanced Action Events" category (diamond ◇ icons) - Select "IA_Fire" (the one with diamond icon, NOT the square icon) - This creates an EVENT node (red, like Event Tick) that fires when the button is pressed -> **NOTE:** +> **NOTE:** +> > - "Enhanced Action Events" (diamond ◇) = triggers when button pressed > - "Enhanced Action Values" (square □) = reads current value continuously -> +> > For firing, we want the EVENT. -#### b) From the "Triggered" execution pin, build the fire rate limiter: +#### b) From the "Triggered" execution pin, build the fire rate limiter 1. **Get current timer value:** - 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 -### Expected Result after Compile: +### Expected Result after Compile + - Compile button shows GREEN checkmark - Fire rate limiter logic works (Print String spams when holding Z) - Actual bullets will be added in [STEP 3.4](part-3-create-bullet.md#step-34-complete-player-firing-logic-bp_player) after BP_Bullet is created @@ -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. -#### a) Create the function: +#### a) Create the function + 1. In My Blueprint panel (left side), find "Functions" section 2. Click the **"+"** button next to "Functions" 3. Name it `HandleDeath` 4. Double-click to open the function graph -#### b) Add placeholder logic (will be completed in Part 6): +#### b) Add placeholder logic (will be completed in Part 6) 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 4. **Connect execution:** + ``` 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. -#### a) Create the function: +#### a) Create the function + 1. In My Blueprint panel → Functions → click **"+"** 2. Name it `TakeHit` 3. Double-click to open the function graph -#### b) Add input parameter: +#### b) Add input parameter + 1. With the function graph open, look at the Details panel (right side) 2. Find "Inputs" section 3. Click **"+"** to add a new input @@ -772,19 +819,22 @@ Now we can create TakeHit, which calls HandleDeath. 5. Type: `Integer` 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:** + - Right-click in empty space → search `Get CurrentLives` → add it - This reads the CurrentLives variable value **Step 2 - Subtract damage from lives:** + - Right-click → search `integer - integer` or `Subtract (int)` → add it - Connect `CurrentLives` (from Step 1) to the TOP input (A) - Connect `Damage` (from the purple entry node) to the BOTTOM input (B) - Result = CurrentLives minus Damage **Step 3 - Clamp the result (prevent negative lives):** + - Right-click → search `Clamp (int)` → add the "Clamp (integer)" node - Connect the Subtract result (from Step 2) to the "Value" input - Set "Min" to `0` (type directly in the field) @@ -792,11 +842,13 @@ Now we can create TakeHit, which calls HandleDeath. - The output is the clamped value (0 or higher) **Step 4 - Store the new lives value:** + - Right-click → search `Set CurrentLives` → add it - Connect the Clamp output (from Step 3) to the input - **IMPORTANT:** Connect execution wire from entry node → Set CurrentLives **Step 5 - Check if player should die:** + - From `Set CurrentLives`, drag execution → search `Branch` → add it - Right-click → search `<= (integer)` (less than or equal) → add it - Connect `CurrentLives` (drag from Set node's output, or add new Get) to TOP input @@ -804,13 +856,15 @@ Now we can create TakeHit, which calls HandleDeath. - Connect the `<=` result (boolean) to Branch's "Condition" input **Step 6 - On TRUE (player is dead):** + - From Branch's "True" execution pin, drag → search `HandleDeath` → add it - This calls the function we created in step 1 **Step 7 - On FALSE (player still alive):** + - Leave the "False" pin unconnected (do nothing, player survives) -#### d) Visual diagram of TakeHit function: +#### 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:** > 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): -#### a) Create the event node: +#### a) Create the event node + 1. Go back to the **Event Graph** tab (not inside a function) 2. Right-click in empty space → search `EnhancedInputAction IA_Special` - Look under "Input" → "Enhanced Action Events" category - Select "IA_Special" (the EVENT with diamond ◇ icon) 3. This creates a red event node that fires when X or Right Mouse is pressed -#### b) Check if special was already used: +#### b) Check if special was already used + 1. From the "Triggered" execution pin, drag → search `Branch` → add it 2. Right-click → search `Get SpecialUsed` → add it 3. Right-click → search `NOT Boolean` → add it 4. Connect SpecialUsed → NOT → Branch Condition - We branch on "NOT SpecialUsed" (true = hasn't been used yet) -#### c) On TRUE (special available) - placeholder: +#### c) On TRUE (special available) - placeholder + 1. From Branch's "True" pin, drag → search `Set SpecialUsed` → add it 2. Check the box to set it to TRUE (marks ability as used) @@ -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)" - 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) -#### 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 -#### g) Test the placeholder: +#### g) Test the placeholder + 1. Press Play 2. Press X key (or Right Mouse Button) 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 -### Expected Result after Compile: +### Expected Result after Compile + - Compile button shows GREEN checkmark - "TakeHit" and "HandleDeath" functions appear under Functions in My Blueprint panel - Event Graph shows IA_Special event with branching logic -### Expected Result in Play mode (at this stage): +### Expected Result in Play mode (at this stage) + - Press X key: "SPECIAL ABILITY ACTIVATED!" appears once in top-left - Press X again: Nothing happens (SpecialUsed is now true) - TakeHit and HandleDeath functions exist but cannot be tested yet (nothing calls them) > **NOTE:** Full testing of damage/death systems requires: +> > - BP_Bullet collision (Part 3) to call TakeHit > - BP_GameDirector (Part 6) for HandleDeath to notify > - BP_Enemy (Part 4) for special ability to destroy - +> > **REMINDER:** Complete the `HandleDeath` function in [Part 6, Step 6.3](part-6-game-director.md#step-63-game-director-update-logic) after creating BP_GameDirector. --- diff --git a/games/unreal/tutorial/part-3-create-bullet.md b/games/unreal/tutorial/part-3-create-bullet.md index bc3b079..a322ac3 100644 --- a/games/unreal/tutorial/part-3-create-bullet.md +++ b/games/unreal/tutorial/part-3-create-bullet.md @@ -23,20 +23,22 @@ 6. **Create Variables:** -| Variable Name | Type | Default Value | -|---------------|------|---------------| -| `TravelDirection` | Vector | (0, 1, 0) | -| `TravelSpeed` | Float | 1200.0 | -| `RemainingLifetime` | Float | 4.0 | -| `IsEnemyProjectile` | Boolean | false | -| `Damage` | Integer | 1 | +| Variable Name | Type | Default Value | +| ------------------- | ------- | ------------- | +| `TravelDirection` | Vector | (0, 1, 0) | +| `TravelSpeed` | Float | 1200.0 | +| `RemainingLifetime` | Float | 4.0 | +| `IsEnemyProjectile` | Boolean | false | +| `Damage` | Integer | 1 | + +### Expected Result after Compile -### Expected Result after Compile: - Compile button shows GREEN checkmark - Components panel shows: DefaultSceneRoot → BulletSprite, BulletCollision, TempVisual - 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) - Sphere collision visible (radius 8) @@ -46,7 +48,7 @@ 1. In Event Graph, from **Event Tick:** -#### a) Calculate movement: +### a) Calculate movement 1. Right-click → `Get TravelDirection` 2. Right-click → `Get TravelSpeed` @@ -70,7 +72,7 @@ - Connect the Add result to "New 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` @@ -115,30 +117,32 @@ Event Tick ──► Set Actor Location ──► Set RemainingLifetime ── 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 2. Click the **"+"** button next to Functions 3. Name the new function `Initialize` 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 2. With the entry node selected, look at the Details panel (right side) 3. Find "Inputs" section and click "+" to add parameters: -| Parameter | Type | Default | -|-----------|------|---------| -| `Direction` | Vector | - | -| `Speed` | Float | - | -| `bIsEnemy` | Boolean | - | -| `Lifetime` | Float | - | -| `DamageValue` | Integer | 1 | +| Parameter | Type | Default | +| ------------- | ------- | ------- | +| `Direction` | Vector | - | +| `Speed` | Float | - | +| `bIsEnemy` | Boolean | - | +| `Lifetime` | Float | - | +| `DamageValue` | Integer | 1 | The entry node should now show 5 input pins. -#### c) Build the function logic: +#### c) Build the function logic 1. **Normalize and set direction:** - 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` - 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. @@ -192,7 +196,8 @@ The entry node should now show 5 input pins. ### 3. Compile and Save -### Expected Result after Compile: +### Expected Result after Compile + - Compile button shows GREEN checkmark - "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 -### 1. ADD THE OVERLAP EVENT: +### 1. ADD THE OVERLAP EVENT 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)"** - 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` - 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) - 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 - 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: - 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). @@ -293,7 +298,8 @@ The entry node should now show 5 input pins. ### 4. Compile and Save -### Expected Result after Compile: +### Expected Result after Compile + - Compile button shows GREEN checkmark - 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. -### 1. Open BP_Player Blueprint: +### 1. Open BP_Player Blueprint + - In Content Drawer, navigate to Content → Blueprints - 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 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` 5. Compile to save the change -### 3. CREATE SPAWN BULLET FUNCTION: +### 3. CREATE SPAWN BULLET FUNCTION #### a) In "My Blueprint" panel, under "Functions", click "+" + #### b) Name the function `SpawnBullet` + #### 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 Details panel (right side), find "Inputs" section - Click "+" to add a new input parameter -| Parameter | Type | -|-----------|------| +| Parameter | Type | +| --------------- | ------ | | `SpawnLocation` | Vector | -| `Direction` | Vector | +| `Direction` | Vector | 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 @@ -352,7 +362,7 @@ The entry node should now show two input pins: SpawnLocation and Direction 4. Connect execution wire: - 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: - 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) -### 4. CREATE FIRE VOLLEY FUNCTION: +### 4. CREATE FIRE VOLLEY FUNCTION #### a) In "My Blueprint" panel, under "Functions", click "+" + #### b) Name the function `FireVolley` + #### 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. First, calculate the starting angle: - + The spread is CENTERED on "straight up". For 3 bullets with 12° spread: - Bullet 0: -12° (left of center) - - Bullet 1: 0° (center, straight up) + - Bullet 1: 0° (center, straight up) - Bullet 2: +12° (right of center) - + **Formula:** `StartAngle = -(VolleySpread * (VolleySize - 1)) / 2` 10. **Calculate StartAngle:** - Right-click → `Get VolleySize` - Right-click → `Subtract (Integer)` → connect VolleySize, type `1` - Result: (VolleySize - 1) = 2 for default - - Right-click → `Get VolleySpread` - Right-click → `Multiply (float)` → connect VolleySpread and (VolleySize-1) - 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` - Result: 24 / 2 = 12 - - Right-click → `Negate (float)` or "Multiply by -1" - Result: -12 (this is StartAngle) - - Right-click → `Set` → create a LOCAL variable "StartAngle" (Float) - OR just keep the wire connected (we'll use it in the loop) 11. **Set up the FOR loop:** - From Branch FALSE pin, drag → right-click → search `For Loop` - "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: - Get VolleySize → Subtract 1 → connect to Last Index - + The loop will run with Index = 0, 1, 2 for VolleySize=3 12. **Inside the loop (from "Loop Body" execution pin):** - + Calculate angle for THIS bullet: - Right-click → `Multiply (float)` - Connect loop "Index" to first input (auto-converts int to float) - 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)` - Connect StartAngle (the -12 from step 10) to first input - - Connect (Index * VolleySpread) to second input - - Result: StartAngle + (Index * VolleySpread) = -12, 0, +12 degrees - + - Connect (Index \* VolleySpread) to second input + - Result: StartAngle + (Index \* VolleySpread) = -12, 0, +12 degrees + This is the angle in DEGREES. Store or continue with this value. 13. **Convert angle from degrees to a direction vector:** - + In Unreal's top-down view (looking down Z axis): - X axis = up/down on screen (vertical) - Y axis = left/right on screen (horizontal) - Angle 0° = straight up = direction (1, 0, 0) - Angle 90° = right = direction (0, 1, 0) - + ``` Direction.X = Cos(angle) // Cos for vertical (forward/up) Direction.Y = Sin(angle) // Sin for horizontal offset Direction.Z = 0 ``` - + BUT Unreal's Sin/Cos use RADIANS, not degrees! - + **a) Convert degrees to radians:** - Right-click → search `Degrees To Radians` → add it - 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):** - Right-click → `Cos (Radians)` → add it - Connect the Degrees To Radians output to Cos input - + **c) Calculate Y component (Sin - horizontal offset):** - Right-click → `Sin (Radians)` → add it - 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 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 - + **d) Make the direction vector:** - Right-click → `Make Vector` - 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):** -- 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 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 -### Expected Result after Compile: +### Expected Result after Compile + - Compile button shows GREEN checkmark - "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 - Bullets travel upward from player position - Rapid fire when holding the button (every 0.08 seconds) diff --git a/games/unreal/tutorial/part-4-create-enemy.md b/games/unreal/tutorial/part-4-create-enemy.md index b47de66..8b314ef 100644 --- a/games/unreal/tutorial/part-4-create-enemy.md +++ b/games/unreal/tutorial/part-4-create-enemy.md @@ -22,31 +22,33 @@ 6. **Create Variables:** -| Variable Name | Type | Default Value | -|---------------|------|---------------| -| `MaxHealth` | Integer | 12 | -| `CurrentHealth` | Integer | 12 | -| `ScoreValue` | Integer | 50 | -| `VerticalSpeed` | Float | 220.0 | -| `HorizontalAmplitude` | Float | 250.0 | -| `HorizontalFrequency` | Float | 1.8 | -| `DespawnY` | Float | -750.0 | -| `FireInterval` | Float | 0.35 | -| `BulletsPerBurst` | Integer | 20 | -| `BurstSpread` | Float | 360.0 | -| `EnemyBulletSpeed` | Float | 1000.0 | -| `EnemyBulletLifetime` | Float | 6.0 | -| `BaseX` | Float | 0.0 | -| `WaveSeed` | Float | 0.0 | -| `FireTimer` | Float | 0.0 | -| `BulletClass` | Class Reference to Actor | (set later) | +| Variable Name | Type | Default Value | +| --------------------- | ------------------------ | ------------- | +| `MaxHealth` | Integer | 12 | +| `CurrentHealth` | Integer | 12 | +| `ScoreValue` | Integer | 50 | +| `VerticalSpeed` | Float | 220.0 | +| `HorizontalAmplitude` | Float | 250.0 | +| `HorizontalFrequency` | Float | 1.8 | +| `DespawnY` | Float | -750.0 | +| `FireInterval` | Float | 0.35 | +| `BulletsPerBurst` | Integer | 20 | +| `BurstSpread` | Float | 360.0 | +| `EnemyBulletSpeed` | Float | 1000.0 | +| `EnemyBulletLifetime` | Float | 6.0 | +| `BaseX` | Float | 0.0 | +| `WaveSeed` | Float | 0.0 | +| `FireTimer` | Float | 0.0 | +| `BulletClass` | Class Reference to Actor | (set later) | + +### Expected Result after Compile -### Expected Result after Compile: - Compile button shows GREEN checkmark - Components panel shows: DefaultSceneRoot → EnemySprite, EnemyCollision, TempVisual - 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) - Box collision visible (30x30x10) @@ -56,20 +58,23 @@ 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) #### d) Random Float in Range (0, FireInterval) → Set FireTimer -### Expected Result after Compile: +### Expected Result after Compile + - Compile button shows GREEN checkmark - 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 starts with different FireTimer offset - This creates varied, non-synchronized enemy behavior @@ -80,11 +85,13 @@ 1. In Event Graph, from **"Event Tick":** -#### a) VERTICAL MOVEMENT: -- Get Actor Location → Break into X, Y, Z -- Subtract (VerticalSpeed * DeltaSeconds) from Y +### a) VERTICAL MOVEMENT + +- 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 - Add WaveSeed - Multiply by HorizontalFrequency @@ -95,7 +102,8 @@ #### c) Set Actor Location with new X, Y (Z stays 0) -#### d) DESPAWN CHECK: +#### d) DESPAWN CHECK + - If Y < DespawnY: 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 - 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 oscillate horizontally in sine wave pattern - Each enemy has different horizontal phase (due to WaveSeed) @@ -129,28 +139,31 @@ ## 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 -#### b) Branch: if FireTimer <= 0: +#### b) Branch: if FireTimer <= 0 + - Reset FireTimer to FireInterval - Call "FireBurst" function -### 2. CREATE "FireBurst" FUNCTION: +### 2. CREATE "FireBurst" FUNCTION #### a) Add function "FireBurst" -#### b) Inside: +#### b) Inside + - Get BulletsPerBurst - Calculate angle step: `360 / BulletsPerBurst` (for full circle) - 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` - Convert to direction vector: - 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 Actor from Class (BP_Bullet) - Get spawned bullet, call Initialize: @@ -171,11 +184,13 @@ ↓ ``` -### Expected Result after Compile: +### Expected Result after Compile + - Compile button shows GREEN checkmark - "FireBurst" function appears under Functions -### Expected Result in Play mode: +### Expected Result in Play mode + - Every 0.35 seconds (FireInterval), enemy fires bullet burst - 20 bullets spawn in a full 360° circle pattern - Bullets travel outward from enemy position @@ -185,37 +200,42 @@ ## Step 4.5: Enemy Damage and Death -### 1. CREATE "ApplyDamage" FUNCTION: +### 1. CREATE "ApplyDamage" FUNCTION #### a) Add input: `DamageAmount` (Integer) -#### b) Inside: +#### b) Inside + - Subtract DamageAmount from CurrentHealth - If CurrentHealth <= 0: - 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)) - Call AddScore, passing ScoreValue - Spawn death effect (optional) - 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 - If successful: - Call TakeHit(1) on player - Call HandleDeath() on self -### Expected Result after Compile: +### Expected Result after Compile + - Compile button shows GREEN checkmark - "ApplyDamage" and "HandleDeath" functions appear under Functions - EnemyCollision has overlap event in Event Graph -### Expected Result in Play mode: +### Expected Result in Play mode + - Player bullets hitting enemy: Enemy health decreases - After 12 hits (MaxHealth): Enemy disappears, score increases by 50 - 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). -### 1. Open BP_Player Blueprint: +### 1. Open BP_Player Blueprint + 1. Content Browser → Blueprints → double-click `BP_Player` 2. Go to **Event Graph** tab 3. Find the `EnhancedInputAction IA_Special` event node (created in Step 2.6) 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 - Press Delete - (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 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 -#### 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 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 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 2. Click "Actor Class" dropdown → select `BP_Bullet` 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 2. From "Array Element" (the current bullet), drag → search `Get IsEnemyProjectile` → add it - This reads the IsEnemyProjectile variable from BP_Bullet 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 2. Connect "Array Element" (the bullet) to Destroy Actor's "Target" 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 -### Expected Result after Compile: +### Expected Result after Compile + - Compile button shows GREEN checkmark - 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: - ALL enemies on screen 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). -### 1. Open BP_Bullet Blueprint: +### 1. Open BP_Bullet Blueprint + 1. Content Browser → Blueprints → double-click `BP_Bullet` 2. Go to **Event Graph** tab 3. Find the `On Component Begin Overlap (BulletCollision)` event node 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 - Press Delete -#### b) Add Cast to BP_Enemy: +#### b) Add Cast to BP_Enemy + 1. Right-click → search `Cast to BP_Enemy` → add it 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 - (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: - From the cast's **"As BP Enemy"** output pin, drag → search `ApplyDamage` - 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) - 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` - 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 -### Expected Result after Compile: +### Expected Result after Compile + - Compile button shows GREEN checkmark - 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: - Enemy takes damage, bullet disappears - After enough hits, enemy dies and awards score diff --git a/games/unreal/tutorial/part-5-create-spawner.md b/games/unreal/tutorial/part-5-create-spawner.md index 1581b5d..98739f0 100644 --- a/games/unreal/tutorial/part-5-create-spawner.md +++ b/games/unreal/tutorial/part-5-create-spawner.md @@ -13,17 +13,18 @@ 5. **Create Variables:** -| Variable Name | Type | Default Value | -|---------------|------|---------------| -| `EnemyClass` | Class Reference | (will reference BP_Enemy) | -| `SpawnAreaHalfWidth` | Float | 900.0 | -| `GameDuration` | Float | 300.0 (5 minutes) | -| `MaxSimultaneousEnemies` | Integer | 120 | -| `ElapsedTime` | Float | 0.0 | -| `SpawnTimer` | Float | 0.0 | -| `SpawningActive` | Boolean | true | +| Variable Name | Type | Default Value | +| ------------------------ | --------------- | ------------------------- | +| `EnemyClass` | Class Reference | (will reference BP_Enemy) | +| `SpawnAreaHalfWidth` | Float | 900.0 | +| `GameDuration` | Float | 300.0 (5 minutes) | +| `MaxSimultaneousEnemies` | Integer | 120 | +| `ElapsedTime` | Float | 0.0 | +| `SpawnTimer` | Float | 0.0 | +| `SpawningActive` | Boolean | true | + +### Expected Result after Compile -### Expected Result after Compile: - Compile button shows GREEN checkmark - Variables panel shows all 7 variables with correct types - No components needed (spawner is invisible logic actor) @@ -32,37 +33,40 @@ ## Step 5.2: Spawn Rate Curve -### 1. Create Variable: +### 1. Create Variable + - `SpawnCurve` (Curve Float) -### 2. To create the curve asset: +### 2. To create the curve asset 1. In Content Browser, right-click → **Miscellaneous → Curve** 2. Select "CurveFloat" 3. Name it `SpawnRateCurve` 4. Double-click to open Curve Editor -### 3. In Curve Editor: +### 3. In Curve Editor - Right-click on the curve → Add Key - Create these keyframes: -| Time | Value | Description | -|------|-------|-------------| -| 0.0 | 0.4 | slow spawn at start | -| 0.5 | 2.0 | medium spawn halfway | -| 1.0 | 4.5 | fast spawn at end | +| Time | Value | Description | +| ---- | ----- | -------------------- | +| 0.0 | 0.4 | slow spawn at start | +| 0.5 | 2.0 | medium spawn halfway | +| 1.0 | 4.5 | fast spawn at end | - The X axis is normalized time (0-1) - The Y axis is spawns per second ### 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) - Smooth interpolation between keyframes -### Expected Result in Play mode: +### Expected Result in Play mode + - Game start: ~0.4 enemies spawn per second (slow) - At 2.5 minutes: ~2 enemies spawn per second (medium) - At 5 minutes: ~4.5 enemies spawn per second (intense) @@ -71,67 +75,81 @@ ## 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 + - If false, do nothing -#### b) Update ElapsedTime: +#### b) Update ElapsedTime + - Add DeltaSeconds to ElapsedTime -#### c) Calculate normalized time: +#### c) Calculate normalized time + - Divide ElapsedTime by GameDuration - Clamp between 0 and 1 -#### d) Get spawn rate from curve: +#### d) Get spawn rate from curve + - Get SpawnCurve - Call `Get Float Value` with normalized time - This returns spawns per second -#### e) Update SpawnTimer: +#### e) Update SpawnTimer + - Subtract DeltaSeconds from SpawnTimer - If SpawnTimer <= 0: - Reset SpawnTimer to `(1.0 / SpawnsPerSecond)` - Call SpawnWave function -### 2. CREATE "SpawnWave" FUNCTION: +### 2. CREATE "SpawnWave" FUNCTION + +#### a) Inside -#### a) Inside: - Calculate burst size based on time: + ``` BaseCount = 1 + (NormalizedTime * 6) BurstSize = BaseCount + Random(0, 2) Clamp between 1 and 12 ``` -#### b) For Loop from 0 to BurstSize - 1: +#### b) For Loop from 0 to BurstSize - 1 + - Call SpawnEnemy function -### 3. CREATE "SpawnEnemy" FUNCTION: +### 3. CREATE "SpawnEnemy" FUNCTION + +#### a) Inside -#### a) Inside: - Check: Get All Actors of Class (BP_Enemy) - Get array length - If >= MaxSimultaneousEnemies: Return (don't spawn) -#### b) Calculate spawn position: +#### b) Calculate spawn position + - X = Random Float in Range (-SpawnAreaHalfWidth, SpawnAreaHalfWidth) - Y = Get this actor's Y position (top of screen) - Z = 0 -#### c) Spawn Actor from Class: +#### c) Spawn Actor from Class + - Class: EnemyClass - Location: calculated position - Rotation: (0, 0, 0) -### 4. CREATE "StopSpawning" FUNCTION: +### 4. CREATE "StopSpawning" FUNCTION + - Set SpawningActive = false -### Expected Result after Compile: +### Expected Result after Compile + - Compile button shows GREEN checkmark - "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 - Spawn rate increases over time following the curve - Maximum 120 enemies on screen at once diff --git a/games/unreal/tutorial/part-6-game-director.md b/games/unreal/tutorial/part-6-game-director.md index 616cdcc..4983043 100644 --- a/games/unreal/tutorial/part-6-game-director.md +++ b/games/unreal/tutorial/part-6-game-director.md @@ -13,15 +13,16 @@ 5. **Create Variables:** -| Variable Name | Type | Default Value | -|---------------|------|---------------| -| `PlayerReference` | Object Reference to BP_Player | - | -| `SpawnerReference` | Object Reference to BP_EnemySpawner | - | -| `GameDuration` | Float | 300.0 | -| `ElapsedTime` | Float | 0.0 | -| `GameActive` | Boolean | true | +| Variable Name | Type | Default Value | +| ------------------ | ----------------------------------- | ------------- | +| `PlayerReference` | Object Reference to BP_Player | - | +| `SpawnerReference` | Object Reference to BP_EnemySpawner | - | +| `GameDuration` | Float | 300.0 | +| `ElapsedTime` | Float | 0.0 | +| `GameActive` | Boolean | true | + +### Expected Result after Compile -### Expected Result after Compile: - Compile button shows GREEN checkmark - Variables panel shows all 5 variables with correct types - No components needed (director is invisible logic actor) @@ -30,27 +31,32 @@ ## 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 first element (index 0) - Set PlayerReference -#### b) Find spawner in scene: +#### b) Find spawner in scene + - Get All Actors of Class → BP_EnemySpawner - Get first element - Set SpawnerReference -#### c) Initialize ScoreManager: +#### c) Initialize ScoreManager + - Get ScoreManager reference - Call RegisterGameStart with initial lives and duration -### Expected Result after Compile: +### Expected Result after Compile + - Compile button shows GREEN checkmark - 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 - UI initializes with correct starting values (Lives: 3, Time: 05:00) @@ -58,41 +64,49 @@ ## Step 6.3: Game Director Update Logic -### 1. From Event Tick: +### 1. From Event Tick #### a) Check if GameActive + - If false, skip everything -#### b) Update elapsed time: +#### b) Update elapsed time + - Add DeltaSeconds to ElapsedTime -#### c) Calculate remaining time: +#### c) Calculate remaining time + - Subtract ElapsedTime from GameDuration - Max with 0 (don't go negative) -#### d) Update UI timer: +#### d) Update UI timer + - Get ScoreManager - Call UpdateTimer with remaining time -#### e) Check for victory: +#### e) Check for victory + - If ElapsedTime >= GameDuration: - Set GameActive = false - Get SpawnerReference → Call StopSpawning - Get ScoreManager → Call HandleGameClear -### 2. CREATE "HandlePlayerDeath" FUNCTION: +### 2. CREATE "HandlePlayerDeath" FUNCTION + +#### a) Inside -#### a) Inside: - Set GameActive = false - Get SpawnerReference → Call StopSpawning - Get ScoreManager → Call HandleGameOver -### Expected Result after Compile: +### Expected Result after Compile + - Compile button shows GREEN checkmark - "HandlePlayerDeath" function appears under Functions - 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 - At 00:00: "Mission Complete" appears, enemies stop spawning - When player dies: "Game Over" appears, enemies stop spawning diff --git a/games/unreal/tutorial/part-7-score-manager-ui.md b/games/unreal/tutorial/part-7-score-manager-ui.md index 9caeec6..802f655 100644 --- a/games/unreal/tutorial/part-7-score-manager-ui.md +++ b/games/unreal/tutorial/part-7-score-manager-ui.md @@ -11,8 +11,10 @@ 3. Name: `WBP_HUD` 4. Double-click to open Widget Designer -### Expected Result: +### Expected Result + Widget Designer opens with: + - Hierarchy panel on left - Canvas preview in center - Details panel on right @@ -22,9 +24,10 @@ Widget Designer opens with: ## Step 7.2: Design HUD Layout ### 1. In the Palette panel (left side), search for "Canvas Panel" + - Drag Canvas Panel to the Hierarchy (if not already there) -### 2. Add Score Text: +### 2. Add Score Text 1. In Palette, search for "Text" 2. Drag "Text" widget onto Canvas Panel in hierarchy @@ -41,7 +44,7 @@ Widget Designer opens with: - Font Size: `24` - Color: White -### 3. Add Lives Text: +### 3. Add Lives Text 1. Drag another "Text" widget 2. Rename to `LivesText` @@ -49,7 +52,7 @@ Widget Designer opens with: 4. Text: `Lives: 3` 5. Same font settings as Score -### 4. Add Timer Text: +### 4. Add Timer Text 1. Drag another "Text" widget 2. Rename to `TimerText` @@ -59,9 +62,10 @@ Widget Designer opens with: ### 5. Click "Compile" and "Save" (top buttons) -### Expected Result: +### Expected Result Preview shows: + ``` ┌────────────────────────────────────┐ │ Score: 0 │ @@ -83,14 +87,15 @@ Preview shows: 5. **Create Variables:** -| Variable Name | Type | Default Value | -|---------------|------|---------------| -| `Score` | Integer | 0 | -| `CurrentLives` | Integer | 3 | -| `HUDWidget` | Object Reference to WBP_HUD | - | -| `HUDWidgetClass` | Class Reference | (set to WBP_HUD) | +| Variable Name | Type | Default Value | +| ---------------- | --------------------------- | ---------------- | +| `Score` | Integer | 0 | +| `CurrentLives` | Integer | 3 | +| `HUDWidget` | Object Reference to WBP_HUD | - | +| `HUDWidgetClass` | Class Reference | (set to WBP_HUD) | + +### Expected Result after Compile -### Expected Result after Compile: - Compile button shows GREEN checkmark - Variables panel shows all 4 variables with correct types @@ -98,25 +103,30 @@ Preview shows: ## 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` - Class: Select WBP_HUD (or use HUDWidgetClass variable) - Owning Player: Get Player Controller (index 0) -#### b) Store widget reference: +#### b) Store widget reference + - Set HUDWidget to the created widget -#### c) Add to viewport: +#### c) Add to viewport + - Right-click → `Add to Viewport` - Connect widget reference as target -### Expected Result after Compile: +### Expected Result after Compile + - Compile button shows GREEN checkmark - 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 displays in top-left corner of screen - Text is visible and readable (white on game background) @@ -125,22 +135,25 @@ Preview shows: ## Step 7.5: Score Manager Functions -### 1. CREATE "RegisterGameStart" FUNCTION: +### 1. CREATE "RegisterGameStart" FUNCTION + +**Inputs:** -**Inputs:** - `InitialLives` (Integer) - `Duration` (Float) **Inside:** + - Set Score = 0 - Set CurrentLives = InitialLives - Update all UI labels -### 2. CREATE "AddScore" FUNCTION: +### 2. CREATE "AddScore" FUNCTION **Input:** `Amount` (Integer) **Inside:** + - Add Amount to Score - Update Score label in HUD: - Get HUDWidget @@ -148,42 +161,50 @@ Preview shows: - Get "ScoreText" widget - Set Text to "Score: " + Score -### 3. CREATE "SetLives" FUNCTION: +### 3. CREATE "SetLives" FUNCTION **Input:** `Lives` (Integer) **Inside:** + - Set CurrentLives = Lives - Update Lives label in HUD -### 4. CREATE "UpdateTimer" FUNCTION: +### 4. CREATE "UpdateTimer" FUNCTION **Input:** `TimeRemaining` (Float) **Inside:** + - Convert to minutes:seconds format: + ``` Minutes = Floor(TimeRemaining / 60) Seconds = Floor(TimeRemaining mod 60) ``` + - Format string: "Time: MM:SS" - Update Timer label -### 5. CREATE "HandleGameOver" FUNCTION: +### 5. CREATE "HandleGameOver" FUNCTION **Inside:** + - Set Timer text to "Game Over" -### 6. CREATE "HandleGameClear" FUNCTION: +### 6. CREATE "HandleGameClear" FUNCTION **Inside:** + - Set Timer text to "Mission Complete" -### Expected Result after Compile: +### Expected Result after Compile + - Compile button shows GREEN checkmark - 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) - Lives display updates when player is hit - Timer counts down in MM:SS format diff --git a/games/unreal/tutorial/part-8-game-mode-level.md b/games/unreal/tutorial/part-8-game-mode-level.md index 1826135..bfae7cd 100644 --- a/games/unreal/tutorial/part-8-game-mode-level.md +++ b/games/unreal/tutorial/part-8-game-mode-level.md @@ -19,7 +19,8 @@ - Default Pawn Class: Select `BP_Player` - Player Controller Class: Keep default -### Expected Result after Compile: +### Expected Result after Compile + - Compile button shows GREEN checkmark - Details panel shows "Default Pawn Class" set to BP_Player @@ -33,7 +34,8 @@ - Default GameMode: Select `BP_BulletHellGameMode` 4. Close Project Settings -### Expected Result: +### Expected Result + - Project Settings shows BP_BulletHellGameMode as Default GameMode - This means the game will automatically spawn BP_Player when Play is pressed @@ -48,7 +50,8 @@ 5. Name: `BulletHellLevel` 6. Click Save -### Expected Result: +### Expected Result + - New level file "BulletHellLevel" appears in Content folder - Level is completely empty (black viewport) - Outliner shows only default actors (if any) @@ -59,7 +62,7 @@ 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) 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` **For Orthographic View:** + - Click on Camera component - In Details, set "Projection Mode" to `Orthographic` - Set "Ortho Width" to `1920` (or your screen width) -### 2. ADD PLAYER: +### 2. ADD PLAYER 1. From Content Browser, drag `BP_Player` into level 2. Position: `X=0, Y=-300, Z=0` (bottom center) -### 3. ADD ENEMY SPAWNER: +### 3. ADD ENEMY SPAWNER 1. Drag `BP_EnemySpawner` into level 2. Position: `X=0, Y=500, Z=0` (top of screen) 3. In Details panel, set EnemyClass to `BP_Enemy` -### 4. ADD GAME DIRECTOR: +### 4. ADD GAME DIRECTOR 1. Drag `BP_GameDirector` into level 2. Position doesn't matter (it's invisible) -### 5. ADD SCORE MANAGER: +### 5. ADD SCORE MANAGER 1. Drag `BP_ScoreManager` into level 2. Position doesn't matter ### 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) - BP_Player visible at bottom center (Y=-300) - BP_EnemySpawner visible at top (Y=500) - BP_GameDirector and BP_ScoreManager in Outliner (invisible actors) -### Expected Result in Outliner: +### Expected Result in Outliner + ``` - CameraActor - BP_Player -- BP_EnemySpawner +- BP_EnemySpawner - BP_GameDirector - BP_ScoreManager ``` @@ -125,7 +131,8 @@ In the level (main viewport), we need to add game actors: - Editor Startup Map: Select `BulletHellLevel` - Game Default Map: Select `BulletHellLevel` -### Expected Result: +### Expected Result + - Project Settings shows BulletHellLevel as both startup and default map - Launching the game (standalone or in editor) loads this level automatically diff --git a/games/unreal/tutorial/part-9-final-setup.md b/games/unreal/tutorial/part-9-final-setup.md index c6de3f0..6b2e671 100644 --- a/games/unreal/tutorial/part-9-final-setup.md +++ b/games/unreal/tutorial/part-9-final-setup.md @@ -6,26 +6,31 @@ ## Step 9.1: Assign Blueprint References -### 1. Open BP_Player: +### 1. Open BP_Player + - In Details panel (with blueprint open) - Set BulletClass: `BP_Bullet` -### 2. Open BP_EnemySpawner: +### 2. Open BP_EnemySpawner + - Set EnemyClass: `BP_Enemy` -### 3. Open BP_Enemy: +### 3. Open BP_Enemy + - Set BulletClass: `BP_Bullet` ### 4. Compile and Save all blueprints -### Expected Result after Compile (all blueprints): +### Expected Result after Compile (all blueprints) + - All blueprints compile with GREEN checkmark - No "None" or missing references in variable defaults - BP_Player.BulletClass → BP_Bullet -- BP_Enemy.BulletClass → BP_Bullet +- BP_Enemy.BulletClass → BP_Bullet - BP_EnemySpawner.EnemyClass → BP_Enemy -### Expected Result in Play mode: +### Expected Result in Play mode + - Player can shoot bullets (BP_Bullet spawns) - Enemies spawn and shoot bullets - All collision/damage systems functional @@ -36,7 +41,7 @@ Now replace the temporary cube visuals with proper colored materials: -### 1. REMOVE TEMPORARY COMPONENTS: +### 1. REMOVE TEMPORARY COMPONENTS 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)) @@ -46,7 +51,7 @@ Now replace the temporary cube visuals with proper colored materials: ### 2. Content Browser → Materials folder -### 3. PLAYER MATERIAL: +### 3. PLAYER MATERIAL 1. Right-click → Material 2. Name: `M_Player` @@ -56,19 +61,21 @@ Now replace the temporary cube visuals with proper colored materials: 6. Connect to Base Color 7. Save and Close -### 4. BULLET MATERIALS: +### 4. BULLET MATERIALS - Create `M_PlayerBullet` - Yellow `(1, 1, 0)` - Create `M_EnemyBullet` - Red `(1, 0, 0)` -### 5. ENEMY MATERIAL: +### 5. ENEMY MATERIAL - Create `M_Enemy` - Magenta `(1, 0, 1)` ### 6. Apply materials to sprite components in each Blueprint + (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 bullets visible as yellow 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) -### 1. In level, add a Plane mesh: +### 1. In level, add a Plane mesh + - Place Actors → Basic → Plane - Scale: `X=20, Y=30, Z=1` - Position: `X=0, Y=0, Z=-100` (behind everything) -### 2. Create dark space material: +### 2. Create dark space material + - `M_Background` - Dark blue/black -### Expected Result in Play mode: +### Expected Result in Play mode + - Dark background visible behind all game elements - Game elements (player, enemies, bullets) clearly visible against background - 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 ### 1. Click "Play" button (green arrow in main toolbar) + OR press `Alt+P` -### 2. TEST CHECKLIST: +### 2. TEST CHECKLIST -| # | Test | Pass? | -|---|------|-------| -| 1 | Player moves with WASD or Arrow keys | ☐ | -| 2 | Player stays within screen bounds | ☐ | -| 3 | Player shoots with Z key or Left Mouse | ☐ | -| 4 | Bullets travel upward | ☐ | -| 5 | Enemies spawn at top of screen | ☐ | -| 6 | Enemies move down with wavy motion | ☐ | -| 7 | Enemies shoot radial bullet patterns | ☐ | -| 8 | Player bullets damage enemies | ☐ | -| 9 | Enemy bullets damage player | ☐ | -| 10 | Score increases when enemies die | ☐ | -| 11 | Lives decrease when player is hit | ☐ | -| 12 | Timer counts down from 5:00 | ☐ | -| 13 | Game shows "Game Over" when lives = 0 | ☐ | -| 14 | Game shows "Mission Complete" after 5 minutes | ☐ | -| 15 | Special ability (X key) clears screen | ☐ | +| # | Test | Pass? | +| --- | --------------------------------------------- | ----- | +| 1 | Player moves with WASD or Arrow keys | ☐ | +| 2 | Player stays within screen bounds | ☐ | +| 3 | Player shoots with Z key or Left Mouse | ☐ | +| 4 | Bullets travel upward | ☐ | +| 5 | Enemies spawn at top of screen | ☐ | +| 6 | Enemies move down with wavy motion | ☐ | +| 7 | Enemies shoot radial bullet patterns | ☐ | +| 8 | Player bullets damage enemies | ☐ | +| 9 | Enemy bullets damage player | ☐ | +| 10 | Score increases when enemies die | ☐ | +| 11 | Lives decrease when player is hit | ☐ | +| 12 | Timer counts down from 5:00 | ☐ | +| 13 | Game shows "Game Over" when lives = 0 | ☐ | +| 14 | Game shows "Mission Complete" after 5 minutes | ☐ | +| 15 | Special ability (X key) clears screen | ☐ | ### 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 - Frame rate stable (60+ FPS recommended) - No crashes or Blueprint errors in Output Log @@ -137,7 +149,8 @@ OR press `Alt+P` 4. Navigate to output folder → WindowsNoEditor → [ProjectName].exe 5. Run the executable to play standalone -### Expected Result: +### Expected Result + - Build completes without errors (check Output Log) - Executable file created in output folder - 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! Your game includes: + - ✅ Player with 3 lives, WASD movement, Z/mouse shooting - ✅ Volley shooting (3 bullets in spread pattern) - ✅ 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 B: Troubleshooting](appendix-b-troubleshooting.md)