Address PR feedback: improve part-1, add Expected Results, split parts 4-9 into separate files

Co-authored-by: kuhyx <147418882+kuhyx@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-01-08 15:19:06 +00:00
parent 8145e92128
commit 1053bdec73
11 changed files with 1547 additions and 246 deletions

View File

@ -10,7 +10,7 @@
- **New Project?** → Start with [Part 1 (C++): Project Setup](part-1-cpp-project-setup.md) - **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 - **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) ### 2. Create C++ Project (New Projects Only)

View File

@ -144,10 +144,18 @@ This tutorial offers **two ways** to implement the game:
### For Code-First Approach (Recommended) ### For Code-First Approach (Recommended)
1. Read [3-Minute Quickstart](QUICKSTART-CODE-FIRST.md) 1. Read [3-Minute Quickstart](QUICKSTART-CODE-FIRST.md)
2. Start with [Part 1 (C++): Project Setup](part-1-cpp-project-setup.md) 2. Follow the C++ tutorial parts:
3. Follow parts 2-3, then [Parts 4-9 Summary](part-4-9-cpp-summary.md) - [Part 1 (C++): Project Setup](part-1-cpp-project-setup.md)
4. **Complete the game in 2-3 hours** instead of 6-8 hours! - [Part 2 (C++): Create Player](part-2-cpp-create-player.md)
5. **Already have Blueprint project?** See [Migration Guide](QUICKSTART-CODE-FIRST.md#migrating-from-blueprint-to-c) - [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 ### For Blueprint Approach

View File

@ -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: 6. On the right side panel, configure:
- **Project Defaults:** **C++** ⚠️ (NOT Blueprint!) - **Project Defaults:** **C++** ⚠️ (NOT Blueprint!)
- **Target Platform:** Desktop - **Target Platform:** Desktop
- **Quality Preset:** Maximum (or Scalable for faster iteration) - **Quality Preset:** Scalable (faster compile times, easier iteration)
- **Starter Content:** UNCHECKED (we don't need it) - **Starter Content:** UNCHECKED (reduces project size and avoids unnecessary assets for this bullet-hell game)
- **Raytracing:** UNCHECKED - **Raytracing:** UNCHECKED
7. At the bottom: 7. At the bottom:
@ -42,41 +42,62 @@ This is the **C++ version** of Part 1. Unlike the Blueprint tutorial which creat
Unreal will: Unreal will:
1. Generate C++ project files (~30 seconds) 1. Generate C++ project files (~30 seconds)
2. **Open your IDE** (Visual Studio, Rider, or VS Code) 2. **Open your IDE** (Visual Studio Code will launch if configured)
3. **Compile the initial project** (2-5 minutes, first time only) 3. **Automatically start compiling** the initial project (2-5 minutes, first time only)
4. Open Unreal Editor - 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 ### Expected Result
After compilation completes: After compilation completes:
- **Unreal Editor opens** with an empty level **Unreal Editor viewport shows:**
- **Your IDE is open** with the project (Visual Studio/Rider/VS Code) - Empty 3D viewport in the center with grid floor
- Main 3D viewport in the center - Main toolbar at the top (File, Edit, Window, etc.)
- Outliner panel on the right (showing "Untitled" level) - 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 ### Troubleshooting
<details> <details>
<summary><b>IDE didn't open?</b></summary> <summary><b>IDE didn't open?</b> (Visual Studio Code on Linux/Arch)</summary>
- Check if Visual Studio or Rider is installed On Arch Linux with VS Code:
- Go to `Edit → Editor Preferences → Source Code` - Install VS Code if not present: `sudo pacman -S code`
- Set "Source Code Editor" to your preferred IDE - Install Unreal Engine extension for VS Code
- Right-click the `.uproject` file → "Generate Visual Studio project files" - In Unreal Editor: `Edit → Editor Preferences → Source Code`
- Open the `.sln` file in your IDE - 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
</details> </details>
<details> <details>
<summary><b>Compilation failed?</b></summary> <summary><b>Compilation failed?</b> (Linux/Arch)</summary>
- Check the Output Log (Window → Developer Tools → Output Log) On Arch Linux:
- Common issue: Missing Visual Studio C++ tools - Ensure you have the required build tools:
- Install "Desktop Development with C++" workload in Visual Studio Installer - `sudo pacman -S base-devel clang lld`
- Try: `File → Refresh Visual Studio Project` - 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"
</details> </details>
@ -107,6 +128,25 @@ BulletHellGame/
The `Source/` folder is what makes this a C++ project! This is where we'll add our game classes. 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 ## Step 1.3: Set Up 2D Game View
@ -117,6 +157,18 @@ Same as Blueprint tutorial:
2. Click the dropdown that says "Perspective" 2. Click the dropdown that says "Perspective"
3. Select "Top" from the dropdown menu (`Alt + J`) 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 ## Step 1.4: Create Folder Structure

View File

@ -435,18 +435,42 @@ Now we create a **minimal Blueprint** that inherits from our C++ class. This Blu
## Step 2.7: Test the Player ## Step 2.7: Test the Player
1. Drag `BP_Player` from Content Browser into the level 1. Drag `BP_Player` from Content Browser into the level viewport
2. Press **Play** (`Alt+P`) 2. Position it near the center (coordinates around X=0, Y=0, Z=0)
3. Press WASD keys - player should move! 3. Press **Play** (`Alt+P`)
4. Press Z or Space - you should see "FIRE!" in Output Log
5. Press X - you should see "SPECIAL ABILITY ACTIVATED!" once
### Expected Result: ### Expected Result
- ✅ Player moves with WASD **In Play Mode Window:**
- ✅ Movement is clamped to bounds - You see the cone/cube player ship from above (top-down view)
- ✅ Fire input detected (logs message) - Ship is positioned at center of screen
- ✅ Special ability works once - 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
--- ---

View File

@ -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)

View File

@ -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<USceneComponent>(TEXT("Root"));
// Mesh component
MeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MeshComp"));
MeshComp->SetupAttachment(RootComponent);
MeshComp->SetCollisionProfileName("NoCollision");
// Load cube mesh
static ConstructorHelpers::FObjectFinder<UStaticMesh> 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<UBoxComponent>(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>(
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<ASTGPawn>(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<ASTGPawn>(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<ASTGEnemy>(OtherActor);
if (Enemy)
{
Enemy->HandleDamage(Damage);
Destroy();
}
}
else
{
// Enemy bullet hits player
ASTGPawn* Player = Cast<ASTGPawn>(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<AActor*> FoundEnemies;
UGameplayStatics::GetAllActorsOfClass(GetWorld(), ASTGEnemy::StaticClass(), FoundEnemies);
for (AActor* Enemy : FoundEnemies)
{
Enemy->Destroy();
}
// Destroy all enemy bullets (not player bullets)
TArray<AActor*> FoundBullets;
UGameplayStatics::GetAllActorsOfClass(GetWorld(), ASTGProjectile::StaticClass(), FoundBullets);
for (AActor* Bullet : FoundBullets)
{
ASTGProjectile* Projectile = Cast<ASTGProjectile>(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)

View File

@ -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<AActor*> 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>(
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)

View File

@ -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<AActor*> FoundDirectors;
UGameplayStatics::GetAllActorsOfClass(GetWorld(), ASTGGameDirector::StaticClass(), FoundDirectors);
if (FoundDirectors.Num() > 0)
{
ASTGGameDirector* Director = Cast<ASTGGameDirector>(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)

View File

@ -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<class UUserWidget> 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<UUserWidget>(GetWorld(), HUDWidgetClass);
if (HUDWidget)
{
HUDWidget->AddToViewport();
}
}
}
void ASTGHUDManager::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void ASTGHUDManager::UpdateScore(int32 NewScore)
{
if (HUDWidget)
{
UTextBlock* ScoreText = Cast<UTextBlock>(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<UTextBlock>(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<UTextBlock>(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<AActor*> FoundManagers;
UGameplayStatics::GetAllActorsOfClass(GetWorld(), ASTGHUDManager::StaticClass(), FoundManagers);
if (FoundManagers.Num() > 0)
{
ASTGHUDManager* HUDMgr = Cast<ASTGHUDManager>(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)

View File

@ -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)

View File

@ -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)