이득우의 언리얼 프로그래밍 Part3 - 네트웍 멀티플레이 프레임웍의 이해 정리 요약

0. 학습 목록

Untitled

  • 서버와 클라이언트 같은 네트워크 모드에 대한 학습.
  • 클라와 서버 접속 테스트 및 소유권에 따른 동작 학습
  • 리플리케이션과 RPC - 연관성, 오너십, 빈도 조정을 학습.
  • 캐릭터 무브먼트 컴포넌트를 사용한 정보 리플리케이션 학습.

1. 네트워크 멀티 플레이어 프레임워크

  • 네트워크상에서 클라-서버 모델이 무엇인지 확인. 서버의 복제인 Proxy에 대한 이해가 중요하다.

2. 게임 모드와 로그인

  • 4개의 네트워크 모드와 그 동작 방식에 대한 학습.
    • 리슨 서버로 시작하였어도 클라이언트 입장 전까지는 스탠드얼론으로 동작.
  • GameMode에서 PreLogin → Login → PostLogin → StartPlay → BeginPlay로 이어지는 흐름과 각 부분에서의 동작.
    • 특히 PreLogin은 서버 혼자일 때 실행X
    • Login과정에서 플레이어 컨트롤러 생성.
  • 서버에만 존재하는 GameMode와 달리 양쪽 모두 존재하는 GameState에서 BeginPlay를 호출.

3. 커넥션과 오너십

  • 게임과 무관한 액터의 초기화는 PostInitializeComponents에서, 게임과 관련된 값은 BeginPlay에서 호출한다.
  • 네트워크 관련 설정 초기화는 PostNetInit에서 진행되며, 이를통해 정보 동기화가 모두 끝난 이후에 BeginPlay가 호출된다.
  • 통신 레벨은 크게 3단계로 구분 가능하다.
    • 소유권을 가지는 PlayerController
    • 중간 연결 단계인 UNetConnection
    • 실제 통신을 담당하는 UNetDriver → 이를 통해 멀티플레이 여부 확인 가능
  • 접속 주체 A와 B는 커넥션이라는 통로를 가지며 논리 통로인 채널을 통해 패킷 묶음인 번치가 이동한다. 이 커넥션을 관리하는 대표 액터는 커넥션의 소유권을 가졌다고 표현한다.
    • 보통 커넥션의 소유권은 플레이어 컨트롤러가 가진다.
    • 리플리케이션 / RPC를 위하여 소유권을 누가 가졌는지 알아야한다.
  • 서버에서 플레이어 컨트롤러 생성→빙의→해당 값 Replicate→클라이언트에서도 소유권 확인.

4. 액터의 역할과 커넥션 핸드셰이킹

  • 서버-클라이언트 구조에서는 서버의 액터가 소유권을 가지며 클라이언트는 이를 복제한 Proxy를 가짐.
  • 각자의 위치에서 본인이 Local, 상대방이 Remote이다. 상대적인 요소.
  • 액터는 None / Authority / AutonomousProxy / SimulatedProxy의 4가지 여할을 가진다.
    • 오로지 Authority 액터만이 신뢰되며, HasAuthority()로 이를 확인.
    • 위의 역할을 기반으로 게임모드, 배경 액터, 플레이어 컨트롤러, 애니메이션 등을 적절히 배치.
  • 커넥션 핸드셰이킹 과정에서 연결 확인 / 로그인 / 전송 속도 조절이 이루어진다. 로그인 부터는 위에서 배운 내용과 연결.
  • 클라이언트에서는 맵을 먼저 준비한 다음 서버에 연결을 시도한다.

5. 액터 리플리케이션 기초

  • 서버화 동기화 되지 않고 클라이언트가 고정으로 로드하는 액터는 NetLoadOnClient 옵션을 설정.
  • 프로퍼티 리플리케이션을 통해 액터의 프로퍼티가 변경되었을 때 상대의 OnRep_ 함수를 호출하게끔 연결
    • bReplicates 속성을 true / UPROPERTY에 Replicated, ReplicatedUsing(콜백함수) 지정
    • Net/UnrealNetwork.h 헤더의 GetLifttimeReplicatedProps 함수에 DOREPLIFETIME 매크로로 리플리케이트 할 프로퍼티 명시

