This document is about: FUSION 2
SWITCH TO

투사체 기본 개념

Level 4

개요

Fusion 발사체는 네트워크 투사체를 구현하는 여러 방법을 시연합니다. 보다 간단한 개요와 학습 경험을 위해, 구현된 예제는 두 개의 별도 프로젝트 - 발사체 기초와 발사체 고급으로 나누어져 있습니다.

이 프로젝트 - 발사체 기초는 주제에 대한 입문 단계로, 모든 일반적인 접근 방식을 설명하고 간단한 예제를 제공합니다. 모든 예제는 주석이 달려 있으며 복잡한 관계없이 유용한 코드 조각을 가져오고 핵심 개념을 이해할 수 있는 독립적인 구조를 가지고 있습니다.

발사체 고급은 슈팅 게임을 구축할 때 발생하는 일반적인 사용 사례를 해결하고 다양한 투사체 유형(반사, 유도, 폭발 등)에 대한 솔루션을 제공하는 심층적인 게임 샘플을 제공합니다. 발사체 고급은 아직 Fusion 2용으로 제공되지 않았지만, 원칙은 동일하게 유지됩니다.

이 문서는 발사체 기초에만 적용됩니다. 발사체 고급에 대한 별도의 프로젝트 페이지를 참조하십시오.

이 샘플은 Client/Server 토폴로지를 사용합니다.
projectiles 개요

특징

다운로드

버전 릴리즈 일자 다운로드
2.0.2 Aug 21, 2024 Fusion Projectiles Essentials 2.0.2 Build 633

요구 사항

  • 유니티 2021.3
  • Fusion AppId: 샘플을 실행하려면 먼저 PhotonEngine 관리 화면에서 Fusion AppId를 생성한 후, Fusion 메뉴에서 접근 가능한 Real Time 설정의 App Id Fusion 필드에 붙여 넣으십시오. 이후 게임 시작하기 섹션의 지침을 따르세요.

올바른 접근 방식 선택

프로젝트에 뛰어들기 전에, Fusion에서 네트워킹 투사체를 위해 사용할 수 있는 다양한 옵션에 대해 논의해 보겠습니다.

A) NetworkObject와 NetworkTransform/NetworkRigidbody 사용

NetworkObject로 투사체를 생성하고, NetworkTransform 또는 NetworkRigidbody를 사용하여 위치를 동기화합니다.

  • ✅ 생성 지연이 문제가 되지 않을 때 간단한 솔루션
  • ✅ 관심 관리가 자동으로 작동함
  • ✅ 적절한 물리적 효과(PhysX)를 사용할 수 있음
  • NetworkObject 생성 오버헤드
  • NetworkTransform/NetworkRigidbody는 모든 변환 업데이트를 네트워크를 통해 전송해야 함
  • ❌ 생성 지연(미리 투사체를 생성해 두어 지연을 줄일 수 있음 - 예제 1 참조)

요점: 복잡한 물리적 투사체가 필요한 특정 상황에서만 소수의 투사체에 적합합니다(e.g. 굴러가는 공). 그렇지 않으면 이 접근 방식을 사용하는 것을 피해야 합니다.

이 접근 방식은 예제 1에 시연되어 있습니다.

B) 발사 데이터가 있는 NetworkObject

투사체를 NetworkObject로 생성하고, 생성된 객체의 NetworkBehaviour에서 네트워크로 전송된 사용자 정의 데이터를 사용하여 서버와 클라이언트에서 전체 투사체 궤적을 계산합니다.

  • ✅ 위치 업데이트에 대역폭이 소모되지 않음
  • NetworkObject 생성 오버헤드
  • ❌ 수동 관심 관리 처리 필요
  • ❌ 생성 지연(미리 투사체를 생성해 두어 지연을 줄일 수 있음 - 예제 2 참조)

요점: 특정 상황에서 소수의 투사체에 적합합니다 - 예를 들어 발사자가 사라지더라도 오랜 시간 동안 지속되는 투사체(e.g. Unreal Tournament의 핵미사일). 이 접근 방식은 비물리적 투사체에 대해서는 A보다 낫지만, 여전히 상당한 단점이 있으므로, 언급된 사용 사례를 제외하고는 다른 솔루션을 사용하는 것이 좋습니다.

