This document is about: FUSION 2
SWITCH TO

Tanknarok

Level 4

개요

Fusion Tanknarok 샘플은 권한 있는 서버(독립 실행형 애플리케이션 또는 서버와 클라이언트 모두를 실행하는 단일 애플리케이션)를 사용하여 호스팅 모드 또는 각 클라이언트가 자신의 캐릭터에 대한 권한을 가지고 한 클라이언트가 "공유" 객체를 제어하는 공유 모드에서 실행되는 작은 멀티플레이어 아레나 스타일의 탱크 게임을 구축하는 방법을 보여줍니다.

이 예의 게임은 협력관계인 Polyblock Studios에서 게임 플레이 로직, 오디오 및 멋진 그래픽을 제공했기 때문에 스팀에서 친숙하게 보일 수 있습니다. 이 샘플은 Photon Fusion으로 이식된 실제 게임의 작은 부분일 뿐입니다. 오리지널 게임은 Photon Bolt를 사용하여 만들어졌습니다.

Fusion Tanknarok 샘플은 예측 네트워크 시스템과 PhysX와 같은 비결정적 강체 물리 엔진을 혼합함으로써 발생하는 복잡한 문제 없이 물리적 유사 효과를 달성할 수 있는 방법을 보여줍니다. Fusion은 유니티 강체가 필요한 경우 동기화를 완전히 지원합니다.

시작하기 전에

프로젝트 설정 > 플레이어 > 기타 설정 > 컬러 공간에서 컬러 공간을 "Linear"로 설정한 3D 템플릿으로 새로운 유니티 프로젝트를 생성합니다.

샘플을 다운로드하고 가져오기 전에 유니티 포스트 프로세싱 패키지가 프로젝트에 포함되어 있는지 확인합니다.

  1. Window > Package Manager로 이동합니다.
  2. Packages: Unity Registry를 선택합니다.
  3. "Post Processing"을 검색하고
  4. 패키지를 설치합니다.

스크린 샷

다운로드

버전 릴리즈 일자 다운로드
2.0.0 Jan 18, 2024 Fusion Tanknarok 2.0.0 Build 392

하이라이트

  • 공유 모드 및 호스트 모드 지원
  • 지연 보상 레이 캐스트
  • 예측 스폰
  • 객체 풀링
  • 완전한 게임 루프

프로젝트

데모를 실행하기 전에 Photon Cloud용 Fusion App Id를 생성하여 PhotonAppSettings 에셋에 복사해야 합니다. App Id는 Photon 관리 화면에서 생성할 수 있습니다. Realtime Id가 아닌 Fusion App Id를 생성해야 합니다.

Fusion 메뉴 Fusion > Realtime Settings 에서 Photon App 설정 에셋을 선택할 수 있습니다

생성된 App Id를 App Id Fusion 필드에 붙여넣기만 하면 됩니다.

폴더 구조

Tanknarok에서 파생된 샘플의 코드는 /Scripts 폴더에 있으며, 범용 유틸리티를 위한 Utility 하위 폴더와 이 예에 특정되지 않는 Fusion 유틸리티를 위한 FusionHelpers 폴더가 있습니다.

나머지 Tanknarok 폴더에는 실제 게임 코드가 다음 하위 폴더로 구분되어 있습니다:

  • Audio - 효과음 및 음악
  • Camera - 카메라 배치 코드
  • Level - 모든 레벨의 로직 및 동작, 파워업 및 기타 플레이어가 아닌 항목
  • Player - 모든 탱크 컨트롤, 무기 및 총알 로직, 탱크 시각화 및 효과
  • UI - 사용자 인터페이스 구성요소

메인 폴더에는GameLauncher 클래스와 최상위 관리자인 GameManager, LevelManager 그리고 PlayerManager가 있습니다.

tank game
탱크 게임 메뉴.

빠른 Fusion 프라이머

Fusion은 NetworkObject 컴포넌트로 네트워크 상태를 파악합니다. 네트워크 상태를 가진 모든 게임 객체에는 NetworkObject가 있어야 합니다. 네트워크 객체 자체는 단순히 게임 객체에 네트워크 전체의 ID를 할당하는 것으로, 실제 네트워크 상태는 NetworkBehaviour에서 파생된 컴포넌트에 저장됩니다. Fusion에는 몇 가지 기본 동작이 포함되어 있는데, 예를 들어 유니티 트랜스폼을 동기화하는 NetworkTransform이 있습니다.

