# Part 3 (C++): Create the Bullet [← Previous: Part 2 (C++) - Create the Player](part-2-cpp-create-player.md) | [Back to Index](README.md) | [Next: Part 4 (C++) - Create the Enemy →](part-4-cpp-create-enemy.md) --- ## Overview Create the bullet/projectile class in C++. Again, we'll copy-paste all variables instead of clicking UI. **Time comparison:** - Blueprint: ~30 minutes (5 variables + Blueprint nodes) - C++ (this part): ~10 minutes (copy-paste code) --- ## Step 3.1: Create STGProjectile C++ Class 1. **Tools → New C++ Class** 2. Choose **"Actor"** as parent class → Next 3. Name: `STGProjectile` → Create Class 4. Wait for compilation --- ## Step 3.2: Copy-Paste STGProjectile.h Replace the entire header file with: > **⚠️ IMPORTANT:** Replace `YOURPROJECTNAME_API` with your actual project's API macro (e.g., `BULLETHELLCPP_API`). ```cpp #pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "STGProjectile.generated.h" class USphereComponent; class UProjectileMovementComponent; class UStaticMeshComponent; UCLASS() class YOURPROJECTNAME_API ASTGProjectile : public AActor { GENERATED_BODY() public: ASTGProjectile(); protected: virtual void BeginPlay() override; public: virtual void Tick(float DeltaTime) override; // ===== COMPONENTS ===== UPROPERTY(VisibleDefaultsOnly, Category=Projectile) USphereComponent* CollisionComp; UPROPERTY(VisibleDefaultsOnly, Category=Projectile) UStaticMeshComponent* MeshComp; UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Movement) UProjectileMovementComponent* ProjectileMovement; // ===== VARIABLES (all with defaults!) ===== UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Gameplay") bool bIsPlayerBullet = false; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Gameplay") float Damage = 1.0f; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Gameplay") FLinearColor BulletColor = FLinearColor::Green; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Gameplay") float Lifetime = 4.0f; // ===== FUNCTIONS ===== UFUNCTION() void OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult); void SetBulletColor(FLinearColor InColor); void SetSpeed(float InSpeed); private: UMaterialInstanceDynamic* DynamicMaterial; }; ``` **What just happened:** 5 bullet variables defined with defaults in 10 seconds! --- ## Step 3.3: Copy-Paste STGProjectile.cpp Replace the implementation file with: ```cpp #include "STGProjectile.h" #include "Components/SphereComponent.h" #include "Components/StaticMeshComponent.h" #include "GameFramework/ProjectileMovementComponent.h" #include "STGPawn.h" ASTGProjectile::ASTGProjectile() { PrimaryActorTick.bCanEverTick = true; // Collision sphere CollisionComp = CreateDefaultSubobject(TEXT("SphereComp")); CollisionComp->InitSphereRadius(5.0f); CollisionComp->SetCollisionProfileName("OverlapAllDynamic"); CollisionComp->OnComponentBeginOverlap.AddDynamic(this, &ASTGProjectile::OnOverlapBegin); RootComponent = CollisionComp; // Visual mesh MeshComp = CreateDefaultSubobject(TEXT("MeshComp")); MeshComp->SetupAttachment(CollisionComp); MeshComp->SetCollisionEnabled(ECollisionEnabled::NoCollision); static ConstructorHelpers::FObjectFinder SphereMesh(TEXT("/Engine/BasicShapes/Sphere")); if (SphereMesh.Succeeded()) { MeshComp->SetStaticMesh(SphereMesh.Object); MeshComp->SetRelativeScale3D(FVector(0.1f, 0.1f, 0.1f)); } // Projectile movement ProjectileMovement = CreateDefaultSubobject(TEXT("ProjectileComp")); ProjectileMovement->UpdatedComponent = CollisionComp; ProjectileMovement->InitialSpeed = 1200.f; ProjectileMovement->MaxSpeed = 1200.f; ProjectileMovement->bRotationFollowsVelocity = true; ProjectileMovement->bShouldBounce = false; ProjectileMovement->ProjectileGravityScale = 0.0f; // No gravity for top-down shooter! } void ASTGProjectile::BeginPlay() { Super::BeginPlay(); // Set lifetime here so Blueprint overrides of Lifetime variable work SetLifeSpan(Lifetime); // Create dynamic material for bullet color if (MeshComp) { DynamicMaterial = MeshComp->CreateAndSetMaterialInstanceDynamic(0); if (DynamicMaterial) { DynamicMaterial->SetVectorParameterValue(TEXT("BaseColor"), BulletColor); DynamicMaterial->SetVectorParameterValue(TEXT("EmissiveColor"), BulletColor * 3.0f); } } } void ASTGProjectile::Tick(float DeltaTime) { Super::Tick(DeltaTime); } 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 (will implement in Part 4) // 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(); } } } } void ASTGProjectile::SetBulletColor(FLinearColor InColor) { BulletColor = InColor; if (DynamicMaterial) { DynamicMaterial->SetVectorParameterValue(TEXT("BaseColor"), BulletColor); DynamicMaterial->SetVectorParameterValue(TEXT("EmissiveColor"), BulletColor * 3.0f); } } void ASTGProjectile::SetSpeed(float InSpeed) { if (ProjectileMovement) { ProjectileMovement->InitialSpeed = InSpeed; ProjectileMovement->MaxSpeed = InSpeed; } } ``` --- ## Step 3.4: Compile 1. Click **Compile** in Unreal Editor 2. Wait for compilation 3. Check for errors --- ## Step 3.5: Update Player Firing Logic Now that we have bullets, let's make the player actually spawn them! ### Open STGPawn.cpp Find the `FireShot()` function and replace it with: ```cpp void ASTGPawn::FireShot() { // Spawn volley of bullets shooting FORWARD (+X direction = toward top of screen) for (int32 i = 0; i < VolleySize; i++) { // Spawn slightly in front of player (+X) FVector SpawnLocation = GetActorLocation() + FVector(50.f, 0.f, 0.f); // Base rotation: FRotator(0,0,0) points in +X direction (forward in top-down view) FRotator SpawnRotation = FRotator::ZeroRotator; // Spread calculation: fan out on Yaw (left/right) float Angle = VolleySpread * (i - (VolleySize - 1) / 2.0f); SpawnRotation.Yaw += Angle; // Spawn bullet UWorld* World = GetWorld(); if (World) { ASTGProjectile* Bullet = World->SpawnActor( ASTGProjectile::StaticClass(), SpawnLocation, SpawnRotation ); if (Bullet) { Bullet->bIsPlayerBullet = true; Bullet->SetSpeed(BulletSpeed); Bullet->SetBulletColor(FLinearColor::Green); } } } } ``` ### Add include at top of STGPawn.cpp: ```cpp #include "STGProjectile.h" // Add this line ``` ### Compile again! --- ## Step 3.6: Test Bullet Firing 1. Press **Play** (`Alt+P`) 2. Move with WASD 3. Press Space to fire 4. You should see spheres shooting toward the top of the screen! ### Expected Result - ✅ Player fires 3 bullets in a spread pattern - ✅ Bullets travel toward the top of the screen (forward direction) - ✅ Bullets auto-destroy after 4 seconds - ✅ Can hold Space to auto-fire > **Note:** Bullets use the default engine material (gray/white). We'll add colored materials in Part 9 (Polish phase). --- ## Comparison Summary ### Blueprint Approach: - Create BP_Bullet Blueprint - Add 5 variables manually (×5 clicks each = 25 clicks) - Create Blueprint nodes for movement (~15 nodes) - Create Blueprint nodes for collision (~10 nodes) - Update BP_Player firing logic (~15 more nodes) - **Total: ~30 minutes** ### C++ Approach: - Create STGProjectile class - Copy-paste header (5 variables with defaults) - Copy-paste implementation - Update one function in STGPawn - Compile twice - **Total: ~10 minutes** **Time saved: 20 minutes** ⚡ --- ## What's Next? In Part 4, we'll create enemies with C++. You'll see how easy it is to copy enemy patterns! --- [← Previous: Part 2 (C++) - Create the Player](part-2-cpp-create-player.md) | [Back to Index](README.md) | [Next: Part 4 (C++) - Create the Enemy →](part-4-cpp-create-enemy.md)