이 접근 방식은 예제 2에 시연되어 있으며, Projectiles Advanced는 오래 지속되는 투사체에 이 접근 방식을 사용합니다(독립형 발세체 참조).

C) 투사체 카운트 속성

발사된 투사체 수만 동기화하고, 표준 유니티 객체로 투사체를 시각화합니다. 입력 권한 및 상태 권한에서는 정확하게 발사되지만, 프록시에서는 보간 된 위치와 회전을 기반으로 발사됩니다. 발사 간격이 낮을 때 마지막 투사체에 대한 정보를 저장하여 이 솔루션의 정밀도 문제를 완화할 수 있습니다(e.g. 충돌 위치).

  • ✅ 간단한 솔루션
  • ✅ 네트워크를 통한 데이터 전송이 가장 적음
  • ✅ 시각적 요소 생성을 서버에서 무시할 수 있음
  • ✅ 제어 객체(플레이어, 무기)로서의 관심 관리
  • ✅ 생성 지연 없음
  • ❌ 히트 스캔 투사체에만 적합함
  • ❌ 프록시에서 부정확함. 프록시는 스냅샷 보간 위치에 있으므로, 발사 위치/방향이 크게 다를 수 있음. (이 점은 발사 간격이 높아 마지막 투사체에 대한 데이터를 저장할 수 없는 경우에만 해당됨)
  • ❌ 투사체의 목표 위치(충돌 위치)에 대한 데이터가 없으므로 프록시에서 투사체의 정확한 경로를 알 수 없음. 이로 인해 일부 투사체 시각적 요소가 벽이나 다른 객체를 통과할 수 있음. (이 점은 발사 간격이 높아 마지막 투사체에 대한 데이터를 저장할 수 없는 경우에만 해당됨)

요점: 프록시의 정확도나 시각적 요소에 크게 신경 쓰지 않는 간단한 사용 사례에 적합합니다 - 예를 들어, 간단한 투사체와 짧은 투사체 시각적 요소(트레일)가 있는 탑다운 슈터 - 또는 마지막 투사체에 대한 추가 네트워크 정보를 제공하는 것이 충분한 경우 => 최대 무기 발사 간격이 상당히 낮고, 여러 투사체를 한 번에 발사하는 것이 필요하지 않음(샷건 스타일). 보통은 약간의 복잡성만 증가시키면서도 투사체 데이터 버퍼를 사용하는 솔루션이 더 좋습니다 - 예제 4를 참조하세요.

이 접근 방식은 예제 3에 시연되어 있습니다.

D) 투사체 데이터 버퍼

각 투사체에 대한 사용자 정의 데이터를 동기

화합니다 - 네트워크 배열 형태로 네트워크 된 NetworkBehaviour에 저장된 링 버퍼. 투사체는 단순한 시각적 요소로, 표준 Unity 객체로 생성됩니다. 최적의 대역폭 사용을 위해 투사체 데이터 세트는 희소하게 유지되어야 합니다 - 예를 들어, 투사체 궤적은 발사 위치, 발사 방향 또는 기타 데이터를 기반으로 계산되지만, 특수한 경우 위치를 지속적으로 업데이트하는 것도 가능합니다.

  • ✅ 우수한 대역폭 소비
  • ✅ 대부분의 투사체 사용 사례를 포괄함
  • ✅ 시각적 요소 생성을 서버에서 무시할 수 있음
  • ✅ 제어 객체(플레이어, 무기)로서의 관심 관리
  • ✅ 생성 지연 없음
  • ❌ 제어 객체 없이 투사체가 존재할 수 없음 = 제어 객체가 사라지면 투사체도 사라짐
  • ❌ 바운싱과 같은 투사체 물리학은 수동으로 계산해야 함

요점: 매우 긴 생명 주기나 중요한 투사체(소유자가 사라져도 계속해서 진행해야 하는 투사체 - 예: Unreal Tournament의 Redeemer 투사체가 소유자가 사라지거나 연결이 끊긴 경우에도 여전히 레벨에서 이동해야 하는 경우) 또는 매우 긴 이동 거리를 가지는 투사체를 제외하고는 대부분의 상황에 적합한 솔루션입니다.

이 접근 방식은 예제 4예제 5에 시연되어 있으며, 발사체 고급 샘플의 핵심 부분이기도 합니다.

