Tanknarok
개요
Fusion Tanknarok 샘플은 권한 있는 서버(서버와 클라이언트를 모두 실행하는 독립 실행형 애플리케이션 또는 단일 애플리케이션)가 있는 호스팅 모드
또는 각 클라이언트가 자체 객체에 대한 권한을 가지고 하나의 클라이언트를 제어하는 공유 모드
와 하나의 클라이언트가 "공유된" 객체를 제어하는 소형 멀티 플레이어 아레나 스타일의 Tank 게임을 구축하는 방법을 보여줍니다.
이 예제의 게임은 당사의 협력사인 Polyblock Studios에서 게임 플레이 로직, 오디오 및 멋진 그래픽을 제공했기 때문에 Steam에서 친숙하게 보일 수 있습니다. 이 샘플은 Photon Fusion으로 포팅 된 실제 게임의 일부에 불과합니다. 오리지널 게임은 Photon Bolt를 사용하여 만들어졌습니다.
Fusion Tanknarok 샘플은 예측 네트워크 시스템을 PhysX와 같은 비결정론적 rigidbody 물리 엔진과 혼합하여 발생하는 복잡함 없이 물리와 유사한 효과를 얻을 수 있는 방법을 보여줍니다. Fusion은 필요할 경우 유니티 rigidbody 동기화를 완전히 지원합니다.
시작하기 전에
3D 템플릿을 사용하여 새 유니티 프로젝트를 만들고, 다음에서 컬러 스페이스를 "Linear"로 설정하십시오.
Project Settings > Player > Other Settings > Color Space
.
샘플을 다운로드하고 가져오기 전에 Unity Post Processing 패키지가 프로젝트에 포함되어 있는지 확인하십시오.
Window > Package Manager
로 이동Packages: Unity Registry
선택- "Post Processing"을 검색하고
- 패키지 설치
스크린샷
다운로드
버전 | 릴리즈 일자 | 다운로드 | ||
---|---|---|---|---|
1.1.7 | Jun 28, 2023 | Fusion Tanknarok 1.1.7 Build 202 |
주요 내용
- 공유 및 호스팅 모드 지원
- 지연 보상 레이캐스트
- 예측 스포닝
- 객체 폴링
- 완전한 게임 루프
프로젝트
데모를 실행하기 전에 Photon Cloud 용 Fusion App ID를 생성하여 PhotonAppSettings
에셋에 복사해야 합니다. 앱 ID는 Photon 관리 화면에서 생성할 수 있습니다. Realtime ID가 아닌 Fusion App ID를 생성하세요.
photon 앱과 설정 에셋은 Fusion 메뉴 Fusion > Realtime Settings
에서 선택할 수 있습니다.
생성된 App Id를 App Id Fusion
필드에 붙여넣기만 하면 됩니다.
폴더 구조
Tanknarok 샘플의 코드는 이 예제에 국한되어 있지 않은 범용 유틸리티용 Utility
하위 폴더 및 Fusion 유틸리티용의 FusionHelpers
폴더와 함께 /Scripts
폴더에 있습니다.
나머지 Tanknarok
폴더에는 실제 게임 코드가 다음과 같은 하위 폴더로 구분되어 있습니다.
- Audio - 음향 효과 및 음악
- Camera - 카메라 위치 코드
- Level - 모든 레벨 로직과 행동, 파워 업 및 플레이이 이외의 아이템들
- Player - 모든 탱크, 무기, 탄환 제어 로직, 탱크 시각화 및 효과
- UI - 사용자 인터페이스 컴포넌트
메인폴더에는 GameLauncher
클래스와 GameManager
, LevelManager
및 PlayerManager
등이 주요 엔트리 포인트입니다.
빠른 Fusion 지침서
Fusion은 NetworkObject
컴포넌트로 네트워크 상태를 식별합니다. 네트워크 상태가 있는 게임 객체에도 NetworkObject
가 있어야 합니다. NetworkObject
자체는 단순히 게임 객체에 네트워크 전체의 ID를 할당하며, 실제 네트워크 상태는 NetworkBehaviour
에서 파생된 컴포넌트에 저장됩니다. Fusion에는 몇 가지 기본 동작이 포함되어 있는데, 예를 들어 유니티 Transform을 동기화하는 NetworkTransform
이 있습니다.
FixedUpdate()
에서 물리 작용이 물리 상태를 변환하는 메소드과 유사하게 NetworkBehaviour
에서는 FixedUpdateNetwork()
메소드로 네트워크 상태를 변환합니다. 이는 렌더링 프레임과 무관하게, 그리고 네트워크의 업데이트와 무관하게, 체크 표시라고 하는 고정된 시간 단계에서 발생합니다. 각 업데이트는 이전 체크 표시에서 상태 외부에서 간단히 작동합니다. 네트워크에서 지정된 틱의 상태가 확인되면 Fusion은 개체 상태를 해당 틱에 롤백하고 그 시간과 현재 틱 사이의 모든 중간 호출을 FixedUpdateNetwork()
에 다시 적용합니다.
현재 로컬 틱은 항상 마지막으로 확인된 틱보다 앞섰기 때문에 업데이트를 "예측"이라고 하며 확인된 상태의 애플리케이션과 FixedUpdateNetwork
메소드의 후속 재실행은 "롤백" 및 "재시뮬레이션"이라고 합니다.
네트워크 상태 없이 컴포넌트가 시뮬레이션의 일부가 될 수 있지만 오버헤드를 줄이기 위해서는 NetworkBehaviour
가 아닌 SimulationBehaviour
에서 파생되어야 합니다. 재시뮬레이션으로 인해 프레임당 FixedUpdateNetwork()
메소드가 여러 번 호출될 수 있습니다. 이 메소드는 재설정되므로 네트워크 상태에서만 작업할 때는 문제가 되지 않지만 네트워크 상태가 아닌 상태에는 델타 변경 사항을 적용할 때는 주의해야 합니다.
시뮬레이션, 예측 및 네트워크 개체에 대한 자세한 내용은 Fusion 매뉴얼을 참조하십시오.
GameLauncher
탱크 게임의 메인 UI는 GameLauncher
클래스가 담당합니다.
게임 모드가 선택되면 GameLauncher는 세션을 만들기 위해 FusionLauncher.Launch()
를 호출합니다. FusionLauncher
가 Fusion 연결 이벤트에 응답하고 제공된 콜백을 호출하여 초기 네트워크 객체를 생성합니다:
- GameManager (호스트 모드의 호스트 또는 공유 모드의 마스터 클라이언트에 의해 생성됩니다.)
- Player (호스트 모드의 호스트 또는 공유 모드의 해당 클라이언트에 의해 생성됩니다.)
준비
플레이어 탱크는 플레이어가 연결되고 "로비" 모드로 완전히 제어될 때 즉시 플레이하며 나머지 탱크가 스폰 되기를 기다립니다.
연결된 모든 플레이어가 준비가 되었음을 표시할 때까지 게임 자체는 시작되지 않습니다. 이 로직은 모든 클라이언트에서 실행되지만 GameManager
의 StateAuthority
를 갖고 있는 클라이언트만이 레벨을 로드할 수 있습니다.
주의: 이 간단한 예에서는 두 레벨이 처음부터 초기 씬에 있으므로 레벨이 "활성화"만큼 "로드"되지 않습니다.
원격 프로시저 호출을 통해 로드됩니다. 호출자는 임의 레벨 인덱스를 생성하여 모든 클라이언트에 전달하여 모든 사용자가 동일한 레벨을 로드하도록 합니다.
c#
if (Object.HasStateAuthority)
RPC_ScoreAndLoad(-1,0, _levelManager.GetRandomLevelIndex());
RPC 자체는 다음과 같이 정의됩니다
[Rpc(sources: RpcSources.StateAuthority, targets: RpcTargets.All, InvokeLocal = true, Channel = RpcChannel.Reliable)]
private void RPC_ScoreAndLoad(int winningPlayerIndex, byte winningPlayerScore, int nextLevelIndex)
{
...
}
레벨 전이
로비에서 레벨로의 전환과 그 반대로의 전환은 TransitionSequence()
코루틴의 LevelManager
가 처리합니다. 전환 자체는 전적으로 로컬 타임으로 실행되지만, 트리거 될 때(RPC_ScoreAndLoad
원격 프로시저 호출에 의해) 뿐만 아니라 종료될 때(playState
를 LEVEL
로 설정하여 클라이언트 간에 동기화됩니다. 이는 GameManager
상태 권한에서만 설정할 수 있는 네트워크 속성이기 때문입니다).
게임이 끝나면 레벨 전환은 로비로 돌아가 루프를 완료하고 게임을 준비 상태로 되돌리는 것과 거의 같은 방식으로 승자를 보여줍니다.
입력 처리
Fusion은 유니티의 표준 입력 처리 메커니즘을 사용하여 플레이어 입력을 캡처하여 네트워크를 통해 전송할 수 있는 데이터 구조체에 저장한 다음 FixedUpdateNetwork()
메소드로 이 데이터 구조체를 제거합니다. 이 예제에서 이 모든 것은 InputController
클래스에 의해 구현되지만 실제 상태 변경은 Player
클래스에 전달됩니다.
슈팅
이 예제의 탱크에는 두 가지 유형의 히트 감지 기능이 있는 4개의 무기가 있습니다.
- 인스턴스 히트
- 발사체
각각 HitScan
및 HitScan
클래스에 의해 구현됩니다. 둘 다 세 가지 중요한 Fusion 기능인 객체 풀링, 예측 산란 및 지연 보상을 사용합니다.
객체 폴링
새 객체를 생성할 때 프레임이 손실되지 않도록 하려면 항상 새 객체를 제거하고 인스턴스화하는 대신 이전 객체를 다시 사용하는 것이 좋습니다. 이것은 어느 게임이나 마찬가지이며, 특히 유니티는 물론 Fusion에서도 마찬가지입니다.
이를 용이하게 하기 위해 애플리케이션에서 재활용 게임 객체를 제공하고 수집하는 후크를 지정할 수 있습니다.
객체 풀은 기본적으로 프리팹을 기반으로 풀에서 객체를 가져가는 메소드와 재사용을 위해 객체를 풀로 반환하는 메소드인 NetworkObjectPool
을 구현해야 합니다.
예측 스포닝
예측 스포닝을 사용하면 상태 권한의 생성 확인이 있을 때까지 클라이언트가 새 네트워크 객체의 생성을 예측하여 임시 로컬 플레이스 홀더를 만들 수 있습니다. Fusion은 플레이스 홀더를 자동으로 실제 네트워크 객체로 승격합니다.
수동으로 처리해야 하는 것은 실패한 예측입니다. 이것은 플레이스 홀더를 파괴하는 것만큼 간단합니다(단순히 유니티 객체임을 기억하십시오). 애플리케이션은 다양한 형태의 페이드아웃 또는 실패 시각화를 자유롭게 구현할 수 있습니다.
또한 플레이스홀더에 상태가 없으므로 애플리케이션은 네트워크 속성에 액세스하지 않는 방식으로 예측 단계 동안 이동을 관리해야 합니다.
지연 보상
로컬에서 각 플레이어는 자신의 (입력 권한) 객체의 예측된 미래 버전과 다른 클라이언트의 객체의 인터- 또는 엑스트라-변환 버전을 보게 됩니다. 둘 다 서버가 보는 것과 정확히 일치하지 않습니다. 따라서 총알과 같은 빠르게 움직이는 물체는 각 머신마다 다른 것에 부딪힐 가능성이 매우 높습니다. 총알을 발사한 사람은 뭔가 잘못됐는지 알아차릴 가능성이 가장 높아요. 그러나 동시에, 서버는 히트 탐지에 대한 권한을 가지고 있어야만 히트 점수가 성공했다고 판단하는 로크 클라이언트를 피할 수 있습니다.
이 문제를 해결하기 위해 Fusion은 지연 보상 레이캐스트를 지원합니다. 이는 기본적으로 서버에서 실행될 때도 클라이언트가 슈팅 당시 본 것을 존중하는 레이 캐스트입니다. 이 작업을 수행하기 위해 많은 스냅샷 보간 마법이 분명히 있습니다. 다행히 개발자에게 지연 보상 레이캐스트를 구현하고 사용하는 것은 일반 유니티 레이캐스트를 사용하는 것만큼 간단합니다.
우리가 알아야 할 것은 그것은 HitBox
라고 불리는 고유의 콜라이더 물체와 함께 제공된다는 것입니다. HitBox
는 객체 계층에 있는 전체 HitBoxRoot
노드의 형제 또는 자식이어야 합니다. 이를 통해 Fusion은 하위 노드에 대해 더 비싼 검사를 수행하기 전에 루트를 신속하게 제거할 수 있습니다.
성능상의 이유로 HitBox
를 정적 엔티티에 적용해서는 안 됩니다. 레이 캐스트를 차단하려면 여전히 정적 환경이 필요하므로 지연 보상 레이 캐스트가 유니티 콜라이더를 선택적으로 확인할 수도 있습니다.
주의할 점은 동적 -즉, 이동-PhysX 콜라이더에는 지연 보상이 적용되지 않는다는 것입니다. 또한 유니티는 정적 콜라이더와 동적 콜라이더를 구별하는 레이 캐스트 쿼리를 제공하지 않습니다. 따라서 PhysX 콜라이더를 필터링하고 정적 결과만 얻으려면 동적 객체의 서로 다른 레이어에 HitBox
/HitBoxRoot
및 PhysX Collider
를 사용하는 것이 좋습니다.