아래 글은 클라이언트도 무기를 장착할 수 있도록 멀티플레이 로직을 확장하는 과정을 설명합니다.
**RPC(Remote Procedure Call)**를 통해 “클라이언트 → 서버”로 함수를 호출하고,
무기 장착과 같은 중요한 동작을 서버가 권위(authority) 있게 처리하는 방법을 단계별로 살펴봅니다.
목차
- 개요: 왜 서버 권한으로 무기를 장착해야 할까?
- 서버 RPC(Server RPC)로 클라이언트 입력 처리
- 무기 상태(Weapon State) 복제와 RepNotify
- 에어리어 스피어(Overlap) 및 위젯 비활성화
- 정리 및 다음 단계
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. 정리 및 다음 단계
- Server RPC
- 클라이언트가 “무기 장착”을 시도하면, 서버가 그 요청을 승인(처리)하도록 함
- UFUNCTION(Server, Reliable) void ServerEquipButtonPressed(); 형태로 선언
- 클라이언트 → 서버로 호출, 서버 쪽에서 EquipWeapon() 실행
- WeaponState 열거형 복제(RepNotify)
- 무기 상태가 바뀔 때, 모든 클라이언트와 서버가 동일한 상태를 갖도록 동기화
- OnRep_WeaponState() 안에서 “픽업 위젯 숨김”, “에어리어 스피어 충돌 끄기” 등 처리
- 결과
- 이제 서버에든 클라이언트에든 상관없이 무기 습득 가능
- 무기가 장착되면 모든 플레이어에게 “위젯 없음, 충돌 꺼짐” 등 시각적으로 반영됨
다음 단계
- 여러 종류 무기 장착: 슬롯을 나눠서 관리하거나, 투척/재장전 로직을 추가
- 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 |