프로젝트 구성

프로젝트는 5개의 독립적인 소규모 예제로 구성되어 있습니다.

/01_NetworkObject 예제 1 - NetworkRigidbody/NetworkTransform이 있는 네트워크 오브젝트
/02_NetworkObjectFireData 예제 2 - 발사 데이터가 있는 네트워크 오브젝트
/03_ProjectileCountProperty 예제 3 - 투사체 카운트 속성
/04_ProjectileDataBuffer_Hitscan 예제 4 - 투사체 데이터 버퍼 - 히트 스캔
/05_ProjectileDataBuffer_Kinematic 예제 5 - 투사체 데이터 버퍼 - 키네마틱
/Common 모든 예제에 공통되는 프리팹, 스크립트 및 소재
/ThirdParty 서드파티 에셋(모델, 효과)

게임 시작하기

각 예제는 자체적인 씬 파일을 가지고 있으며, 열어서 실행할 수 있습니다. 또는 Start 씬(/Common/Start)에서 모든 예제를 시작할 수 있습니다.

예제 씬을 시작하면, 게임을 어떤 모드에서 시작할지 선택할 수 있는 Fusion 표준 NetworkDebugStart 창의 약간 수정된 버전이 나타납니다.

디버그 시작

멀티 피어

Start Host + 1 Client 또는 Start Host + 2 Clients를 선택하면 멀티 피어 모드에서 게임이 시작됩니다. 이 접근 방식은 투사체가 프록시에서 어떻게 동작하는지 테스트하는 데 강력히 추천됩니다.

피어 간 전환은 숫자 키 패드의 0, 1, 2, 3 키를 눌러 전환할 수 있습니다.

피어 전환은 Runner Visibility Controls 창을 사용하여도 가능합니다(상단 메뉴 Fusion/Windows/Runner Visibility Controls).

runner visibility controls

프록시에서 발사가 어떻게 동작하는지 확인하려면, Client A만 보이도록 설정하고, Client B만 입력 제공자로 활성화하세요. 이제 Client B에서 발사된 투사체가 Client A의 시점에서 어떻게 보이는지 관찰할 수 있습니다.

runner visibility controls

컨트롤

이동은 W, S, A, D 키를 사용하고, 발사는 Mouse1을 사용합니다.

커서 잠금 또는 해제를 위해 ENTER 키를 사용하세요.

예제

발사체 기초는 올바른 접근 방식 선택 섹션에서 소개된 모든 접근 방식에 대한 예제를 제공합니다. 모든 스크립트에는 주석이 달려 있으므로 특정 코드를 조사해 보세요.

모든 예제는 플레이어가 더미 큐브를 쏠 수 있는 동일한 물리적 환경을 사용합니다. 투사체 발사 시 예측을 더 잘 시연하기 위해 물리 예측이 활성화되어 있습니다(Common/Prefabs/RunnerBase 프리팹의 RunnerSimulatePhysics3D 컴포넌트 참조). 큐브는 모든 클라이언트에서 시뮬레이션되어야 하므로, 이러한 객체에 대한 시뮬레이션이 Cube 스크립트에서 활성화됩니다. 그러나 물리 예측은 성능 집약적이므로, 게임이 반응형 물리학에 크게 의존하지 않는 한 이 설정은 일반적으로 꺼야 합니다.

예제 1과 예제 2는 네트워크 객체의 생성을 사용합니다. 이는 초보자에게 가장 명백한 접근 방식이지만, 여러 가지 이유로 권장되지 않습니다. 그중 하나는 생성 지연 문제로 인한 복잡성입니다. 간단한 투사체 접근 방식을 위해서는 예제 3으로 바로 이동하는 것이 좋습니다.

예제 1 - NetworkRigidbody/NetworkTransform이 있는 네트워크 오브젝트

예제 1은 환경에서 튕기고 구를 수 있는 물리적 투사체를 보여줍니다. 투사체는 독립적인 동작을 가진 생성된 네트워크 객체입니다. 투사체의 위치와 회전(및 기타 물리적 속성)은 NetworkRigidbody를 통해 동기화됩니다.

projectiles 예제 1