FixedUpdate()의 물리적 동작이 물리적 상태를 변환하는 것과 유사하게 NetworkBehaviourFixedUpdateNetwork() 메소드에서 네트워크 상태를 변환합니다. 이것은 틱이라고 하는 고정된 시간 단계에서 렌더링 프레임 속도와 네트워크로부터의 업데이트와는 무관하게 발생합니다. 각각의 업데이트는 이전 틱에서 단순히 상태를 벗어나 작동합니다. 네트워크에 의해 해당 틱의 상태가 확인되면 Fusion은 객체 상태를 해당 틱으로 롤백하고 그때부터 현재 틱 사이의 모든 중간 호출을 FixedUpdateNetwork()에 다시 적용합니다.

현재 로컬 틱은 항상 마지막으로 확인된 틱보다 앞서 있으므로 업데이트를 "예측"이라고 하며, 검증된 상태의 적용 및 이후 FixedUpdateNetwork 메소드의 재실행을 "롤백" 및 "재시뮬레이션"이라고 합니다.

컴포넌트는 네트워크 상태가 없이 시뮬레이션에 포함될 수 있지만 오버헤드를 줄이기 위해 NetworkBehaviour 대신 SimulationBehaviour에서 파생되어야 합니다. 재시뮬레이션으로 인해 FixedUpdateNetwork() 메소드는 프레임당 여러 번 호출될 수 있습니다. 네트워크 상태만 사용하면 재설정되므로 문제가 되지 않지만 델타 변경 사항을 네트워크 상태가 아닌 상태에 적용할 때는 주의하십시오.

시뮬레이션, 예측 및 네트워크 개체에 대한 자세한 내용은 Fusion 매뉴얼을 참조하십시오.

GameLauncher

탱크 게임의 메인 UI는 GameLauncher 클래스가 담당합니다.

게임 모드가 선택되면 게임 런처는 FusionLauncher.Launch()를 호출하여 세션을 설정합니다. FusionLauncher는 Fusion 연결 이벤트에 응답하고 제공된 콜백을 호출하여 초기 네트워크 개체를 생성합니다:

  • GameManager(호스트 모드의 호스트 또는 공유 모드의 마스터 클라이언트에 의해 스폰 됨)
  • 플레이어(호스트 모드의 호스트 또는 공유 모드의 각 클라이언트에 의해 스폰 됨)

준비 완료

플레이어 탱크는 플레이어가 연결되면 즉시 스폰 되고 남은 탱크가 스폰 되기를 기다리는 동안 플레이할 수 있는 "로비" 모드로 완전히 제어할 수 있습니다.

게임 자체는 연결된 모든 플레이어가 준비 완료를 표시할 때까지 시작되지 않습니다. 이 논리는 모든 클라이언트에서 실행되지만 GameManager 인스턴스의 StateAuthority을 가진 클라이언트만이 레벨을 로드할 수 있습니다.

주의: 이 단순화된 예제에서는 두 레벨이 모두 처음부터 초기 씬에 있으므로 레벨이 "활성화"된 것보다는 "로딩"되지 않습니다.

로딩은 원격 프로시저 호출로 이루어집니다. 호출자는 무작위 레벨 인덱스를 생성하여 모든 클라이언트에게 전달하여 모든 사람이 동일한 레벨을 로드할 수 있도록 합니다.

C#

    if (Object.HasStateAuthority) {
        RPC_ScoreAndLoad(-1,0, _levelManager.GetRandomLevelIndex());
    }

RPC 자체는 다음과 같이 정의되어 있습니다.

C#

    [Rpc(sources: RpcSources.StateAuthority, targets: RpcTargets.All, InvokeLocal = true, Channel = RpcChannel.Reliable)]
    private void RPC_ScoreAndLoad(int winningPlayerIndex, byte winningPlayerScore, int nextLevelIndex)
    {
        ...
    }
tank game
선수들이 준비하기를 기다리는 중.

레벨 전환

로비에서 레벨로, 그 반대로 전환은 TransitionSequence() 코루틴에 있는 LevelManager에 의해 처리됩니다. 트랜지션 자체는 전적으로 로컬 시간에 실행되지만 트랜지션이 트리거 될 때(RPC_ScoreAndLoad 원격 프로시저 호출에 의해) 그리고 종료될 때(playStateLEVEL로 설정함으로써) 클라이언트 간에 동기화됩니다(이것은 GameManager 상태 권한에서만 설정할 수 있는 네트워크 속성이기 때문에).

게임이 끝나면 레벨 전환이 로비로 돌아와 거의 동일한 방식으로 승자를 보여주며 루프를 완료하고 게임을 Ready Up 상태로 되돌립니다.

tank game

입력 처리

Fusion은 유니티의 표준 입력 처리 메커니즘을 사용하여 플레이어의 입력을 캡처하여 네트워크를 통해 전송할 수 있는 데이터 구조에 저장한 후 FixedUpdateNetwork() 메소드에서 이 데이터 구조를 제거합니다. 이 예에서는 이 모든 것을 InputController 클래스로 구현하지만 실제 상태 변경은 플레이어 클래스로 넘겨줍니다.

