언리얼 엔진을 사용한 멀티플레이 FPS 게임에서는 플레이어의 총알 발사와 관련된 시스템이 게임의 타격감과 몰입도에 큰 영향을 미칩니다. 단순한 총알 발사에서 벗어나 멀티플레이 환경에서는 서버와 클라이언트 간 동기화, 충돌 판정, 예측 기능까지 구현해야 하기 때문에 더욱 복잡해집니다. 이번 글에서는 언리얼 엔진에서 서버와 클라이언트 간의 네트워크 동기화가 가능한 총알 시스템을 구축하는 법을 단계별 코드와 함께 자세히 설명해 드리겠습니다.
이 글은 언리얼 엔진을 사용해 게임 개발을 하고 있는 분들이 바로 적용할 수 있는 고급 예시 코드와 설명을 포함하며, 동기화 처리 및 예측 보정 기능도 다룹니다.
목차
1. 총알 시스템의 중요성과 개요
멀티플레이 FPS 게임에서 총알 시스템은 단순한 발사 효과 이상의 역할을 합니다. 각 플레이어가 발사하는 총알의 위치, 방향, 속도, 그리고 타격 판정이 서버와 모든 클라이언트에서 일관되게 처리되어야 하기 때문에 네트워크 동기화가 필수입니다. 특히, 클라이언트 예측을 통해 네트워크 지연에도 일관된 플레이 경험을 제공해야 합니다.
2. 언리얼 엔진에서 총알 시스템 구현하기
총알 시스템을 구현하기 위해 탄환의 속도와 방향을 설정하고, 각 탄환이 충돌 시 처리될 동작을 정의해야 합니다. 멀티플레이 게임에서는 서버가 탄환을 생성하고 각 클라이언트에 이를 동기화하는 방식으로 진행합니다.
아래는 언리얼 엔진에서 총알 시스템을 구축하는 전체 코드입니다. 이 코드는 탄환의 발사, 이동, 충돌 처리 및 네트워크 동기화, 예측 기능을 포함합니다.
1) 탄환 클래스 (ABullet) 구현
탄환 클래스는 탄환의 속성, 이동 방식, 충돌 처리 등을 정의합니다. 탄환은 서버에서만 생성되어 모든 클라이언트에 동기화되며, 충돌 시 타격 판정을 수행합니다.
// Bullet.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Bullet.generated.h"
UCLASS()
class MYGAME_API ABullet : public AActor
{
GENERATED_BODY()
public:
ABullet();
protected:
virtual void BeginPlay() override;
public:
virtual void Tick(float DeltaTime) override;
// 충돌 시 호출될 함수
UFUNCTION()
void OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit);
// 서버에서 생성된 탄환을 클라이언트에 복제
UFUNCTION(NetMulticast, Reliable)
void Multicast_SpawnBullet(const FVector& SpawnLocation, const FRotator& Direction);
// 탄환의 이동 속도 설정
UPROPERTY(EditAnywhere, Category = "Movement")
float Speed = 3000.0f;
UPROPERTY(EditAnywhere, Category = "Damage")
float Damage = 20.0f;
private:
UPROPERTY(VisibleAnywhere)
class UProjectileMovementComponent* ProjectileMovement;
};
// Bullet.cpp
#include "Bullet.h"
#include "GameFramework/ProjectileMovementComponent.h"
#include "Components/SphereComponent.h"
ABullet::ABullet()
{
PrimaryActorTick.bCanEverTick = true;
// 충돌 판정 콜리전 설정
UPrimitiveComponent* CollisionComp = CreateDefaultSubobject<USphereComponent>(TEXT("CollisionComp"));
CollisionComp->InitSphereRadius(5.0f);
RootComponent = CollisionComp;
// 탄환 이동 컴포넌트 설정
ProjectileMovement = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("ProjectileMovement"));
ProjectileMovement->InitialSpeed = Speed;
ProjectileMovement->MaxSpeed = Speed;
ProjectileMovement->bRotationFollowsVelocity = true;
ProjectileMovement->bShouldBounce = false;
// 충돌 이벤트 바인딩
CollisionComp->OnComponentHit.AddDynamic(this, &ABullet::OnHit);
}
void ABullet::BeginPlay()
{
Super::BeginPlay();
}
void ABullet::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
// 탄환 충돌 처리
void ABullet::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
if (OtherActor && OtherActor != this && OtherComp)
{
// 충돌 시 타격 처리
OtherActor->TakeDamage(Damage, FDamageEvent(), GetInstigatorController(), this);
Destroy();
}
}
// 서버에서 생성된 탄환을 클라이언트에 복제
void ABullet::Multicast_SpawnBullet_Implementation(const FVector& SpawnLocation, const FRotator& Direction)
{
SetActorLocationAndRotation(SpawnLocation, Direction);
}
2) 무기 클래스 (Weapon) 구현
무기 클래스는 플레이어가 탄환을 발사할 때 사용하는 클래스입니다. ServerFire
함수를 통해 서버에서만 탄환을 발사하고, 이 탄환이 모든 클라이언트에 동기화되도록 합니다.
// Weapon.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Weapon.generated.h"
UCLASS()
class MYGAME_API AWeapon : public AActor
{
GENERATED_BODY()
public:
AWeapon();
protected:
virtual void BeginPlay() override;
public:
UFUNCTION(BlueprintCallable)
void Fire();
// 서버에서만 호출되는 함수
UFUNCTION(Server, Reliable, WithValidation)
void ServerFire();
void ServerFire_Implementation();
bool ServerFire_Validate();
private:
UPROPERTY(EditAnywhere, Category = "Bullet")
TSubclassOf<class ABullet> BulletClass;
UPROPERTY(EditAnywhere, Category = "Fire")
FVector MuzzleLocation;
};
// Weapon.cpp
#include "Weapon.h"
#include "Bullet.h"
#include "Engine/World.h"
#include "Net/UnrealNetwork.h"
AWeapon::AWeapon()
{
PrimaryActorTick.bCanEverTick = true;
bReplicates = true;
}
void AWeapon::BeginPlay()
{
Super::BeginPlay();
}
void AWeapon::Fire()
{
if (HasAuthority())
{
ServerFire();
}
else
{
// 클라이언트에서의 탄환 예측 처리
FVector SpawnLocation = GetActorLocation() + MuzzleLocation;
FRotator Direction = GetActorRotation();
if (BulletClass)
{
ABullet* Bullet = GetWorld()->SpawnActor<ABullet>(BulletClass, SpawnLocation, Direction);
Bullet->Multicast_SpawnBullet(SpawnLocation, Direction);
}
}
}
void AWeapon::ServerFire_Implementation()
{
Fire();
}
bool AWeapon::ServerFire_Validate()
{
return true;
}
3. 장점과 단점
- 장점: 서버와 클라이언트 간 동기화가 가능해 멀티플레이어 환경에서 일관된 타격 감각을 제공합니다. 예측 기능을 통해 클라이언트에서도 네트워크 지연을 최소화하여 자연스러운 플레이를 구현할 수 있습니다.
- 단점: 동기화와 예측 처리가 잘못될 경우 타격 판정의 불일치가 발생할 수 있으며, 복잡한 구현이 필요한 만큼 성능 부담이 존재합니다.
4. 마무리
이번 글에서는 언리얼 엔진에서 멀티플레이 FPS 게임을 위한 탄환 시스템을 구현하는 방법에 대해 자세히 다뤘습니다. 이 코드를 활용하면 탄환의 발사, 이동, 충돌, 동기화까지 다양한 기능을 간단하게 구현할 수 있습니다.