이러한 투사체를 발사하는 것은 NetworkRunner를 사용하여 생성하고 사용자 정의 투사체 스크립트에서 Fire를 호출하는 것만큼 간단할 수 있습니다:

C#

var projectile = Runner.Spawn(projectilePrefab, fireTransform.position, fireTransform.rotation, Object.InputAuthority);
projectile.Fire(fireTransform.forward * 10f);

Fire 메서드는 물리적 투사체에 임펄스를 추가할 수 있습니다:

C#

public void Fire(Vector3 impulse)
{
    _rigidbody.AddForce(impulse, ForceMode.Impulse);
}

그러나 이 단순한 접근 방식에는 단점이 있습니다. Fusion 2에서는 NetworkObject 생성이 예측되지 않기 때문에, 모든 투사체는 상태 권한에 의해 생성됩니다. 네트워크 조건이 이상적이지 않을 때, 발사 입력 후 실제로 투사체가 발사되기까지 상당한 지연이 발생할 것입니다. 이 생성 지연은 애니메이션이나 발사 비주얼 효과 뒤에 숨길 수 있습니다. 숨기기가 불가능한 경우, 예제 1에서 시연된 대로 미리 투사체를 생성하고 준비된 객체를 사용하여 예측된 투사체 발사를 활용하여 이를 완화할 수 있습니다(이 고급 솔루션은 다른 예제를 탐색한 후에 다시 살펴보는 것이 좋습니다).

예제 2 - 발사 데이터가 있는 네트워크 오브젝트

예제 2는 시간에 따라 이동하는 키네마틱 투사체를 보여줍니다. 투사체는 여전히 생성된 네트워크 객체이지만, 투사체의 위치와 회전은 네트워크를 통해 매 틱마다 위치/회전을 동기화하는 대신, 초기 발사 데이터를 기반으로 각 클라이언트에서 계산됩니다.

참고: 히트 스캔과 키네마틱 투사체의 차이는 발사체 고급 샘플의 투사체 유형 섹션에서 설명됩니다.

발사는 이전 예제와 유사합니다:

C#

var projectile = Runner.Spawn(projectilePrefab, fireTransform.position, fireTransform.rotation, Object.InputAuthority);
projectile.Fire(fireTransform.position, fireTransform.forward * 10f);

public void Fire(Vector3 position, Vector3 velocity)
{
    // Save fire data
    _fireTick = Runner.Tick;
    _firePosition = position;
    _fireVelocity = velocity;
}

그러나 투사체의 움직임은 초기 파라미터에서 계산됩니다:

C#

[Networked]
private int _fireTick { get; set; }
[Networked]
private Vector3 _firePosition { get; set; }
[Networked]
private Vector3 _fireVelocity { get; set; }

// Same method can be used both for FUN and Render calls
private Vector3 GetMovePosition(float currentTick)
{
    float time = (currentTick - _fireTick) * Runner.DeltaTime;

    if (time <= 0f)
        return _firePosition;

    return _firePosition + _fireVelocity * time;
}

보통 Render 호출에서만 실제 투사체의 변환을 이동시키는 것으로 충분합니다. FixedUpdateNetwork에서는 레이 캐스트를 발사하고 잠재적인 충돌을 해결하기 위해 이전 위치와 다음 위치를 계산할 수 있습니다.

C#

public override void FixedUpdateNetwork()
{
    if (IsProxy == true)
        return;

    // Previous and next position is calculated based on the initial parameters.
    // There is no point in actually moving the object in FUN.
    var previousPosition = GetMovePosition(Runner.Tick - 1);
    var nextPosition = GetMovePosition(Runner.Tick);

    var direction = nextPosition - previousPosition;

    if (Runner.LagCompensation.Raycast(previousPosition, direction, direction.magnitude, Object.InputAuthority,
             out var hit, _hitMask, HitOptions.IncludePhysX | HitOptions.IgnoreInputAuthority))
    {
        // Resolve collision
    }
}

예제 1과 마찬가지로, 이 접근 방식은 상태 권한이 투사체를 생성할 때까지 기다려야 한다는 단점이 있습니다. 해결책은 예제 1과 동일합니다.

예제 3 - 투사체 카운트 속성

