발사체 기본
개요
Fusion Projectiles는 네트워크로 연결된 발사체를 구현하는 다양한 방법을 보여줍니다. 간단한 개요 및 학습 경험을 위해 예제 구현은 발사체 기본 및 발사체 고급라는 두 개의 개별 프로젝트로 분할되었습니다.
이 프로젝트(Projectiles Essentials)는 해당 주제에 대한 진입점 역할을 합니다. 모든 일반적인 접근 방식의 간단한 예를 설명하고 제공하는 것을 목표로 합니다. 모든 예제는 주석 처리되고 유용한 코드를 수집하고 복잡한 관계없이 핵심 개념을 이해할 수 있는 독립적인 구조를 가지고 있습니다.
발사체 고급은 슈팅 게임을 구축할 때 일반적인 사용 사례를 해결하고 다양한 발사체 유형(발사, 호밍, 폭발 등)에 대한 솔루션을 제시하는 게임 샘플 역할을 합니다.
이 설명서에서는 발사체 기본만 다룹니다. 발사체 고급은 별도의 프로젝트 페이지를 참조하십시오
HostMode
토폴로지를 사용합니다.특징
- 발사체 개념을 이해하는 데 도움이 되는 코드가 주석이 달린 5개의 작은 독립적인 예제
- KCC 애드온을 사용하여 이동하는 간단한 FPS 플레이어
- 플레이어가 상자를 쏠 수 있는 물리 기반 환경
다운로드
버전 | 릴리즈 일자 | 다운로드 | ||
---|---|---|---|---|
1.1.6 | Apr 14, 2023 | Fusion Projectiles Essentials 1.1.6 Build 183 |
필요 사양
- 유니티 2021.3
- Fusion AppId: 샘플 실행을 위해서는, 먼저 PhotonEngine 관리 화면에서 Fusion AppId를 생성하고 Real Time 설정(Fusion 메뉴에서 접근 가능)의
App Id Fusion
에 붙여 넣습니다. 게임 시작하기 섹션에서부터 계속합니다.
적절한 방식 선택하기
프로젝트에 참여하기 전에 Fusion에서 네트워킹 발사체에 사용할 수 있는 몇 가지 옵션에 대해 설명하겠습니다.
A) NetworkTransform/NetworkRigidbody를 가진 NetworkObject
NetworkObject
로 발사체 스폰, NetworkTransform
또는 NetworkRigidbody
를 사용하여 위치 동기화.
- ✅ 예측된 스폰이 포함되지 않은 경우의 간단한 해결책
- ✅ 관심 관리가 즉시 실행
- ✅ 적절한 물리(PhysX) 사용 가능
- ❌
NetworkObject
인스턴스화 오버헤드 - ❌
NetworkTransform
/NetworkRigidbody
네트워크를 통해 모든 변환 업데이트를 전송하는 것을 의미 - ❌ 필요한 경우 예측된 스폰 처리
선택 방식: 특정 상황에서 소수의 발사체에만 적합합니다. 예를 들어 복잡한 물리적 발사체가 필요한 경우(예: 롤링 볼). 그렇지 않으면 이 접근 방식을 사용하지 않아야 합니다.
예제 1에 이 접근법에 대하여 다룹니다.
B) 발사 데이터가 있는 NetworkObject
발사체를 NetworkObject
로 생성하고, 생성된 객체의 NetworkBehaviour
에서 네트워크로 연결된 사용자 지정 데이터(일반적으로 발사 틱, 발사 위치 및 발사 방향)를 사용하여 서버 및 클라이언트의 전체 발사체 궤적을 계산합니다.
- ✅ 위치 업데이트에 대역폭 비용이 들지 않습니다
- ❌
NetworkObject
인스턴스화 오버헤드 - ❌ 필요한 경우 예측 스폰 처리
- ❌ 수동 관심 관리 처리
선택 방식: 특정 상황에서 소수의 발사체에만 적합합니다. 예를 들어, 사격자보다 오래 살 수 있는 매우 긴 발사체(예: 언리얼 토너먼트의 핵 로켓)에 적합합니다. 이 접근 방식은 일반적으로 비물리적 발사체의 경우 A보다 낫지만, 여전히 상당한 감소가 있으며 언급된 사용 사례를 제외하고는 추가 솔루션을 선호하여 피해야 합니다.
예제 2에 이 접근법에 대해 설명하였으며 발사체 고급에서는 아주 오래 있는 발사체(독립 실행형 발사체)에서 사용하였습니다.
C) Projectile Count 속성
모든 NetworkBehaviour
에 저장된 발사체 수만 동기화합니다. 발사체는 시각적이며 표준 유니티 개체로 생성됩니다. 입력 권한과 상태 권환에서는 정확하게 촬영되지만 프록시의 경우 보간 된 위치와 회전을 기반으로 촬영됩니다. 낮은 움직임으로 발사체가 발사될 때, 이 솔루션의 정밀도 문제를 완화하는 마지막 발사체(예: 충격 위치)에 대한 정보를 저장할 수 있습니다.
- ✅ 간단한 해결책
- ✅ 네트워크를 통해 전송되는 최소 데이터양
- ✅ 서버에서 시각자료의 생성을 무시
- ✅ 통제 대상(플레이어, 무기)의 일부로서의 관심 관리
- ✅ 예측 스폰 필요 없음
- ❌ 히트 스캔 발사체에만 적합
- ❌ 프록시의 부정확함. 프록시는 스냅샷 보간 위치에 있으므로 발사 위치/방향이 상당히 다를 수 있습니다. (이 점은 높은 발사 발생률로 인해 마지막 발사체에 대한 데이터를 저장할 수 없는 경우에만 적용됨)
- ❌ 발사체 목표 위치(타격 위치)에 대한 데이터가 없으므로 프록시에서 정확한 발사체 경로를 알 수 없습니다. 이는 일부 발사체 시각 자료가 발사체 경로 끝을 알 수 없기 때문에 벽과 다른 물체를 통해 이동할 수 있음을 의미합니다. (이 점은 높은 발사 발생률로 인해 마지막 발사체에 대한 데이터를 저장할 수 없는 경우에만 적용됨)
요점: 프록시의 정밀도와 시각에 크게 신경 쓰지 않는 단순한 사용 사례에 적합한 솔루션입니다. 단순한 발사체와 짧은 발사체 비주얼을 가진 탑 다운 슈터 - 또는 마지막 발사체에 대해서만 추가적인 네트워크 정보를 갖는 것으로 충분할 때 => 최대 무기 이동이 상당히 느리며 여러 발의 발사체를 한 번에 발사할 필요가 없습니다(발사 스타일). 일반적으로 발사체 데이터 버퍼를 사용하는 솔루션이 복잡성이 약간 증가하는 더 나은 옵션이지만, 예제 4를 참조하십시오.
이 접근법은 예제 3에서 보여줍니다.
D) 발사체 데이터 버퍼
NetworkBehaviour
의 링 버퍼 형태로 네트워크 배열에 저장된 각 발사체에 대한 사용자 정의 데이터를 동기화합니다. 발사체는 표준 유니티 객체로 생성된 시각적인 것일 뿐입니다. 최적의 대역폭 사용을 위해 발사체 데이터 세트는 희소해야 합니다. 예를 들어 발사체 궤적은 발사 위치, 발사 방향 또는 기타 데이터에서 계산됩니다. 그러나 일부 특수 용도의 경우 위치를 지속적으로 업데이트할 수도 있습니다.
- ✅ 좋은 대역폭 소비
- ✅ 대부분의 발사체 사용 사례 적용
- ✅ 서버에서 시각자료의 생성을 무시 가능
- ✅ 통제 대상(플레이어, 무기)의 일부로서의 관심 관리
- ✅ 예측 스폰 필요 없음
- ❌ 제어 객체 없이는 발사체가 존재할 수 없음 = 그것보다 오래 살 수 없음
- ❌ 바운스와 같은 발사체 물리학은 수동으로만 계산
요점: 수명이 매우 길거나 중요한 발사체(예: 소유자보다 오래 살 수 있는 발사체 -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
)을 사용하여 피어를 전환할 수 있습니다.
프록시에서 슈팅이 어떻게 동작하는지 보려면 클라이언트 A만 표시되도록 설정하고 클라이언트 B만 입력 공급자로 사용하도록 설정합니다. 클라이언트 B에서 촬영한 내용을 클라이언트 A의 관점에서 확인할 수 있습니다.
제어
이동은 W
, S
, A
, D
키를 사용하고 발사는 Mouse1
입니다.
ENTER
키를 사용하여 커서의 잠금 또는 해제를 합니다.
예제
발사체 기초는 올바른 접근 방식 선택 섹션에서 소개된 모든 접근 방식의 예를 제공합니다. 모든 스크립트에 주석이 달렸으므로 특정 코드에 대해 자유롭게 조사하십시오.
모든 예는 플레이어가 더미 상자를 쏠 수 있는 동일한 물리학 플레이 그라운드를 사용합니다. 발사체를 발사할 때 예측을 더 잘 표시하려면 물리적 예측을 설정합니다(NetworkProjectConfig 에셋 / 서버 물리 모드를 ClientPrediction으로 설정). 물리학을 예측하는 것은 성능이 떨어지므로 게임이 물리학을 기반으로 하지 않는 한 이 설정을 해제해야 합니다.
예제 1 및 예제 2에서는 네트워크 객체의 생성을 사용합니다. 초보자에게 가장 확실한 접근 방식을 나타내지만 위에서 언급한 많은 이유로 그러한 접근 방식은 권장되지 않습니다. 그중 하나는 복잡성(특히 스폰 예측으로 인해)입니다. 발사체에 대한 간단한 접근은 예제 3으로 바로 이동합니다.
예제 1 - NetworkRigidbody/NetworkTransform가 있는 네트워크 객체
예제 1은 환경에서 바운스 및 롤링이 가능한 물리적 발사체를 보여줍니다. 발사체는 고유한 동작을 가진 생성된 네트워크 개체입니다. 발사체의 위치와 회전(및 다른 물리적 특성)은 NetworkRigidbody
를 통해 동기화됩니다.
이러한 발사체를 발사하는 것은 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);
}
그러나 이러한 단순한 접근 방식에는 단점이 있습니다. 발사체 스폰이 예측되지 않기 때문에 네트워크 조건이 이상적이지 않을 때 발사체가 실제로 발사되기 전에 발사체 입력 후 상당한 일시 중지가 발생합니다. 이 문제를 완화하는 일반적인 방법은 스폰 예측을 사용하는 것입니다.
스폰 예측은 예제 2에 나와 있습니다. 그러나 이 예제와 같은 물리적 발사체의 경우, 예측적으로 생성된 물체(예: 발사체 더미)와 씬에 이미 등록된 네트워크 객체 사이의 복잡한 물리적 상호 작용으로 인해 스폰 예측은 권장되지 않습니다. 가장 간단한 해결책은 일부 발사체를 미리 생성하여 버퍼에 저장하고 발사 입력 후 버퍼에서 이미 완전히 생성된 발사체를 사용하는 것입니다. 이러한 접근 방식은 예제 1의 일부로 Predicted
하위 폴더를 확인합니다. 이 솔루션은 더 고급이므로 다른 예제를 살펴본 후 다시 사용해 보십시오.
예제 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;
}
일반적으로 렌더 호출에서만 실제 발사체 변형을 움직이는 것으로 충분합니다. 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
}
}
네트워크 객체의 생성이 포함되어 있기 때문입니다. 다양한 네트워크 조건에서 최상의 촬영 경험을 얻으려면 스폰 예측을 사용해야 합니다. 스폰 예측을 활성화하려면 예측키를 사용하여 Runner.Spawn
를 호출합니다:
C#
var key = new NetworkObjectPredictionKey()
{
Byte0 = (byte)Runner.Tick, // Low number part is enough
Byte1 = (byte)Object.InputAuthority.RawEncoded,
};
Runner.Spawn(projectilePrefab, fireTransform.position, fireTransform.rotation,
Object.InputAuthority, predictionKey: key);
예측으로 생성된 객체는 기본적으로 표준 NetworkBehaviour
호출을 모방하는 IPredictedSpawnBehaviour
인터페이스를 구현해야 합니다. 이 예제에서는 예측된 생성에 동일한 객체를 사용합니다. 즉, 객체에 네트워크 상태가 없을 때 네트워크 속성에 접근할 수 없으므로 네트워크 속성에 대한 모든 액세스를 로컬 필드에서 지원해야 합니다. 네트워크 속성이 단일 네트워크 구조의 일부일 때 이 접근 방식은 약간 더 간단합니다:
C#
public struct FireData : INetworkStruct
{
public int FireTick;
public Vector3 FirePosition;
public Vector3 FireVelocity;
}
예제 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 Property에서 한 단계 업그레이드된 것입니다.
발사체 데이터 버퍼 접근 방식은 원형 버퍼 역할을 하는 ProjectileData
구조체의 고정 배열을 사용합니다. 즉, 실행 시 Input/State 권한이 버퍼를 현재 버퍼 헤드(fireCount
속성)를 기반으로 한 데이터로 채우고 모든 클라이언트가 로컬 헤드(visibleFireCount
)를 기반으로 한 렌더 호출에서 시각적으로 따라잡습니다. 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
, FireVelocity
및 FireTick
이 설정됨)와 발사체가 환경과 충돌할 때(HitPosition
및 FinishTick
이 설정됨)로 단일 발사체당 두 번 설정됩니다. 특별한 정당성이 입증된 경우에는 물론 발사체 고급 Homing Projectiles을 참조하여 데이터를 보다 빈번하게 업데이트할 수 있습니다.
발사체 시간에 대하여
노트: 먼저 Fusion에서 예측이 작동하는 방식을 이해해야 합니다
퓨전에서는 두 개의 주요 시간 프레임을 인식합니다. 로컬 시간 범위는 로컬 플레이어와 로컬 객체의 시간입니다. 원격 시간 범위는 원격 플레이어와 원격 개체의 시간입니다. 호스트/서버의 모든 개체는 항상 로컬 시간 범위 내에서 시뮬레이션되고 렌더링 됩니다. 클라이언트는 두 시간대 모두에서 객체를 렌더링 합니다. 일반적으로 로컬은 로컬 객체에 사용되고 원격은 원격 객체에 사용됩니다. 시뮬레이션을 위해 클라이언트는 프록시 개체(다른 플레이어의 발사체)가 로컬 시간 범위 내에 렌더링 되지 않는 한 일반적으로 로컬 개체(= FixedUpdateNetwork
는 프록시에 대해 실행되지 않음)만 시뮬레이션하고 있으며 오버슈팅을 방지하기 위해 가능한 충돌 및 기타 상호 작용에 대한 데이터가 필요합니다.
실제로 이것은 서로 옆에 서서 같은 틱으로 발사체를 발사하는 두 명의 다른 플레이어 A와 B를 의미합니다. A 플레이어 입장에서는 발사체 A가 즉시 발사되지만, B 플레이에서 서버로 이동하는 정보가 처리되어 A 플레이어에게 전송됩니다. 이 정보가 마침내 도착하면 플레이어 A가 이미 몇 번 앞서고 있으며(= 마지막으로 알려진 서버 상태에서 미래를 예측하고 있습니다), 발사체 A의 렌더링도 앞서고 있습니다. 발사체 A가 이미 플레이어 A로부터 3미터 떨어진 곳에 있다고 가정해 보겠습니다. 이제 발사체 B를 렌더링 하는 방법에 대해 두 가지 옵션이 있습니다. 플레이어 B에서 3m 떨어져 있어야 하는 위치의 로컬 시간 범위에서 "정확하게" 렌더링하도록 선택할 수 있지만, 이는 플레이어 B에서 3m 떨어진 곳에서 투사 가능한 비주얼이 갑자기 나타난다는 것을 의미합니다. 이러한 동작은 일반적으로 바람직하지 않으므로 원격 시간 범위 내에서 발사체 B를 렌더링 합니다. 즉, 발사체 B는 플레이어 B의 무기에서 직접 발사되지만, 발사체가 같은 틱으로 발사되었음에도 불구하고 플레이어 A의 발사체보다 3m 뒤로 렌더링 됩니다. 이것이 바로 이 샘플의 모든 관련 예제에서 발사체 렌더링 시간이 다음과 같이 계산되는 이유입니다:
C#
float renderTime = Object.IsProxy ? Runner.InterpolationRenderTime : Runner.SimulationRenderTime;
고급 발사체 타이밍 노트
복잡한 타이밍 주제가 궁금한 경우에만 읽으십시오. 일부 발사체를 쏘기만 하면 되는 경우에는 이 항목을 자세히 이해할 필요가 없습니다.
렌더링(또는 Render
호출에서 값을 읽음)과 관련하여 기술적으로 몇 가지 시간 프레임이 있습니다:
- 로컬
- = 마지막 순방향 눈금의 값
- 렌더 값: 입력/상태 권한에서 네트워크 속성을 읽음
- 렌더 시간:
Runner.Tick * Runner.DeltaTime
또는Runner.SimulationTime
- 로컬 인터폴링
- = 마지막 두 전진 틱 사이에 보간 된 값
- 렌더 값: 입력/상태 권한에서 Interpolator 값을 읽음
- 렌더 시간: 서버에서 호출할 때 (상태 권한)
(Runner.Tick - 1 + Runner.StateAlpha) * Runner.DeltaTime
또는Runner.SimulationRenderTime
또는Runner.InterpolationRenderTime
- 로컬 외삽
- = 마지막 순방향 눈금에서 추정한 값
- 렌더 값: 입력/상태 권한에서 네트워크 속성 읽기 + 값의 렌더 부분을 로컬로 계산
- 렌더 시간:
(Runner.Tick + Runner.StateAlpha) * Runner.DeltaTime
또는Runner.SimulationRenderTime + Runner.DeltaTime
- 리모트
- = 최신 서버 눈금의 값 (=> 최근에 수신한 틱)
- 렌더 값: 프록시에서 네트워크 속성 읽기(재시뮬레이션 중에 프록시에서 속성이 수정되지 않은 경우)
- 렌더 시간:
Runner.Simulation.LatestServerState.Tick * Runner.DeltaTime
또는Runner.Simulation.LatestServerState.Time
- 리모트 인터폴링
- = 시뮬레이션의
InterpFrom
과InterpTo
사이에 보간 되는 값(최신 서버 틱이 보간에 사용되기 전에 안전 창이 있어야 하므로InterpFrom
과InterpTo
는 최신 서버 틱보다 훨씬 더 과거의 값임) - 렌더 값: 프록시에서 Interpolator 값 읽기
- Time in Render:
Runner.InterpolationRenderTime
또는(Runner.Simulation.InterpFrom.Tick + (Runner.Simulation.InterpTo.Tick - Runner.Simulation.InterpFrom.Tick) * Runner.Simulation.InterpAlpha) * Runner.DeltaTime
- = 시뮬레이션의
이러한 시간 프레임 정의를 보면, 예제 5의 발사체는 입력/상태 권한의 경우 로컬 인터폴 시간 프레임으로 렌더링 되고 프록시의 경우 원격 인터폴 시간 프레임으로 렌더링 됩니다. 그러나 이것이 정확하게 맞으려면 각 인터폴레이터에서 fireCount
및 projectileData
속성을 읽어야 합니다. 이는 단순화를 위해 생략되었습니다. 원격 보간 시간이지만 원격 값(및 로컬 보간 시간이지만 로컬 값)을 사용하고 있습니다. 따라서 촬영은 기술적으로 필요한 것보다 프록시에서 약간 더 빨리 발생합니다. 이 경우의 차이는 복잡성을 정당화하지 않습니다. 전체 보간 예제의 경우 발사체 고급을 선택합니다.