praca_magisterska/games/unreal/tutorial/part-3-cpp-create-bullet.md

325 lines
9.0 KiB
Markdown
Raw Permalink 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:
> **⚠️ 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<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;
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<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 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>(
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)