예제 3은 무기 자체에 투사체 카운트만을 직접 동기화하는 매우 효율적인 접근 방식을 보여줍니다. 이 접근 방식은 히트 스캔 투사체를 나타내며, 투사체가 발사되자마자 즉시 충돌이 평가됩니다. 즉, 타격 효과(예: 대미지, 물리적 충격)가 즉시 대상에 적용되지만, 대부분의 경우 짧은 시간 동안 공중을 날아다니는 더미 투사체 시각 효과를 생성해도 괜찮습니다. 이 예제에서는 더미 투사체가 사용됩니다(Common/Scripts/DummyFlyingProjectile.cs 참조).

C#

public class Weapon : NetworkBehaviour
{
    [SerializeField]
    private LayerMask _hitMask;
    [SerializeField]
    private Transform _fireTransform;

    [Networked]
    private int _fireCount { get; set; }

    private int _visibleFireCount;

    public void FireProjectile()
    {
        // Whole projectile path and effects are immediately processed (= hitscan projectile)
        if (Runner.LagCompensation.Raycast(_fireTransform.position, _fireTransform.forward,
                100f, Object.InputAuthority, out var hit, _hitMask))
        {
            // Resolve collision
        }

        _fireCount++;
    }

    public override void Spawned()
    {
        _visibleFireCount = _fireCount;
    }

    public override void Render()
    {
        if (_visibleFireCount < _fireCount)
        {
            // Show fire effect
        }

        _visibleFireCount = _fireCount;
    }
}

이 접근 방식은 매우 간단하고 효율적이며, 예측이 자동으로 작동합니다(네트워크 객체 생성이 포함되지 않기 때문에 생성 지연을 처리할 필요가 없습니다). 올바른 접근 방식 선택 섹션에서 언급했듯이, 이 접근 방식은 투사체 데이터 버퍼(예제 4)를 사용하여 쉽게 완화할 수 있는 몇 가지 단점이 있습니다.

예제 4 - 투사체 데이터 버퍼 - 히트 스캔

예제 4는 투사체 데이터 버퍼의 사용을 보여줍니다. 버퍼의 히트 스캔 버전은 이전 예제의 Projectile Count 속성보다 한 단계 더 발전된 방식이지만, 여전히 단순함을 유지합니다.

projectiles 예제 4

투사체 데이터 버퍼 접근 방식은 고정 배열의 ProjectileData 구조체를 사용하여 원형 버퍼로 작동합니다. 즉, 발사 시 입력/상태 권한은 현재 버퍼 헤드(fireCount 속성)를 기준으로 데이터를 채우고, 모든 클라이언트는 자신들의 로컬 헤드(visibleFireCount)를 기준으로 Render 호출에서 시각적으로 이를 따라갑니다. ProjectileData에는 모든 클라이언트에서 투사체를 재구성하는 데 필요한 모든 데이터가 포함됩니다.

C#

public class Weapon : NetworkBehaviour
{
    [SerializeField]
    private LayerMask _hitMask;
    [SerializeField]
    private Transform _fireTransform;

    [Networked]
    private int _fireCount { get; set; }
    [Networked, Capacity(32)]
    private NetworkArray<ProjectileData> _projectileData { get; }

    private int _visibleFireCount;

    public void FireProjectile()
    {
        var hitPosition = Vector3.zero;

        var hitOptions = HitOptions.IncludePhysX | HitOptions.IgnoreInputAuthority;

        // Whole projectile path and effects are immediately processed (= hitscan projectile)
        if (Runner.LagCompensation.Raycast(_fireTransform.position, _fireTransform.forward,
                100f, Object.InputAuthority, out var hit, _hitMask))
        {
            // Resolve collision

            hitPosition = hit.Point;
        }

        _projectileData.Set(_fireCount % _projectileData.Length, new ProjectileData()
        {
            HitPosition = hitPosition,
        });

        _fireCount++;
    }

    public override void Spawned()
    {
        _visibleFireCount = _fireCount;
    }

    public override void Render()
    {
        if (_visibleFireCount < _fireCount)
        {
            // Play fire effects (e.g. fire sound, muzzle particle)
        }

        for (int i = _visibleFireCount; i < _fireCount; i++)
        {
            var data = _projectileData[i % _projectileData.Length];

            // Show projectile visuals (e.g. spawn dummy flying projectile or trail
            // from fireTransform to data.HitPosition or spawn impact effect on data.HitPosition)
        }

        _visibleFireCount = _fireCount;
    }

