본문 바로가기
Unreal 게임 개발/Unreal 강의 개인 정리

네트워크 캐릭터 Stat 구현 & 최적화 요소 - Unreal Network MultiPlayer Framework

by daisy0461 2025. 3. 12.

캐릭터 Item과 Stat 구현 시 고려할 사항

클라이언트와 서버는 Level에 있는 상제와 Item 상자에 설정된 Overlap Event에 반응해서 Stat이나 Item을 추가한다.

이렇게 Item이나 Stat가 변경 될 때

 

서버, 클라1, 클라2가 존재한다고 가정하면

서버가 클라1에게 클라1에 대한 상세한 Stat 정보를 줘야하지만 클라2의 상세한 정보(Stat정보)를 주지 않아도 된다.

클라2의 정말 필요한 정보(HP)만 클라1에게 줘도 무관하다.

그리고 모든 클라이언트의 Stat정보를 주게되면 네트워크 통신 데이터양도 많아지기에 성능상 좋지 않다.

 

즉, 서버는 각 클라이언트에게 필요한 정보만 전달하는 것이 좋다.

 

구현 예시

게임 플레이에 영향을 주지 않는 시각적인 부분(Effect)는 서버와 클라이언트가 동시에 재생한다.

게임 플레이에 영향을 미치는 Stat과 같은 데이터는 서버에서만 받도록 설계한다.

EquipWeapon을 했을 때 HasAuthority()로 서버를 구분하여 서버일 경우에만 Stat을 변경하도록 한다.

하지만 시각적인 부분인 Weapon->SetSkeletalMesh 같은 경우 서버와 클라이언트 구분 없이 수행한다.

Stat만 변경하는 ReadScroll, DrinkPostion의 경우엔 서버에서만 수행하도록 한다.

 

Stat을 변경할 때 리플리케이션을 해야하기에 OnRep_ 함수를 설정해서 프로퍼티 리플리케이션이 되도록 하고

 GetLifetimeReplicatedProps에 추가해서 리플리케이션 되도록 한다.

Stat이 변경되었을 때 broadcast를 통해 Stat이 변했음을 알린다.

이렇게 되면 정상적으로 작동한다.

 

하지만 문제점이 있다.

다음과 같이 코드를 만들 경우 처음 말했던 모든 클라이언트에게 Stat정보가 보내지게 된다.

해당 문제는 다음과 같이 변경하면 된다.

GetLifetimeReplicatedProps에서 DOREPLIFETIME을 DOREPLIFETIME_CONDITION으로 변경해주고

세번째 인자로 COND_OwnerOnly를 추가해주면 해당 캐릭터를 소유한 Autonomous Proxy에게만 해당 데이터를 전달한다. 

 

이때 COND_OwnerOnly로 지정한 Stat에 MaxHP와 같은 다른 클라이언트에게 넘겨줘야하는 정보가 있다면 어떻게 할까?

 

간단하게

MaxHp 프로퍼티 리플리케이션으로 추가해주면 된다.

 

 

최적화요소

일반적인 경우

  • 불필요한 Actor의 리플리케이션 옵션 끄기
  • Actor에 맞는 최적의 업데이트 빈도(Frequency)와 우선권(Priority) 설정
  • 연관성(Relevancy)를 위한 최적의 조건 설정
  • 프로퍼티 리플리케이션의 조건 설정
  • 필요시 휴면(Doramancy) 상태 설정
  • 데이터 크기의 양자화(ex. FVector_NetQuantize)

기타 심화 경우

  • 구조체 데이터 전송의 최적화 설계
    이를 UnrealEngine에서 제공하는 NetSerialize를 사용하면 된다.
    이 구조체를 사용해서 데이터를 줄여서 패키징할 수 있다.
    데이터 양을 최소화할 수 있으며 데이터가 자주 바뀔 때 유용하다.
    플래그를 설정해 불필요한 데이터 전송을 건너뛸 수 있다.
  • 프로러티 리플리케이션의 푸시 모델 설정
    네트워크로 전송할 프로퍼티를 필요할 때만 데이터를 전송하는 방법이다.
  • 델타 시리얼라이제이션 기법 사용
    큰 데이터가 있을 때 모든 데이터를 보내는 것은 네트워크 대역폭을 많이 사용하게 된다.
    이때 변한 데이터(Index)만 마크해서 해당 데이터만 보내는 기법이다.
    (FFastArraySerializer 구조체를 상속 받아 쉽게 구현 가능)
  • 빠른 리플리케이션 엔진의 교체 (ReplicationGraph, Iris)

NetSerialize 사용 예시

다음과 같이 BaseStat라는 구조체에 MaxHp, Attack, AttackSpeed, MovementSpeed를 바꿔주는 Item을 먹으면

클라이언트에선 다음과 같이 MaxHp, Attack, AttackSpeed, MovementSpeed 4개의 프로퍼티가 
32bits(float)로 전송이 된것을 확인할 수 있다.

위 4개를 개별적인 프로퍼티로 언리얼이 인식해서 네트워크로 전송한 것이다.
위에서 확인할 수 있듯이 구조체의 일부가 바꼈다고 해서 구조체의 모든 데이터를 전송하진 않는다.

 

여기서 더 최적화하기 위해 NetSerialize를 사용하려면

Stat구조체에 다음과 같은 헤더를 추가하고

다음과 같이 작성하면 된다.

여기서

왼쪽 코드가 값을 저장하는 로직이며 오른쪽 코드가 값을 불러들어오는 로직이다.

Stat 구조체에 다음과 같이 작성을 해줘야 Unreal Engine에서 해당 특성을 확인하고 NetSerialize함수를 호출해준다.

 

이후의 결과는

BaseStat에 관한 것들이 64bit로 압축된 을 확인할 수 있다.

이때 변경이 되지 않은 데이터도 보내게 된다는 단점이 있지만
데이터 양을 128bit에서 64bit으로 줄인 것을 확인할 수 있다.