아래 글은 클라이언트도 무기를 장착할 수 있도록 멀티플레이 로직을 확장하는 과정을 설명합니다.
**RPC(Remote Procedure Call)**를 통해 “클라이언트 → 서버”로 함수를 호출하고,
무기 장착과 같은 중요한 동작을 서버가 권위(authority) 있게 처리하는 방법을 단계별로 살펴봅니다.


목차

  1. 개요: 왜 서버 권한으로 무기를 장착해야 할까?
  2. 서버 RPC(Server RPC)로 클라이언트 입력 처리
  3. 무기 상태(Weapon State) 복제와 RepNotify
  4. 에어리어 스피어(Overlap) 및 위젯 비활성화
  5. 정리 및 다음 단계

1. 개요: 왜 서버 권한으로 무기를 장착해야 할까?

멀티플레이 환경에서,

  • 클라이언트는 로컬에서 입력(E 키 누름 등)을 처리하지만,
  • 무기 습득이나 공격 등의 “중요한 게임 로직”은 서버가 결정해야 합니다(서버 권위).

따라서 클라이언트가 “무기를 장착하겠다”고 입력을 했을 때, 서버에 그 의사를 전달하여 실제 장착 로직을 수행시키는 구조가 필요합니다.
이를 실현하는 것이 바로 RPC(Remote Procedure Call)Server RPC입니다.


2. 서버 RPC(Server RPC)로 클라이언트 입력 처리

2.1 Server RPC 함수 선언

// BlasterCharacter.h (발췌)
UCLASS()
class ABlasterCharacter : public ACharacter
{
    GENERATED_BODY()

private:
    UFUNCTION(Server, Reliable)
    void ServerEquipButtonPressed();

    void EquipButtonPressed(); // 로컬에서 E 키를 눌렀을 때 호출
    // ...
};
  • UFUNCTION(Server, Reliable)
    • Server: 클라이언트 → 서버로 호출되어, 서버 측에서 실행되는 함수임을 의미
    • Reliable: 네트워크 패킷이 유실되지 않도록 보장. (중요 이벤트일 경우 사용. 너무 자주 호출은 지양)

2.2 Server RPC 함수 정의

// BlasterCharacter.cpp
void ABlasterCharacter::ServerEquipButtonPressed_Implementation()
{
    // 이미 서버 함수이므로 HasAuthority() 검사 불필요
    if (Combat)
    {
        // 실제 무기 장착 로직 (CombatComponent 등에서 EquipWeapon 호출)
        Combat->EquipWeapon(OverlappingWeapon);
    }
}
  • RPC 함수 정의 시, 언리얼은 함수 이름 끝에 _Implementation 을 자동으로 요구합니다.
  • “서버가 이 로직을 수행”하기 때문에, 여기서 바로 EquipWeapon(전투 컴포넌트의 함수)을 호출하면 됩니다.

2.3 클라이언트: E 키 입력 처리

void ABlasterCharacter::EquipButtonPressed()
{
    // 로컬에서 E 키가 눌렸을 때 호출
    // 서버 권한이 있으면 직접 EquipWeapon (ex: Listen Server)
    if (HasAuthority())
    {
        if (Combat) Combat->EquipWeapon(OverlappingWeapon);
    }
    else
    {
        // 클라이언트라면 RPC로 서버에 통보
        ServerEquipButtonPressed();
    }
}
  • 서버(Authority)일 경우: 이미 권한이 있으므로 즉시 Combat->EquipWeapon 호출
  • 클라이언트(비권한)일 경우: ServerEquipButtonPressed()를 호출 → 서버가 실제 EquipWeapon 진행

이렇게 하면 클라이언트가 무기를 주우려 할 때도, 서버가 최종적으로 권위있는 장착 로직을 실행합니다.


3. 무기 상태(Weapon State) 복제와 RepNotify

3.1 무기 상태(WeaponState) 열거형 복제

무기 클래스(AWeapon)에 WeaponState라는 열거형 변수를 두고,
‘초기(Initial)’ → **‘장착(Equipped)’**처럼 상태를 바꾸면,
클라이언트와 서버가 동일한 상태를 유지해야 합니다.

// Weapon.h
UENUM(BlueprintType)
enum class EWeaponState : uint8
{
    EWS_Initial UMETA(DisplayName="Initial State"),
    EWS_Equipped UMETA(DisplayName="Equipped"),
    // ...
};

UCLASS()
class AWeapon : public AActor
{
    GENERATED_BODY()

public:
    // WeaponState를 복제, 변경 시 통지 (RepNotify)
    UPROPERTY(ReplicatedUsing = OnRep_WeaponState)
    EWeaponState WeaponState;

    UFUNCTION()
    void OnRep_WeaponState();

    virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;

    void SetWeaponState(EWeaponState NewState); // 서버에서 상태 변경 시 호출
    // ...
};
// Weapon.cpp
#include "Net/UnrealNetwork.h"

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

    DOREPLIFETIME(AWeapon, WeaponState);
}

