mirror of
https://github.com/kuhyx/praca_magisterska.git
synced 2026-07-04 13:23:05 +02:00
feat: finish part 2/9 split manual into parts
This commit is contained in:
parent
927dd5b697
commit
3d732ef8d7
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -393,6 +393,8 @@ K2Node_VariableGet=CameraComponent.CameraSettings.Overscan,CameraComponent.PostP
|
||||
K2Node_EnhancedInputAction=CameraComponent.CameraSettings.Overscan,CameraComponent.PostProcess.Lens
|
||||
K2Node_VariableSet=CameraComponent.CameraSettings.Overscan,CameraComponent.PostProcess.Lens
|
||||
K2Node_GetInputActionValue=CameraComponent.CameraSettings.Overscan,CameraComponent.PostProcess.Lens
|
||||
EdGraph=CameraComponent.CameraSettings.Overscan,CameraComponent.PostProcess.Lens
|
||||
K2Node_FunctionEntry=CameraComponent.CameraSettings.Overscan,CameraComponent.PostProcess.Lens
|
||||
|
||||
[/Script/GraphEditor.GraphEditorSettings]
|
||||
DataPinStyle=BPST_VariantA
|
||||
@ -684,6 +686,8 @@ InputTriggerTimedBase="\"Object\" "
|
||||
InputTriggerHoldAndRelease="\"Object\" "
|
||||
InputTriggerTap="\"Object\" "
|
||||
InputTriggerPulse="\"Object\" "
|
||||
EdGraphNode_Comment="\"Object\" "
|
||||
EdGraphNode="\"Object\" "
|
||||
|
||||
[AssetEditorSubsystem]
|
||||
CleanShutdown=False
|
||||
@ -1588,6 +1592,14 @@ EdGraph.Graph=True
|
||||
EdGraph.Inputs=True
|
||||
EdGraph.Outputs=True
|
||||
K2Node_CallFunction.Graph=True
|
||||
K2Node_FunctionEntry.GraphNodeDetail=True
|
||||
K2Node_FunctionEntry.Graph=True
|
||||
K2Node_FunctionEntry.Inputs=True
|
||||
K2Node_FunctionEntry.Outputs=True
|
||||
K2Node_CallFunction.Inputs=True
|
||||
K2Node_CallFunction.Outputs=True
|
||||
EdGraphNode_Comment.GraphNodeDetail=True
|
||||
EdGraphNode_Comment.Comment=True
|
||||
|
||||
[DetailMultiObjectNodeExpansion]
|
||||
GeneralProjectSettings=True
|
||||
@ -1661,6 +1673,7 @@ PreserveScaleRatio=True
|
||||
|
||||
[DetailCategoriesAdvanced]
|
||||
InputAction.Action=False
|
||||
EdGraph.Graph=True
|
||||
|
||||
[AssetEditorToolkitTabLocation]
|
||||
/Game/Input/IA_Move.IA_Move=1
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
102
games/unreal/tutorial/README.md
Normal file
102
games/unreal/tutorial/README.md
Normal file
@ -0,0 +1,102 @@
|
||||
# Unreal Engine Bullet Hell Game - Complete Tutorial
|
||||
|
||||
This tutorial recreates the Unity "magisterka_1" bullet-hell shooter in Unreal Engine 5.
|
||||
|
||||
## Game Features
|
||||
|
||||
- Player ship with movement and shooting
|
||||
- Enemies that move downward with sinusoidal horizontal motion
|
||||
- Enemy spawning that increases over time
|
||||
- Radial bullet patterns from enemies
|
||||
- Score, lives, and timer UI
|
||||
- Special "bomb" ability to clear screen
|
||||
- 5-minute game duration with victory/defeat conditions
|
||||
|
||||
---
|
||||
|
||||
## 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)
|
||||
- [Step 2.4: Set Up Enhanced Input System](part-2-create-player.md#step-24-set-up-enhanced-input-system)
|
||||
- [Step 2.5: Create Player Firing Logic](part-2-create-player.md#step-25-create-player-firing-logic)
|
||||
- [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)
|
||||
- [Step 4.4: Enemy Firing Logic](part-4-create-enemy.md#step-44-enemy-firing-logic)
|
||||
- [Step 4.5: Enemy Damage and Death](part-4-create-enemy.md#step-45-enemy-damage-and-death)
|
||||
- [Step 4.6: Complete Special Ability in BP_Player](part-4-create-enemy.md#step-46-complete-special-ability-in-bp_player)
|
||||
|
||||
### 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)
|
||||
- [Step 7.4: Score Manager Initialization](part-7-score-manager-ui.md#step-74-score-manager-initialization)
|
||||
- [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)
|
||||
- [Step 8.4: Set Up Level Components](part-8-game-mode-level.md#step-84-set-up-level-components)
|
||||
- [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)
|
||||
- [Step 9.4: Test the Game](part-9-final-setup.md#step-94-test-the-game)
|
||||
- [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)
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. Start with [Part 1: Project Setup](part-1-project-setup.md)
|
||||
2. Follow each part in order
|
||||
3. Test frequently using the "EXPECTED RESULT" sections
|
||||
4. Use the [Troubleshooting](appendix-b-troubleshooting.md) appendix if you encounter issues
|
||||
|
||||
---
|
||||
|
||||
## 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.*
|
||||
99
games/unreal/tutorial/appendix-a-variables.md
Normal file
99
games/unreal/tutorial/appendix-a-variables.md
Normal file
@ -0,0 +1,99 @@
|
||||
# Appendix A: Complete Variable Reference
|
||||
|
||||
[← Part 9: Final Setup](part-9-final-setup.md) | [Back to Index](README.md) | [Appendix B: Troubleshooting →](appendix-b-troubleshooting.md)
|
||||
|
||||
---
|
||||
|
||||
## 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 |
|
||||
|
||||
---
|
||||
|
||||
## 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 |
|
||||
|
||||
---
|
||||
|
||||
## 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 |
|
||||
|
||||
---
|
||||
|
||||
## 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 |
|
||||
|
||||
---
|
||||
|
||||
## BP_GameDirector Variables
|
||||
|
||||
| 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 |
|
||||
|
||||
---
|
||||
|
||||
## 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 |
|
||||
|
||||
---
|
||||
|
||||
[← Part 9: Final Setup](part-9-final-setup.md) | [Back to Index](README.md) | [Appendix B: Troubleshooting →](appendix-b-troubleshooting.md)
|
||||
171
games/unreal/tutorial/appendix-b-troubleshooting.md
Normal file
171
games/unreal/tutorial/appendix-b-troubleshooting.md
Normal file
@ -0,0 +1,171 @@
|
||||
# Appendix B: Troubleshooting
|
||||
|
||||
[← Appendix A: Variables](appendix-a-variables.md) | [Back to Index](README.md) | [Appendix C: Unity Conversion →](appendix-c-unity-conversion.md)
|
||||
|
||||
---
|
||||
|
||||
## Player doesn't move
|
||||
|
||||
<details>
|
||||
<summary><b>Click to expand solutions</b></summary>
|
||||
|
||||
1. Verify IMC_Default Input Mapping Context is added in BeginPlay
|
||||
2. Check that IA_Move Input Action exists and has correct key bindings
|
||||
3. Ensure "Possess" is called on player pawn (automatic with GameMode)
|
||||
4. Verify MoveSpeed > 0
|
||||
5. Check if blueprint has errors (Compile button should be green checkmark)
|
||||
6. Make sure Enhanced Input plugin is enabled (Edit → Plugins → Enhanced Input)
|
||||
7. **CRITICAL:** Verify Project Settings → Input → Default Classes are set to Enhanced Input
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## Bullets don't spawn
|
||||
|
||||
<details>
|
||||
<summary><b>Click to expand solutions</b></summary>
|
||||
|
||||
1. Check BulletClass variable is set to BP_Bullet
|
||||
2. Verify SpawnActor node has valid class
|
||||
3. Check that fire logic is connected to execution flow
|
||||
4. Ensure the FireVolley function is connected to the fire rate limiter
|
||||
5. Verify BP_Bullet blueprint compiles without errors
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## No collisions detected
|
||||
|
||||
<details>
|
||||
<summary><b>Click to expand solutions</b></summary>
|
||||
|
||||
1. Verify collision components have "Generate Overlap Events" checked
|
||||
2. Check collision channels are set correctly
|
||||
3. Ensure "Collision Enabled" is set to "Query Only" or "Query and Physics"
|
||||
4. Verify both actors have collision components (not just one)
|
||||
5. Check that the Cast To nodes succeed (add print statements to debug)
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## UI doesn't appear
|
||||
|
||||
<details>
|
||||
<summary><b>Click to expand solutions</b></summary>
|
||||
|
||||
1. Check HUD widget is created and added to viewport
|
||||
2. Verify ScoreManager is in the level
|
||||
3. Check widget blueprint compiles without errors
|
||||
4. Ensure "Add to Viewport" node is connected in execution flow
|
||||
5. Verify text color is visible against background (try white text)
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## Enemies don't spawn
|
||||
|
||||
<details>
|
||||
<summary><b>Click to expand solutions</b></summary>
|
||||
|
||||
1. Check EnemyClass is set in Spawner
|
||||
2. Verify SpawningActive is true
|
||||
3. Check spawn position is within camera view
|
||||
4. Ensure BP_EnemySpawner is placed in the level
|
||||
5. Verify the SpawnCurve asset is assigned
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## Game runs too fast/slow
|
||||
|
||||
<details>
|
||||
<summary><b>Click to expand solutions</b></summary>
|
||||
|
||||
1. All movement should multiply by DeltaSeconds
|
||||
2. Check speed values (may need adjustment for Unreal scale)
|
||||
3. Unreal uses centimeters; multiply Unity values by ~100
|
||||
4. Verify no duplicate Event Tick processing
|
||||
5. Check frame rate (use `stat fps` console command)
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## Input works in editor but not in packaged build
|
||||
|
||||
<details>
|
||||
<summary><b>Click to expand solutions</b></summary>
|
||||
|
||||
1. Ensure all Input Actions and IMC_Default are saved
|
||||
2. Check that assets are not in a "Developer" folder (excluded from builds)
|
||||
3. Verify Project Settings input classes are set correctly
|
||||
4. Try clearing Saved and Intermediate folders, then rebuild
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## Blueprint won't compile
|
||||
|
||||
<details>
|
||||
<summary><b>Click to expand solutions</b></summary>
|
||||
|
||||
1. Look at the Compiler Results panel for specific errors
|
||||
2. Check for circular references between blueprints
|
||||
3. Ensure all required inputs are connected
|
||||
4. Verify variable types match (no type mismatches)
|
||||
5. Look for "Access None" errors (null references)
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## Camera doesn't show the game correctly
|
||||
|
||||
<details>
|
||||
<summary><b>Click to expand solutions</b></summary>
|
||||
|
||||
1. Verify camera position: `X=0, Y=0, Z=1000`
|
||||
2. Verify camera rotation: `X=-90, Y=0, Z=0` (looking down)
|
||||
3. Check "Auto Activate for Player" is enabled
|
||||
4. For orthographic: Set Projection Mode and Ortho Width
|
||||
5. Ensure no other cameras are taking priority
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
|
||||
---
|
||||
|
||||
[← Appendix A: Variables](appendix-a-variables.md) | [Back to Index](README.md) | [Appendix C: Unity Conversion →](appendix-c-unity-conversion.md)
|
||||
259
games/unreal/tutorial/appendix-c-unity-conversion.md
Normal file
259
games/unreal/tutorial/appendix-c-unity-conversion.md
Normal file
@ -0,0 +1,259 @@
|
||||
# Appendix C: Unity to Unreal Conversion Notes
|
||||
|
||||
[← Appendix B: Troubleshooting](appendix-b-troubleshooting.md) | [Back to Index](README.md)
|
||||
|
||||
---
|
||||
|
||||
## Scale Conversion
|
||||
|
||||
| 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)`
|
||||
|
||||
---
|
||||
|
||||
## Coordinate System
|
||||
|
||||
| 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
|
||||
|
||||
---
|
||||
|
||||
## Common Operations Comparison
|
||||
|
||||
### 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
|
||||
4. Use "Get Action Value" node to read input
|
||||
|
||||
---
|
||||
|
||||
### Instantiate / Spawn
|
||||
|
||||
**Unity:**
|
||||
```csharp
|
||||
Instantiate(prefab, position, rotation);
|
||||
```
|
||||
|
||||
**Unreal:**
|
||||
```
|
||||
Spawn Actor from Class
|
||||
├── Class: YourBlueprintClass
|
||||
├── Spawn Transform Location: position
|
||||
└── Spawn Transform Rotation: rotation
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Destroy
|
||||
|
||||
**Unity:**
|
||||
```csharp
|
||||
Destroy(gameObject);
|
||||
```
|
||||
|
||||
**Unreal:**
|
||||
```
|
||||
Destroy Actor
|
||||
└── Target: Self (or reference)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Delta Time
|
||||
|
||||
**Unity:**
|
||||
```csharp
|
||||
Time.deltaTime
|
||||
```
|
||||
|
||||
**Unreal:**
|
||||
```
|
||||
Get World Delta Seconds
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Find Objects
|
||||
|
||||
**Unity:**
|
||||
```csharp
|
||||
FindObjectOfType<PlayerController>();
|
||||
FindObjectsOfType<Enemy>();
|
||||
```
|
||||
|
||||
**Unreal:**
|
||||
```
|
||||
Get All Actors of Class
|
||||
├── Actor Class: BP_Player
|
||||
└── Out Actors: (array)
|
||||
|
||||
Then: Get (a ref) → index 0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Singleton Pattern
|
||||
|
||||
**Unity:**
|
||||
```csharp
|
||||
public static GameManager Instance { get; private set; }
|
||||
|
||||
void Awake() {
|
||||
Instance = this;
|
||||
}
|
||||
```
|
||||
|
||||
**Unreal Options:**
|
||||
1. **Game Instance** - Persists across levels
|
||||
2. **Subsystem** - Engine-managed singleton
|
||||
3. **Get All Actors of Class** - Find at runtime
|
||||
|
||||
---
|
||||
|
||||
### Coroutines vs Timers
|
||||
|
||||
**Unity:**
|
||||
```csharp
|
||||
StartCoroutine(DelayedAction());
|
||||
|
||||
IEnumerator DelayedAction() {
|
||||
yield return new WaitForSeconds(2.0f);
|
||||
DoSomething();
|
||||
}
|
||||
```
|
||||
|
||||
**Unreal:**
|
||||
```
|
||||
Set Timer by Function Name
|
||||
├── Function Name: "DelayedAction"
|
||||
├── Time: 2.0
|
||||
└── Looping: false
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
---
|
||||
|
||||
### Tags
|
||||
|
||||
**Unity:**
|
||||
```csharp
|
||||
if (other.CompareTag("Enemy")) { }
|
||||
```
|
||||
|
||||
**Unreal:**
|
||||
```
|
||||
Actor Has Tag
|
||||
├── Target: Other Actor
|
||||
└── Tag: "Enemy"
|
||||
```
|
||||
|
||||
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 |
|
||||
| Cross Product | `Vector3.Cross(a, b)` | `Cross Product` node |
|
||||
|
||||
---
|
||||
|
||||
### Random
|
||||
|
||||
**Unity:**
|
||||
```csharp
|
||||
Random.Range(0f, 1f);
|
||||
Random.Range(0, 10); // int, exclusive max
|
||||
```
|
||||
|
||||
**Unreal:**
|
||||
```
|
||||
Random Float in Range
|
||||
├── Min: 0.0
|
||||
└── Max: 1.0
|
||||
|
||||
Random Integer in Range
|
||||
├── Min: 0
|
||||
└── Max: 9 // inclusive!
|
||||
```
|
||||
|
||||
> ⚠️ **Note:** Unreal's integer range is INCLUSIVE on both ends!
|
||||
|
||||
---
|
||||
|
||||
### Debug
|
||||
|
||||
**Unity:**
|
||||
```csharp
|
||||
Debug.Log("Message");
|
||||
Debug.DrawLine(start, end, Color.red);
|
||||
```
|
||||
|
||||
**Unreal:**
|
||||
```
|
||||
Print String
|
||||
└── In String: "Message"
|
||||
|
||||
Draw Debug Line
|
||||
├── Line Start: start
|
||||
├── Line End: end
|
||||
└── Line Color: Red
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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) |
|
||||
|
||||
---
|
||||
|
||||
[← Appendix B: Troubleshooting](appendix-b-troubleshooting.md) | [Back to Index](README.md)
|
||||
97
games/unreal/tutorial/part-1-project-setup.md
Normal file
97
games/unreal/tutorial/part-1-project-setup.md
Normal file
@ -0,0 +1,97 @@
|
||||
# Part 1: Project Setup
|
||||
|
||||
[← Back to Index](README.md) | [Next: Part 2 - Create the Player →](part-2-create-player.md)
|
||||
|
||||
---
|
||||
|
||||
## Step 1.1: Create New Project
|
||||
|
||||
1. Open Epic Games Launcher
|
||||
2. Click "Unreal Engine" tab on the left sidebar
|
||||
3. Click yellow "Launch" button next to your UE5 version
|
||||
4. Wait for Unreal Engine to open (this may take 1-2 minutes)
|
||||
|
||||
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
|
||||
- **Quality Preset:** Maximum
|
||||
- **Starter Content:** UNCHECKED (we don't need it)
|
||||
- **Raytracing:** UNCHECKED
|
||||
|
||||
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
|
||||
|
||||
---
|
||||
|
||||
## Step 1.2: Set Up 2D Game View
|
||||
|
||||
Since bullet-hell games are typically 2D, we'll set up a top-down orthographic view.
|
||||
|
||||
1. In the main viewport, look at the top-left corner
|
||||
2. Click the dropdown that says "Perspective"
|
||||
3. Select "Top" from the dropdown menu (`Alt + J`)
|
||||
|
||||
4. To set up proper camera for the game:
|
||||
- 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
|
||||
|
||||
6. Close Project Settings window (X in top right)
|
||||
|
||||
---
|
||||
|
||||
## Step 1.3: Create Folder Structure
|
||||
|
||||
1. Look at the Content Browser at the bottom of the screen
|
||||
2. You should see "Content" folder on the left panel
|
||||
|
||||
3. Right-click on "Content" folder → **New Folder**
|
||||
- Name it: `Blueprints`
|
||||
|
||||
4. Right-click on "Content" folder → **New Folder**
|
||||
- Name it: `Materials`
|
||||
|
||||
5. Right-click on "Content" folder → **New Folder**
|
||||
- Name it: `Sprites` (for 2D textures)
|
||||
|
||||
6. Right-click on "Content" folder → **New Folder**
|
||||
- Name it: `UI`
|
||||
|
||||
### Expected Result
|
||||
|
||||
Content Browser shows 4 folders:
|
||||
|
||||
```
|
||||
Content/
|
||||
├── Blueprints/
|
||||
├── Materials/
|
||||
├── Sprites/
|
||||
└── UI/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
[← Back to Index](README.md) | [Next: Part 2 - Create the Player →](part-2-create-player.md)
|
||||
946
games/unreal/tutorial/part-2-create-player.md
Normal file
946
games/unreal/tutorial/part-2-create-player.md
Normal file
@ -0,0 +1,946 @@
|
||||
# Part 2: Create the Player
|
||||
|
||||
[← Previous: Part 1 - Project Setup](part-1-project-setup.md) | [Back to Index](README.md) | [Next: Part 3 - Create the Bullet →](part-3-create-bullet.md)
|
||||
|
||||
---
|
||||
|
||||
## Step 2.1: Create Player Blueprint
|
||||
|
||||
1. In Content Browser, double-click "Blueprints" folder to open it
|
||||
|
||||
2. Right-click in empty space → **Blueprint Class**
|
||||
|
||||
3. In the popup window "Pick Parent Class":
|
||||
- Click "Pawn" (NOT Character - we want simple 2D control)
|
||||
- Click "Select"
|
||||
|
||||
4. Name the new Blueprint: `BP_Player`
|
||||
|
||||
5. Double-click "BP_Player" to open the Blueprint Editor
|
||||
|
||||
### Expected Result
|
||||
|
||||
A new tab opens showing the Blueprint Editor with:
|
||||
- Components panel on the left
|
||||
- Viewport in the center
|
||||
- Details panel on the right
|
||||
- Tab bar above the Viewport with: Viewport | Construction Script | Event Graph (third from left)
|
||||
|
||||
---
|
||||
|
||||
## Step 2.2: Add Player Visual Components
|
||||
|
||||
1. In the Components panel (left side), you'll see "DefaultSceneRoot"
|
||||
- Click on "DefaultSceneRoot" to select it (this ensures new components are added as children)
|
||||
|
||||
2. With DefaultSceneRoot selected, click **"Add"** button (green, top of Components panel)
|
||||
- Search for "Paper Sprite"
|
||||
- Click "Paper Sprite" to add it (it will be added as a child of DefaultSceneRoot)
|
||||
- Rename it to `PlayerSprite` (click on it, then press F2)
|
||||
|
||||
> **NOTE:** If Paper Sprite is not available:
|
||||
> - Go to Edit → Plugins (from main menu bar)
|
||||
> - Search for "Paper2D"
|
||||
> - Make sure "Paper2D" plugin is ENABLED
|
||||
> - Restart the editor if prompted
|
||||
|
||||
3. With "PlayerSprite" selected, look at Details panel (right side):
|
||||
- Find "Source Sprite" property
|
||||
- For now, leave it empty (we'll create sprites later)
|
||||
- Under "Transform", set Scale to `(1.0, 1.0, 1.0)`
|
||||
|
||||
4. With DefaultSceneRoot still selected, click **"Add"** button again:
|
||||
- Search for "Box Collision"
|
||||
- Click "Box Collision" to add it (it will be added as a child of DefaultSceneRoot)
|
||||
- Rename it to `PlayerCollision`
|
||||
|
||||
5. With "PlayerCollision" selected, in Details panel:
|
||||
- Under "Shape", set Box Extent to: `X=25, Y=25, Z=10`
|
||||
- Under "Collision", click "Collision Presets" dropdown
|
||||
- Select "Custom..."
|
||||
- Set "Collision Enabled" to "Query Only (No Physics Collision)"
|
||||
- Enable "Generate Overlap Events" box
|
||||
|
||||
6. With DefaultSceneRoot still selected, click **"Add"** button:
|
||||
- Search for "Arrow"
|
||||
- Click "Arrow" to add it (it will be added as a child of DefaultSceneRoot)
|
||||
- This shows which direction is "forward" (useful for debugging)
|
||||
|
||||
7. With DefaultSceneRoot still selected, click **"Add"** button:
|
||||
- Search for "Cube" → add "Cube" (Static Mesh)
|
||||
- Rename it to `TempVisual`
|
||||
- In Details panel, set Scale to `(0.5, 0.5, 0.1)` so it's small and flat
|
||||
- This provides a visible placeholder until we add proper sprites in [Part 9](part-9-final-setup.md)
|
||||
|
||||
8. With DefaultSceneRoot still selected, click **"Add"** button:
|
||||
- Search for "Camera" → add "Camera"
|
||||
- Rename it to `PlayerCamera`
|
||||
- In Details panel, set Location to: `X=0, Y=0, Z=1000` (1000 units above player)
|
||||
- Set Rotation to: `X=0, Y=-90, Z=0` (looking straight down)
|
||||
- In Details panel, find "Activation" category → "Auto Activate" checkbox
|
||||
- This should already be ENABLED by default
|
||||
- If not, check/enable it - this tells Unreal to use THIS camera when playing
|
||||
- This camera will follow the player, making testing easy
|
||||
|
||||
> **NOTE:** This temporary camera ensures you can see the player during testing.
|
||||
> In [Part 8](part-8-game-mode-level.md), we'll set up a proper fixed camera for the final game.
|
||||
|
||||
### Expected Result
|
||||
|
||||
Components panel shows:
|
||||
|
||||
```
|
||||
DefaultSceneRoot
|
||||
├── PlayerSprite (Paper Sprite)
|
||||
├── PlayerCollision (Box Collision)
|
||||
├── Arrow
|
||||
├── TempVisual (Cube)
|
||||
└── PlayerCamera (Camera)
|
||||
```
|
||||
|
||||
9. **IMPORTANT - Enable Auto Possession for testing:**
|
||||
- Click on "BP_Player (Self)" at the top of the Components panel (the root)
|
||||
- In Details panel, find "Pawn" category
|
||||
- Find "Auto Possess Player" dropdown (currently set to "Disabled")
|
||||
- Change it to **"Player 0"**
|
||||
- This makes the engine automatically possess this pawn when playing, which activates the PlayerCamera
|
||||
|
||||
> **NOTE:** This setting will be overridden later when we set up the GameMode
|
||||
> in [Part 8](part-8-game-mode-level.md), which handles possession automatically.
|
||||
|
||||
---
|
||||
|
||||
## Step 2.3: Create Player Variables
|
||||
|
||||
*(Continue in the same BP_Player Blueprint Editor tab that was opened in Step 2.1)*
|
||||
|
||||
1. In the Blueprint Editor, look at the left panel (below the Components panel)
|
||||
2. Find "My Blueprint" section
|
||||
3. Under "Variables", click the **"+"** button
|
||||
|
||||
4. Create these variables one by one (click +, then set properties in Details):
|
||||
|
||||
> **NOTE:** Variables are NOT added to the Components hierarchy. They appear in a
|
||||
> separate "Variables" list within the "My Blueprint" panel. Variables store
|
||||
> data values, while Components are physical/visual parts of the actor.
|
||||
|
||||
| # | Variable Name | Type | Default Value |
|
||||
|---|---------------|------|---------------|
|
||||
| 1 | `MoveSpeed` | Float | 750.0 |
|
||||
| 2 | `BoundsMin` | Vector 2D | X=-850, Y=-450 |
|
||||
| 3 | `BoundsMax` | Vector 2D | X=850, Y=450 |
|
||||
| 4 | `FireInterval` | Float | 0.08 |
|
||||
| 5 | `FireTimer` | Float | 0.0 |
|
||||
| 6 | `BulletSpeed` | Float | 2200.0 |
|
||||
| 7 | `MaxLives` | Integer | 3 |
|
||||
| 8 | `CurrentLives` | Integer | 3 |
|
||||
| 9 | `VolleySize` | Integer | 3 |
|
||||
| 10 | `VolleySpread` | Float | 12.0 |
|
||||
| 11 | `SpecialUsed` | Boolean | false (unchecked) |
|
||||
| 12 | `BulletClass` | Class Reference* | (set later) |
|
||||
|
||||
> *For BulletClass: In the type dropdown, go to "Object Types" category → "Actor" → select "Class Reference" (the subcategory under Actor). This will hold reference to bullet blueprint class for spawning.
|
||||
|
||||
> **TIP:** For Variable 1 (MoveSpeed), click the "eye" icon to make it public/editable. Compile the blueprint (Click on "Compile" third option from left or simply `Ctrl + Alt`) before setting the default value.
|
||||
|
||||
5. Click **"Compile"** button (top left, blue checkmark icon)
|
||||
6. Click **"Save"** button (floppy disk icon next to Compile)
|
||||
|
||||
### Expected Result
|
||||
|
||||
My Blueprint panel shows:
|
||||
- All 12 variables you created listed under "Variables"
|
||||
- A "Components" category with 4 component references (DefaultSceneRoot, PlayerSprite, PlayerCollision, Arrow) - these are automatically created from the components you added in Step 2.2
|
||||
|
||||
---
|
||||
|
||||
## Step 2.4: Set Up Enhanced Input System
|
||||
|
||||
UE5 uses the Enhanced Input System. Before creating movement logic in our Blueprint, we first need to create Input Actions and an Input Mapping Context.
|
||||
|
||||
### 0. ENABLE ENHANCED INPUT IN PROJECT SETTINGS (CRITICAL)
|
||||
|
||||
Before creating any input assets, you **MUST** configure the project to use Enhanced Input as the default input system:
|
||||
|
||||
1. Go to **Edit → Project Settings**
|
||||
2. In the left sidebar, scroll down to "Engine" section
|
||||
3. Click on "Input"
|
||||
4. Find "Default Classes" section (near the top)
|
||||
5. Set "Default Player Input Class" to: `EnhancedPlayerInput`
|
||||
6. Set "Default Input Component Class" to: `EnhancedInputComponent`
|
||||
7. Close Project Settings
|
||||
|
||||
> ⚠️ **WARNING:** If you skip this step, WASD controls will NOT work even if everything else is set up correctly!
|
||||
|
||||
### 1. SET UP INPUT ASSETS (in Content Drawer)
|
||||
|
||||
#### A) CREATE INPUT ACTIONS
|
||||
|
||||
1. Open Content Drawer (`Ctrl+Space` or click "Content Drawer" at bottom)
|
||||
2. In Content folder, right-click → **New Folder** → name it `Input`
|
||||
|
||||
3. Inside Input folder, right-click → **Input → Input Action**
|
||||
- Name it `IA_Move`
|
||||
- Double-click to open
|
||||
- Set "Value Type" to `Axis2D (Vector2D)`
|
||||
- Save and close
|
||||
|
||||
4. Create another Input Action: `IA_Fire`
|
||||
- Set "Value Type" to `Digital (bool)` (default)
|
||||
|
||||
5. Create another Input Action: `IA_Special`
|
||||
- Set "Value Type" to `Digital (bool)`
|
||||
|
||||
#### B) CREATE INPUT MAPPING CONTEXT
|
||||
|
||||
1. In Input folder, right-click → **Input → Input Mapping Context**
|
||||
- Name it `IMC_Default`
|
||||
- Double-click to open
|
||||
|
||||
2. Click **"+"** next to "Mappings" to add IA_Move:
|
||||
- Select "IA_Move" from dropdown
|
||||
- Click "+" under IA_Move to add key bindings:
|
||||
|
||||
> **NOTE:** In Unreal's top-down view, X axis = up/down, Y axis = left/right
|
||||
|
||||
**For W key (move UP on screen):**
|
||||
- Click "+", select "W"
|
||||
- No modifiers needed (outputs X positive = up)
|
||||
- TOTAL MODIFIERS FOR W: **0**
|
||||
|
||||
**For S key (move DOWN on screen):**
|
||||
- Click "+", select "S"
|
||||
- Click "+" next to "Modifiers" → Add "Negate"
|
||||
- TOTAL MODIFIERS FOR S: **1** (Negate only)
|
||||
|
||||
**For A key (move LEFT on screen):**
|
||||
- Click "+", select "A"
|
||||
- Click "+" next to "Modifiers" → Add "Swizzle Input Axis Values" → Order: "YXZ"
|
||||
- Click "+" next to "Modifiers" AGAIN → Add "Negate"
|
||||
- TOTAL MODIFIERS FOR A: **2** (Swizzle AND Negate)
|
||||
|
||||
**For D key (move RIGHT on screen):**
|
||||
- Click "+", select "D"
|
||||
- Click "+" next to "Modifiers" → Add "Swizzle Input Axis Values" → Order: "YXZ"
|
||||
- TOTAL MODIFIERS FOR D: **1** (Swizzle only)
|
||||
|
||||
**Summary Table - verify your IMC_Default matches this:**
|
||||
|
||||
| Key | Modifiers | Output Vector |
|
||||
|-----|-----------|---------------|
|
||||
| W | (none) | (1, 0) = UP |
|
||||
| S | Negate | (-1, 0) = DOWN |
|
||||
| A | Swizzle Input Axis Values (YXZ) + Negate | (0, -1) = LEFT |
|
||||
| D | Swizzle Input Axis Values (YXZ) | (0, 1) = RIGHT |
|
||||
|
||||
After adding all keys, press `Ctrl+S` to SAVE IMC_Default!
|
||||
|
||||
3. Click "+" to add **IA_Fire**:
|
||||
- Select "IA_Fire"
|
||||
- Add key: `Z`
|
||||
- Add key: `Left Mouse Button`
|
||||
|
||||
4. Click "+" to add **IA_Special**:
|
||||
- Select "IA_Special"
|
||||
- Add key: `X`
|
||||
- Add key: `Right Mouse Button`
|
||||
|
||||
5. Save the Input Mapping Context
|
||||
|
||||
### 2. NOW OPEN BP_PLAYER BLUEPRINT
|
||||
|
||||
1. In Content Drawer, navigate to Content → Blueprints
|
||||
2. Double-click "BP_Player" to open the Blueprint Editor
|
||||
3. Click on **"Event Graph"** tab (above the main view, third tab from left, next to Construction Script)
|
||||
|
||||
You should see three default events (red nodes):
|
||||
- Event BeginPlay
|
||||
- Event ActorBeginOverlap
|
||||
- Event Tick
|
||||
|
||||
### 3. ADD MAPPING CONTEXT IN BEGINPLAY
|
||||
|
||||
From "Event BeginPlay" node:
|
||||
|
||||
1. Right-click in empty space → search `Get Player Controller` → add it
|
||||
- Use the one under "Game" → "Player" category
|
||||
- It has a "Player Index" input (default 0) and "Return Value" output
|
||||
2. From the blue "Return Value" output, drag and search `Get Enhanced Input Local Player Subsystem`
|
||||
3. From that blue output, drag and search `Add Mapping Context`
|
||||
4. For "Mapping Context" input:
|
||||
- Click dropdown and select `IMC_Default`
|
||||
- Or drag IMC_Default from Content Drawer
|
||||
5. Set "Priority" to `0`
|
||||
|
||||
6. **CRITICAL - Connect the EXECUTION wire (white wire):**
|
||||
- The WHITE TRIANGLE pins are different from the BLUE CIRCLE pins!
|
||||
- Blue circles = DATA (what values to use)
|
||||
- White triangles = EXECUTION (when to run)
|
||||
|
||||
- On "Event BeginPlay", find the WHITE TRIANGLE on the RIGHT side
|
||||
- On "Add Mapping Context", find the WHITE TRIANGLE on the LEFT side
|
||||
- Click and DRAG from BeginPlay's white triangle to Add Mapping Context's white triangle
|
||||
|
||||
**WITHOUT THIS WHITE WIRE, THE CODE NEVER RUNS!**
|
||||
|
||||
Your graph should look like this:
|
||||
|
||||
```
|
||||
┌──────────────────┐ ┌─────────────────────────────────────┐
|
||||
│ Event BeginPlay │ │ Add Mapping Context │
|
||||
│ ├────────►│► │
|
||||
│ │ WHITE │ Target ○────────────────┐ │
|
||||
└──────────────────┘ WIRE │ Mapping Context: IMC_Default │
|
||||
│ Priority: 0 │
|
||||
└─────────────────────────────────────┘
|
||||
▲
|
||||
┌──────────────────┐ │
|
||||
│Get Player │ ┌─────────────────────────┐
|
||||
│Controller ├───►│ Enhanced Input Local │
|
||||
│ Player Index [0] │ │ Player Subsystem ├───┘
|
||||
└──────────────────┘ └─────────────────────────┘
|
||||
(blue data wires)
|
||||
```
|
||||
|
||||
**VERIFY:** You must have BOTH:
|
||||
- Blue data wires connecting the nodes (passes the subsystem reference)
|
||||
- White execution wire from Event BeginPlay to Add Mapping Context (makes it run)
|
||||
|
||||
### 4. INPUT DEBUG TEST (verify input works - separate from movement logic)
|
||||
|
||||
Before creating movement, let's verify input is working with a separate debug setup. This debug logic is COMPLETELY INDEPENDENT from movement - you can delete it later without affecting anything.
|
||||
|
||||
#### a) Create debug variables (these are ONLY for debugging):
|
||||
- In My Blueprint panel → Variables → click "+"
|
||||
- Name: `DEBUG_LastMoveInput`
|
||||
- Type: Vector 2D
|
||||
- Default Value: (0, 0)
|
||||
- Compile to save
|
||||
|
||||
#### b) Create a SEPARATE debug event using a Custom Event:
|
||||
- Right-click in empty space → search `Custom Event` → add it
|
||||
- Name it `DEBUG_PrintInput`
|
||||
- This keeps debug logic completely isolated
|
||||
|
||||
#### c) Build the debug logic from DEBUG_PrintInput:
|
||||
|
||||
1. **Get the current input value:**
|
||||
- Right-click → "Get Player Controller"
|
||||
- Drag → "Get Enhanced Input Local Player Subsystem"
|
||||
- Drag → search "IA_Move" (under Input → Enhanced Action Values)
|
||||
|
||||
2. **Compare to last value (only print on change):**
|
||||
- Right-click → "Not Equal (Vector2D)"
|
||||
- Connect IA_Move to TOP input
|
||||
- "Get DEBUG_LastMoveInput" → connect to BOTTOM input
|
||||
|
||||
3. **Branch on change:**
|
||||
- From DEBUG_PrintInput WHITE pin → "Branch"
|
||||
- Connect != result to Condition
|
||||
|
||||
4. **On TRUE (changed):**
|
||||
- "Set DEBUG_LastMoveInput" = IA_Move value
|
||||
- Then → "Print String" with IA_Move value
|
||||
|
||||
#### d) Call the debug event from Event Tick:
|
||||
- From "Event Tick" WHITE pin → search `DEBUG_PrintInput`
|
||||
- This adds a node that calls your custom event
|
||||
|
||||
**Visual (two separate areas in your graph):**
|
||||
|
||||
```
|
||||
AREA 1 - Event Tick (will later have movement):
|
||||
┌─────────────┐ ┌─────────────────────┐
|
||||
│ Event Tick │─────►│ DEBUG_PrintInput │
|
||||
└─────────────┘ │ (call custom event) │
|
||||
└─────────────────────┘
|
||||
|
||||
AREA 2 - Debug logic (completely separate):
|
||||
┌─────────────────────┐
|
||||
│ DEBUG_PrintInput │
|
||||
│ (Custom Event) │
|
||||
└──────────┬──────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────┐
|
||||
│ Branch │
|
||||
│ Condition: IA_Move != Last │
|
||||
└──────┬──────────────────────┘
|
||||
│ TRUE
|
||||
▼
|
||||
┌──────────────────┐ ┌──────────────┐
|
||||
│Set DEBUG_LastMove│────►│ Print String │
|
||||
└──────────────────┘ └──────────────┘
|
||||
```
|
||||
|
||||
#### e) Compile and Save
|
||||
#### f) Drag BP_Player into level, Press Play (`Alt+P`)
|
||||
#### g) IMPORTANT: Click inside the game viewport to give it keyboard focus!
|
||||
#### h) Press WASD keys and look at top-left of screen
|
||||
|
||||
**Expected:** Message appears ONLY when you press or release a key:
|
||||
- Press W: prints "X=1.0 Y=0.0"
|
||||
- Release W: prints "X=0.0 Y=0.0"
|
||||
- Log is NOT spammed every frame
|
||||
|
||||
**TO DISABLE DEBUG LATER:** Simply delete the "DEBUG_PrintInput" call node from Event Tick. The Custom Event and its logic can stay (unused) or be deleted entirely - movement will be unaffected either way.
|
||||
|
||||
#### Debugging Steps (if you see "X=0.0 Y=0.0" always):
|
||||
|
||||
<details>
|
||||
<summary><b>DEBUGGING STEP 1</b> - Verify the pawn is possessed</summary>
|
||||
|
||||
- When playing, look at the Outliner (right panel)
|
||||
- Find BP_Player in the list
|
||||
- If it shows a small controller icon next to it, it's possessed
|
||||
- If NOT possessed: Check that "Auto Possess Player" = "Player 0" in BP_Player
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>DEBUGGING STEP 2</b> - Verify the game window has keyboard focus</summary>
|
||||
|
||||
- After pressing Play, CLICK INSIDE THE GAME VIEWPORT
|
||||
- The viewport must have focus to receive keyboard input
|
||||
- Try pressing `Shift+F1` to release mouse, then click viewport again
|
||||
- Then press WASD again
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>DEBUGGING STEP 3</b> - Test with a simpler Print String</summary>
|
||||
|
||||
- Delete the current Print String setup
|
||||
- From Event Tick, drag → Print String
|
||||
- Type "Tick is running" in the In String field (just plain text)
|
||||
- Play the game - you should see "Tick is running" spam in top-left
|
||||
- If you DON'T see this: The blueprint isn't running at all
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>DEBUGGING STEP 4</b> - Test BeginPlay is running</summary>
|
||||
|
||||
- From Event BeginPlay, AFTER the Add Mapping Context node
|
||||
- Add another Print String with text "BeginPlay executed"
|
||||
- Connect the white wire: BeginPlay → Add Mapping Context → Print String
|
||||
- Play the game - you should see "BeginPlay executed" once at start
|
||||
- If you DON'T see this: BeginPlay isn't running (pawn not spawned?)
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>DEBUGGING STEP 5</b> - Check if there are MULTIPLE BP_Player in level</summary>
|
||||
|
||||
- Look in Outliner for multiple BP_Player instances
|
||||
- Delete ALL of them
|
||||
- Drag in exactly ONE fresh BP_Player from Content Drawer
|
||||
- Try again
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>DEBUGGING STEP 6</b> - Nuclear option (create fresh test)</summary>
|
||||
|
||||
- Create a NEW blueprint: "BP_InputTest" (Pawn class)
|
||||
- Add ONLY: Auto Possess Player = Player 0
|
||||
- In Event Graph: Event BeginPlay → Add Mapping Context (IMC_Default)
|
||||
- In Event Graph: Event Tick → Print String (connected to Get IA_Move)
|
||||
- Drag this minimal test blueprint into level
|
||||
- If THIS works, something is wrong with your BP_Player
|
||||
</details>
|
||||
|
||||
**IF THIS TEST WORKS:** Continue to step 5. You can optionally delete the DEBUG_PrintInput call from Event Tick to stop the debug prints.
|
||||
**IF THIS TEST FAILS:** Work through debugging steps above.
|
||||
|
||||
### 5. MOVEMENT LOGIC - Create this node network
|
||||
|
||||
> **NOTE:** If you added debug from step 4, Event Tick already has a wire to DEBUG_PrintInput. To have BOTH debug AND movement run from Event Tick, you need a Sequence node (one output pin = one wire only).
|
||||
|
||||
#### a) INSERT A SEQUENCE NODE between Event Tick and debug:
|
||||
- Delete the wire from Event Tick to DEBUG_PrintInput
|
||||
- Right-click → search `Sequence` → add it
|
||||
- Connect Event Tick → Sequence (input)
|
||||
- Connect Sequence "Then 0" → DEBUG_PrintInput
|
||||
- Sequence "Then 1" will be used for movement
|
||||
|
||||
```
|
||||
RESULT:
|
||||
Event Tick → Sequence ─┬─ Then 0 → DEBUG_PrintInput
|
||||
│
|
||||
└─ Then 1 → (movement, next steps)
|
||||
```
|
||||
|
||||
If you skipped debug, just connect Event Tick directly to movement.
|
||||
|
||||
#### b) Starting point for movement:
|
||||
- If using Sequence: drag from "Then 1" output
|
||||
- If no debug: drag from Event Tick directly
|
||||
|
||||
#### c) Right-click → search `Get Player Controller` and add it
|
||||
(This is a NEW Get Player Controller - separate from the one in debug)
|
||||
|
||||
#### d) From Player Controller output, drag → `Get Enhanced Input Local Player Subsystem`
|
||||
|
||||
#### e) From subsystem output, drag and right-click → search `IA_Move`
|
||||
- Look under "Input" → "Enhanced Action Values" → select "IA_Move"
|
||||
- This node returns the current value of the IA_Move input action
|
||||
|
||||
#### f) The output is an Input Action Value (Vector2D since we set IA_Move to Axis2D).
|
||||
Right-click the output pin → "Split Struct Pin" to get X and Y components,
|
||||
OR drag from output and search "To Vector 2D" to convert it
|
||||
|
||||
#### g) Right-click → `Make Vector`
|
||||
- Connect the X from movement input to X
|
||||
- Connect the Y from movement input to Y
|
||||
- Set Z to 0
|
||||
|
||||
#### h) Right-click → `Normalize`
|
||||
- Connect the vector output to Normalize input
|
||||
|
||||
#### i) Right-click → `Get World Delta Seconds`
|
||||
|
||||
#### j) Right-click → `Get MoveSpeed` (your variable)
|
||||
|
||||
#### k) Right-click → Multiply (float * float)
|
||||
- Connect Delta Seconds output to first input
|
||||
- Connect MoveSpeed output to second input
|
||||
|
||||
#### l) Right-click → Multiply (vector * float)
|
||||
- Connect Normalized vector (from step h) to vector input
|
||||
- Connect (DeltaSeconds * MoveSpeed) result (from step k) to float input
|
||||
- This output is the "movement delta" - how far to move this frame
|
||||
|
||||
#### m) Right-click → `Get Actor Location`
|
||||
|
||||
#### n) Right-click → Add (vector + vector)
|
||||
- Connect current location (from step m) to first input
|
||||
- Connect movement delta (from step l) to second input
|
||||
|
||||
#### o) CLAMP X COORDINATE:
|
||||
|
||||
First, break apart the new position vector from step n:
|
||||
- Right-click on the output pin of the Add node (from step n) → "Split Struct Pin"
|
||||
- This splits the vector into three separate pins: X, Y, Z
|
||||
|
||||
Now clamp the X value:
|
||||
- Right-click in empty space → search `Clamp (float)` → add it
|
||||
- Connect the "X" output (from the split Add node) to "Value" input of Clamp
|
||||
|
||||
Get the min bound:
|
||||
- Right-click → search `Get BoundsMin` (your variable) → add it
|
||||
- Right-click on BoundsMin output pin → "Split Struct Pin" (splits into X, Y)
|
||||
- Connect BoundsMin's "X" to Clamp's "Min" input
|
||||
|
||||
Get the max bound:
|
||||
- Right-click → search `Get BoundsMax` (your variable) → add it
|
||||
- Right-click on BoundsMax output pin → "Split Struct Pin"
|
||||
- Connect BoundsMax's "X" to Clamp's "Max" input
|
||||
|
||||
The Clamp node now outputs the X position clamped within bounds.
|
||||
|
||||
#### p) CLAMP Y COORDINATE:
|
||||
- Right-click → add another `Clamp (float)` node
|
||||
- Connect the "Y" output (from the split Add node in step o) to "Value"
|
||||
- Connect BoundsMin's "Y" (already split) to "Min"
|
||||
- Connect BoundsMax's "Y" (already split) to "Max"
|
||||
|
||||
#### q) Right-click → `Make Vector`
|
||||
- Connect clamped X
|
||||
- Connect clamped Y
|
||||
- Set Z to 0
|
||||
|
||||
#### r) Right-click → `Set Actor Location`
|
||||
- Connect the clamped vector to "New Location"
|
||||
|
||||
#### s) CRITICAL - Connect the execution wire (white wire):
|
||||
- If using Sequence (from step a): drag from "Then 1" to Set Actor Location
|
||||
- If no debug: drag from Event Tick to Set Actor Location
|
||||
- Without this execution wire, the movement code NEVER runs!
|
||||
|
||||
**Final Structure (with debug - using Sequence node):**
|
||||
```
|
||||
Event Tick → Sequence ─┬─ Then 0 → DEBUG_PrintInput
|
||||
│
|
||||
└─ Then 1 → [movement] → Set Actor Location
|
||||
```
|
||||
|
||||
**Final Structure (without debug):**
|
||||
```
|
||||
Event Tick → [movement logic] → Set Actor Location
|
||||
```
|
||||
|
||||
**TO REMOVE DEBUG:** Delete the wire from "Then 0" to DEBUG_PrintInput. Movement on "Then 1" is completely unaffected.
|
||||
|
||||
### 6. Click Compile and Save
|
||||
|
||||
### Expected Result after Compile:
|
||||
- Compile button shows GREEN checkmark (no errors)
|
||||
- No warnings about unconnected pins
|
||||
|
||||
### How to Test at This Stage:
|
||||
1. Open any level (or the default "Untitled" level)
|
||||
2. From Content Drawer, drag BP_Player into the viewport
|
||||
3. Press Play (`Alt+P`)
|
||||
4. Click inside the game viewport to give it keyboard focus
|
||||
5. You should see the cube (TempVisual added in Step 2.2) and control it with WASD
|
||||
|
||||
### Expected Result when tested:
|
||||
- Player pawn moves smoothly when pressing WASD keys
|
||||
- Movement is frame-rate independent (consistent speed)
|
||||
- Player cannot move outside the screen bounds (stops at edges)
|
||||
- Releasing keys immediately stops movement (no drift)
|
||||
|
||||
> **NOTE:** Full game testing will be possible after completing [Part 8 (Level Setup)](part-8-game-mode-level.md).
|
||||
|
||||
### Visual Diagram of Movement Nodes:
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌──────────┐
|
||||
│ Event Tick │────►│ Sequence │─┬─ Then 0 ──► DEBUG_PrintInput (optional)
|
||||
└─────────────┘ └──────────┘ │
|
||||
└─ Then 1 ──► Movement logic ──► Set Actor Location
|
||||
|
||||
(If no debug, skip Sequence and connect Event Tick directly to movement)
|
||||
|
||||
┌──────────────────────────────────┐ ┌────────────────────┐
|
||||
│ Get Enhanced Input Subsystem │ │ Set Actor Location │
|
||||
│ → Get Action Value (IA_Move) │ └────────────────────┘
|
||||
│ → Get as Vector2D │ ▲
|
||||
└────────────────┬─────────────────┘ │
|
||||
│ ┌───────────────┐
|
||||
▼ │ Clamped Vector│
|
||||
┌────────────────────────────┐ └───────────────┘
|
||||
│ Make Vector │───►Normalize───► ▲
|
||||
│ X=Input.X, Y=Input.Y, Z=0 │ (direction) │
|
||||
└────────────────────────────┘ │ ┌─────────────┐
|
||||
▼ │ + Location │
|
||||
┌──────────────┐ └─────────────┘
|
||||
│ * MoveSpeed │─────────┘
|
||||
│ * DeltaTime │
|
||||
└──────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2.5: Create Player Firing Logic
|
||||
|
||||
1. Continue in Event Graph, we'll add firing after movement
|
||||
|
||||
2. After the movement logic, add firing check using Enhanced Input:
|
||||
|
||||
#### a) Right-click → search `IA_Fire`
|
||||
- Look under "Input" → "Enhanced Action Events" category (diamond ◇ icons)
|
||||
- Select "IA_Fire" (the one with diamond icon, NOT the square icon)
|
||||
- This creates an EVENT node (red, like Event Tick) that fires when the button is pressed
|
||||
|
||||
> **NOTE:**
|
||||
> - "Enhanced Action Events" (diamond ◇) = triggers when button pressed
|
||||
> - "Enhanced Action Values" (square □) = reads current value continuously
|
||||
>
|
||||
> For firing, we want the EVENT.
|
||||
|
||||
#### b) From the "Triggered" execution pin, build the fire rate limiter:
|
||||
|
||||
1. **Get current timer value:**
|
||||
- Right-click → `Get FireTimer` (your variable)
|
||||
|
||||
2. **Subtract frame time from it:**
|
||||
- Right-click → `Get World Delta Seconds` (time since last frame, ~0.016 at 60fps)
|
||||
- Right-click → search "float - float" or "Subtract" → add Subtract node
|
||||
- Connect FireTimer to the TOP input (A)
|
||||
- Connect Delta Seconds to the BOTTOM input (B)
|
||||
- Result = FireTimer minus DeltaSeconds
|
||||
|
||||
3. **Store the new timer value:**
|
||||
- Right-click → `Set FireTimer`
|
||||
- Connect the Subtract result to it
|
||||
- Connect execution wire: Triggered → Set FireTimer
|
||||
|
||||
4. **Check if we can fire:**
|
||||
- From Set FireTimer, drag execution → `Branch`
|
||||
- Right-click → `<= (float)` (less than or equal)
|
||||
- Connect FireTimer (use the output from Set, or Get it again) to top input
|
||||
- Type `0` in bottom input
|
||||
- Connect result to Branch's Condition
|
||||
|
||||
5. **On TRUE branch (timer expired, can fire):**
|
||||
- Right-click → `Set FireTimer`
|
||||
- Right-click → `Get FireInterval` (your variable)
|
||||
- Connect FireInterval to Set FireTimer (resets the cooldown)
|
||||
- For now, add a Print String with text "FIRE!" to verify it works
|
||||
- Leave the execution wire after this Set FireTimer OPEN (we'll connect FireVolley function here in [Step 3.4](part-3-create-bullet.md#step-34-complete-player-firing-logic-bp_player))
|
||||
|
||||
**Visual:**
|
||||
|
||||
```
|
||||
┌──────────────────┐
|
||||
│ IA_Fire Triggered│
|
||||
└────────┬─────────┘
|
||||
│ (white wire)
|
||||
▼
|
||||
┌─────────────────────────────────────┐
|
||||
│ Set FireTimer │
|
||||
│ = FireTimer - GetWorldDeltaSeconds │
|
||||
└────────┬────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐ TRUE ┌─────────────────────┐
|
||||
│ Branch │───────────►│ Set FireTimer │──► Print "FIRE!"
|
||||
│ FireTimer <= 0 │ │ = FireInterval │ (placeholder)
|
||||
└────────┬────────┘ └─────────────────────┘
|
||||
│ FALSE
|
||||
▼
|
||||
(do nothing)
|
||||
```
|
||||
|
||||
**TEST NOW:** Press Play, hold Z key. You should see "FIRE!" spam in top-left at rapid intervals (every 0.08 seconds = FireInterval).
|
||||
|
||||
> **NOTE:** Since IA_Fire was set up with Z key and Left Mouse Button in the Input Mapping Context, both inputs will trigger this action automatically.
|
||||
|
||||
### 3. PLACEHOLDER FOR BULLET SPAWNING
|
||||
|
||||
The Print String "FIRE!" is a placeholder. We will replace it with actual bullet spawning in [STEP 3.4](part-3-create-bullet.md#step-34-complete-player-firing-logic-bp_player), AFTER creating BP_Bullet in Part 3.
|
||||
|
||||
Leave the execution wire open after the "Set FireTimer = FireInterval" node. We'll connect FireVolley function there in STEP 3.4.
|
||||
|
||||
### 4. Compile and Save
|
||||
|
||||
### Expected Result after Compile:
|
||||
- Compile button shows GREEN checkmark
|
||||
- Fire rate limiter logic works (Print String spams when holding Z)
|
||||
- Actual bullets will be added in [STEP 3.4](part-3-create-bullet.md#step-34-complete-player-firing-logic-bp_player) after BP_Bullet is created
|
||||
|
||||
---
|
||||
|
||||
## Step 2.6: Create Player Damage and Special Ability
|
||||
|
||||
> **IMPORTANT - Dependency Note:**
|
||||
> The `HandleDeath` function needs to call `BP_GameDirector`, which we create in [Part 6](part-6-game-director.md).
|
||||
> In this step, we will create **placeholder functions** that compile but aren't fully functional.
|
||||
> We will complete `HandleDeath` in [Part 6](part-6-game-director.md#step-63-game-director-update-logic) after GameDirector exists.
|
||||
|
||||
---
|
||||
|
||||
### 1. CREATE "HandleDeath" FUNCTION (Placeholder):
|
||||
|
||||
We create this FIRST because `TakeHit` will call it. For now, it's a placeholder.
|
||||
|
||||
#### a) Create the function:
|
||||
1. In My Blueprint panel (left side), find "Functions" section
|
||||
2. Click the **"+"** button next to "Functions"
|
||||
3. Name it `HandleDeath`
|
||||
4. Double-click to open the function graph
|
||||
|
||||
#### b) Add placeholder logic (will be completed in Part 6):
|
||||
|
||||
1. The function graph opens with a purple "HandleDeath" entry node
|
||||
|
||||
2. **Hide the player:**
|
||||
- From the entry node's WHITE execution pin, drag and search `Set Actor Hidden in Game`
|
||||
- Check the "New Hidden" checkbox (set to TRUE)
|
||||
- This makes the player invisible when they die
|
||||
|
||||
3. **Add a placeholder comment:**
|
||||
- Right-click in empty space → search `Add Comment` (or press C)
|
||||
- Type: "TODO: Call GameDirector.HandlePlayerDeath (Part 6)"
|
||||
- Position it near the end of the function
|
||||
|
||||
4. **Connect execution:**
|
||||
```
|
||||
HandleDeath (entry) ──► Set Actor Hidden in Game
|
||||
│
|
||||
▼
|
||||
[TODO comment - no connection needed]
|
||||
```
|
||||
|
||||
5. Click "Compile" - should show GREEN checkmark
|
||||
|
||||
> **NOTE:** This function is incomplete! We'll add the GameDirector call in [Part 6, Step 6.3](part-6-game-director.md#step-63-game-director-update-logic).
|
||||
|
||||
---
|
||||
|
||||
### 2. CREATE "TakeHit" FUNCTION:
|
||||
|
||||
Now we can create TakeHit, which calls HandleDeath.
|
||||
|
||||
#### a) Create the function:
|
||||
1. In My Blueprint panel → Functions → click **"+"**
|
||||
2. Name it `TakeHit`
|
||||
3. Double-click to open the function graph
|
||||
|
||||
#### b) Add input parameter:
|
||||
1. With the function graph open, look at the Details panel (right side)
|
||||
2. Find "Inputs" section
|
||||
3. Click **"+"** to add a new input
|
||||
4. Name: `Damage`
|
||||
5. Type: `Integer`
|
||||
6. The purple entry node now shows a "Damage" output pin
|
||||
|
||||
#### c) Build the function logic:
|
||||
|
||||
**Step 1 - Get current lives:**
|
||||
- Right-click in empty space → search `Get CurrentLives` → add it
|
||||
- This reads the CurrentLives variable value
|
||||
|
||||
**Step 2 - Subtract damage from lives:**
|
||||
- Right-click → search `integer - integer` or `Subtract (int)` → add it
|
||||
- Connect `CurrentLives` (from Step 1) to the TOP input (A)
|
||||
- Connect `Damage` (from the purple entry node) to the BOTTOM input (B)
|
||||
- Result = CurrentLives minus Damage
|
||||
|
||||
**Step 3 - Clamp the result (prevent negative lives):**
|
||||
- Right-click → search `Clamp (int)` → add the "Clamp (integer)" node
|
||||
- Connect the Subtract result (from Step 2) to the "Value" input
|
||||
- Set "Min" to `0` (type directly in the field)
|
||||
- Set "Max" to `99` (or any high number - we just want to prevent negatives)
|
||||
- The output is the clamped value (0 or higher)
|
||||
|
||||
**Step 4 - Store the new lives value:**
|
||||
- Right-click → search `Set CurrentLives` → add it
|
||||
- Connect the Clamp output (from Step 3) to the input
|
||||
- **IMPORTANT:** Connect execution wire from entry node → Set CurrentLives
|
||||
|
||||
**Step 5 - Check if player should die:**
|
||||
- From `Set CurrentLives`, drag execution → search `Branch` → add it
|
||||
- Right-click → search `<= (integer)` (less than or equal) → add it
|
||||
- Connect `CurrentLives` (drag from Set node's output, or add new Get) to TOP input
|
||||
- Type `0` in BOTTOM input
|
||||
- Connect the `<=` result (boolean) to Branch's "Condition" input
|
||||
|
||||
**Step 6 - On TRUE (player is dead):**
|
||||
- From Branch's "True" execution pin, drag → search `HandleDeath` → add it
|
||||
- This calls the function we created in step 1
|
||||
|
||||
**Step 7 - On FALSE (player still alive):**
|
||||
- Leave the "False" pin unconnected (do nothing, player survives)
|
||||
|
||||
#### d) Visual diagram of TakeHit function:
|
||||
|
||||
```
|
||||
┌────────────────────────┐
|
||||
│ TakeHit (entry) │
|
||||
│ Damage ○───────┼──────────────────────┐
|
||||
└───────────┬────────────┘ │
|
||||
│ (white execution wire) │
|
||||
▼ ▼
|
||||
┌───────────────────────────────────────────────────────┐
|
||||
│ Set CurrentLives │
|
||||
│ = Clamp( CurrentLives - Damage, Min=0, Max=99 ) │
|
||||
└───────────┬───────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────┐
|
||||
│ Branch │
|
||||
│ Condition: CurrentLives <= 0│
|
||||
└───────┬─────────────┬───────┘
|
||||
│ TRUE │ FALSE
|
||||
▼ ▼
|
||||
┌───────────────┐ (nothing)
|
||||
│ HandleDeath │
|
||||
└───────────────┘
|
||||
```
|
||||
|
||||
#### e) Click Compile and Save
|
||||
|
||||
---
|
||||
|
||||
### 3. SPECIAL ABILITY (Screen Clear) - Input Setup Only:
|
||||
|
||||
> **IMPORTANT - Dependency Note:**
|
||||
> The special ability needs to destroy BP_Enemy and BP_Bullet actors, which don't exist yet.
|
||||
> In this step, we create ONLY the input handling and mark-as-used logic.
|
||||
> We will complete the destruction logic in [Part 4, Step 4.6](part-4-create-enemy.md#step-46-complete-special-ability-in-bp_player) after BP_Enemy exists.
|
||||
|
||||
Using Enhanced Input (IA_Special was already set up with X and Right Mouse in Step 2.4):
|
||||
|
||||
#### a) Create the event node:
|
||||
1. Go back to the **Event Graph** tab (not inside a function)
|
||||
2. Right-click in empty space → search `EnhancedInputAction IA_Special`
|
||||
- Look under "Input" → "Enhanced Action Events" category
|
||||
- Select "IA_Special" (the EVENT with diamond ◇ icon)
|
||||
3. This creates a red event node that fires when X or Right Mouse is pressed
|
||||
|
||||
#### b) Check if special was already used:
|
||||
1. From the "Triggered" execution pin, drag → search `Branch` → add it
|
||||
2. Right-click → search `Get SpecialUsed` → add it
|
||||
3. Right-click → search `NOT Boolean` → add it
|
||||
4. Connect SpecialUsed → NOT → Branch Condition
|
||||
- We branch on "NOT SpecialUsed" (true = hasn't been used yet)
|
||||
|
||||
#### c) On TRUE (special available) - placeholder:
|
||||
1. From Branch's "True" pin, drag → search `Set SpecialUsed` → add it
|
||||
2. Check the box to set it to TRUE (marks ability as used)
|
||||
|
||||
3. **Add placeholder Print String:**
|
||||
- From Set SpecialUsed, drag → search `Print String` → add it
|
||||
- Type: "SPECIAL ABILITY ACTIVATED!" in the In String field
|
||||
- This verifies the input works; we'll replace it with actual destruction in Part 4
|
||||
|
||||
4. **Add a placeholder comment:**
|
||||
- Right-click in empty space → search `Add Comment` (or press C)
|
||||
- Type: "TODO: Add enemy/bullet destruction (Part 4)"
|
||||
- Position it after the Print String
|
||||
|
||||
#### d) On FALSE (special already used):
|
||||
- Leave unconnected (nothing happens on second press)
|
||||
|
||||
#### e) Visual diagram (placeholder version):
|
||||
|
||||
```
|
||||
┌─────────────────────────────┐
|
||||
│ EnhancedInputAction │
|
||||
│ IA_Special - Triggered │
|
||||
└───────────┬─────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────┐
|
||||
│ Branch │
|
||||
│ Condition: NOT SpecialUsed │
|
||||
└───────┬─────────────┬───────┘
|
||||
│ TRUE │ FALSE
|
||||
▼ ▼
|
||||
┌───────────────┐ (nothing - ability already used)
|
||||
│Set SpecialUsed│
|
||||
│ = true │
|
||||
└───────┬───────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────┐
|
||||
│ Print String │
|
||||
│ "SPECIAL ABILITY ACTIVATED!" │
|
||||
└────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
[TODO comment - destruction logic added in Part 4]
|
||||
```
|
||||
|
||||
#### f) Compile and Save
|
||||
|
||||
#### g) Test the placeholder:
|
||||
1. Press Play
|
||||
2. Press X key (or Right Mouse Button)
|
||||
3. You should see "SPECIAL ABILITY ACTIVATED!" appear once
|
||||
4. Pressing X again should do nothing (SpecialUsed = true)
|
||||
|
||||
> **REMINDER:** Complete the special ability with actual enemy/bullet destruction in [Part 4, Step 4.6](part-4-create-enemy.md#step-46-complete-special-ability-in-bp_player) after creating BP_Enemy.
|
||||
|
||||
---
|
||||
|
||||
### 4. Compile and Save
|
||||
|
||||
### Expected Result after Compile:
|
||||
- Compile button shows GREEN checkmark
|
||||
- "TakeHit" and "HandleDeath" functions appear under Functions in My Blueprint panel
|
||||
- Event Graph shows IA_Special event with branching logic
|
||||
|
||||
### Expected Result in Play mode (at this stage):
|
||||
- Press X key: "SPECIAL ABILITY ACTIVATED!" appears once in top-left
|
||||
- Press X again: Nothing happens (SpecialUsed is now true)
|
||||
- TakeHit and HandleDeath functions exist but cannot be tested yet (nothing calls them)
|
||||
|
||||
> **NOTE:** Full testing of damage/death systems requires:
|
||||
> - BP_Bullet collision (Part 3) to call TakeHit
|
||||
> - BP_GameDirector (Part 6) for HandleDeath to notify
|
||||
> - BP_Enemy (Part 4) for special ability to destroy
|
||||
|
||||
> **REMINDER:** Complete the `HandleDeath` function in [Part 6, Step 6.3](part-6-game-director.md#step-63-game-director-update-logic) after creating BP_GameDirector.
|
||||
|
||||
---
|
||||
|
||||
[← Previous: Part 1 - Project Setup](part-1-project-setup.md) | [Back to Index](README.md) | [Next: Part 3 - Create the Bullet →](part-3-create-bullet.md)
|
||||
458
games/unreal/tutorial/part-3-create-bullet.md
Normal file
458
games/unreal/tutorial/part-3-create-bullet.md
Normal file
@ -0,0 +1,458 @@
|
||||
# Part 3: Create the Bullet
|
||||
|
||||
[← Previous: Part 2 - Create the Player](part-2-create-player.md) | [Back to Index](README.md) | [Next: Part 4 - Create the Enemy →](part-4-create-enemy.md)
|
||||
|
||||
---
|
||||
|
||||
## Step 3.1: Create Bullet Blueprint
|
||||
|
||||
1. In Content Browser → Blueprints folder
|
||||
2. Right-click → **Blueprint Class** → **Actor**
|
||||
3. Name it: `BP_Bullet`
|
||||
4. Double-click to open
|
||||
|
||||
5. **Add Components:**
|
||||
- Paper Sprite → name `BulletSprite`
|
||||
- Sphere Collision → name `BulletCollision`
|
||||
- Radius: `8`
|
||||
- Generate Overlap Events: **CHECKED**
|
||||
- Collision Preset: Custom → Query Only
|
||||
- Cube (Static Mesh) → name `TempVisual`
|
||||
- Scale: `(0.1, 0.1, 0.05)` - small bullet-sized cube
|
||||
- This provides visibility until proper visuals in [Part 9](part-9-final-setup.md)
|
||||
|
||||
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 |
|
||||
|
||||
### 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):
|
||||
- Small cube visible (the TempVisual placeholder)
|
||||
- Sphere collision visible (radius 8)
|
||||
|
||||
---
|
||||
|
||||
## Step 3.2: Bullet Movement Logic
|
||||
|
||||
1. In Event Graph, from **Event Tick:**
|
||||
|
||||
#### a) Calculate movement:
|
||||
- Get TravelDirection
|
||||
- Multiply by TravelSpeed
|
||||
- Multiply by Delta Seconds
|
||||
- Add to current location
|
||||
- Set Actor Location
|
||||
|
||||
#### b) Check lifetime:
|
||||
- Subtract Delta Seconds from RemainingLifetime
|
||||
- If <= 0: Destroy Actor
|
||||
|
||||
### 2. CREATE "Initialize" FUNCTION:
|
||||
|
||||
#### a) Add input parameters:
|
||||
| Parameter | Type | Default |
|
||||
|-----------|------|---------|
|
||||
| `Direction` | Vector | - |
|
||||
| `Speed` | Float | - |
|
||||
| `bIsEnemy` | Boolean | - |
|
||||
| `Lifetime` | Float | - |
|
||||
| `DamageValue` | Integer | 1 |
|
||||
|
||||
#### b) Inside:
|
||||
- Normalize Direction → Set TravelDirection
|
||||
- Set TravelSpeed = Speed
|
||||
- Set IsEnemyProjectile = bIsEnemy
|
||||
- Set RemainingLifetime = Lifetime
|
||||
- Set Damage = DamageValue
|
||||
|
||||
### 3. Compile and Save
|
||||
|
||||
### Expected Result after Compile:
|
||||
- Compile button shows GREEN checkmark
|
||||
- "Initialize" function appears under Functions with 5 input parameters
|
||||
|
||||
### Expected Result in Play mode (when spawned by player):
|
||||
- Bullets move in their assigned direction at TravelSpeed
|
||||
- Bullets automatically destroy after RemainingLifetime seconds (default 4s)
|
||||
- Movement is smooth and frame-rate independent
|
||||
|
||||
---
|
||||
|
||||
## Step 3.3: Bullet Collision Logic
|
||||
|
||||
1. Click on "BulletCollision" component in Components panel
|
||||
|
||||
2. In Details panel, scroll to "Events" section
|
||||
- Click "+" next to **"On Component Begin Overlap"**
|
||||
- This creates event node in Event Graph
|
||||
|
||||
3. In the Event Graph, from "On Component Begin Overlap":
|
||||
|
||||
#### a) First, check if this is enemy projectile:
|
||||
- Get IsEnemyProjectile
|
||||
- Branch
|
||||
|
||||
#### b) IF IS ENEMY PROJECTILE (True branch):
|
||||
- Get Other Actor from the overlap event
|
||||
- Cast to BP_Player
|
||||
- If cast succeeds:
|
||||
- Call TakeHit on player, passing Damage
|
||||
- Destroy this bullet (Destroy Actor)
|
||||
|
||||
#### c) IF IS PLAYER BULLET (False branch):
|
||||
- Get Other Actor
|
||||
- Cast to BP_Enemy
|
||||
- If cast succeeds:
|
||||
- Call ApplyDamage on enemy, passing Damage
|
||||
- Destroy this bullet
|
||||
|
||||
### 4. Compile and Save
|
||||
|
||||
### Expected Result after Compile:
|
||||
- Compile button shows GREEN checkmark
|
||||
- Event Graph shows "On Component Begin Overlap" event connected to Branch
|
||||
|
||||
### Expected Result in Play mode:
|
||||
- Player bullets (IsEnemyProjectile=false) hitting enemies:
|
||||
- Enemy takes damage, bullet disappears
|
||||
- Enemy bullets (IsEnemyProjectile=true) hitting player:
|
||||
- Player takes hit, bullet disappears
|
||||
- Bullets passing through other objects: No effect (bullets ignore non-targets)
|
||||
|
||||
---
|
||||
|
||||
## Step 3.4: Complete Player Firing Logic (BP_Player)
|
||||
|
||||
Now that BP_Bullet exists, we can complete the player's firing functions.
|
||||
|
||||
### 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:
|
||||
|
||||
1. In My Blueprint panel, find the `BulletClass` variable
|
||||
2. Click on it to select it
|
||||
3. In Details panel, find "Default Value"
|
||||
4. Click the dropdown and select `BP_Bullet`
|
||||
5. Compile to save the change
|
||||
|
||||
### 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:
|
||||
- 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 |
|
||||
|-----------|------|
|
||||
| `SpawnLocation` | Vector |
|
||||
| `Direction` | Vector |
|
||||
|
||||
The entry node should now show two input pins: SpawnLocation and Direction
|
||||
|
||||
#### e) Build the spawning logic:
|
||||
|
||||
1. Right-click → search `Spawn Actor from Class` → add it
|
||||
|
||||
2. For the "Class" input:
|
||||
- Right-click → `Get BulletClass` (your variable)
|
||||
- Connect to "Class" pin
|
||||
|
||||
3. For the "Spawn Transform" input:
|
||||
- Right-click on the "Spawn Transform" pin → "Split Struct Pin"
|
||||
- This splits it into Location, Rotation, Scale
|
||||
- Connect "SpawnLocation" (from entry node) to "Spawn Transform Location"
|
||||
- Leave Rotation and Scale at defaults
|
||||
|
||||
4. Connect execution wire:
|
||||
- Drag from SpawnBullet entry node → Spawn Actor from Class
|
||||
|
||||
#### f) Initialize the spawned bullet:
|
||||
|
||||
5. The "Spawn Actor from Class" has a "Return Value" output (the spawned actor)
|
||||
- Drag from Return Value → search `Cast to BP_Bullet`
|
||||
- This works now because BP_Bullet exists!
|
||||
|
||||
6. From the cast's "As BP Bullet" output, drag → search `Initialize`
|
||||
- This calls the Initialize function on the bullet (created in Step 3.2)
|
||||
- Connect "Direction" (from entry node) to Initialize's Direction input
|
||||
- Right-click → `Get BulletSpeed` → connect to Speed input
|
||||
- For "bIsEnemy" input: leave unchecked (false) - player bullets aren't enemy
|
||||
- For "Lifetime": type `4.0` (or leave default)
|
||||
|
||||
7. Connect execution wires:
|
||||
- Spawn Actor → Cast to BP_Bullet → Initialize
|
||||
|
||||
**Visual:**
|
||||
|
||||
```
|
||||
┌───────────────────────┐ ┌─────────────────────────┐
|
||||
│ SpawnBullet │─────►│ Spawn Actor from Class │
|
||||
│ SpawnLocation ○ │ │ Class: BulletClass │
|
||||
│ Direction ○ │ │ Location: SpawnLocation │
|
||||
└───────────────────────┘ └───────────┬─────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────┐
|
||||
│ Cast to BP_Bullet │
|
||||
└───────────┬─────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────┐
|
||||
│ Initialize │
|
||||
│ Direction: Direction │
|
||||
│ Speed: BulletSpeed │
|
||||
│ bIsEnemy: false │
|
||||
└─────────────────────────┘
|
||||
```
|
||||
|
||||
#### g) Compile (should have no errors now)
|
||||
|
||||
### 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:
|
||||
|
||||
---
|
||||
|
||||
**STEP 1 - Get the volley size and check for single bullet case:**
|
||||
|
||||
1. You should see a purple "FireVolley" entry node on the left
|
||||
- This is where execution starts when the function is called
|
||||
|
||||
2. Right-click → search `Get VolleySize` → add it
|
||||
- This gets your variable value (default 3)
|
||||
|
||||
3. Right-click → search `Equal (Integer)` or `==` → add it
|
||||
- Connect VolleySize output to the TOP input (A)
|
||||
- Type `1` in the BOTTOM input (B)
|
||||
- This checks: is VolleySize exactly 1?
|
||||
|
||||
4. From FireVolley entry node, drag execution → `Branch`
|
||||
- Connect the == result to Branch's "Condition" input
|
||||
- TRUE = single bullet, FALSE = multiple bullets in spread
|
||||
|
||||
---
|
||||
|
||||
**STEP 2 - TRUE branch: Fire single bullet straight up:**
|
||||
|
||||
5. From Branch TRUE pin, drag execution → right-click → search `SpawnBullet` (the function you created in step 3 above)
|
||||
|
||||
6. For SpawnBullet's "Spawn Location" input:
|
||||
- Right-click → `Get Actor Location`
|
||||
- Connect the output to Spawn Location
|
||||
|
||||
7. For SpawnBullet's "Direction" input (fire straight UP):
|
||||
- Right-click → `Make Vector`
|
||||
- Set X = 1 (up in top-down view)
|
||||
- Set Y = 0
|
||||
- Set Z = 0
|
||||
- Connect to Direction input
|
||||
|
||||
8. After SpawnBullet, drag execution → `Return Node`
|
||||
- This exits the function early (don't continue to the loop)
|
||||
|
||||
**Visual so far:**
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌────────────────────┐
|
||||
│ FireVolley │─────►│ Branch │
|
||||
│ (entry) │ │ VolleySize == 1 │
|
||||
└─────────────┘ └─────────┬──────────┘
|
||||
│ TRUE
|
||||
▼
|
||||
┌─────────────────────┐
|
||||
│ SpawnBullet │
|
||||
│ Location: ActorLoc │───► Return
|
||||
│ Direction: (1,0,0) │
|
||||
└─────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**STEP 3 - FALSE branch: Fire multiple bullets in a spread pattern:**
|
||||
|
||||
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 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
|
||||
|
||||
- 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)
|
||||
- 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)
|
||||
|
||||
- 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
|
||||
|
||||
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 = forward/back (up/down on screen)
|
||||
- Y axis = left/right
|
||||
- Angle 0° = straight up = direction (1, 0, 0)
|
||||
- Angle 90° = right = direction (0, 1, 0)
|
||||
|
||||
```
|
||||
Direction.X = Cos(angle)
|
||||
Direction.Y = Sin(angle)
|
||||
Direction.Z = 0
|
||||
```
|
||||
|
||||
BUT Unreal's Sin/Cos use RADIANS, not degrees!
|
||||
|
||||
**a) Convert degrees to radians:**
|
||||
- Right-click → search `Radians` or `Degrees to Radians`
|
||||
- Connect your angle (from step 12) to input
|
||||
- Output is angle in radians
|
||||
|
||||
**b) Calculate X component (Cos):**
|
||||
- Right-click → `Cos (Radians)`
|
||||
- Connect radians output
|
||||
- This is the X direction component
|
||||
|
||||
**c) Calculate Y component (Sin):**
|
||||
- Right-click → `Sin (Radians)`
|
||||
- Connect radians output (same value as Cos input)
|
||||
- This is the Y direction component
|
||||
|
||||
**d) Make the direction vector:**
|
||||
- Right-click → `Make Vector`
|
||||
- Connect Cos result to X
|
||||
- Connect Sin result to Y
|
||||
- Set Z = 0
|
||||
|
||||
14. **Spawn the bullet:**
|
||||
- From the loop body execution, drag → `SpawnBullet` (from step 3)
|
||||
- Spawn Location: `Get Actor Location`
|
||||
- Direction: Connect the Make Vector from step 13
|
||||
|
||||
15. **Connect loop completion:**
|
||||
- The For Loop has a "Completed" execution pin
|
||||
- This fires AFTER all iterations are done
|
||||
- Drag from "Completed" → `Return Node` (or leave unconnected)
|
||||
|
||||
**Complete Visual:**
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌────────────────────┐
|
||||
│ FireVolley │─────►│ Branch │
|
||||
│ (entry) │ │ VolleySize == 1 │
|
||||
└─────────────┘ └──────┬───────┬─────┘
|
||||
│TRUE │FALSE
|
||||
▼ ▼
|
||||
┌──────────┐ ┌─────────────────────────────┐
|
||||
│SpawnBullet│ │ Calculate StartAngle │
|
||||
│Dir:(1,0,0)│ │ = -(Spread*(Size-1))/2 │
|
||||
└─────┬────┘ └──────────────┬──────────────┘
|
||||
│ ▼
|
||||
▼ ┌─────────────────┐
|
||||
Return │ For Loop │
|
||||
│ 0 to Size-1 │
|
||||
└───────┬─────────┘
|
||||
│ Loop Body
|
||||
▼
|
||||
┌──────────────────────────────┐
|
||||
│ Angle = Start + (i * Spread) │
|
||||
│ Radians = DegreesToRadians │
|
||||
│ Dir.X = Cos(Radians) │
|
||||
│ Dir.Y = Sin(Radians) │
|
||||
│ SpawnBullet(Location, Dir) │
|
||||
└──────────────────────────────┘
|
||||
```
|
||||
|
||||
**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:
|
||||
|
||||
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)
|
||||
3. Delete the Print String "FIRE!" placeholder
|
||||
4. From the "Set FireTimer = FireInterval" node, drag execution → `FireVolley`
|
||||
|
||||
**Updated Visual:**
|
||||
|
||||
```
|
||||
┌─────────────────┐ TRUE ┌─────────────────────┐
|
||||
│ Branch │───────────►│ Set FireTimer │──► FireVolley
|
||||
│ FireTimer <= 0 │ │ = FireInterval │
|
||||
└─────────────────┘ └─────────────────────┘
|
||||
```
|
||||
|
||||
### 6. Compile and Save
|
||||
|
||||
### 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:
|
||||
- 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)
|
||||
- Bullets disappear after 4 seconds (RemainingLifetime)
|
||||
|
||||
---
|
||||
|
||||
[← Previous: Part 2 - Create the Player](part-2-create-player.md) | [Back to Index](README.md) | [Next: Part 4 - Create the Enemy →](part-4-create-enemy.md)
|
||||
349
games/unreal/tutorial/part-4-create-enemy.md
Normal file
349
games/unreal/tutorial/part-4-create-enemy.md
Normal file
@ -0,0 +1,349 @@
|
||||
# Part 4: Create the Enemy
|
||||
|
||||
[← Previous: Part 3 - Create the Bullet](part-3-create-bullet.md) | [Back to Index](README.md) | [Next: Part 5 - Create Enemy Spawner →](part-5-create-spawner.md)
|
||||
|
||||
---
|
||||
|
||||
## Step 4.1: Create Enemy Blueprint
|
||||
|
||||
1. Content Browser → Blueprints
|
||||
2. Right-click → **Blueprint Class** → **Actor**
|
||||
3. Name: `BP_Enemy`
|
||||
4. Double-click to open
|
||||
|
||||
5. **Add Components:**
|
||||
- Paper Sprite → `EnemySprite`
|
||||
- Box Collision → `EnemyCollision`
|
||||
- Box Extent: `X=30, Y=30, Z=10`
|
||||
- Generate Overlap Events: **CHECKED**
|
||||
- Cube (Static Mesh) → name `TempVisual`
|
||||
- Scale: `(0.6, 0.6, 0.1)` - larger than player cube
|
||||
- This provides visibility until proper visuals in [Part 9](part-9-final-setup.md)
|
||||
|
||||
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) |
|
||||
|
||||
### 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):
|
||||
- Cube visible (the TempVisual placeholder, larger than player)
|
||||
- Box collision visible (30x30x10)
|
||||
|
||||
---
|
||||
|
||||
## Step 4.2: Enemy Initialization
|
||||
|
||||
1. In Event Graph, from **"Event BeginPlay":**
|
||||
|
||||
#### a) Set CurrentHealth = MaxHealth
|
||||
|
||||
#### b) Get Actor Location → Break Vector → Set BaseX = X value
|
||||
|
||||
#### 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:
|
||||
- Compile button shows GREEN checkmark
|
||||
- BeginPlay event connected to variable setters
|
||||
|
||||
### 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
|
||||
|
||||
---
|
||||
|
||||
## Step 4.3: Enemy Movement Logic
|
||||
|
||||
1. In Event Graph, from **"Event Tick":**
|
||||
|
||||
#### a) VERTICAL MOVEMENT:
|
||||
- Get Actor Location → Break into X, Y, Z
|
||||
- Subtract (VerticalSpeed * DeltaSeconds) from Y
|
||||
|
||||
#### b) HORIZONTAL SINE WAVE:
|
||||
- Get Game Time in Seconds
|
||||
- Add WaveSeed
|
||||
- Multiply by HorizontalFrequency
|
||||
- Calculate Sine of result
|
||||
- Multiply by HorizontalAmplitude
|
||||
- Add to BaseX
|
||||
- This becomes new X position
|
||||
|
||||
#### c) Set Actor Location with new X, Y (Z stays 0)
|
||||
|
||||
#### d) DESPAWN CHECK:
|
||||
- If Y < DespawnY: Destroy Actor
|
||||
- If Abs(X) > 1400: Destroy Actor
|
||||
|
||||
**Visual Diagram:**
|
||||
|
||||
```
|
||||
┌─────────────┐
|
||||
│ Event Tick │
|
||||
└──────┬──────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ Get Location │
|
||||
│ NewY = Y - (VerticalSpeed * DeltaSeconds) │
|
||||
│ NewX = BaseX + Sin(Time * Freq) * Amplitude │
|
||||
│ Set Location (NewX, NewY, 0) │
|
||||
└──────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Expected Result after Compile:
|
||||
- Compile button shows GREEN checkmark
|
||||
- Event Tick connected to movement and despawn logic
|
||||
|
||||
### 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)
|
||||
- Enemies disappear when Y < -750 or |X| > 1400
|
||||
|
||||
---
|
||||
|
||||
## Step 4.4: Enemy Firing Logic
|
||||
|
||||
### 1. Continue in Event Tick (after movement):
|
||||
|
||||
#### a) Decrease FireTimer by DeltaSeconds
|
||||
|
||||
#### b) Branch: if FireTimer <= 0:
|
||||
- Reset FireTimer to FireInterval
|
||||
- Call "FireBurst" function
|
||||
|
||||
### 2. CREATE "FireBurst" FUNCTION:
|
||||
|
||||
#### a) Add function "FireBurst"
|
||||
|
||||
#### 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:
|
||||
- Calculate angle: `i * AngleStep`
|
||||
- Convert to direction vector:
|
||||
- X = Sin(angle in radians)
|
||||
- Y = -Cos(angle in radians) ← negative because enemies fire DOWN
|
||||
- Spawn bullet:
|
||||
- Spawn Actor from Class (BP_Bullet)
|
||||
- Get spawned bullet, call Initialize:
|
||||
- Direction = calculated direction
|
||||
- Speed = EnemyBulletSpeed
|
||||
- bIsEnemy = **true**
|
||||
- Lifetime = EnemyBulletLifetime
|
||||
|
||||
**Visual: Radial bullet pattern (20 bullets in 360°)**
|
||||
|
||||
```
|
||||
↑
|
||||
↗ | ↖
|
||||
↗ | ↖
|
||||
→───────●───────←
|
||||
↘ | ↙
|
||||
↘ | ↙
|
||||
↓
|
||||
```
|
||||
|
||||
### Expected Result after Compile:
|
||||
- Compile button shows GREEN checkmark
|
||||
- "FireBurst" function appears under Functions
|
||||
|
||||
### 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
|
||||
- Different enemies fire at different times (randomized FireTimer)
|
||||
|
||||
---
|
||||
|
||||
## Step 4.5: Enemy Damage and Death
|
||||
|
||||
### 1. CREATE "ApplyDamage" FUNCTION:
|
||||
|
||||
#### a) Add input: `DamageAmount` (Integer)
|
||||
|
||||
#### b) Inside:
|
||||
- Subtract DamageAmount from CurrentHealth
|
||||
- If CurrentHealth <= 0:
|
||||
- Call HandleDeath
|
||||
|
||||
### 2. CREATE "HandleDeath" FUNCTION:
|
||||
|
||||
#### 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:
|
||||
|
||||
#### 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:
|
||||
- Compile button shows GREEN checkmark
|
||||
- "ApplyDamage" and "HandleDeath" functions appear under Functions
|
||||
- EnemyCollision has overlap event in Event Graph
|
||||
|
||||
### 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
|
||||
|
||||
---
|
||||
|
||||
## Step 4.6: Complete Special Ability in BP_Player
|
||||
|
||||
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. 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:
|
||||
|
||||
#### 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:
|
||||
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:
|
||||
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):
|
||||
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):
|
||||
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:
|
||||
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:
|
||||
|
||||
```
|
||||
┌─────────────────────────────┐
|
||||
│ EnhancedInputAction │
|
||||
│ IA_Special - Triggered │
|
||||
└───────────┬─────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────┐
|
||||
│ Branch │
|
||||
│ Condition: NOT SpecialUsed │
|
||||
└───────┬─────────────┬───────┘
|
||||
│ TRUE │ FALSE
|
||||
▼ ▼
|
||||
┌───────────────┐ (nothing - ability already used)
|
||||
│Set SpecialUsed│
|
||||
│ = true │
|
||||
└───────┬───────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────┐
|
||||
│ Get All Actors of Class │
|
||||
│ Actor Class: BP_Enemy │
|
||||
│ Out Actors ○───────────────┼──────────┐
|
||||
└───────────┬────────────────┘ │
|
||||
│ │
|
||||
▼ ▼
|
||||
┌────────────────────────────┐ ┌─────────────┐
|
||||
│ For Each Loop │──►│Destroy Actor│
|
||||
│ Array ○──────────────────┼───│Target: Enemy│
|
||||
│ │ └─────────────┘
|
||||
│ Loop Body ───────────────┼───────────┘
|
||||
│ Completed ───────────────┼───┐
|
||||
└────────────────────────────┘ │
|
||||
│
|
||||
┌────────────────────┘
|
||||
▼
|
||||
┌────────────────────────────┐
|
||||
│ Get All Actors of Class │
|
||||
│ Actor Class: BP_Bullet │
|
||||
│ Out Actors ○───────────────┼──────────┐
|
||||
└───────────┬────────────────┘ │
|
||||
│ │
|
||||
▼ ▼
|
||||
┌────────────────────────────┐ ┌─────────────────────────────────┐
|
||||
│ For Each Loop │──►│ Get IsEnemyProjectile │
|
||||
│ Array ○──────────────────┼───│ (from Array Element) │
|
||||
│ │ └───────────────┬─────────────────┘
|
||||
│ Loop Body ───────────────┼───────────────────┘
|
||||
└────────────────────────────┘ │
|
||||
▼
|
||||
┌─────────────────────────────┐
|
||||
│ Branch │
|
||||
│ Condition: IsEnemyProjectile│
|
||||
└───────┬─────────────┬───────┘
|
||||
│ TRUE │ FALSE
|
||||
▼ ▼
|
||||
┌───────────────┐ (skip - player bullet)
|
||||
│ Destroy Actor │
|
||||
│ Target: Bullet│
|
||||
└───────────────┘
|
||||
```
|
||||
|
||||
### 4. Compile and Save
|
||||
|
||||
### 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:
|
||||
- Press X (or Right Mouse Button) once:
|
||||
- ALL enemies on screen instantly disappear
|
||||
- ALL enemy bullets (red) instantly disappear
|
||||
- Player bullets (yellow) remain unaffected
|
||||
- "SPECIAL ABILITY ACTIVATED!" no longer prints (we deleted it)
|
||||
- Press X again: Nothing happens (SpecialUsed = true, one use only)
|
||||
|
||||
---
|
||||
|
||||
[← Previous: Part 3 - Create the Bullet](part-3-create-bullet.md) | [Back to Index](README.md) | [Next: Part 5 - Create Enemy Spawner →](part-5-create-spawner.md)
|
||||
142
games/unreal/tutorial/part-5-create-spawner.md
Normal file
142
games/unreal/tutorial/part-5-create-spawner.md
Normal file
@ -0,0 +1,142 @@
|
||||
# Part 5: Create Enemy Spawner
|
||||
|
||||
[← Previous: Part 4 - Create the Enemy](part-4-create-enemy.md) | [Back to Index](README.md) | [Next: Part 6 - Game Director →](part-6-game-director.md)
|
||||
|
||||
---
|
||||
|
||||
## Step 5.1: Create Spawner Blueprint
|
||||
|
||||
1. Content Browser → Blueprints
|
||||
2. Right-click → **Blueprint Class** → **Actor**
|
||||
3. Name: `BP_EnemySpawner`
|
||||
4. Double-click to open
|
||||
|
||||
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 |
|
||||
|
||||
### 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)
|
||||
|
||||
---
|
||||
|
||||
## Step 5.2: Spawn Rate Curve
|
||||
|
||||
### 1. Create Variable:
|
||||
- `SpawnCurve` (Curve Float)
|
||||
|
||||
### 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:
|
||||
|
||||
- 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 |
|
||||
|
||||
- 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:
|
||||
- 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:
|
||||
- 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)
|
||||
|
||||
---
|
||||
|
||||
## Step 5.3: Spawning Logic
|
||||
|
||||
### 1. In Event Graph, from Event Tick:
|
||||
|
||||
#### a) Check if SpawningActive is true
|
||||
- If false, do nothing
|
||||
|
||||
#### b) Update ElapsedTime:
|
||||
- Add DeltaSeconds to ElapsedTime
|
||||
|
||||
#### c) Calculate normalized time:
|
||||
- Divide ElapsedTime by GameDuration
|
||||
- Clamp between 0 and 1
|
||||
|
||||
#### d) Get spawn rate from curve:
|
||||
- Get SpawnCurve
|
||||
- Call `Get Float Value` with normalized time
|
||||
- This returns spawns per second
|
||||
|
||||
#### e) Update SpawnTimer:
|
||||
- Subtract DeltaSeconds from SpawnTimer
|
||||
- If SpawnTimer <= 0:
|
||||
- Reset SpawnTimer to `(1.0 / SpawnsPerSecond)`
|
||||
- Call SpawnWave function
|
||||
|
||||
### 2. CREATE "SpawnWave" FUNCTION:
|
||||
|
||||
#### 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:
|
||||
- Call SpawnEnemy function
|
||||
|
||||
### 3. CREATE "SpawnEnemy" FUNCTION:
|
||||
|
||||
#### a) Inside:
|
||||
- Check: Get All Actors of Class (BP_Enemy)
|
||||
- Get array length
|
||||
- If >= MaxSimultaneousEnemies: Return (don't spawn)
|
||||
|
||||
#### 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:
|
||||
- Class: EnemyClass
|
||||
- Location: calculated position
|
||||
- Rotation: (0, 0, 0)
|
||||
|
||||
### 4. CREATE "StopSpawning" FUNCTION:
|
||||
- Set SpawningActive = false
|
||||
|
||||
### Expected Result after Compile:
|
||||
- Compile button shows GREEN checkmark
|
||||
- "SpawnWave", "SpawnEnemy", "StopSpawning" functions appear under Functions
|
||||
|
||||
### 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
|
||||
- When StopSpawning called: No new enemies appear
|
||||
|
||||
---
|
||||
|
||||
[← Previous: Part 4 - Create the Enemy](part-4-create-enemy.md) | [Back to Index](README.md) | [Next: Part 6 - Game Director →](part-6-game-director.md)
|
||||
102
games/unreal/tutorial/part-6-game-director.md
Normal file
102
games/unreal/tutorial/part-6-game-director.md
Normal file
@ -0,0 +1,102 @@
|
||||
# Part 6: Create Game Director
|
||||
|
||||
[← Previous: Part 5 - Create Enemy Spawner](part-5-create-spawner.md) | [Back to Index](README.md) | [Next: Part 7 - Score Manager / UI →](part-7-score-manager-ui.md)
|
||||
|
||||
---
|
||||
|
||||
## Step 6.1: Create Game Director Blueprint
|
||||
|
||||
1. Content Browser → Blueprints
|
||||
2. Right-click → **Blueprint Class** → **Actor**
|
||||
3. Name: `BP_GameDirector`
|
||||
4. Double-click to open
|
||||
|
||||
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 |
|
||||
|
||||
### 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)
|
||||
|
||||
---
|
||||
|
||||
## Step 6.2: Game Director Initialization
|
||||
|
||||
### 1. From Event BeginPlay:
|
||||
|
||||
#### a) Find player in scene:
|
||||
- Get All Actors of Class → BP_Player
|
||||
- Get first element (index 0)
|
||||
- Set PlayerReference
|
||||
|
||||
#### b) Find spawner in scene:
|
||||
- Get All Actors of Class → BP_EnemySpawner
|
||||
- Get first element
|
||||
- Set SpawnerReference
|
||||
|
||||
#### c) Initialize ScoreManager:
|
||||
- Get ScoreManager reference
|
||||
- Call RegisterGameStart with initial lives and duration
|
||||
|
||||
### Expected Result after Compile:
|
||||
- Compile button shows GREEN checkmark
|
||||
- BeginPlay event connected to "Get All Actors of Class" nodes
|
||||
|
||||
### Expected Result in Play mode:
|
||||
- Game Director automatically finds Player, Spawner, and ScoreManager
|
||||
- UI initializes with correct starting values (Lives: 3, Time: 05:00)
|
||||
|
||||
---
|
||||
|
||||
## Step 6.3: Game Director Update Logic
|
||||
|
||||
### 1. From Event Tick:
|
||||
|
||||
#### a) Check if GameActive
|
||||
- If false, skip everything
|
||||
|
||||
#### b) Update elapsed time:
|
||||
- Add DeltaSeconds to ElapsedTime
|
||||
|
||||
#### c) Calculate remaining time:
|
||||
- Subtract ElapsedTime from GameDuration
|
||||
- Max with 0 (don't go negative)
|
||||
|
||||
#### d) Update UI timer:
|
||||
- Get ScoreManager
|
||||
- Call UpdateTimer with remaining time
|
||||
|
||||
#### e) Check for victory:
|
||||
- If ElapsedTime >= GameDuration:
|
||||
- Set GameActive = false
|
||||
- Get SpawnerReference → Call StopSpawning
|
||||
- Get ScoreManager → Call HandleGameClear
|
||||
|
||||
### 2. CREATE "HandlePlayerDeath" FUNCTION:
|
||||
|
||||
#### a) Inside:
|
||||
- Set GameActive = false
|
||||
- Get SpawnerReference → Call StopSpawning
|
||||
- Get ScoreManager → Call HandleGameOver
|
||||
|
||||
### 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:
|
||||
- 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
|
||||
|
||||
---
|
||||
|
||||
[← Previous: Part 5 - Create Enemy Spawner](part-5-create-spawner.md) | [Back to Index](README.md) | [Next: Part 7 - Score Manager / UI →](part-7-score-manager-ui.md)
|
||||
194
games/unreal/tutorial/part-7-score-manager-ui.md
Normal file
194
games/unreal/tutorial/part-7-score-manager-ui.md
Normal file
@ -0,0 +1,194 @@
|
||||
# Part 7: Create Score Manager / UI
|
||||
|
||||
[← Previous: Part 6 - Game Director](part-6-game-director.md) | [Back to Index](README.md) | [Next: Part 8 - Game Mode and Level →](part-8-game-mode-level.md)
|
||||
|
||||
---
|
||||
|
||||
## Step 7.1: Create UI Widget Blueprint
|
||||
|
||||
1. Content Browser → **UI** folder
|
||||
2. Right-click → **User Interface → Widget Blueprint**
|
||||
3. Name: `WBP_HUD`
|
||||
4. Double-click to open Widget Designer
|
||||
|
||||
### Expected Result:
|
||||
Widget Designer opens with:
|
||||
- Hierarchy panel on left
|
||||
- Canvas preview in center
|
||||
- Details panel on right
|
||||
|
||||
---
|
||||
|
||||
## 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:
|
||||
|
||||
1. In Palette, search for "Text"
|
||||
2. Drag "Text" widget onto Canvas Panel in hierarchy
|
||||
3. Rename it `ScoreText` (right-click → Rename)
|
||||
4. In Details panel:
|
||||
- Under "Slot (Canvas Panel Slot)":
|
||||
- Anchors: Top-Left (click dropdown, select corner)
|
||||
- Position X: `20`
|
||||
- Position Y: `20`
|
||||
- Size X: `300`
|
||||
- Size Y: `40`
|
||||
- Under "Appearance":
|
||||
- Text: `Score: 0`
|
||||
- Font Size: `24`
|
||||
- Color: White
|
||||
|
||||
### 3. Add Lives Text:
|
||||
|
||||
1. Drag another "Text" widget
|
||||
2. Rename to `LivesText`
|
||||
3. Position X: `20`, Y: `60`
|
||||
4. Text: `Lives: 3`
|
||||
5. Same font settings as Score
|
||||
|
||||
### 4. Add Timer Text:
|
||||
|
||||
1. Drag another "Text" widget
|
||||
2. Rename to `TimerText`
|
||||
3. Position X: `20`, Y: `100`
|
||||
4. Text: `Time: 05:00`
|
||||
5. Same font settings
|
||||
|
||||
### 5. Click "Compile" and "Save" (top buttons)
|
||||
|
||||
### Expected Result:
|
||||
|
||||
Preview shows:
|
||||
```
|
||||
┌────────────────────────────────────┐
|
||||
│ Score: 0 │
|
||||
│ Lives: 3 │
|
||||
│ Time: 05:00 │
|
||||
│ │
|
||||
│ │
|
||||
└────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 7.3: Create Score Manager Blueprint
|
||||
|
||||
1. Content Browser → Blueprints
|
||||
2. Right-click → **Blueprint Class** → **Actor**
|
||||
3. Name: `BP_ScoreManager`
|
||||
4. Double-click to open
|
||||
|
||||
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) |
|
||||
|
||||
### Expected Result after Compile:
|
||||
- Compile button shows GREEN checkmark
|
||||
- Variables panel shows all 4 variables with correct types
|
||||
|
||||
---
|
||||
|
||||
## Step 7.4: Score Manager Initialization
|
||||
|
||||
### 1. From Event BeginPlay:
|
||||
|
||||
#### 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:
|
||||
- Set HUDWidget to the created widget
|
||||
|
||||
#### c) Add to viewport:
|
||||
- Right-click → `Add to Viewport`
|
||||
- Connect widget reference as target
|
||||
|
||||
### Expected Result after Compile:
|
||||
- Compile button shows GREEN checkmark
|
||||
- BeginPlay event connected to Create Widget → Add to Viewport
|
||||
|
||||
### 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)
|
||||
|
||||
---
|
||||
|
||||
## Step 7.5: Score Manager Functions
|
||||
|
||||
### 1. CREATE "RegisterGameStart" FUNCTION:
|
||||
|
||||
**Inputs:**
|
||||
- `InitialLives` (Integer)
|
||||
- `Duration` (Float)
|
||||
|
||||
**Inside:**
|
||||
- Set Score = 0
|
||||
- Set CurrentLives = InitialLives
|
||||
- Update all UI labels
|
||||
|
||||
### 2. CREATE "AddScore" FUNCTION:
|
||||
|
||||
**Input:** `Amount` (Integer)
|
||||
|
||||
**Inside:**
|
||||
- Add Amount to Score
|
||||
- Update Score label in HUD:
|
||||
- Get HUDWidget
|
||||
- Cast to WBP_HUD
|
||||
- Get "ScoreText" widget
|
||||
- Set Text to "Score: " + Score
|
||||
|
||||
### 3. CREATE "SetLives" FUNCTION:
|
||||
|
||||
**Input:** `Lives` (Integer)
|
||||
|
||||
**Inside:**
|
||||
- Set CurrentLives = Lives
|
||||
- Update Lives label in HUD
|
||||
|
||||
### 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:
|
||||
|
||||
**Inside:**
|
||||
- Set Timer text to "Game Over"
|
||||
|
||||
### 6. CREATE "HandleGameClear" FUNCTION:
|
||||
|
||||
**Inside:**
|
||||
- Set Timer text to "Mission Complete"
|
||||
|
||||
### Expected Result after Compile:
|
||||
- Compile button shows GREEN checkmark
|
||||
- All 6 functions appear under Functions panel
|
||||
|
||||
### 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
|
||||
- "Game Over" or "Mission Complete" replaces timer at game end
|
||||
|
||||
---
|
||||
|
||||
[← Previous: Part 6 - Game Director](part-6-game-director.md) | [Back to Index](README.md) | [Next: Part 8 - Game Mode and Level →](part-8-game-mode-level.md)
|
||||
134
games/unreal/tutorial/part-8-game-mode-level.md
Normal file
134
games/unreal/tutorial/part-8-game-mode-level.md
Normal file
@ -0,0 +1,134 @@
|
||||
# Part 8: Create Game Mode and Level
|
||||
|
||||
[← Previous: Part 7 - Score Manager / UI](part-7-score-manager-ui.md) | [Back to Index](README.md) | [Next: Part 9 - Final Setup and Testing →](part-9-final-setup.md)
|
||||
|
||||
---
|
||||
|
||||
## Step 8.1: Create Custom Game Mode
|
||||
|
||||
1. Content Browser → Blueprints
|
||||
2. Right-click → **Blueprint Class**
|
||||
3. In popup, expand "All Classes"
|
||||
4. Search for "Game Mode Base"
|
||||
5. Select "Game Mode Base" and click Select
|
||||
6. Name: `BP_BulletHellGameMode`
|
||||
7. Double-click to open
|
||||
|
||||
8. In the Details panel (with BP_BulletHellGameMode open):
|
||||
- Find "Classes" section
|
||||
- Default Pawn Class: Select `BP_Player`
|
||||
- Player Controller Class: Keep default
|
||||
|
||||
### Expected Result after Compile:
|
||||
- Compile button shows GREEN checkmark
|
||||
- Details panel shows "Default Pawn Class" set to BP_Player
|
||||
|
||||
---
|
||||
|
||||
## Step 8.2: Configure Project to Use Game Mode
|
||||
|
||||
1. Go to **Edit → Project Settings**
|
||||
2. In left sidebar, click "Maps & Modes"
|
||||
3. Under "Default Modes":
|
||||
- Default GameMode: Select `BP_BulletHellGameMode`
|
||||
4. Close Project Settings
|
||||
|
||||
### Expected Result:
|
||||
- Project Settings shows BP_BulletHellGameMode as Default GameMode
|
||||
- This means the game will automatically spawn BP_Player when Play is pressed
|
||||
|
||||
---
|
||||
|
||||
## Step 8.3: Create Game Level
|
||||
|
||||
1. **File → New Level**
|
||||
2. Select "Empty Level"
|
||||
3. **File → Save Current Level As**
|
||||
4. Navigate to Content folder
|
||||
5. Name: `BulletHellLevel`
|
||||
6. Click Save
|
||||
|
||||
### Expected Result:
|
||||
- New level file "BulletHellLevel" appears in Content folder
|
||||
- Level is completely empty (black viewport)
|
||||
- Outliner shows only default actors (if any)
|
||||
|
||||
---
|
||||
|
||||
## Step 8.4: Set Up Level Components
|
||||
|
||||
In the level (main viewport), we need to add game actors:
|
||||
|
||||
### 1. ADD CAMERA:
|
||||
|
||||
1. In Place Actors panel (left side, or Window → Place Actors)
|
||||
2. Search for "Camera Actor"
|
||||
3. Drag into viewport
|
||||
4. Position camera:
|
||||
- In Details panel, under Transform:
|
||||
- Location: `X=0, Y=0, Z=1000`
|
||||
- Rotation: `X=-90, Y=0, Z=0` (looking down)
|
||||
5. Set as default camera:
|
||||
- In Details panel, find "Auto Activate for Player"
|
||||
- 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:
|
||||
|
||||
1. From Content Browser, drag `BP_Player` into level
|
||||
2. Position: `X=0, Y=-300, Z=0` (bottom center)
|
||||
|
||||
### 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:
|
||||
|
||||
1. Drag `BP_GameDirector` into level
|
||||
2. Position doesn't matter (it's invisible)
|
||||
|
||||
### 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:
|
||||
- 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:
|
||||
```
|
||||
- CameraActor
|
||||
- BP_Player
|
||||
- BP_EnemySpawner
|
||||
- BP_GameDirector
|
||||
- BP_ScoreManager
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 8.5: Set Default Level
|
||||
|
||||
1. **Edit → Project Settings**
|
||||
2. Maps & Modes
|
||||
3. Under "Default Maps":
|
||||
- Editor Startup Map: Select `BulletHellLevel`
|
||||
- Game Default Map: Select `BulletHellLevel`
|
||||
|
||||
### Expected Result:
|
||||
- Project Settings shows BulletHellLevel as both startup and default map
|
||||
- Launching the game (standalone or in editor) loads this level automatically
|
||||
|
||||
---
|
||||
|
||||
[← Previous: Part 7 - Score Manager / UI](part-7-score-manager-ui.md) | [Back to Index](README.md) | [Next: Part 9 - Final Setup and Testing →](part-9-final-setup.md)
|
||||
174
games/unreal/tutorial/part-9-final-setup.md
Normal file
174
games/unreal/tutorial/part-9-final-setup.md
Normal file
@ -0,0 +1,174 @@
|
||||
# Part 9: Final Setup and Testing
|
||||
|
||||
[← Previous: Part 8 - Game Mode and Level](part-8-game-mode-level.md) | [Back to Index](README.md) | [Appendix A: Variables →](appendix-a-variables.md)
|
||||
|
||||
---
|
||||
|
||||
## Step 9.1: Assign Blueprint References
|
||||
|
||||
### 1. Open BP_Player:
|
||||
- In Details panel (with blueprint open)
|
||||
- Set BulletClass: `BP_Bullet`
|
||||
|
||||
### 2. Open BP_EnemySpawner:
|
||||
- Set EnemyClass: `BP_Enemy`
|
||||
|
||||
### 3. Open BP_Enemy:
|
||||
- Set BulletClass: `BP_Bullet`
|
||||
|
||||
### 4. Compile and Save 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_EnemySpawner.EnemyClass → BP_Enemy
|
||||
|
||||
### Expected Result in Play mode:
|
||||
- Player can shoot bullets (BP_Bullet spawns)
|
||||
- Enemies spawn and shoot bullets
|
||||
- All collision/damage systems functional
|
||||
|
||||
---
|
||||
|
||||
## Step 9.2: Create Final Visuals (Replace Placeholder)
|
||||
|
||||
Now replace the temporary cube visuals with proper colored materials:
|
||||
|
||||
### 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))
|
||||
3. Press Delete to remove it
|
||||
4. Select "PlayerCamera" and delete it (the level camera from [Step 8.4](part-8-game-mode-level.md#step-84-set-up-level-components) will be used)
|
||||
5. Repeat for BP_Bullet and BP_Enemy (delete their TempVisual cubes)
|
||||
|
||||
### 2. Content Browser → Materials folder
|
||||
|
||||
### 3. PLAYER MATERIAL:
|
||||
|
||||
1. Right-click → Material
|
||||
2. Name: `M_Player`
|
||||
3. Double-click to open Material Editor
|
||||
4. Create Vector3 node (hold 3, click)
|
||||
5. Set to blue color `(0, 0.5, 1)`
|
||||
6. Connect to Base Color
|
||||
7. Save and Close
|
||||
|
||||
### 4. BULLET MATERIALS:
|
||||
|
||||
- Create `M_PlayerBullet` - Yellow `(1, 1, 0)`
|
||||
- Create `M_EnemyBullet` - Red `(1, 0, 0)`
|
||||
|
||||
### 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:
|
||||
- Player visible as blue shape
|
||||
- Player bullets visible as yellow shapes
|
||||
- Enemy bullets visible as red shapes
|
||||
- Enemies visible as magenta shapes
|
||||
- All game elements distinguishable by color
|
||||
|
||||
---
|
||||
|
||||
## Step 9.3: Add Background (Optional)
|
||||
|
||||
### 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:
|
||||
- `M_Background` - Dark blue/black
|
||||
|
||||
### 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)
|
||||
|
||||
---
|
||||
|
||||
## Step 9.4: Test the Game
|
||||
|
||||
### 1. Click "Play" button (green arrow in main toolbar)
|
||||
OR press `Alt+P`
|
||||
|
||||
### 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 | ☐ |
|
||||
|
||||
### 3. To stop playing: Press `ESC` or click "Stop" button
|
||||
|
||||
### 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
|
||||
- Game is playable from start to victory/defeat
|
||||
|
||||
---
|
||||
|
||||
## Step 9.5: Build Standalone Game
|
||||
|
||||
1. **File → Package Project → Windows** (or your platform)
|
||||
2. Select output folder
|
||||
3. Wait for build to complete
|
||||
4. Navigate to output folder → WindowsNoEditor → [ProjectName].exe
|
||||
5. Run the executable to play standalone
|
||||
|
||||
### Expected Result:
|
||||
- Build completes without errors (check Output Log)
|
||||
- Executable file created in output folder
|
||||
- Running .exe launches the game in fullscreen
|
||||
- Game plays identically to editor Play mode
|
||||
- Can close with `Alt+F4` or in-game quit (if implemented)
|
||||
|
||||
---
|
||||
|
||||
## 🎉 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)
|
||||
- ✅ Enemies with sine-wave movement
|
||||
- ✅ Radial bullet patterns (20 bullets per burst)
|
||||
- ✅ Progressive difficulty (spawn rate increases over 5 minutes)
|
||||
- ✅ Score, lives, and timer UI
|
||||
- ✅ Victory after 5 minutes survival
|
||||
- ✅ Game over when lives reach 0
|
||||
|
||||
---
|
||||
|
||||
### Additional Resources:
|
||||
|
||||
- [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)
|
||||
|
||||
---
|
||||
|
||||
[← Previous: Part 8 - Game Mode and Level](part-8-game-mode-level.md) | [Back to Index](README.md) | [Appendix A: Variables →](appendix-a-variables.md)
|
||||
Loading…
Reference in New Issue
Block a user