praca_magisterska/games/unreal/tutorial/part-3-cpp-create-bullet.md
Copilot dd009a0029
Add C++ code-first tutorial for Unreal bullet-hell game (#2)
* Initial plan

* Add C++ code-first tutorial approach for Unreal game

Co-authored-by: kuhyx <147418882+kuhyx@users.noreply.github.com>

* Add quick start guide for code-first approach

Co-authored-by: kuhyx <147418882+kuhyx@users.noreply.github.com>

* Restructure C++ tutorial into part-by-part format and add migration guide

Co-authored-by: kuhyx <147418882+kuhyx@users.noreply.github.com>

* 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>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: kuhyx <147418882+kuhyx@users.noreply.github.com>
2026-01-08 16:44:56 +01:00

318 lines
8.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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:
```cpp
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "STGProjectile.generated.h"
class USphereComponent;
class UProjectileMovementComponent;
class UStaticMeshComponent;
UCLASS()
class BULLETHELLGAME_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 "STGEnemy.h" // Will create in Part 4
#include "STGPawn.h"
ASTGProjectile::ASTGProjectile()
{
PrimaryActorTick.bCanEverTick = true;
// Collision sphere
CollisionComp = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComp"));
CollisionComp->InitSphereRadius(5.0f);
CollisionComp->SetCollisionProfileName("OverlapAllDynamic");
CollisionComp->OnComponentBeginOverlap.AddDynamic(this, &ASTGProjectile::OnOverlapBegin);
RootComponent = CollisionComp;
// Visual mesh
MeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MeshComp"));
MeshComp->SetupAttachment(CollisionComp);
MeshComp->SetCollisionEnabled(ECollisionEnabled::NoCollision);
static ConstructorHelpers::FObjectFinder<UStaticMesh> 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<UProjectileMovementComponent>(TEXT("ProjectileComp"));
ProjectileMovement->UpdatedComponent = CollisionComp;
ProjectileMovement->InitialSpeed = 1200.f;
ProjectileMovement->MaxSpeed = 1200.f;
ProjectileMovement->bRotationFollowsVelocity = true;
ProjectileMovement->bShouldBounce = false;
// Auto-destroy after lifetime
InitialLifeSpan = Lifetime;
}
void ASTGProjectile::BeginPlay()
{
Super::BeginPlay();
// 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<ASTGEnemy>(OtherActor);
// if (Enemy) { Enemy->HandleDamage(Damage); Destroy(); }
}
else
{
// Enemy bullet hits player
ASTGPawn* Player = Cast<ASTGPawn>(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
for (int32 i = 0; i < VolleySize; i++)
{
FVector SpawnLocation = GetActorLocation() + FVector(0.f, 0.f, 50.f);
FRotator SpawnRotation = FRotator(90.f, 0.f, 0.f);
// Spread calculation
float Angle = VolleySpread * (i - (VolleySize - 1) / 2.0f);
SpawnRotation.Yaw += Angle;
// Spawn bullet
UWorld* World = GetWorld();
if (World)
{
ASTGProjectile* Bullet = World->SpawnActor<ASTGProjectile>(
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 Z or Space to fire
4. You should see green spheres shooting upward!
### Expected Result:
- ✅ Player fires 3 bullets in a spread pattern
- ✅ Bullets are green
- ✅ Bullets auto-destroy after 4 seconds
- ✅ Can hold Z to auto-fire
---
## 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)