    private struct ProjectileData : INetworkStruct
    {
        public Vector3 HitPosition;
    }
}

ProjectileData 구조체는 가능한 한 작게 유지하고, 가능하면 시간이 지남에 따라 변하지 않는 데이터를 사용하는 것이 좋습니다. 이렇게 하면 많은 수의 투사체를 발사할 때에도 대역폭을 효율적으로 사용할 수 있습니다.

이 접근 방식은 상당히 간단하고 효율적이며 유연하여 대부분의 게임에 권장됩니다. 게임에서 키네마틱 투사체가 필요할 경우, 버퍼의 약간 더 복잡한 버전이 필요합니다 - 예제 5로 진행하세요.

예제 5 - 투사체 데이터 버퍼 - 키네마틱

많은 경우 게임에서 투사체는 무언가에 충돌하거나 소멸하기 전에 환경을 통해 일정 시간 동안 이동합니다. 우리는 이러한 투사체를 키네마틱 투사체 또는 간단히 투사체라고 부릅니다. 투사체 데이터 버퍼 솔루션은 입력/상태 권한에서 투사체의 상태를 기반으로 데이터를 업데이트하고, 모든 클라이언트에서 투사체의 시각적 표현을 처리하고 업데이트하는 방식으로 조정되어야 합니다.

C#

private struct ProjectileData : INetworkStruct
{
    public int FireTick;
    public int FinishTick;

    public Vector3 FirePosition;
    public Vector3 FireVelocity;
    public Vector3 HitPosition;
}

ProjectileData 구조체는 가능한 한 작게 유지하고, 데이터는 가급적 많이 변하지 않도록 해야 합니다. 이 예제에서는 단일 투사체에 대해 두 번 데이터가 설정됩니다 - 한 번은 투사체가 발사될 때(FirePosition, FireVelocityFireTick이 설정됨)이고, 또

한 번은 투사체가 환경에 충돌할 때(HitPositionFinishTick이 설정됨)입니다. 특별히 정당화된 경우, 데이터를 더 자주 업데이트할 수도 있습니다 - 발사체 고급의 유도 투사체를 참조하세요.

투사체 타이밍에 대해

참고: 먼저 Fusion에서 예측이 어떻게 작동하는지 이해해야 합니다.

Fusion에서는 두 가지 주요 시간 프레임을 인식합니다. 로컬 시간 프레임은 로컬 플레이어와 로컬 객체의 시간입니다. 원격 시간 프레임은 원격 플레이어와 원격 객체의 시간입니다. 호스트/서버의 모든 객체는 항상 로컬 시간 프레임에서 시뮬레이션되고 렌더링 됩니다. 클라이언트는 두 시간 프레임 모두에서 객체를 렌더링 합니다 - 일반적으로 로컬은 로컬 객체에 사용되고 원격은 원격 객체에 사용됩니다. 시뮬레이션에서는 클라이언트가 일반적으로 로컬 객체만 시뮬레이션합니다(= FixedUpdateNetwork는 프록시에 대해 실행되지 않음). 다른 플레이어의 투사체와 같은 프록시 객체가 로컬 시간 프레임에서 렌더링 되고 충돌이나 기타 상호작용에 대한 데이터가 필요하여 오버슈팅을 방지해야 하는 경우를 제외하고는 시뮬레이션이 실행되지 않습니다.