6. 액터 리플리케이션 빈도와 연관성

  • 언리얼 인사이트 실행을 위한 배치파일 생성 / 사용 방법
    • 서버와 클라이언트에서 들어오고 나온 패킷 종류 및 용량을 알 수 있다.
  • 클라-서버 통신 빈도는 NetUpdateFrequency 값으로 관리한다.
    • 이는 빈도 최대값이며, 서버 Tick이 이보다 낮으면 서버 Tick으로 적용된다.
    • 전송 데이터 절감을 위해 전송 빈도를 줄이고 보간으로 채우는 방식을 주로 사용.
  • 클라이언트와 ‘연관성’이 있는 액터의 정보만 보내기 위하여 선별이 진행된다. IsNetRelevantFor
    • 틱마다 모든 커넥션과 액터에 대한 연관성 점검. 플레이어 화면에 보이는가? / 오너 확인 / 뷰어와의 거리(NetCullDistance)

7. 액터 리플리케이션 로우 레벨 플로우

  • 한정된 대역폭에서 모든 정보를 보낼 수 없기에 우선권에 의한 선별이 진행된다. 전송되지 못한 패킷은 다음 틱으로 밀려난다.
    • 우선권은 절대 순위가 아닌 비율 지정이며, 오래 밀린 패킷일수록 높은 우선권을 가진다. → NetPriority에서 관리
  • 휴면(Dormancy) 액터는 연관성이 있더라도 리플리케이션 진행X → 액터 휴면 옵션 지정 가능.
  • 리플리케이션은 조건을 지정할 수 있다. 다만 필요한 경우에만 사용할 것.
  • 리플리케이션 과정은 NetDriver::ServerReplicateActors에서 확인할 수 있다.
    • 서버는 휴면여부 / 업데이트 타이밍 / 연관성을 파악하여 Consider List에 추가
    • 클라이언트는 각 액터에 대하여 ConsiderList를 확인하여 리플리케이션 목록 정렬 및 처리.

8. RPC 기초

  • 원격 프로시저 호출. 클라→서버 통신의 유일한 수단이다.
    • 클라→서버는 검사를 위해 Validate함수를 지정할 수 있다.
  • RPC함수는 UPROPERTY 매크로에 Client / Server / Multicast를 지정해야하며, 신뢰성 조건인 Reliable, UnReliable을 지정해야한다.
    • 호출하는 쪽에 해당 매크로를 붙인다. 호출되는 쪽은 해당 매크로가 붙은 함수 이름에 _Implementation을 추가한다.
    • RPC와 프로퍼티 리플리케이션 모두 클라이언트에 영향을 미칠 수 있지만, RPC는 동기화를 보장하지 않는다. → RPC는 휘발성 데이터에 사용을 권장.
    • 오너십 작동 방식의 이해가 필요하다.
  • RPC 및 리플리케이트는 가능한 적은 수료 유지해야하며, 낮은 빈도로 호출하는 것이 좋다.
  • HasAuthority로 호출 지점을, IsLocallyControlled를 통해 오너십을 파악.

9. 캐릭터 공격 구현

  • 중요 판정은 서버에서 프로퍼티 리플리케이션으로, 시각 효과는 RPC로.
  • 기본적인 공격 흐름
    • (클→서) ServerRPC로 공격 명령을 전달
    • (서) 실제 공격 진행 및 판정 & MulticastRPC로 애니메이션 실행
    • (서→클) 프로퍼티 변경 리플리케이션
  • 액터 컴포넌트 리플리케이션을 위해서는 SetReplicated를 true로 설정하라. 그래야 ReadyForReplication을 호출하여 준비가 완료됨을 알린다.

10. 캐릭터 공격 구현 개선

  • 위의 공격 로직은 통신 부하가 가해졌을 때 서버-클라의 괴리가 커질 수 있다. 따라서 아래와 같이 공격 흐름을 개선.
    • (클→서) ServerRPC를 통해 공격 명령 전달
      • (서) 공격 명령 받은 이후 개별 공격 시작
      • (클) 클라이언트 개별 공격 시작 → 공격 결과 ServerRPC호출
    • (서) 클라이언트가 보낸 공격 결과를 서버의 결과와 비교. 이후 사용 or 보간.
    • (서→클) 최종 프로퍼티 리플리케이션
  • 그래도 결국 중요한 연산은 서버에서 진행하고 판단한다.

