network simulation loop
title: "네트워크 시뮬레이션 루프" tags: "fusion"
소개
Fusion은 사용되는 네트워크 모드에 관계없이 일관된 시간 단계로 이산 틱 기반 시뮬레이션을 실행합니다. 이를 처리하는 전체 프로세스를 네트워크 시뮬레이션 루프라고 합니다. 모든 로직은 NetworkBehaviour
의 하위 클래스에 작성되며, 이는 일반 유니티 게임 객체가 이 시뮬레이션 루프의 중심 부분임을 의미합니다.
Fusion은 세계 상태의 스냅샷을 앞으로 이동하는 것 외에도 로컬 플레이어 입력 또는 물리 기반 객체를 기반으로 미래 상태를 예측할 수 있습니다(물리에 대해 클라이언트 측 예측을 켜는 것은 선택 사항임).
현재 알려진 데이터와 사용자 지정 코드(예: 발사체 궤적 외삽)를 기반으로 모든 객체에 대한 클라이언트 측 예측 로직을 작성할 수 있습니다.
이 문서에서는 Fusion의 시뮬레이션 루프가 작동하는 방식과 관련된 가장 중요한 개념에 대해 더 자세히 설명합니다.
틱
네트워크로 연결된 머신들 사이에 자연적으로 발생하는 타이밍 불일치가 있는데, Fusion은 시간으로 직접 구동하는 대신 틱 이라고 불리는 이산 추상적인 시간 단위를 사용하여 시뮬레이션을 처리합니다. 틱은 특정 클라이언트와 호스트에 전달되는 실제 시간과 분리됩니다. 하드웨어 시계 대신 틱을 사용하면 네트워크 세션의 모든 클라이언트가 "시간"의 개념에 대한 공통된 기준 프레임을 공유할 수 있습니다. 틱은 드리프트 하지 않고 정밀도 문제의 영향을 받지 않으며, 이는 여러 네트워크로 연결된 머신에서 미래와 과거의 사건에 대해 정확하게 추론하는 데 매우 중요합니다.
각 틱 사이의 시간 간격은 NetworkProjectConfig
의 Simulation > Tick Rate
로 정의됩니다. 틱 속도는 헤르츠(Hz) 단위로 정의되므로 60분의 1초의 타임 스텝은 델타(1/60초)와 같습니다. 즉, 이전 틱과 현재 틱 사이에 실제로 얼마나 많은 시간이 흘렀는지에 관계없이 시스템이 60분의 1초의 타임 스텝을 사용하여 게임 상태를 진행합니다. 코드에서 타임 스텝은 NetworkRunner.DeltaTime
속성을 통해 액세스할 수 있습니다.
중요: 시뮬레이션 루프에 사용되는 틱 속도는 렌더링 레이트(초당 프레임)와 다릅 니다!
각 틱에는 해당하는 상태 스냅샷이 있으며 이 스냅샷에는 해당 시점의 세계의 "진실"이 명시적으로 표시됩니다. 다음 틱에 대한 새 스냅샷을 생성하기 위해 Fusion은 모든 NetworkBehaviour
에서 FixedUpdateNetwork()
를 호출하여 자신이 담당하는 GameObject의 전반적인 상태 부분을 업데이트합니다. Fusion은 이를 특정 GameObject에 대해 상태 권한을 가진다고 말합니다.
어떤 특정한 대상에 대해 상태 권한을 갖는 머신이 딱 하나 있습니다.
- 클라이언트/서버 설정에서 서버는 항상 서버입니다.
- 각 클라이언트가 자신의 객체를 소유하는 클라이언트 권한 설정에서는 일반적으로 각 클라이언트가 생성한 객체에 대한 권한을 유지합니다.
입력 처리
Fusion은 클라이언트 측 예측을 유도하기 위해 입력들을 사용합니다. 클라이언트는 로컬(시간이 오래된) 정보와 로컬 플레이어들의 입력에 기초하여 다음 서버 상태를 예측합니다. 실제 상태가 수신되면, 클라이언트는 새로운 미래 상태를 다시 시뮬레이션하기 위해 이 새로운 데이터를 기반으로 사용할 것입니다.
예측을 가능하게 하기 위해 입력 처리를 두 단계로 나누었습니다.
- 입력 폴링: Fusion은 특정 개체에 대해 입력 권한을 사용하여 클라이언트의 로컬 하드웨어에서 입력을 수집합니다.
- 입력 소비: 입력은 로컬 시뮬레이션에 의해 적용되며 권한 있는 시뮬레이션에 포함될 서버와 공유됩니다.
입력 정의, 폴링 및 소비에 대한 자세한 내용은 매뉴얼의 네트워크 입력 페이지를 참조하십시오.
예측
예측은 클라이언트가 현재 로컬 정보에 기초하여 미래 서버 상태를 예측 하는 것을 포함합니다. 서버로부터 수신된 실제 게임 상태 스냅샷에 기초한 로컬 정보는 네트워크화된 디바이스들 사이의 상속 지연으로 인해 항상 시간이 오래된 것입니다. 클라이언트는 로컬 게임 상태가 서버 게임 상태와 일치하도록 하기 위해 예측을 사용합니다. 이것은 네트워크 지연이 플레이어 경험에 미치는 영향을 완화하기 위해 필요합니다. 네트워크 지연은 항상 서버에서 클라이언트로 그리고 다시 왕복하는 것과 같습니다. 이산 틱의 관점에서 추론될 수 있습니다.
다음과 같은 특징을 가진 시나리오를 상상해 보십시오:
- 현재 서버 틱: 100
- 틱율 : 1/60초
- 클라이언트 1의 지연 시간은 4틱입니다(서버에서 클라이언트로 2틱 + 클라이언트에서 서버로 2틱)
- 클라이언트 2의 지연 시간은 6틱입니다(서버에서 클라이언트로 3틱 + 클라이언트에서 서버로 3틱)
클라이언트가 각각의 지연 시간을 인식하고 있지만, 클라이언트가 계속 유지하기 위해 몇 개의 틱을 예측해야 하는지 알려주는 것은 서버입니다. 예측을 통해 클라이언트는 서버가 주어진 틱에 대해 필요한 시간에 맞춰 입력을 보낼 수 있습니다. 클라이언트가 입력 권한을 가진 객체는 로컬 입력(예: 플레이어 캐릭터)을 사용하여 예측되어 즉각적인 응답을 하는 것처럼 착각합니다. 객체의 상태를 명시적으로 제어할 수는 없지만 상태를 주도하는 입력을 제어할 수 있기 때문에 입력 권한이라고 합니다.
예를 들어, 클라이언트 1이 플레이어 캐릭터를 이동할 때, 서버로부터 수신한 마지막으로 검증된 틱에 기초하여 미래 상태를 예측합니다. 이러한 예측들에 기초하여, 클라이언트 1은 서버의 미래로 2 틱을 무엇을 할 것인지를 서버에 알려줍니다(즉, 틱 102). 이것은 입력이 유선을 통해 이동하고 틱 102에 대한 검증된 상태의 시뮬레이션 동안 소비될 시간에 맞춰 서버에 의해 수신될 충분한 시간을 남깁니다. 클라이언트 1의 플레이어 이동을 방해하는 것이 없는 한, 클라이언트 1의 예측한 틱 102와 서버의 검증된 틱 102는 동일한 상태에 있을 것입니다.
클라이언트 1이 지연 시간을 보상할 수 있는 충분한 틱을 미래에 예측하도록 하기 위해 서버는 항상 서버 상태보다 적어도 2틱 앞서라고 말합니다. 반면 클라이언트 2는 지연 시간이 더 길기 때문에 미래에 3틱을 예측해야 합니다.
그림에서 알 수 있듯이 클라이언트 1과 클라이언트 2는 서버에 대한 상대적인 지연 시간 때문에 정확히 동일한 상태에 있습니다. 클라이언트는 다른 클라이언트의 정보가 아직 자신에게 도달하지 않았기 때문에 자신의 "진실"이 잘못되었다는 사실을 알고 있습니다. 여기서 복제와 조정이 이루어집니다.
복제
Fusion은 콤팩트 메모리 버퍼의 상태 진행을 관리하고 시뮬레이션합니다. Fusion은 서버로부터 상태 스냅샷을 받으면 스냅샷을 메모리 버퍼에 언패킹합니다. 버퍼에 보관된 현재 상태가 예측된 상태를 기반으로 했으므로 스냅샷의 상태로 대체하면 클라이언트가 서버로부터 받은 스냅샷과 관련된 틱으로 효과적으로 시간을 되돌릴 수 있습니다. 그런 다음 Fusion은 수신된 상태와 클라이언트의 로컬 예측 상태 사이의 모든 틱에 대해 시뮬레이션 루프를 한 번 실행하여 더 정확한 예측 상태를 제외하고 스냅샷을 받기 전의 상태로 되돌립니다.
조정
클라이언트가 보다 최근의, 그리고 따라서 보다 정확한 세계의 상태를 갖는 새로운 스냅샷을 수신하면, 클라이언트는 그것의 로컬 상태를 그것의 상태와 조정합니다. 조정은 클라이언트에 의해 수신된 마지막 서버 스냅샷을 복제하고 서버 스냅샷과 연관된 틱으로부터 현재 틱까지 로컬 게임 상태를 재 시뮬레이션함으로써 수행됩니다.
최종 결과는 서버가 항상 견고한 조정 상태에 있고 예기치 않은 이벤트가 발생했을 때 클라이언트가 점진적으로 자가 조정한다는 것입니다. 이 절차는 클라이언트의 상태를 서버와 조정하기 위해 새로운 검증된 상태를 기반으로 이전 입력의 효과를 재시뮬레이션하기 때문에 퓨전에서 재시뮬레이션 이라고 합니다.
이것은 클라이언트 1과 2가 서버에서 충돌한 경우에 특히 중요합니다. 서버가 특정 틱에 대해 모든 개체의 종료 상태를 독점적으로 결정하기 때문에 서버가 그들의 위치를 해결합니다. 이것이 서버가 국가 권한을 가지고 있다고 말하는 이유입니다.
예제
서버는 오래된 데이터의 꾸준한 스트림을 클라이언트에 전송합니다. 검증된 틱 100이 클라이언트 1에 의해 수신될 때, 이는 틱 100의 검증된 상태를 메모리 버퍼에 복제하여 이전의 예측한 틱 100을 덮어씁니다.
- 예상치 못한 일이 발생하지 않으면 로컬 예측된 틱 100은 클라이언트 1과 서버가 완벽하게 동기화된 상태임을 의미하며, 클라이언트 1이 서버보다 2 틱 앞서 있습니다.
- 검증된 틱이 클라이언트 1의 예측과 일치하지 않으면 검증된 틱 100이 수정된 상태로 사용됩니다.
어느 경우에나 클라이언트는 입력을 100, 101 및 102부터 순차적으로 다시 적용하여 새로운 틱 103에 도달합니다.
클라이언트 1의 관점에서 시간적으로 앞으로 나아가면 수정된 틱 101을 수신하고 102와 103에 대한 입력을 적용하고 수정된 상태에 도달할 때 틱 104에 있을 것입니다.
클라이언트 2는 시뮬레이션 루프에서 정확히 동일한 절차를 거칩니다. 유일한 차이점은 더 높은 지연 시간으로 인해 서버 검증된 틱 1이 틱을 수신하는 데 약간의 지연 시간이 발생한다는 것입니다. 이는 앞으로 예측함으로써 이를 보상합니다.
FixedUpdateNetwork()
FixedUpdateNetwork()
는 현재 틱 상태를 다음 틱 상태로 진행하기 위해 해당 씬의 모든 NetworkBehaviour
컴포넌트에 대해 FixedUpdateNetwork()
를 호출합니다. FixedUpdateNetwork
는 복제 및 예측 과정에서 호출되기 때문에 로컬 상태는 네트워크 상태에서 파생되어야 합니다. FixedUpdateNetwork
에서 점진적인 델타 업데이트를 로컬 상태에 적용하면 변경 사항이 너무 많이 적용될 가능성이 높습니다.
스냅샷 보간
일반적으로 각 클라이언트는 단일 또는 선택된 몇 개의 GameObjects
에 대한 입력 권한만 가지며, 나머지 GameObjects
는 스냅샷 업데이트를 통해 상태를 클라이언트에 제공합니다. 서버가 전송하는 시간과 클라이언트가 수신 및 복제하는 시간 사이에 지연 시간이 있기 때문에 예측된 상태와 달리 스냅샷은 항상 서버 뒤에 있습니다.
Fusion은 이러한 "원격"으로 제어되는 GameObjects
를 프록시 라고 부릅니다.
스냅샷이 프록시의 현재(시각) 상태에 그대로 적용되면 클라이언트의 렌더링 속도 대신 시뮬레이션의 틱 속도로 애니메이션화 및 업데이트되는 것처럼 보입니다. 이를 방지하기 위해서는 다음과 같은 몇 가지 이유가 있습니다:
- 대역폭을 절약하기 위해서. 종종 렌더링보다 훨씬 낮은 주파수에서 시뮬레이션을 실행하는 것이 바람직합니다. 예를 들어, 30Hz 네트워크 시뮬레이션 주파수와 120Hz 렌더링 주파수가 있습니다.
- 네트워크 패킷은 일정한 속도로 도착하지 않습니다. 무작위적인 측면이 큰 이벤트에 게임의 렌더링 빈도를 묶으면 불안한 움직임이 발생합니다.
네트워크 패킷-주파수와 렌더링의 새로 고침 빈도를 분리하면서 부드러운 상태 전환을 제공하기 위해 Fusion은 두 스냅샷 사이의 "렌더 상태"를 보간합니다. 보간은 틱이 아닌 실시간으로 매끄럽게 하는 것을 목표로 하며, 이상적으로 보간은 사용 가능한 가장 최근의 두 스냅샷을 기반으로 합니다. 그러나 스냅샷이 일정한 속도로 도착하지 않을 수 있습니다. 따라서 작은 마진(버퍼)으로 인해 이러한 현상이 발생하지 않도록 방지하지 않으면 일정한 속도의 보간이 일정하지 않은 네트워크 스냅샷을 따라잡을 위험이 있습니다.
Fusion의 많은 강점 중 하나는 현재 네트워크 조건에 따라 필요한 오프셋과 버퍼링을 조정하는 보간 알고리즘입니다. 이는 클라이언트에게 가능한 한 적은 대기 시간을 제공하면서도 부드러운 시각적 기능을 제공합니다.
네트워크 트랜스폼과 같은 Fusion 핵심 컴포넌트는 보간을 사용하여 시각적 컴포넌트를 변환하지만 사용자 지정 네트워크 속성에 대해서도 Fusion 내장 보간기에 접근하여 사용할 수 있습니다. 보간기에 대한 자세한 내용은 여기에서 확인할 수 있습니다.
Back to top