개요

서버가 클라이언트의 최신 상태를 유지하기 위해 주로 사용하는 것은 액터이다. 서버가 특정 클라이언트를 업데이트할 때가 되면, 서버는 지난 번 업데이트 이후 변경되었다고 보는 연관 액터 전부를 수집한 다음 해당 액터의 최신 상태를 유지하기에 충분할 만큼의 정보를 클라이언트에 전송한다.

 

네트워크 모드의 종류

1. NM_Standalone

ㄴ 로컬 머신에서 실행되며 원격 머신에서 클라이언트를 받지 않는 서버. 싱글 플레이어, 로컬 멀티플레이 게임에 적합

 

2. NM_DedicatedServer

ㄴ 로컬 플레이어가 없어 그래픽, 사용자 입력, 플레이어 관련 기능을 처리하지 안하도 되어 효율적인 실행이 가능한 전용 서버, 퍼포먼스가 중요해서 신뢰성있는 서버가 필요한 경우에 사용된다.

 

3. NM_ListenServer

ㄴ 로컬 플레이어가 있는 서버(호스트로). 원격 플레이어에게도 접속을 허락한다.

ㄴ 데디케이티드 서버가 불필요한 경쟁 또는 협력 게임에 좋다.

ㄴ 호스트에게는 네트워크 지연 시간이 없어 유리하게 작용할 여지가 있다.

ㄴ 호스트에 의해 게임이 중단될 수 있다.

 

4. NM_Client

ㄴ 1~3은 서버 모드고, 4번은 클라이언트 모드

ㄴ 2, 3 서버에 접속할 수 있다.

ㄴ 서버측 로직은 실행되지 않는다.

 

에디터에서 테스트할 때는 Editor Preferences > Level Editor > Multiplayer Options에서 네트워크 모드를 지정해 테스트할 수 있다.

 

캐릭터의 스켈레탈 메시와 애니메이션 블루프린트 등의 겉모습을 표현하는 컴포넌트는 리플레케이트되지 않습니다. 하지만 캐릭터의 속도와 같이 게임플레이 및 무브먼트와 연관성이 있는 변수는 리플리케이트되며, 애니메이션 블루프린트는 업데이트될 때 이러한 변수를 읽어 들입니다. 게임플레이 변수가 정확하게 업데이트되면 각 클라이언트 측 캐릭터 사본 또한 시각적 표현을 업데이트할 것입니다.

 

RepNofity로 플레이어 체력 리플리케이트하기

플레이어에게는 게임플레이 도중 대미지를 가할 수 있는 체력 값이 필요합니다. 이 값은 모든 클라이언트가 각 플레이어의 체력에 대해 동기화된 정보를 갖도록 리플리케이트되어야 하며, 대미지를 입는 플레이어에게 피드백을 제공해야 합니다

 

ThirdPersonMPCharacter.h

/** 플레이어의 현재 체력. 0이 되면 죽은 것으로 간주됩니다.*/
UPROPERTY(ReplicatedUsing=OnRep_CurrentHealth)
float CurrentHealth;

/** 현재 체력에 가해진 변경에 대한 RepNotify*/
UFUNCTION()
void OnRep_CurrentHealth();

RepNotify 함수로 OnRep_CurrentHealth를 지정했다.

 

ThirdPersonMPCharacter.cpp에 #include 구문 추가해야 함

#include "Net/UnrealNetwork.h"
#include "Engine/Engine.h"

 

GetLifetimeReplicatedProps 오버라이드

ㄴ Replicated 지정자로 지정된 모든 프로퍼티를 리플리케이트하며, 프로퍼티의 리플리케이트 방식을 구성하도록 지원한다.

 

void AThirdPersonMPCharacter::GetLifetimeReplicatedProps(TArray <FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);

	//현재 체력 리플리케이트
	DOREPLIFETIME(AThirdPersonMPCharacter, CurrentHealth);
}

Super(캐릭터 클래스)의 GetLifetimeReplicatedProps를 호출하지 않으면 액터의 부모 클래스에서 상속받은 프로퍼티가 부모 클래스에서도 리플리케이트하도록 지정되어 있더라도 리플리케이트되지 않는다.

 

void AThirdPersonMPCharacter::OnRep_CurrentHealth()
{
    OnHealthUpdate();
}

 

서버에서 체력의 값이 변경되면 클라이언트에서의 OnRep_CurrentHealth (RepNotify 함수)가 호출된다.

 

플레이어가 대미지에 반응하게 만들기

/** 최대 체력 게터*/
UFUNCTION(BlueprintPure, Category="Health")
FORCEINLINE float GetMaxHealth() const { return MaxHealth; } 

/** 현재 체력 게터*/
UFUNCTION(BlueprintPure, Category="Health")
FORCEINLINE float GetCurrentHealth() const { return CurrentHealth; }

/** 현재 체력 세터. 값을 0과 MaxHealth 사이로 범위제한하고 OnHealthUpdate를 호출합니다. 서버에서만 호출되어야 합니다.*/
UFUNCTION(BlueprintCallable, Category="Health")
void SetCurrentHealth(float healthValue);

/** 대미지를 받는 이벤트. APawn에서 오버라이드됩니다.*/
UFUNCTION(BlueprintCallable, Category = "Health")
float TakeDamage( float DamageTaken, struct FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser ) override;

캐릭터 클래스 밖에서 플레이어의 체력을 수정할 방법을 만들기 (세터 게터), 데미지 받는 이벤트를 선언한다.

 