void AWeapon::OnRep_WeaponState()
{
    // 클라이언트에서 WeaponState가 변경되었을 때의 동작
    switch (WeaponState)
    {
    case EWeaponState::EWS_Equipped:
        ShowPickupWidget(false);
        AreaSphere->SetCollisionEnabled(ECollisionEnabled::NoCollision);
        break;
    // ...
    }
}

void AWeapon::SetWeaponState(EWeaponState NewState)
{
    WeaponState = NewState;

    // 서버에서도 동일한 동작을 처리
    switch (WeaponState)
    {
    case EWeaponState::EWS_Equipped:
        ShowPickupWidget(false);
        AreaSphere->SetCollisionEnabled(ECollisionEnabled::NoCollision);
        break;
    // ...
    }
}
  • RepNotify (OnRep_WeaponState): 클라이언트에서 WeaponState가 갱신될 때 자동 호출
  • 서버에서는 SetWeaponState()를 통해 무기 상태를 바꾸며, 위젯 비활성화, 충돌 끄기 등을 즉시 적용
  • 클라이언트는 **OnRep_WeaponState()**를 통해 동일한 동작을 구현 → UI나 충돌 상태가 동기화

4. 에어리어 스피어(Overlap) 및 위젯 비활성화

4.1 무기 장착 후 위젯 숨기기

  • 픽업 위젯을 서버에서 ShowPickupWidget(false) 하더라도, 클라이언트에선 상태가 달라질 수 있음
  • WeaponState를 “Equipped”로 바꾸면서 OnRep_WeaponState()에서 숨기도록 하면, 모든 클라이언트가 자동 동기화

4.2 Overlap 충돌 비활성화

  • 장착된 무기는 더이상 “겹침 이벤트”가 발생하면 안 되므로, AreaSphere->SetCollisionEnabled(ECollisionEnabled::NoCollision)
  • 서버에서만 충돌을 꺼도, Overlap 이벤트 자체가 서버에서 발생하므로 문제없음

예시:

// CombatComponent.cpp (EquipWeapon 내부)
void UCombatComponent::EquipWeapon(AWeapon* WeaponToEquip)
{
    if (!WeaponToEquip) return;

    // 무기 상태 변경 (Equipped)
    WeaponToEquip->SetWeaponState(EWeaponState::EWS_Equipped);

    // 무기를 소켓에 부착하거나, 소유자 설정 등
    // ...
}

무기 상태를 Equipped로 변경하면, SetWeaponState() 및 **OnRep_WeaponState()**를 통해

  • 위젯 숨김,
  • 에어리어 스피어 충돌 해제,
  • 기타 필요한 설정(Ex. 물리 시뮬 비활성화)
    등을 서버와 클라이언트에서 동시에 처리하게 됩니다.

5. 정리 및 다음 단계

  1. Server RPC
    • 클라이언트가 “무기 장착”을 시도하면, 서버가 그 요청을 승인(처리)하도록 함
    • UFUNCTION(Server, Reliable) void ServerEquipButtonPressed(); 형태로 선언
    • 클라이언트 → 서버로 호출, 서버 쪽에서 EquipWeapon() 실행
  2. WeaponState 열거형 복제(RepNotify)
    • 무기 상태가 바뀔 때, 모든 클라이언트와 서버가 동일한 상태를 갖도록 동기화
    • OnRep_WeaponState() 안에서 “픽업 위젯 숨김”, “에어리어 스피어 충돌 끄기” 등 처리
  3. 결과
    • 이제 서버에든 클라이언트에든 상관없이 무기 습득 가능
    • 무기가 장착되면 모든 플레이어에게 “위젯 없음, 충돌 꺼짐” 등 시각적으로 반영됨

다음 단계

  • 여러 종류 무기 장착: 슬롯을 나눠서 관리하거나, 투척/재장전 로직을 추가
  • Ownership(Owner) & 조건부 복제: SetOwner()와 Owner RepNotify를 활용해 필요 정보만 복제
  • HUD 표시: 예: 현재 장착 무기 정보, 탄약 수, 재장전 여부 등

이처럼 RPC + 변수 복제(RepNotify)를 조합하면,
**“클라이언트 입력 → 서버 권위 로직 실행 → 클라이언트 동기화”**라는 멀티플레이 구조를 유연하게 구현할 수 있습니다.


결론

이번 강의에서 배운 주요 포인트:

  • Server RPC를 사용해 클라이언트가 서버에 “무기 장착”을 요청
  • 무기 상태(WeaponState) RepNotify로 모든 클라이언트가 “장착/비장착” 상태를 동기화
  • 장착된 무기는 에어리어 스피어 충돌(Overlap) 및 위젯을 자동으로 비활성화

이제 서버-클라이언트 어디서든 무기를 정상적으로 습득하고, 위젯이나 충돌이 올바르게 동기화됩니다.
멀티플레이 코드에서 자주 쓰이는 패턴이므로, 다른 상호작용 로직에도 응용해 보시기 바랍니다.

 

'UnraealEngine > Multiplayer Shooter' 카테고리의 다른 글

47. Equipping Weapons  (0) 2025.03.13
46.Variable Replication  (0) 2025.03.13
44~46  (0) 2025.03.13
41.Seamless Travel and Lobby  (0) 2025.03.12
42.Network Role  (0) 2025.03.12

+ Recent posts