슈팅

이 예제의 탱크에는 두 가지 유형의 히트 감지 기능이 있는 4가지 다른 무기가 있습니다:

  • 인스턴트 히트
  • 발사체

각각 HitScanBullet클래스로 구현됩니다. 둘 다 세 가지 중요한 Fusion 기능인 객체 풀링, 예측 스폰 및 지연 보상을 사용합니다.

weapons
폭탄 폭발.

객체 폴링

새로운 객체를 생성할 때 프레임 드롭을 피하기 위해서는 항상 새로운 객체를 파괴하고 인스턴스화하는 대신 오래된 객체를 다시 사용하는 것이 좋습니다. 이것은 모든 게임, 특히 유니티, 심지어 Fusion에서도 마찬가지입니다.

이를 용이하게 하기 위해 Fusion은 애플리케이션이 재활용 게임 객체를 제공하고 수집하기 위한 후크를 지정할 수 있도록 합니다.

객체 풀은 기본적으로 프리팹을 기반으로 객체를 풀에서 가져오는 방식과 재사용을 위해 객체를 풀로 반환하는 방식을 가진 NetworkObjectPool을 구현해야 합니다.

예측 스폰

예측 스폰을 통해 클라이언트는 생성이 상태 권한에 의해 확인될 때까지 임시 로컬 플레이스 홀더를 생성하여 새로운 네트워크 객체의 생성을 예측할 수 있습니다. Fusion은 확인되면 플레이스 홀더를 실제 네트워크 객체로 자동 승격합니다.

수동으로 처리해야 하는 것은 실패 예측입니다. 플레이스 홀더를 파괴하는 것처럼 간단할 수 있습니다(기억하세요, 단지 유니티 객체입니다). 애플리케이션은 다양한 형태의 페이드 아웃 또는 실패 시각화를 자유롭게 구현할 수 있습니다.

또한 플레이스 홀더는 상태가 없으므로 애플리케이션은 네트워크 속성에 접근하지 않는 방식으로 예측 단계에서 이동을 관리해야 합니다.

지연 보상

로컬에서 각 플레이어는 자신의 입력 권한 객체와 다른 클라이언트 객체의 보간 또는 외삽 버전을 봅니다. 따라서 총알과 같이 빠르게 움직이는 물체는 각 머신에서 다른 것들에 명중할 가능성이 매우 높습니다. 총알을 쏜 사람은 무언가가 떨어져 있는지 가장 쉽게 알아차릴 수 있는 사람입니다. 그러나 동시에 서버는 단순히 성공적인 적중을 했다고 판단하는 늦은 클라이언트를 피하기 위해 히트 감지 권한을 가져야 합니다.

Fusion은 이 문제를 해결하기 위해 지연 보상 레이 캐스트를 지원합니다. 이는 기본적으로 레이 캐스트로 서버에서 실행할 때에도 클라이언트가 촬영 당시에 본 것을 존중합니다. 분명히 이 작업을 수행하기 위해 많은 스냅샷 보간 마법이 배후에서 진행되고 있습니다. 개발자들에게 다행스럽게도 지연 보상 레이 캐스트를 구현하고 사용하는 것은 일반 유니티 레이 캐스트를 사용하는 것처럼 모든 것이 간단합니다.

여기서 알아야 할 것은 HitBox라는 자체 유형의 콜라이더 객체가 함께 제공된다는 것입니다. HitBox는 객체 계층에서 HitBoxRoot 노드를 완전히 포함하는 형제이거나 자식이어야 합니다. 이를 통해 Fusion은 하위 노드에 대해 더 비싼 검사를 수행하기 전에 루트를 빠르게 제거할 수 있습니다.

성능상의 이유로 정적 엔티티에 HitBox를 적용해서는 안 됩니다. 하지만 레이 캐스트를 차단하기 위해서는 정적인 환경이 여전히 필요하기 때문에 지연 보상된 레이 캐스트는 선택적으로 Unity 콜라이더로 확인할 수 있습니다.

주의할 점은 지연 보상이 동적 - 즉 움직이는 PhysX 콜라이더에는 작동하지 않는다는 것입니다. 또한 유니티는 정적 콜라이더와 동적 콜라이더를 구별하는 레이 캐스트 쿼리를 제공하지 않습니다. 따라서 PhysX 콜라이더를 필터링하고 정적 결과만 얻도록 권장하는 것은 동적 개체의 다른 레이어에 HitBox/HitBoxRoot와 PhysX Collider(둘 다 필요한 경우)를 설정하는 것입니다.

weapons
승리.
Back to top