void AThirdPersonMPCharacter::SetCurrentHealth(float healthValue)
{
    if (GetLocalRole() == ROLE_Authority)
    {
        CurrentHealth = FMath::Clamp(healthValue, 0.f, MaxHealth);
        OnHealthUpdate();
    }
}

SetHealth는 서버에서만 실행되도록 제한하기 위해 액터의 네트워크 역할을 확인하여 서버 역할인 경우에만 호출될 수 있도록 한다.

 

float AThirdPersonMPCharacter::TakeDamage(float DamageTaken, struct FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
    float damageApplied = CurrentHealth - DamageTaken;
    SetCurrentHealth(damageApplied);
    return damageApplied;
}

TakeDamage는 Actor 클래스에 정의되어 있어서 오버라이드해서 사용한다.

 

이 구현의 장점은 서버, 클라이언트, NetMulticast RPC를 사용하지 않고 CurrentHealth 프로퍼티의 리플리케이션에만 의존하여 모든 변경을 트리거하므로 네트워크 전반에 전송되는 정보의 양이 압축된다는 점이다.

 

ThirdPersonMPProjectile 액터를 생성

SphereComponent, StaticMeshComponent, ProjectileMovementComponent, ParticleSystem, float(Damage)를 프로퍼티로 추가한다. SphereComponent를 루트 컴포넌트로 설정하고, 생성자에서는 각 컴포넌트들을 초기화 해준다.

 

bReplicates = true로 설정하면 액터가 리플리케이트 대상이 된다. (생성, 소멸이 리플리케이트 된다)

 

발사체가 대미지를 유발하게 만들기

void AThirdPersonMPProjectile::OnProjectileImpact(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{   
    if ( OtherActor )
    {
        UGameplayStatics::ApplyPointDamage(OtherActor, Damage, NormalImpulse, Hit, GetInstigator()->Controller, this, DamageType);
    }

    Destroy();
}

발사체가 오브젝트에 충돌할 때 호출할 함수, 유효한 액터와 부딪힐 경우 ApplyPointDamage 함수를 호출하여 발생한 위치에 대미지를 가하며, 발사체는 소멸된다.

 

//발사체 충돌 함수를 히트 이벤트에 등록
if (GetLocalRole() == ROLE_Authority)
{
    SphereComponent->OnComponentHit.AddDynamic(this, &AThirdPersonMPProjectile::OnProjectileImpact);
}

서버만 이 게임플레이 로직을 실행하도록 OnProjectileImpact 등록 전에 GetLocalRole() == ROLE_Authority 을 확인합니다.

 

발사체 발사하기 (RPC 함수 사용하기)

UPROPERTY(EditDefaultsOnly, Category="Gameplay|Projectile")
TSubclassOf<class AThirdPersonMPProjectile> ProjectileClass;

/** 발사 딜레이, 단위는 초. 테스트 발사체의 발사 속도를 제어하는 데 사용되지만, 서버 함수의 추가분이 SpawnProjectile을 입력에 직접 바인딩하지 않게 하는 역할도 합니다.*/
UPROPERTY(EditDefaultsOnly, Category="Gameplay")
float FireRate;

/** true인 경우 발사체를 발사하는 프로세스 도중입니다. */
bool bIsFiringWeapon;

/** 무기 발사 시작 함수*/
UFUNCTION(BlueprintCallable, Category="Gameplay")
void StartFire();

/** 무기 발사 종료 함수. 호출되면 플레이어가 StartFire를 다시 사용할 수 있습니다.*/
UFUNCTION(BlueprintCallable, Category = "Gameplay")
void StopFire();  

/** 발사체를 스폰하는 서버 함수*/
UFUNCTION(Server, Reliable)
void HandleFire();

발사체 발사에 사용할 변수와 함수입니다. HandleFire 는 이 튜토리얼에서 구현하는 유일한 RPC로, 서버에서 발사체를 스폰합니다. Server 지정자가 있기 때문에 클라이언트에서 이를 호출하려는 모든 시도는 네트워크를 통해 서버의 권위 있는 캐릭터로 전달됩니다.

 

HandleFire 에는 Reliable 지정자도 있기 때문에 호출될 때마다 신뢰할 수 있는 RPC의 큐 등록에 배치되며, 서버가 수신에 성공하면 큐 등록에서 제거됩니다. 이는 서버가 이 함수의 호출을 확실하게 수신하도록 보장합니다. 그러나 너무 많은 RPC가 제거 없이 동시에 배치되면 신뢰할 수 있는 RPC의 큐 등록이 추가분을 유발할 수 있으며, 그 경우 사용자가 강제로 연결 해제됩니다. 그러므로 플레이어가 이 함수를 호출하는 빈도에 주의해야 합니다.

 

HandleFire 는 서버 RPC이며 CPP 파일의 구현에서는 반드시 함수 이름에 접미사 _Implementation 이 추가되어야 하기 때문입니다. 여기서의 구현은 캐릭터의 회전 제어를 사용하여 카메라가 향한 방향을 구한 다음 해당 방향으로 발사체를 스폰하여 플레이어가 조준할 수 있게 합니다. 그러면 발사체의 발사체 무브먼트 컴포넌트가 해당 방향을 향한 이동을 처리합니다.

 

HandleFire_Implemetation이 아닌 HandleFire으로 선언했어도 빌드 과정에서 HandleFire_Implementation 선언부가 생성되는듯 하다.

 

 

참조:

https://docs.unrealengine.com/5.0/ko/multiplayer-programming-quick-start-for-unreal-engine/

 

멀티플레이어 프로그래밍 퀵스타트

C++에서 간단한 멀티플레이어 게임을 만듭니다.

docs.unrealengine.com

 

+ Recent posts