diff --git a/games/unreal/tutorial/QUICKSTART-CODE-FIRST.md b/games/unreal/tutorial/QUICKSTART-CODE-FIRST.md index 7984387..a14dc5f 100644 --- a/games/unreal/tutorial/QUICKSTART-CODE-FIRST.md +++ b/games/unreal/tutorial/QUICKSTART-CODE-FIRST.md @@ -10,7 +10,7 @@ - **New Project?** → Start with [Part 1 (C++): Project Setup](part-1-cpp-project-setup.md) - **Already have Blueprint project?** → See [Migration Guide](#migrating-from-blueprint-to-c) below -- **Just need code?** → See existing implementations in `Source/MCPGameProject/` +- **Just need reference code?** → See existing implementations in `Source/MCPGameProject/` ### 2. Create C++ Project (New Projects Only) diff --git a/games/unreal/tutorial/README.md b/games/unreal/tutorial/README.md index 86dae02..beb316b 100644 --- a/games/unreal/tutorial/README.md +++ b/games/unreal/tutorial/README.md @@ -144,10 +144,18 @@ This tutorial offers **two ways** to implement the game: ### For Code-First Approach (Recommended) 1. Read [3-Minute Quickstart](QUICKSTART-CODE-FIRST.md) -2. Start with [Part 1 (C++): Project Setup](part-1-cpp-project-setup.md) -3. Follow parts 2-3, then [Parts 4-9 Summary](part-4-9-cpp-summary.md) -4. **Complete the game in 2-3 hours** instead of 6-8 hours! -5. **Already have Blueprint project?** See [Migration Guide](QUICKSTART-CODE-FIRST.md#migrating-from-blueprint-to-c) +2. Follow the C++ tutorial parts: + - [Part 1 (C++): Project Setup](part-1-cpp-project-setup.md) + - [Part 2 (C++): Create Player](part-2-cpp-create-player.md) + - [Part 3 (C++): Create Bullet](part-3-cpp-create-bullet.md) + - [Part 4 (C++): Create Enemy](part-4-cpp-create-enemy.md) + - [Part 5 (C++): Create Spawner](part-5-cpp-create-spawner.md) + - [Part 6 (C++): Create Game Director](part-6-cpp-create-game-director.md) + - [Part 7 (C++): Create UI](part-7-cpp-create-ui.md) + - [Part 8 (C++): Create Game Mode](part-8-cpp-create-game-mode.md) + - [Part 9 (C++): Final Setup](part-9-cpp-final-setup.md) +3. **Complete the game in 2-3 hours** instead of 6-8 hours! +4. **Already have Blueprint project?** See [Migration Guide](QUICKSTART-CODE-FIRST.md#migrating-from-blueprint-to-c) ### For Blueprint Approach diff --git a/games/unreal/tutorial/part-1-cpp-project-setup.md b/games/unreal/tutorial/part-1-cpp-project-setup.md index 8f2ab47..38ed727 100644 --- a/games/unreal/tutorial/part-1-cpp-project-setup.md +++ b/games/unreal/tutorial/part-1-cpp-project-setup.md @@ -28,8 +28,8 @@ This is the **C++ version** of Part 1. Unlike the Blueprint tutorial which creat 6. On the right side panel, configure: - **Project Defaults:** **C++** ⚠️ (NOT Blueprint!) - **Target Platform:** Desktop - - **Quality Preset:** Maximum (or Scalable for faster iteration) - - **Starter Content:** UNCHECKED (we don't need it) + - **Quality Preset:** Scalable (faster compile times, easier iteration) + - **Starter Content:** UNCHECKED (reduces project size and avoids unnecessary assets for this bullet-hell game) - **Raytracing:** UNCHECKED 7. At the bottom: @@ -42,41 +42,62 @@ This is the **C++ version** of Part 1. Unlike the Blueprint tutorial which creat Unreal will: 1. Generate C++ project files (~30 seconds) -2. **Open your IDE** (Visual Studio, Rider, or VS Code) -3. **Compile the initial project** (2-5 minutes, first time only) -4. Open Unreal Editor +2. **Open your IDE** (Visual Studio Code will launch if configured) +3. **Automatically start compiling** the initial project (2-5 minutes, first time only) + - You'll see a terminal/console window showing compilation progress + - On Linux: Uses clang++ or g++ to compile C++ files + - Progress shown as: "Compiling C++ source files...", "Linking...", etc. +4. Open Unreal Editor once compilation completes -> **⚠️ IMPORTANT:** Do NOT close the IDE or compilation window! Wait for compilation to finish. +> **⚠️ IMPORTANT:** Do NOT close the IDE or compilation window! Wait for compilation to finish. You'll see "Build succeeded" or similar message when done. ### Expected Result After compilation completes: -- **Unreal Editor opens** with an empty level -- **Your IDE is open** with the project (Visual Studio/Rider/VS Code) -- Main 3D viewport in the center -- Outliner panel on the right (showing "Untitled" level) +**Unreal Editor viewport shows:** +- Empty 3D viewport in the center with grid floor +- Main toolbar at the top (File, Edit, Window, etc.) +- Outliner panel on the right showing "Untitled" level actors +- Details panel on the right side (currently empty) +- Content Drawer button at bottom (click it or press `Ctrl+Space` to open) + +**VS Code (your IDE) shows:** +- BulletHellGame project folder open +- Left sidebar with file explorer showing `Source/` folder +- `STGPawn.h` and `STGPawn.cpp` or similar auto-generated files + +**What you should see in Unreal Editor:** +- A flat grid representing the game world (top-down view by default in new projects) +- No errors in the Output Log +- The word "Ready" or compilation success message in bottom-right corner + +> **NOTE:** If you see a 3D perspective view instead of top-down, don't worry - we'll fix that in Step 1.3. ### Troubleshooting
-IDE didn't open? +IDE didn't open? (Visual Studio Code on Linux/Arch) -- Check if Visual Studio or Rider is installed -- Go to `Edit → Editor Preferences → Source Code` -- Set "Source Code Editor" to your preferred IDE -- Right-click the `.uproject` file → "Generate Visual Studio project files" -- Open the `.sln` file in your IDE +On Arch Linux with VS Code: +- Install VS Code if not present: `sudo pacman -S code` +- Install Unreal Engine extension for VS Code +- In Unreal Editor: `Edit → Editor Preferences → Source Code` +- Set "Source Code Editor" to "Visual Studio Code" +- Right-click the `.uproject` file in file manager → "Generate VSCode project files" +- Open the project folder in VS Code
-Compilation failed? +Compilation failed? (Linux/Arch) -- Check the Output Log (Window → Developer Tools → Output Log) -- Common issue: Missing Visual Studio C++ tools - - Install "Desktop Development with C++" workload in Visual Studio Installer -- Try: `File → Refresh Visual Studio Project` +On Arch Linux: +- Ensure you have the required build tools: + - `sudo pacman -S base-devel clang lld` +- Check the Output Log (Window → Developer Tools → Output Log) for specific errors +- If missing libraries, install them via pacman +- Try regenerating project files: Right-click `.uproject` → "Generate VSCode project files"
@@ -107,6 +128,25 @@ BulletHellGame/ The `Source/` folder is what makes this a C++ project! This is where we'll add our game classes. +### Expected Result + +**In File Explorer (your Linux file manager):** +``` +BulletHellGame/ +├── BulletHellGame.uproject # Double-click to open project +├── Source/ # ✅ C++ source code folder +│ └── BulletHellGame/ +│ ├── BulletHellGame.h +│ ├── BulletHellGame.cpp +│ └── BulletHellGame.Build.cs +├── Content/ # Assets and Blueprints +├── Config/ # Project configuration +├── Binaries/ # Compiled game (gitignore this) +└── Intermediate/ # Build files (gitignore this) +``` + +**Verify the Source folder exists** - this confirms C++ is enabled! + --- ## Step 1.3: Set Up 2D Game View @@ -117,6 +157,18 @@ Same as Blueprint tutorial: 2. Click the dropdown that says "Perspective" 3. Select "Top" from the dropdown menu (`Alt + J`) +### Expected Result + +**Viewport changes:** +- Camera now looks straight down at the grid +- You see the game world from a bird's-eye view (perfect for bullet-hell) +- Grid appears as horizontal lines (X and Y axes visible) +- Objects will appear flat when placed in this view + +**Visual confirmation:** +- Top-left corner now shows "Top" instead of "Perspective" +- The viewport manipulation gizmo (3D arrows) now shows only X and Y axes prominently + --- ## Step 1.4: Create Folder Structure diff --git a/games/unreal/tutorial/part-2-cpp-create-player.md b/games/unreal/tutorial/part-2-cpp-create-player.md index f697f98..64061b9 100644 --- a/games/unreal/tutorial/part-2-cpp-create-player.md +++ b/games/unreal/tutorial/part-2-cpp-create-player.md @@ -435,18 +435,42 @@ Now we create a **minimal Blueprint** that inherits from our C++ class. This Blu ## Step 2.7: Test the Player -1. Drag `BP_Player` from Content Browser into the level -2. Press **Play** (`Alt+P`) -3. Press WASD keys - player should move! -4. Press Z or Space - you should see "FIRE!" in Output Log -5. Press X - you should see "SPECIAL ABILITY ACTIVATED!" once +1. Drag `BP_Player` from Content Browser into the level viewport +2. Position it near the center (coordinates around X=0, Y=0, Z=0) +3. Press **Play** (`Alt+P`) -### Expected Result: +### Expected Result -- ✅ Player moves with WASD -- ✅ Movement is clamped to bounds -- ✅ Fire input detected (logs message) -- ✅ Special ability works once +**In Play Mode Window:** +- You see the cone/cube player ship from above (top-down view) +- Ship is positioned at center of screen +- Background is the default gray/blue Unreal grid +- Camera follows the player from above + +**When pressing WASD keys:** +- **W** - Ship moves upward on screen (toward top) +- **S** - Ship moves downward on screen (toward bottom) +- **A** - Ship moves left +- **D** - Ship moves right +- Movement stops immediately when you release keys (no sliding) +- Ship stops at screen edges (cannot move outside bounds) + +**When pressing Z or Space:** +- You see "FIRE!" message appear in top-left corner of screen (yellow text) +- Message appears repeatedly while holding the button +- No bullets yet (we'll add those after creating bullet class) + +**When pressing X:** +- "SPECIAL ABILITY ACTIVATED!" appears once in top-left corner +- Pressing X again does nothing (ability used up) + +**Visual Check:** +- Ship model is visible (cone or cube shape) +- Ship rotates/faces the direction of movement +- No errors in Output Log +- Frame rate counter shows stable FPS (if enabled) + +**To exit Play mode:** Press `Esc` or click the "Stop" button in the toolbar --- diff --git a/games/unreal/tutorial/part-4-9-cpp-summary.md b/games/unreal/tutorial/part-4-9-cpp-summary.md deleted file mode 100644 index 821a07b..0000000 --- a/games/unreal/tutorial/part-4-9-cpp-summary.md +++ /dev/null @@ -1,210 +0,0 @@ -# Parts 4-9 (C++): Complete Game Implementation - -[← Previous: Part 3 (C++) - Create the Bullet](part-3-cpp-create-bullet.md) | [Back to Index](README.md) - ---- - -## Overview - -Parts 4-9 follow the same pattern: create C++ classes, copy-paste code, create minimal Blueprint children for visuals. - -Due to length constraints, this document summarizes Parts 4-9. Full implementations are in the existing `Source/MCPGameProject/` folder. - ---- - -## Part 4: Create the Enemy (C++) - -### Quick Summary: - -1. **Tools → New C++ Class** → Actor → Name: `STGEnemy` -2. Copy variables block (15 enemy stats with defaults) -3. Implement movement (sinusoidal wave pattern) -4. Implement firing (radial bullet burst) -5. Compile -6. Create `BP_Enemy` Blueprint child - -### Key Code (STGEnemy.h excerpt): - -```cpp -// 15 variables in one copy-paste! -UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stats") -int32 MaxHealth = 12; - -UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stats") -float VerticalSpeed = 220.0f; - -UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stats") -float HorizontalAmplitude = 250.0f; - -UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stats") -float FireInterval = 0.35f; - -UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stats") -int32 BulletsPerBurst = 20; -// ... etc -``` - -**Time saved:** Blueprint (90 min) vs C++ (25 min) = **65 min** ⚡ - -Full implementation: See `Source/MCPGameProject/STGEnemy.h/cpp` - ---- - -## Part 5: Create Enemy Spawner (C++) - -### Quick Summary: - -1. **Tools → New C++ Class** → Actor → Name: `STGEnemySpawner` -2. Copy spawner variables (spawn rate, area, max enemies) -3. Implement difficulty curve (spawn rate increases over time) -4. Compile -5. Place spawner in level - -### Key Code: - -```cpp -UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning") -float SpawnAreaHalfWidth = 900.0f; - -UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning") -float BaseSpawnRate = 2.0f; - -UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning") -int32 MaxSimultaneousEnemies = 120; -``` - -**Time: ~8 minutes** - ---- - -## Part 6: Create Game Director (C++) - -### Quick Summary: - -1. **Tools → New C++ Class** → Actor → Name: `STGGameDirector` -2. Manage game timer (300 seconds) -3. Handle victory/defeat conditions -4. Compile -5. Place in level - -### Key Code: - -```cpp -UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Game") -float GameDuration = 300.0f; - -UPROPERTY(BlueprintReadOnly, Category = "Game") -float ElapsedTime = 0.0f; - -UPROPERTY(BlueprintReadOnly, Category = "Game") -bool bGameActive = true; -``` - -**Time: ~10 minutes** - ---- - -## Part 7: Create UI (C++) - -### Quick Summary: - -For UI, we still use **Widget Blueprints** (visual layout) but bind to C++ for data. - -1. Create Widget Blueprint `WBP_HUD` in UI folder -2. Add Text blocks for Score, Lives, Timer -3. Bind to C++ properties via Blueprint events -4. Create in Player's BeginPlay - -**Time: ~15 minutes** - ---- - -## Part 8: Create Game Mode (C++) - -### Quick Summary: - -1. **Tools → New C++ Class** → Game Mode Base → Name: `STGGameMode` -2. Set default pawn class to STGPawn -3. Handle player death/respawn -4. Compile -5. Set in Project Settings → Maps & Modes - -### Key Code: - -```cpp -ASTGGameMode::ASTGGameMode() -{ - // Set default pawn - DefaultPawnClass = ASTGPawn::StaticClass(); -} -``` - -**Time: ~5 minutes** - ---- - -## Part 9: Final Setup (C++) - -### Quick Summary: - -1. Assign visual meshes to Blueprint children (BP_Player, BP_Enemy, BP_Bullet) -2. Set materials and colors -3. Create background plane -4. Test complete game -5. Build for standalone - -**Time: ~15 minutes** - ---- - -## Total Time Comparison - -| Part | Blueprint Time | C++ Time | Saved | -|------|---------------|----------|-------| -| 1. Project Setup | 5 min | 10 min | -5 min (one-time) | -| 2. Player | 60 min | 15 min | 45 min ⚡ | -| 3. Bullet | 30 min | 10 min | 20 min ⚡ | -| 4. Enemy | 90 min | 25 min | 65 min ⚡ | -| 5. Spawner | 40 min | 8 min | 32 min ⚡ | -| 6. Game Director | 45 min | 10 min | 35 min ⚡ | -| 7. UI | 30 min | 15 min | 15 min ⚡ | -| 8. Game Mode | 20 min | 5 min | 15 min ⚡ | -| 9. Final Setup | 40 min | 15 min | 25 min ⚡ | -| **TOTAL** | **~6-8 hours** | **~2-3 hours** | **~4-5 hours** ⚡⚡⚡ | - ---- - -## Full Reference Implementations - -All complete C++ files are available in: - -``` -Source/MCPGameProject/ -├── STGPawn.h/cpp (Player) -├── STGProjectile.h/cpp (Bullets) -├── STGEnemy.h/cpp (Enemies) -├── STGGameMode.h/cpp (Game rules) -├── STGHUD.h/cpp (UI binding) -└── STGEffects.h/cpp (Visual effects) -``` - -You can: -1. Copy these files directly to your project -2. Modify as needed -3. Compile and use - ---- - -## Key Takeaway - -**The C++ approach saves 60% development time** by: - -✅ Copy-pasting variable blocks (seconds vs minutes) -✅ Using IDE tools (autocomplete, refactoring) -✅ Version control friendly (readable diffs) -✅ Type-safe (compiler catches errors) -✅ Easier to maintain (code is documentation) - ---- - -[← Previous: Part 3 (C++) - Create the Bullet](part-3-cpp-create-bullet.md) | [Back to Index](README.md) diff --git a/games/unreal/tutorial/part-4-cpp-create-enemy.md b/games/unreal/tutorial/part-4-cpp-create-enemy.md new file mode 100644 index 0000000..a120e82 --- /dev/null +++ b/games/unreal/tutorial/part-4-cpp-create-enemy.md @@ -0,0 +1,548 @@ +# Part 4 (C++): Create the Enemy + +[← Previous: Part 3 (C++) - Create the Bullet](part-3-cpp-create-bullet.md) | [Back to Index](README.md) | [Next: Part 5 (C++) - Create Enemy Spawner →](part-5-cpp-create-spawner.md) + +--- + +## Overview + +Create enemy ships in C++. Again, we'll copy-paste all 15 enemy variables instead of clicking UI 75+ times. + +**Time comparison:** +- Blueprint approach (Part 4): ~90 minutes (16 variables + complex Blueprint nodes) +- C++ approach (this part): ~25 minutes (copy-paste code) + +--- + +## Step 4.1: Create STGEnemy C++ Class + +### In Unreal Editor: + +1. Go to **Tools → New C++ Class** (top menu bar) +2. In the "Choose Parent Class" window: + - Click **"Actor"** (enemies are independent game objects) + - Click **"Next"** + +3. In the "Name Your New Actor" window: + - **Name:** `STGEnemy` + - **Path:** Should be `Source/BulletHellGame/` (default) + - Click **"Create Class"** + +### What Happens Next: + +1. Unreal generates `STGEnemy.h` and `STGEnemy.cpp` +2. VS Code opens with the new files +3. Project compiles automatically (~30-60 seconds) +4. Unreal Editor refreshes + +--- + +## Step 4.2: Define Enemy Variables in STGEnemy.h + +**This is where the magic happens!** Instead of clicking UI 75+ times (16 variables × ~5 clicks each), we'll **copy-paste** all variables at once. + +### Open STGEnemy.h in VS Code + +Replace the entire file content with: + +```cpp +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "STGEnemy.generated.h" + +class UStaticMeshComponent; +class UBoxComponent; + +UCLASS() +class BULLETHELLGAME_API ASTGEnemy : public AActor +{ + GENERATED_BODY() + +public: + ASTGEnemy(); + +protected: + virtual void BeginPlay() override; + +public: + virtual void Tick(float DeltaTime) override; + + // ===== COMPONENTS ===== + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components") + UStaticMeshComponent* MeshComp; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components") + UBoxComponent* CollisionComp; + + // ===== HEALTH & SCORE (copy-paste all 15 variables!) ===== + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stats") + int32 MaxHealth = 12; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Stats") + int32 CurrentHealth = 12; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stats") + int32 ScoreValue = 50; + + // ===== MOVEMENT ===== + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stats") + float VerticalSpeed = 220.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stats") + float HorizontalAmplitude = 250.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stats") + float HorizontalFrequency = 1.8f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stats") + float DespawnY = -750.0f; + + // ===== FIRING ===== + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stats") + float FireInterval = 0.35f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stats") + int32 BulletsPerBurst = 20; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stats") + float BurstSpread = 360.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stats") + float EnemyBulletSpeed = 1000.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stats") + float EnemyBulletLifetime = 6.0f; + + // ===== FUNCTIONS ===== + void Fire(); + void HandleDamage(float DamageAmount); + + UFUNCTION() + void OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, + UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, + bool bFromSweep, const FHitResult& SweepResult); + +private: + FTimerHandle TimerHandle_Fire; + float BaseX = 0.0f; + float WaveSeed = 0.0f; + float ElapsedTime = 0.0f; +}; +``` + +### What Just Happened? + +You just defined **15 gameplay variables + 2 components** with default values in ~30 seconds! + +In the Blueprint tutorial, this would require: +- 16 variables × 5 clicks each = **80 clicks** +- **~20 minutes** of manual work +- High chance of typos or wrong types + +**Time saved: 19.5 minutes** ⚡ + +--- + +## Step 4.3: Implement STGEnemy Logic + +### Open STGEnemy.cpp in VS Code + +Replace the file content with: + +```cpp +#include "STGEnemy.h" +#include "Components/StaticMeshComponent.h" +#include "Components/BoxComponent.h" +#include "Kismet/GameplayStatics.h" +#include "STGProjectile.h" +#include "STGPawn.h" + +ASTGEnemy::ASTGEnemy() +{ + PrimaryActorTick.bCanEverTick = true; + + // Root component + RootComponent = CreateDefaultSubobject(TEXT("Root")); + + // Mesh component + MeshComp = CreateDefaultSubobject(TEXT("MeshComp")); + MeshComp->SetupAttachment(RootComponent); + MeshComp->SetCollisionProfileName("NoCollision"); + + // Load cube mesh + static ConstructorHelpers::FObjectFinder CubeMesh(TEXT("/Engine/BasicShapes/Cube")); + if (CubeMesh.Succeeded()) + { + MeshComp->SetStaticMesh(CubeMesh.Object); + MeshComp->SetRelativeScale3D(FVector(0.6f, 0.6f, 0.1f)); + } + + // Collision component + CollisionComp = CreateDefaultSubobject(TEXT("CollisionComp")); + CollisionComp->SetupAttachment(RootComponent); + CollisionComp->SetBoxExtent(FVector(30.f, 30.f, 10.f)); + CollisionComp->SetCollisionProfileName("OverlapAllDynamic"); + CollisionComp->SetGenerateOverlapEvents(true); + CollisionComp->OnComponentBeginOverlap.AddDynamic(this, &ASTGEnemy::OnOverlapBegin); +} + +void ASTGEnemy::BeginPlay() +{ + Super::BeginPlay(); + + CurrentHealth = MaxHealth; + BaseX = GetActorLocation().X; + WaveSeed = FMath::FRand() * 1000.0f; + ElapsedTime = 0.0f; + + // Start firing timer + GetWorldTimerManager().SetTimer(TimerHandle_Fire, this, &ASTGEnemy::Fire, + FireInterval, true, FireInterval); +} + +void ASTGEnemy::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + + ElapsedTime += DeltaTime; + + // Sinusoidal movement (wave pattern) + FVector NewLocation = GetActorLocation(); + + // Move downward + NewLocation.X -= VerticalSpeed * DeltaTime; + + // Horizontal sine wave + float HorizontalOffset = HorizontalAmplitude * FMath::Sin( + HorizontalFrequency * (ElapsedTime + WaveSeed) + ); + NewLocation.Y = BaseX + HorizontalOffset; + + SetActorLocation(NewLocation); + + // Check if enemy should despawn (moved off screen) + if (NewLocation.X < DespawnY) + { + Destroy(); + } +} + +void ASTGEnemy::Fire() +{ + // Fire radial burst of bullets + for (int32 i = 0; i < BulletsPerBurst; i++) + { + // Calculate angle for this bullet in the burst + float AngleDeg = (BurstSpread / BulletsPerBurst) * i; + float AngleRad = FMath::DegreesToRadians(AngleDeg); + + // Calculate direction vector + FVector Direction = FVector( + FMath::Cos(AngleRad), + FMath::Sin(AngleRad), + 0.0f + ); + + // Spawn location slightly below enemy + FVector SpawnLocation = GetActorLocation() + FVector(0.f, 0.f, -30.f); + + // Spawn bullet + ASTGProjectile* Bullet = GetWorld()->SpawnActor( + ASTGProjectile::StaticClass(), + SpawnLocation, + Direction.Rotation() + ); + + if (Bullet) + { + Bullet->bIsPlayerBullet = false; + Bullet->SetSpeed(EnemyBulletSpeed); + Bullet->SetBulletColor(FLinearColor::Red); + Bullet->Lifetime = EnemyBulletLifetime; + } + } +} + +void ASTGEnemy::HandleDamage(float DamageAmount) +{ + CurrentHealth -= DamageAmount; + + if (CurrentHealth <= 0) + { + // Award score to player + ASTGPawn* Player = Cast(UGameplayStatics::GetPlayerPawn(GetWorld(), 0)); + if (Player) + { + Player->AddScore(ScoreValue); + } + + // Destroy enemy + Destroy(); + } +} + +void ASTGEnemy::OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, + UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, + bool bFromSweep, const FHitResult& SweepResult) +{ + // Enemy collides with player - damage player + if (OtherActor && OtherActor != this) + { + ASTGPawn* Player = Cast(OtherActor); + if (Player) + { + Player->TakeHit(1); + Destroy(); // Enemy dies on collision with player + } + } +} +``` + +--- + +## Step 4.4: Update Bullet Collision Logic + +Now that enemies exist, we can complete the bullet collision logic. + +### Open STGProjectile.cpp + +Find the `OnOverlapBegin` function and update the player bullet section: + +```cpp +void ASTGProjectile::OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, + UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, + bool bFromSweep, const FHitResult& SweepResult) +{ + if (OtherActor && OtherActor != this) + { + if (bIsPlayerBullet) + { + // Player bullet hits enemy + ASTGEnemy* Enemy = Cast(OtherActor); + if (Enemy) + { + Enemy->HandleDamage(Damage); + Destroy(); + } + } + else + { + // Enemy bullet hits player + ASTGPawn* Player = Cast(OtherActor); + if (Player) + { + Player->TakeHit(1); + Destroy(); + } + } + } +} +``` + +### Add include at top of STGProjectile.cpp: + +```cpp +#include "STGEnemy.h" // Add this line +``` + +--- + +## Step 4.5: Compile the Code + +### In Unreal Editor: + +1. Click **"Compile"** button (bottom right) +2. Wait for compilation (~30-60 seconds) +3. Check for errors in Output Log + +OR + +### In VS Code: + +1. Open terminal in VS Code (`Ctrl+` `) +2. Run build command (project-specific) +3. Wait for build to finish + +### Expected Result: + +- ✅ Compilation succeeds +- ✅ "C++ Classes" folder in Content Browser shows `STGEnemy` +- ✅ No errors in Output Log + +--- + +## Step 4.6: Create Blueprint Child + +Create a **minimal Blueprint** that inherits from STGEnemy. This Blueprint is ONLY for visual assets! + +1. In Content Browser, navigate to `Content → Blueprints` +2. Right-click → **Blueprint Class** +3. In "Pick Parent Class" window: + - Click "All Classes" dropdown + - Search for `STGEnemy` + - Select it + - Click "Select" +4. Name it: `BP_Enemy` +5. Double-click to open + +### In the Blueprint Editor: + +**Components (Left Panel):** +- You'll see `MeshComp` and `CollisionComp` from C++ +- NO NEED to add them manually! + +**Variables (My Blueprint Panel):** +- You'll see all 15 variables from C++ (MaxHealth, VerticalSpeed, etc.) +- NO NEED to create them! +- All default values are already set from C++! + +**What to do:** +- NOTHING for now! All logic is in C++ +- Compile and Save + +--- + +## Step 4.7: Test Enemy Spawning + +1. Drag `BP_Enemy` from Content Browser into the level +2. Position it above the player (Y = 0, X = 500 or so) +3. Press **Play** (`Alt+P`) + +### Expected Result + +**In Play Mode Window:** + +**Enemy behavior:** +- ✅ Enemy appears above player +- ✅ Enemy moves downward smoothly +- ✅ Enemy moves in sine wave pattern (left-right oscillation) +- ✅ Enemy fires red bullet bursts in circular pattern +- ✅ Bullets spread out in all directions (360-degree burst) + +**Player vs Enemy:** +- ✅ Player bullets (green) destroy enemy when they hit +- ✅ Enemy bullets (red) damage player when they hit +- ✅ Player takes damage and loses life when hit +- ✅ Touching enemy directly damages player and destroys enemy + +**Visual confirmation:** +- Enemy is a cube/box shape (red-ish by default) +- Enemy fires 20 red bullets in a circular burst every 0.35 seconds +- Player can shoot enemy with green bullets +- Score increases when enemy is destroyed (check Output Log or will see in UI later) + +**To test collision:** +- Fly player into enemy - both should take damage +- Let enemy bullets hit player - player should take damage +- Shoot enemy with player bullets - enemy health should decrease + +**Despawning:** +- After ~10 seconds, enemy moves off bottom of screen and disappears (auto-destroy) + +--- + +## Step 4.8: Update Player Special Ability + +Now that enemies exist, we can complete the special ability to destroy all enemies on screen. + +### Open STGPawn.cpp + +Find the `UseSpecial()` function and replace it with: + +```cpp +void ASTGPawn::UseSpecial() +{ + if (!bSpecialUsed) + { + bSpecialUsed = true; + + // Destroy all enemies on screen + TArray FoundEnemies; + UGameplayStatics::GetAllActorsOfClass(GetWorld(), ASTGEnemy::StaticClass(), FoundEnemies); + for (AActor* Enemy : FoundEnemies) + { + Enemy->Destroy(); + } + + // Destroy all enemy bullets (not player bullets) + TArray FoundBullets; + UGameplayStatics::GetAllActorsOfClass(GetWorld(), ASTGProjectile::StaticClass(), FoundBullets); + for (AActor* Bullet : FoundBullets) + { + ASTGProjectile* Projectile = Cast(Bullet); + if (Projectile && !Projectile->bIsPlayerBullet) + { + Projectile->Destroy(); + } + } + + UE_LOG(LogTemp, Warning, TEXT("SPECIAL ABILITY - Screen Cleared!")); + } +} +``` + +### Add include at top of STGPawn.cpp: + +```cpp +#include "STGEnemy.h" // Add this line +``` + +### Compile again! + +--- + +## Step 4.9: Test Special Ability + +1. Place several `BP_Enemy` instances in the level (or duplicate the existing one) +2. Press **Play** +3. Let enemies spawn and fire bullets +4. Press **X** to activate special ability + +### Expected Result in Play Mode: + +- ✅ All enemies disappear instantly when X is pressed +- ✅ All red enemy bullets disappear instantly +- ✅ Player's green bullets remain (not destroyed) +- ✅ Message "SPECIAL ABILITY - Screen Cleared!" appears in Output Log +- ✅ Pressing X again does nothing (ability can only be used once per game) + +--- + +## Comparison Summary + +### Blueprint Approach (Part 4): + +1. Create BP_Enemy Blueprint ✅ +2. Add 2 components manually +3. Add 16 variables one-by-one = **~20 minutes of clicking** +4. Create Blueprint nodes for movement (~40 nodes with sine wave math) +5. Create Blueprint nodes for firing (~30 nodes for radial burst) +6. Create Blueprint nodes for collision detection +7. Create Blueprint nodes for health/damage system +8. Update BP_Player special ability nodes +9. **Total: ~90 minutes** + +### C++ Approach (This Part): + +1. Create STGEnemy C++ class ✅ +2. Copy-paste header file with 15 variables **30 seconds** +3. Copy-paste implementation file with all logic +4. Update bullet collision (5 lines) +5. Update player special ability (10 lines) +6. Compile +7. Create BP_Enemy (inherits everything from C++) +8. **Total: ~25 minutes** + +**Time saved: 65 minutes** ⚡⚡⚡ + +--- + +## What's Next? + +In Part 5, we'll create the enemy spawner in C++. You'll see how easy it is to manage spawn rates and difficulty curves with code! + +--- + +[← Previous: Part 3 (C++) - Create the Bullet](part-3-cpp-create-bullet.md) | [Back to Index](README.md) | [Next: Part 5 (C++) - Create Enemy Spawner →](part-5-cpp-create-spawner.md) diff --git a/games/unreal/tutorial/part-5-cpp-create-spawner.md b/games/unreal/tutorial/part-5-cpp-create-spawner.md new file mode 100644 index 0000000..8f8f1ef --- /dev/null +++ b/games/unreal/tutorial/part-5-cpp-create-spawner.md @@ -0,0 +1,247 @@ +# Part 5 (C++): Create Enemy Spawner + +[← Previous: Part 4 (C++) - Create the Enemy](part-4-cpp-create-enemy.md) | [Back to Index](README.md) | [Next: Part 6 (C++) - Create Game Director →](part-6-cpp-create-game-director.md) + +--- + +## Overview + +Create an enemy spawner that gradually increases difficulty over time. Copy-paste variables instead of manual UI configuration. + +**Time comparison:** +- Blueprint: ~40 minutes (variables + curve setup + Blueprint nodes) +- C++ (this part): ~10 minutes (copy-paste code) + +--- + +## Step 5.1: Create STGEnemySpawner C++ Class + +1. **Tools → New C++ Class** → Actor → Name: `STGEnemySpawner` +2. Wait for compilation + +--- + +## Step 5.2: Define Spawner Variables + +### Open STGEnemySpawner.h in VS Code + +Replace content with: + +```cpp +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "STGEnemySpawner.generated.h" + +UCLASS() +class BULLETHELLGAME_API ASTGEnemySpawner : public AActor +{ + GENERATED_BODY() + +public: + ASTGEnemySpawner(); + +protected: + virtual void BeginPlay() override; + +public: + virtual void Tick(float DeltaTime) override; + + // ===== SPAWNING VARIABLES ===== + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning") + float SpawnAreaHalfWidth = 900.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning") + float GameDuration = 300.0f; // 5 minutes + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning") + int32 MaxSimultaneousEnemies = 120; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning") + float BaseSpawnInterval = 2.0f; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Spawning") + float ElapsedTime = 0.0f; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Spawning") + bool bSpawningActive = true; + +private: + float SpawnTimer = 0.0f; + float CurrentSpawnInterval = 2.0f; + + void SpawnEnemy(); + float CalculateSpawnInterval(); + FVector GetRandomSpawnLocation(); +}; +``` + +--- + +## Step 5.3: Implement Spawner Logic + +### Open STGEnemySpawner.cpp + +Replace with: + +```cpp +#include "STGEnemySpawner.h" +#include "STGEnemy.h" +#include "Kismet/GameplayStatics.h" + +ASTGEnemySpawner::ASTGEnemySpawner() +{ + PrimaryActorTick.bCanEverTick = true; +} + +void ASTGEnemySpawner::BeginPlay() +{ + Super::BeginPlay(); + + ElapsedTime = 0.0f; + SpawnTimer = 0.0f; + CurrentSpawnInterval = BaseSpawnInterval; +} + +void ASTGEnemySpawner::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + + if (!bSpawningActive) + return; + + ElapsedTime += DeltaTime; + + // Stop spawning after game duration + if (ElapsedTime >= GameDuration) + { + bSpawningActive = false; + return; + } + + // Update spawn interval based on difficulty curve + CurrentSpawnInterval = CalculateSpawnInterval(); + + // Spawn timer + SpawnTimer -= DeltaTime; + if (SpawnTimer <= 0.0f) + { + // Check enemy count + TArray FoundEnemies; + UGameplayStatics::GetAllActorsOfClass(GetWorld(), ASTGEnemy::StaticClass(), FoundEnemies); + + if (FoundEnemies.Num() < MaxSimultaneousEnemies) + { + SpawnEnemy(); + } + + SpawnTimer = CurrentSpawnInterval; + } +} + +void ASTGEnemySpawner::SpawnEnemy() +{ + FVector SpawnLocation = GetRandomSpawnLocation(); + FRotator SpawnRotation = FRotator::ZeroRotator; + + ASTGEnemy* NewEnemy = GetWorld()->SpawnActor( + ASTGEnemy::StaticClass(), + SpawnLocation, + SpawnRotation + ); +} + +float ASTGEnemySpawner::CalculateSpawnInterval() +{ + // Difficulty curve: spawn faster as time progresses + float GameProgress = ElapsedTime / GameDuration; // 0.0 to 1.0 + + // Start at BaseSpawnInterval, reduce to 0.5 seconds at end + float MinInterval = 0.5f; + float Interval = FMath::Lerp(BaseSpawnInterval, MinInterval, GameProgress); + + return FMath::Max(Interval, MinInterval); +} + +FVector ASTGEnemySpawner::GetRandomSpawnLocation() +{ + // Spawn at top of screen, random X position + FVector SpawnLoc = GetActorLocation(); + SpawnLoc.Y = FMath::FRandRange(-SpawnAreaHalfWidth, SpawnAreaHalfWidth); + SpawnLoc.X = 800.0f; // Top of play area + SpawnLoc.Z = 0.0f; + + return SpawnLoc; +} +``` + +--- + +## Step 5.4: Compile + +1. Click **Compile** in Unreal Editor +2. Wait for compilation +3. Check for errors + +--- + +## Step 5.5: Place Spawner in Level + +1. From Content Browser "C++ Classes" folder, find `STGEnemySpawner` +2. Can either: + - **Option A**: Drag directly into level (no Blueprint needed!) + - **Option B**: Create `BP_EnemySpawner` Blueprint child for tweaking values in editor + +3. Position spawner at origin (0, 0, 0) or anywhere - location doesn't matter for spawning + +--- + +## Step 5.6: Test Spawning + +1. Remove any manually-placed enemies from level +2. Make sure spawner is placed +3. Press **Play** + +### Expected Result in Play Mode: + +**Spawning behavior:** +- ✅ Enemies spawn at top of screen every ~2 seconds initially +- ✅ Spawn rate gradually increases (gets faster over time) +- ✅ Enemies spawn at random horizontal positions +- ✅ Maximum of 120 enemies on screen at once +- ✅ After 5 minutes (300 seconds), spawning stops + +**Visual confirmation:** +- Enemies appear from top edge of screen +- Each spawns at a different Y position (left-right spread) +- As time passes, enemies spawn more frequently +- Game becomes progressively harder + +**Performance:** +- Even with many enemies, game maintains 60 FPS +- No lag or stuttering when many enemies/bullets on screen + +--- + +## Comparison Summary + +### Blueprint: ~40 minutes +- Create spawner Blueprint +- Add 6 variables manually +- Create spawn rate curve asset +- Complex Blueprint nodes for curve sampling +- Random position calculation nodes +- Enemy counting logic + +### C++: ~10 minutes +- Create C++ class +- Copy-paste variables and logic +- Compile +- Place in level + +**Time saved: 30 minutes** ⚡ + +--- + +[← Previous: Part 4 (C++) - Create the Enemy](part-4-cpp-create-enemy.md) | [Back to Index](README.md) | [Next: Part 6 (C++) - Create Game Director →](part-6-cpp-create-game-director.md) diff --git a/games/unreal/tutorial/part-6-cpp-create-game-director.md b/games/unreal/tutorial/part-6-cpp-create-game-director.md new file mode 100644 index 0000000..56f0e91 --- /dev/null +++ b/games/unreal/tutorial/part-6-cpp-create-game-director.md @@ -0,0 +1,167 @@ +# Part 6 (C++): Create Game Director + +[← Previous: Part 5 (C++) - Create Enemy Spawner](part-5-cpp-create-spawner.md) | [Back to Index](README.md) | [Next: Part 7 (C++) - Create UI →](part-7-cpp-create-ui.md) + +--- + +## Overview + +Create a Game Director to manage the game timer, victory/defeat conditions, and overall game state. + +**Time:** ~10 minutes + +--- + +## Step 6.1: Create STGGameDirector C++ Class + +1. **Tools → New C++ Class** → Actor → Name: `STGGameDirector` +2. Wait for compilation + +--- + +## Step 6.2: Define Game Director Variables + +### STGGameDirector.h: + +```cpp +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "STGGameDirector.generated.h" + +UCLASS() +class BULLETHELLGAME_API ASTGGameDirector : public AActor +{ + GENERATED_BODY() + +public: + ASTGGameDirector(); + +protected: + virtual void BeginPlay() override; + +public: + virtual void Tick(float DeltaTime) override; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Game") + float GameDuration = 300.0f; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Game") + float ElapsedTime = 0.0f; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Game") + bool bGameActive = true; + + void OnPlayerDied(); + void OnVictory(); + void OnGameOver(); +}; +``` + +--- + +## Step 6.3: Implement Game Director + +### STGGameDirector.cpp: + +```cpp +#include "STGGameDirector.h" +#include "Kismet/GameplayStatics.h" + +ASTGGameDirector::ASTGGameDirector() +{ + PrimaryActorTick.bCanEverTick = true; +} + +void ASTGGameDirector::BeginPlay() +{ + Super::BeginPlay(); + ElapsedTime = 0.0f; + bGameActive = true; +} + +void ASTGGameDirector::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + + if (!bGameActive) + return; + + ElapsedTime += DeltaTime; + + // Check for victory (survived full duration) + if (ElapsedTime >= GameDuration) + { + OnVictory(); + } +} + +void ASTGGameDirector::OnPlayerDied() +{ + OnGameOver(); +} + +void ASTGGameDirector::OnVictory() +{ + bGameActive = false; + UE_LOG(LogTemp, Warning, TEXT("VICTORY! You survived %f seconds!"), ElapsedTime); + + // Pause game + UGameplayStatics::SetGamePaused(GetWorld(), true); +} + +void ASTGGameDirector::OnGameOver() +{ + bGameActive = false; + UE_LOG(LogTemp, Warning, TEXT("GAME OVER! Survived %f seconds"), ElapsedTime); + + // Pause game + UGameplayStatics::SetGamePaused(GetWorld(), true); +} +``` + +--- + +## Step 6.4: Update Player Death to Notify Director + +### In STGPawn.cpp, update HandleDeath(): + +```cpp +void ASTGPawn::HandleDeath() +{ + SetActorHiddenInGame(true); + + // Find and notify Game Director + TArray FoundDirectors; + UGameplayStatics::GetAllActorsOfClass(GetWorld(), ASTGGameDirector::StaticClass(), FoundDirectors); + if (FoundDirectors.Num() > 0) + { + ASTGGameDirector* Director = Cast(FoundDirectors[0]); + if (Director) + { + Director->OnPlayerDied(); + } + } +} +``` + +Add include: `#include "STGGameDirector.h"` + +--- + +## Step 6.5: Place Director in Level and Test + +1. Drag `STGGameDirector` from C++ Classes into level +2. Press Play +3. Wait 5 minutes OR let player die + +### Expected Result: + +- ✅ Timer counts up from 0 +- ✅ After 300 seconds: "VICTORY!" message, game pauses +- ✅ If player dies: "GAME OVER!" message, game pauses + +--- + +[← Previous: Part 5 (C++) - Create Enemy Spawner](part-5-cpp-create-spawner.md) | [Back to Index](README.md) | [Next: Part 7 (C++) - Create UI →](part-7-cpp-create-ui.md) diff --git a/games/unreal/tutorial/part-7-cpp-create-ui.md b/games/unreal/tutorial/part-7-cpp-create-ui.md new file mode 100644 index 0000000..415d5c2 --- /dev/null +++ b/games/unreal/tutorial/part-7-cpp-create-ui.md @@ -0,0 +1,209 @@ +# Part 7 (C++): Create UI + +[← Previous: Part 6 (C++) - Create Game Director](part-6-cpp-create-game-director.md) | [Back to Index](README.md) | [Next: Part 8 (C++) - Create Game Mode →](part-8-cpp-create-game-mode.md) + +--- + +## Overview + +For UI, we use **Widget Blueprints** for visual layout (drag-and-drop is better for UI design), but bind to C++ properties for data. + +**Time:** ~15 minutes + +--- + +## Step 7.1: Create 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 + +--- + +## Step 7.2: Design HUD Layout + +In Widget Designer: + +1. **Add Text blocks** (from Palette → Common): + - `txt_Score` - Display score + - `txt_Lives` - Display lives + - `txt_Timer` - Display time remaining + +2. **Position them** (top-left corner): + - Score: Top-left (0, 0) + - Lives: Below score (0, 30) + - Timer: Below lives (0, 60) + +3. **Style text:** + - Font Size: 24 + - Color: White + - Set default text: "Score: 0", "Lives: 3", "Time: 300" + +4. **Compile and Save** + +--- + +## Step 7.3: Create HUD Manager C++ Class + +We'll create a simple C++ class to update the UI. + +### Create STGHUDManager.h: + +```cpp +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "STGHUDManager.generated.h" + +UCLASS() +class BULLETHELLGAME_API ASTGHUDManager : public AActor +{ + GENERATED_BODY() + +public: + ASTGHUDManager(); + +protected: + virtual void BeginPlay() override; + +public: + virtual void Tick(float DeltaTime) override; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UI") + TSubclassOf HUDWidgetClass; + + UFUNCTION(BlueprintCallable, Category = "UI") + void UpdateScore(int32 NewScore); + + UFUNCTION(BlueprintCallable, Category = "UI") + void UpdateLives(int32 NewLives); + + UFUNCTION(BlueprintCallable, Category = "UI") + void UpdateTimer(float TimeRemaining); + +private: + class UUserWidget* HUDWidget; +}; +``` + +### STGHUDManager.cpp: + +```cpp +#include "STGHUDManager.h" +#include "Blueprint/UserWidget.h" +#include "Components/TextBlock.h" + +ASTGHUDManager::ASTGHUDManager() +{ + PrimaryActorTick.bCanEverTick = true; +} + +void ASTGHUDManager::BeginPlay() +{ + Super::BeginPlay(); + + if (HUDWidgetClass) + { + HUDWidget = CreateWidget(GetWorld(), HUDWidgetClass); + if (HUDWidget) + { + HUDWidget->AddToViewport(); + } + } +} + +void ASTGHUDManager::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); +} + +void ASTGHUDManager::UpdateScore(int32 NewScore) +{ + if (HUDWidget) + { + UTextBlock* ScoreText = Cast(HUDWidget->GetWidgetFromName(TEXT("txt_Score"))); + if (ScoreText) + { + ScoreText->SetText(FText::Format(FText::FromString("Score: {0}"), NewScore)); + } + } +} + +void ASTGHUDManager::UpdateLives(int32 NewLives) +{ + if (HUDWidget) + { + UTextBlock* LivesText = Cast(HUDWidget->GetWidgetFromName(TEXT("txt_Lives"))); + if (LivesText) + { + LivesText->SetText(FText::Format(FText::FromString("Lives: {0}"), NewLives)); + } + } +} + +void ASTGHUDManager::UpdateTimer(float TimeRemaining) +{ + if (HUDWidget) + { + UTextBlock* TimerText = Cast(HUDWidget->GetWidgetFromName(TEXT("txt_Timer"))); + if (TimerText) + { + int32 Seconds = FMath::CeilToInt(TimeRemaining); + TimerText->SetText(FText::Format(FText::FromString("Time: {0}s"), Seconds)); + } + } +} +``` + +--- + +## Step 7.4: Integrate HUD with Game + +1. Place `STGHUDManager` in level +2. Select it, in Details panel: + - Set **HUD Widget Class** → `WBP_HUD` + +3. Update `STGGameDirector` to update timer: + +```cpp +void ASTGGameDirector::Tick(float DeltaTime) +{ + // ... existing code ... + + // Update HUD + TArray FoundManagers; + UGameplayStatics::GetAllActorsOfClass(GetWorld(), ASTGHUDManager::StaticClass(), FoundManagers); + if (FoundManagers.Num() > 0) + { + ASTGHUDManager* HUDMgr = Cast(FoundManagers[0]); + if (HUDMgr) + { + HUDMgr->UpdateTimer(GameDuration - ElapsedTime); + } + } +} +``` + +4. Update `STGPawn` to update score/lives when they change + +--- + +## Step 7.5: Test UI + +Press Play + +### Expected Result: + +**In Play Mode:** +- ✅ UI appears in top-left corner +- ✅ "Score: 0", "Lives: 3", "Time: 300" displayed +- ✅ Timer counts down from 300 +- ✅ Score increases when enemies are destroyed +- ✅ Lives decrease when player is hit +- ✅ Text is readable (white on dark background) + +--- + +[← Previous: Part 6 (C++) - Create Game Director](part-6-cpp-create-game-director.md) | [Back to Index](README.md) | [Next: Part 8 (C++) - Create Game Mode →](part-8-cpp-create-game-mode.md) diff --git a/games/unreal/tutorial/part-8-cpp-create-game-mode.md b/games/unreal/tutorial/part-8-cpp-create-game-mode.md new file mode 100644 index 0000000..d67919f --- /dev/null +++ b/games/unreal/tutorial/part-8-cpp-create-game-mode.md @@ -0,0 +1,96 @@ +# Part 8 (C++): Create Game Mode + +[← Previous: Part 7 (C++) - Create UI](part-7-cpp-create-ui.md) | [Back to Index](README.md) | [Next: Part 9 (C++) - Final Setup →](part-9-cpp-final-setup.md) + +--- + +## Overview + +Create a custom Game Mode to set the default pawn class and manage game rules. + +**Time:** ~5 minutes + +--- + +## Step 8.1: Create STGGameMode C++ Class + +1. **Tools → New C++ Class** +2. Choose **"Game Mode Base"** as parent +3. Name: `STGGameMode` +4. Create Class + +--- + +## Step 8.2: Define Game Mode + +### STGGameMode.h: + +```cpp +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/GameModeBase.h" +#include "STGGameMode.generated.h" + +UCLASS() +class BULLETHELLGAME_API ASTGGameMode : public AGameModeBase +{ + GENERATED_BODY() + +public: + ASTGGameMode(); +}; +``` + +### STGGameMode.cpp: + +```cpp +#include "STGGameMode.h" +#include "STGPawn.h" + +ASTGGameMode::ASTGGameMode() +{ + // Set default pawn class to our player + DefaultPawnClass = ASTGPawn::StaticClass(); + + // Disable auto-possess (we'll handle spawning ourselves) + bStartPlayersAsSpectators = false; +} +``` + +--- + +## Step 8.3: Set Game Mode in Project Settings + +1. **Edit → Project Settings** +2. **Project → Maps & Modes** +3. Under "Default Modes": + - **Default GameMode** → Select `STGGameMode` +4. Close Project Settings + +--- + +## Step 8.4: Test + +Create a new level or use existing one: + +1. **File → New Level** → Empty Level +2. Save as `BulletHellLevel` +3. Add: + - Player Start (from Place Actors panel) + - STGEnemySpawner + - STGGameDirector + - STGHUDManager + - Directional Light (for visibility) + +4. Press Play + +### Expected Result: + +- ✅ Player automatically spawns at Player Start location +- ✅ No need to manually drag BP_Player into level +- ✅ All game systems work together + +--- + +[← Previous: Part 7 (C++) - Create UI](part-7-cpp-create-ui.md) | [Back to Index](README.md) | [Next: Part 9 (C++) - Final Setup →](part-9-cpp-final-setup.md) diff --git a/games/unreal/tutorial/part-9-cpp-final-setup.md b/games/unreal/tutorial/part-9-cpp-final-setup.md new file mode 100644 index 0000000..b09c5c0 --- /dev/null +++ b/games/unreal/tutorial/part-9-cpp-final-setup.md @@ -0,0 +1,160 @@ +# Part 9 (C++): Final Setup + +[← Previous: Part 8 (C++) - Create Game Mode](part-8-cpp-create-game-mode.md) | [Back to Index](README.md) + +--- + +## Overview + +Polish the game with visual assets, materials, and final tweaks. + +**Time:** ~15 minutes + +--- + +## Step 9.1: Assign Better Visuals to Blueprints + +Now we use Blueprints for what they're good at: assigning visual assets! + +### BP_Player: + +1. Open `BP_Player` +2. Select `ShipMesh` component +3. In Details: + - **Static Mesh** → Keep Cone or choose a spaceship model + - **Material** → Create or assign a green emissive material +4. Compile and Save + +### BP_Enemy: + +1. Open `BP_Enemy` +2. Select `MeshComp` component +3. In Details: + - **Static Mesh** → Keep Cube or choose enemy model + - **Material** → Create or assign a red emissive material +4. Compile and Save + +### BP_Bullet (if you created one): + +1. Same process - assign materials for player (green) vs enemy (red) bullets + +--- + +## Step 9.2: Create Background + +1. In level, add **Plane** (from Place Actors → Basic → Plane) +2. Scale it large (Scale: 50, 50, 1) +3. Position below player (Z: -100) +4. Assign dark material (black or dark blue) +5. This provides contrast for bullets and enemies + +--- + +## Step 9.3: Adjust Camera + +If camera needs adjustment: + +1. Select BP_Player in level +2. Find `SpringArm` component +3. Adjust: + - **Target Arm Length** - Higher = further away camera + - **Socket Offset** - Adjust view position + +--- + +## Step 9.4: Final Testing + +Press Play and verify: + +**Gameplay:** +- ✅ Player moves smoothly with WASD +- ✅ Player fires green bullets in spread pattern +- ✅ Enemies spawn from top +- ✅ Enemies move in sine wave pattern +- ✅ Enemies fire red bullet bursts +- ✅ Collision detection works (bullets hit enemies, enemies hit player) +- ✅ Score increases when enemies destroyed +- ✅ Lives decrease when hit +- ✅ Special ability clears screen (X key) +- ✅ Timer counts down +- ✅ Victory after 300 seconds +- ✅ Game over if lives reach 0 + +**Visual Polish:** +- ✅ Player and enemies have distinct colors +- ✅ Bullets are visible and distinguishable +- ✅ UI is readable +- ✅ Background provides good contrast + +**Performance:** +- ✅ 60 FPS with 100+ bullets on screen +- ✅ No lag or stuttering + +--- + +## Step 9.5: Build Standalone Game (Optional) + +To build a playable executable: + +1. **File → Package Project → Windows** (or Linux) +2. Choose output folder +3. Wait for build (5-10 minutes) +4. Run the `.exe` file from output folder + +--- + +## Completion Summary + +### Total Time Comparison + +| Part | Blueprint | C++ | Time Saved | +|------|-----------|-----|------------| +| 1. Project Setup | 5 min | 10 min | -5 min | +| 2. Player | 60 min | 15 min | 45 min ⚡ | +| 3. Bullet | 30 min | 10 min | 20 min ⚡ | +| 4. Enemy | 90 min | 25 min | 65 min ⚡ | +| 5. Spawner | 40 min | 10 min | 30 min ⚡ | +| 6. Game Director | 45 min | 10 min | 35 min ⚡ | +| 7. UI | 30 min | 15 min | 15 min ⚡ | +| 8. Game Mode | 20 min | 5 min | 15 min ⚡ | +| 9. Final Setup | 40 min | 15 min | 25 min ⚡ | +| **TOTAL** | **6-8 hours** | **2-3 hours** | **4-5 hours saved!** ⚡⚡⚡ | + +### Key Benefits Achieved + +✅ **90% faster variable definition** - copy-paste vs clicking +✅ **Version control friendly** - readable C++ diffs instead of binary Blueprints +✅ **Type-safe** - compiler catches errors before runtime +✅ **IDE support** - autocomplete, refactoring, debugging +✅ **Easier to maintain** - code is documentation +✅ **Reusable** - copy C++ files to new projects instantly + +### Hybrid Approach Used + +- **C++ for logic** - All game mechanics, variables, algorithms +- **Blueprints for visuals** - Meshes, materials, colors +- **Best of both worlds** - Fast development + visual asset management + +--- + +## What's Next? + +**Extend the game:** +- Add power-ups +- Multiple enemy types +- Boss battles +- Sound effects and music +- Particle effects +- Leaderboard system + +**All easily done in C++** with the same copy-paste efficiency! + +--- + +## Congratulations! 🎉 + +You've built a complete bullet-hell game in Unreal Engine using C++ in 2-3 hours instead of 6-8 hours with Blueprints! + +--- + +[← Previous: Part 8 (C++) - Create Game Mode](part-8-cpp-create-game-mode.md) | [Back to Index](README.md)