실제로 이는 서로 가까이 서 있는 플레이어 A와 B가 동일한 틱에 투사체를 발사했을 때 다음과 같은 상황을 의미합니다. 플레이어 A의 관점에서 투사체 A는 즉시 발사되지만, 플레이어 A는 아직 투사체 B에 대한 정보를 가지고 있지 않습니다. 이 정보는 아직 플레이어 B로부터 서버로 전송되어 처리된 후 플레이어 A에게 전송되고 있기 때문입니다. 이 정보가 도착했을 때, 플레이어 A는 이미 몇 틱 앞서 있습니다(= 마지막으로 알려진 서버 상태에서 미래로 예측하고 있음), 그리고 투사체 A의 렌더링도 앞서 있습니다. 예를 들어 투사체 A는 이미 플레이어 A로부터 3미터 떨어져 있다고 가정해 보겠습니다. 이제 우리는 투사체 B를 렌더링 하는 방법에 대해 두 가지 옵션이 있습니다. 로컬 시간 프레임에서 "올바르게" 렌더링 하여 원래 위치에서 3미터 떨어진 플레이어 B 위치에 투사체를 배치할 수 있습니다 - 하지만 이 경우 투사체 시각적 효과가 플레이어 B 위치에서 3미터 떨어진 곳에서 갑자기 나타나는 것처럼 보일 것입니다. 이러한 행동은 일반적으로 바람직하지 않으므로, 우리는 투사체 B를 원격 시간 프레임에서 렌더링 하는 것을 선택합니다. 이는 투사체 B가 플레이어 B의 무기에서 바로 발사되지만, 동일한 틱에 발사되었음에도 불구하고 투사체 A보다 3미터 뒤에 렌더링 된다는 의미입니다. 이것이 바로 이 샘플의 모든 관련 예제에서 투사체를 렌더링 하는 시간이 다음과 같이 계산되는 이유입니다:

C#

float renderTime = Object.IsProxy ? Runner.RemoteRenderTime : Runner.LocalRenderTime;

고급 투사체 타이밍 노트

복잡한 타이밍 주제에 대해 궁금한 경우에만 읽으십시오. 단순히 투사체를 발사하는 것만 원한다면 이 주제를 세부적으로 이해할 필요는 없습니다.

렌더링(또는 Render 호출에서 값을 읽는 것)과 관련하여 기술적으로 여러 가지 시간 프레임이 있습니다:

  • 로컬 시간(시뮬레이션 시간)
    • = 마지막 포워드 틱의 값
    • Render에서의 값: 입력/상태 권한에서 네트워크 속성 읽기
    • Render에서의 시간: Runner.SimulationTime 또는 Runner.Tick * Runner.DeltaTime
  • 로컬 렌더링 시간
    • = 마지막 두 포워드 틱 사이에서 보간 된 값
    • Render에서의 값: 입력/상태 권한에서 Interpolator 값 읽기
    • Render에서의 시간: Runner.LocalRenderTime 또는 (Runner.Tick - 1 + Runner.LocalAlpha) * Runner.DeltaTime
  • 로컬 렌더링 시간 - 외삽
    • = 마지막 포워드 틱에서 외삽된 값
    • Render에서의 값: 입력/상태 권한에서 네트워크 속성 읽기 + 로컬에서 값의 렌더링 부분 계산
    • Render에서의 시간: Runner.LocalRenderTime + Runner.DeltaTime 또는 (Runner.Tick + Runner.LocalAlpha) * Runner.DeltaTime
  • 원격 시간
    • = 최신 서버 틱의 값(=> 마지막으로 수신된 틱)
    • Render에서의 값: 프록시에서 네트워크 속성 읽기(프록시에서 속성이 재시뮬레이션 중 수정되지 않은 경우)
    • Render에서의 시간: Runner.LatestServerTick * Runner.DeltaTime
  • 원격 렌더링 시간
    • = 원격 보간 틱 사이에서 보간 된 값(안전 창이 필요하여 최신 서버 틱이 보간에 사용되기 전에 원격 보간 틱이 더 과거에 있음)
    • Render에서의 값: 프록시에서 Interpolator 값 읽기
    • Render에서의 시간: Runner.RemoteRenderTime

이러한 시간 프레임 정의를 살펴보면, 예제 5의 투사체는 입력/상태 권한에서는 로컬 보간 시간 프레임에서, 프록시에서는 원격 보간 시간 프레임에서 렌더링 됩니다. 그러나 이것이 정확히 맞으려면 fireCountprojectileData 속성을 각각의 보간기에서 읽어야 합니다. 이는 단순화를 위해 생략되었습니다 - 우리는 원격 렌더링 시간을 사용하지만 원격 값을 사용합니다(그리고 로컬 렌더링 시간을 사용하지만 로컬 값을 사용). 따라서 프록시에서 발사가 기술적으로 해야 할 때보다 약간 더 일찍 발생합니다. 이 경우 차이가 복잡성을 정당화하지 않습니다. 전체 보간 예제는 발사체 고급을 참조하십시오.

Back to top