11. 움직임 리플리케이션

  • 지금까지는 특정 프로퍼티나 RPC를 통한 함수 실행을 중심으로 알아봤다. 서버와 클라이언트는 각자의 움직임과 물리 동작을 어떻게 복제하는가?
  • 캐릭터 무브먼트 컴포넌트의 동작 과정은 아래와 같다.
    • (클) 클라이언트의 움직임 발생 → 틱마다 가속도 및 정보를 기록 → PerformMovement(실제 움직임) → 움직임 정보 수집 및 정제 → 서버로 전송(ServerMove)
    • (서) 받은 움직임 동작을 토대로 서버 캐릭터 움직임(MoveAutonomous)실행 → 최종 결과 기반으로 클라이언트와 움직임 비교
    • (서→클) 비교 결과를 통해 클라이언트 위치 조정 혹은 보존
  • LogNetPlayerMovement=VeryVerbose 설정을 통해 움직임 리플리케이션 디버깅 가능.

12. 물리 움직임 리플리케이션

  • 움직임 뿐만 아니라 물리 상태까지 전송하는 경우, RPC가 아닌 프로퍼티 리플리케이션을 사용한다. → SimulatedProxy의 경우.
    • (서) GatherCurrentMovement에서 물리, 일반 움직임 구분하여 처리
    • (서) FRepMovement 구조체에 움직임 및 물리 정보 저장. 물리 정보 저장은 bRepPhysics 플래그로 조정 가능. (FRigidBodyState와 호환 구조체)
    • (서→클) ReplicatedMovement는 움직임 기록 변수. 해당 변수 리플리케이트 프로퍼티를 통해 클라의 OnRep 호출
    • (클) OnRep으로 호출된 함수에서 움직임 및 물리 움직임 처리.
      • ApplayRigidbodyState() - 물리처리
      • SimulateTick() - 움직임 처리. 캡슐 이동으로 위치를 확보하고 SmoothClientPosition으로 메시 움직임 보간.
  • BP에서 옵션 체크 잘하기.
    • Replicated 옵션 체크 - 데이터만 넘기고 클라이언트 물리 동작X
    • ReplicateMovement옵션 체크 - 물리 동작까지는 하는데 동기화X
    • Static Mesh Replicate Movement 옵션 체크 - 동기화O

13. 캐릭터 무브먼트의 확장

  • 네트워크 연결이 필요한 특정 동작을 추가할 때, 캐릭터 무브먼트 컴포넌트를 확장 구현하는 방식을 사용할 수 있다.
    • FNetworkPredictionData_Client_Character (움직임 발생 추적)
      • AllocateNewMove, FreeMove
    • FSavedMove_Character (움직임 저장)
      • GetPredictionData_Client
  • 기존의 생성자를 대신하여 ObjectInitializer를 통해 새로 만든 컴포넌트를 사용하게끔 지정.

14. 캐릭터 아이템과 스탯의 구현

  • 스탯과 아이탬은 특정 클라이언트에게만 전달되고, 다른 클라이언트들에게는 전달되지 않아야한다.
    • GetLifetimeReplicatedProps의 매크로로 DOREPLIFETIME_CONDITION 매크로 사용.
  • 언리얼에서는 멀티플레이어 최적화를 위하여 NetSerialize / 푸시모델 설정 / FFastArraySerializer / Iris 시스템 등을 제공한다.
    • 구조체에 여러 프로퍼티가있을 때, 자동으로 변경된 정보만 뽑아서 전송한다.

15. PvP 게임의 완성

  • 게임을 위한 부가 기능 구현

+Alpha

  • ini 파일 사용법
  • 주체 확인에 유용한 로그 작성법을 배웠다. → Ch.15 기준으로 사용할 것.
  • 타이머 사용법

내가 사용하고자 하는 것에 어떻게 적용하지?

  • 접속 플로우
    • 로그인 및 게임 시작까지의 준비 과정 - 로비에서 메인 게임까지 생성
    • 세션 생성 및 온라인 서브시스템은 Udemy에서 배운걸로 시도하자.
  • 멀티 플레이어 동작
    • 기본적인 애니메이션 재생 및 이동 리플리케이션에 적용할 수 있다.
  • 물리 리플리케이션
    • 카오스 디스트럭션을 네트워크에 적용시키기 위해서 최적화가 반드시 필요함을 깨달았다. 강의 후반부에 배운 최적화 방법을 다양하게 사용해보기.

완강 후 느낀점

  • 핵심 클래스들에 대한 이해가 필요하다.

    핵심 클래스란 Actor, Character와 같은 큼지막한 클래스를 의미한다. 다양한 함수가 이들 내부에 구현되어 있는데, 존재조차 모르는 함수들을 끌어다 사용할 수는 없지 않은가? 코드와 주석을 살펴보며 어떠한 기능이 있는지 정리가 필요하다.