리슨 서버 vs 데디서버 - 총격 및 데미지 연산 코드 비교
아래는 리슨 서버 방식과 데디서버 방식에서 플레이어가 총을 쏠 때 데미지 연산을 수행하는 코드입니다.
리슨 서버: 서버 역할을 하는 클라이언트(호스트)가 직접 로컬에서 총을 쏘고 데미지를 처리
데디서버: 모든 데미지 연산을 서버에서 수행하며, 클라이언트는 요청만 전송
1. 리슨 서버 방식
총을 쏘는 액션을 클라이언트에서 실행할 수 있으며, 필요 시 서버에서 확인
즉시 반응성이 뛰어나지만, 호스트의 권한이 커서 보안상 취약
// MyCharacter.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "MyCharacter.generated.h"
UCLASS()
class AMyCharacter : public ACharacter
{
GENERATED_BODY()
public:
AMyCharacter();
UFUNCTION()
void FireWeapon();
UFUNCTION(Server, Reliable)
void ServerFireWeapon();
UFUNCTION(NetMulticast, Reliable)
void MulticastApplyDamage(AActor* HitActor, float DamageAmount);
protected:
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
private:
UPROPERTY(EditDefaultsOnly, Category = "Weapon")
float WeaponDamage = 25.0f;
};
// MyCharacter.cpp
#include "MyCharacter.h"
#include "GameFramework/PlayerController.h"
#include "Kismet/GameplayStatics.h"
#include "Engine/World.h"
AMyCharacter::AMyCharacter()
{
PrimaryActorTick.bCanEverTick = true;
}
void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
PlayerInputComponent->BindAction("Fire", IE_Pressed, this, &AMyCharacter::FireWeapon);
}
void AMyCharacter::FireWeapon()
{
if (HasAuthority())
{
ServerFireWeapon();
}
else
{
ServerFireWeapon();
}
}
void AMyCharacter::ServerFireWeapon_Implementation()
{
FHitResult HitResult;
FVector Start = GetActorLocation();
FVector End = Start + (GetActorForwardVector() * 1000.0f);
FCollisionQueryParams Params;
Params.AddIgnoredActor(this);
if (GetWorld()->LineTraceSingleByChannel(HitResult, Start, End, ECC_Visibility, Params))
{
AActor* HitActor = HitResult.GetActor();
if (HitActor)
{
MulticastApplyDamage(HitActor, WeaponDamage);
}
}
}
void AMyCharacter::MulticastApplyDamage_Implementation(AActor* HitActor, float DamageAmount)
{
if (HitActor)
{
UGameplayStatics::ApplyDamage(HitActor, DamageAmount, GetController(), this, UDamageType::StaticClass());
}
}
// MyDedicatedCharacter.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "MyDedicatedCharacter.generated.h"
UCLASS()
class AMyDedicatedCharacter : public ACharacter
{
GENERATED_BODY()
public:
AMyDedicatedCharacter();
UFUNCTION(Server, Reliable)
void ServerFireWeapon();
protected:
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
private:
UPROPERTY(EditDefaultsOnly, Category = "Weapon")
float WeaponDamage = 25.0f;
};
// MyDedicatedCharacter.cpp
#include "MyDedicatedCharacter.h"
#include "GameFramework/PlayerController.h"
#include "Kismet/GameplayStatics.h"
#include "Engine/World.h"
AMyDedicatedCharacter::AMyDedicatedCharacter()
{
PrimaryActorTick.bCanEverTick = true;
}
void AMyDedicatedCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
PlayerInputComponent->BindAction("Fire", IE_Pressed, this, &AMyDedicatedCharacter::ServerFireWeapon);
}
void AMyDedicatedCharacter::ServerFireWeapon_Implementation()
{
if (!HasAuthority())
{
return;
}
FHitResult HitResult;
FVector Start = GetActorLocation();
FVector End = Start + (GetActorForwardVector() * 1000.0f);
FCollisionQueryParams Params;
Params.AddIgnoredActor(this);
if (GetWorld()->LineTraceSingleByChannel(HitResult, Start, End, ECC_Visibility, Params))
{
AActor* HitActor = HitResult.GetActor();
if (HitActor)
{
UGameplayStatics::ApplyDamage(HitActor, WeaponDamage, GetController(), this, UDamageType::StaticClass());
}
}
}
코드 차이점 요약
항목 리슨 서버 방식 데디서버 방식
총격 처리 위치 클라이언트에서 실행 후 서버 검증 서버에서만 실행
서버 RPC 사용 ServerFireWeapon() 요청 후 MulticastApplyDamage() 실행 ServerFireWeapon()에서 바로 데미지 처리
보안성 낮음 (클라이언트가 RPC 요청 가능) 높음 (서버에서만 연산 수행)
반응 속도 즉각적 (로컬에서 실행) 약간 느림 (네트워크 왕복 필요)
클라이언트 조작 방지 어려움 (클라이언트 해킹 가능) 쉬움 (모든 연산을 서버에서 처리)
결론
리슨 서버 방식: 빠른 반응성이 필요하지만, 해킹에 취약한 게임에 적합
데디서버 방식: 보안이 중요한 멀티플레이 게임에서 권장됨