네트워크 입력
소개
입력 정의, 폴링 및 소비는 Fusion의 핵심입니다.
입력 구조체 정의
입력 구조체는 필요에 따라 단순 또는 복잡한 데이터를 저장할 수 있습니다. Fusion은 실제로 변화된 데이터만 전송합니다. 따라서 데이터를 압축(예: 버튼에 플래그 사용) 하는 것이 유리하지만 거의 사용되지 않는 필드를 포함해도 좋습니다. 이러한 하나의 예는 거의 한 개만 있더라도 여러 로컬 플레이어로부터 입력을 수집하는 것입니다. 이러한 다른 플레이어들이 실제로 입력을 제공하지 않을 경우 로컬 메모리만 사용하며, 입력 구조체의 대역폭의 사용에는 영향을 미치지 않습니다.
입력 구조체는 다음 제약 사항들이 있습니다:
INetworkInput
으로부터 상속을 받아야 합니다.- 원시 자료형과 구조체만 포함할 수 있습니다.
- 입력 구조체와 입력 구조체를 담고 있는 모든 구조체들은 최상위 레벨 구조체이어야 합니다. (예, 클래스 안에서 중첩될 수 없습니다) 그리고,
- 부울 값은
bool
대신NetworkBool
을 사용합니다 - C#은 플랫폼 전반에 걸쳐 일관된 크기의 부울을 적용하지 않기 때문에NetworkBool
을 사용하여 단일 비트로 올바르게 직렬화합니다.
Fusion은 구조체의 유형을 지능적으로 매핑합니다. 이렇게 하면 다른 플레이 모드나 게임의 다른 부분에 다른 구조체를 사용할 수 있습니다. 입력의 압축을 해제할 때 Fusion은 올바른 유형의 사용 가능한 입력만 리턴합니다.
C#
public struct MyInput : INetworkedInput {
public Vector3 aimDirection;
}
버튼
INetworkInput
구조체에서 버튼이 눌렸다는 것을 저장할 수 있는 편리한 래퍼를 제공하는 특별한 NetworkButtons
타입이 있습니다.
입력 구조체에 버튼을 추가하기 위해서는 아래와 같이 간단히 처리할 수 있습니다:
- 버튼들에 대한 열거형을 생성합니다 ( 중요: 명시적으로 정의되어야 하며 0에서 시작해야 합니다.) 그리고,
INetworkedInput
에NetworkButtons
변수를 추가합니다.
C#
enum MyButtons {
Forward = 0,
Backward = 1,
Left = 2,
Right = 3,
}
public struct MyInput : INetworkInput {
public NetworkButtons buttons;
public Vector3 aimDirection;
}
NetworkButtons
변수에서 직접 값을 할당하고 읽는 데 사용할 수 있는 API는 다음과 같습니다:
void Set(int button, bool state)
: 버튼 및 버튼 상태에 대한 열거 값을 받습니다. (pressed = true, not pressed = false);bool IsSet(int button)
: 버튼에 대한 열거 값을 사용하고 해당 부울 상태를 리턴합니다.
NetworkButtons
타입은 상태 비저장이므로 버튼의 이전 상태에 대한 메타 데이터를 보유하지 않습니다. NetworkButtons
에서 제공하는 다음 메소드 세트를 사용하려면 버튼의 이전 상태를 추적해야 합니다. 이 작업은 각 플레이어에 대해 이전 상태의 [Networked]
버전을 만들면 쉽게 수행됩니다.
public class PlayerInputExample : NetworkBehaviour {
[Networked] public NetworkButtons ButtonsPrevious { get; set; }
...
}
이를 통해 버튼의 현재 상태를 이전 상태와 비교하여 버튼이 방금 눌렸는지 또는 해제되었는지 판단할 수 있습니다.
NetworkButtons GetPressed(NetworkButtons previous)
: 방금 누른 모든 버튼에 대한 값의 집합을 리턴합니다.NetworkButtons GetReleased(NetworkButtons previous)
: 방금 버튼이 떼어진 모든 버튼에 대한 값 집합을 리턴합니다.(NetworkButtons, NetworkButtons) GetPressedOrReleased(NetworkButtons previous)
: 방금 눌렀다 놓은 버튼에 대한 값의 튜플을 반환합니다.
중요: 버튼 값을 지정하기 위해서 Input.GetKey()
만 사용하세요. Input.GetKeyDown()
또는 Input.GetKeyUp()
은 Fusion 틱과 동기화되지 않아 누락될 수 있으므로 사용하면 안됩니다.
폴 입력
Fusion은 로컬 클라이언트를 폴링하고 이전에 정의된 입력 구조체를 채워 입력을 수집합니다. Fusion Runner는 단일 입력 구조체만 추적하므로 예기치 않은 동작을 방지하기 위해 입력 폴링을 한곳에서 구현하는 것이 좋습니다.
Fusion Runner는 INetworkRunnerCallbacks.OnInput()
메소드를 호출하여 입력을 폴링 합니다. OnInput()
의 구현은 선택한 데이터로 INetworkInput
에서 상속되는 모든 구조체를 생성할 수 있습니다. 입력된 구조체는 제공된 NetworkInput
에서 Set()
를 호출하여 Fusion으로 다시 전달됩니다.
중요: 폴링 사이트가 여러 개 있으면 마지막 입력 구조체 버전을 제외한 모든 사이트를 덮어씁니다.
SimulationBehaviour
Fusion 자동으로 INetworkRunnerCallbacks
인터페이스를 구현하는 모든 SimulationBehaviour
와 NetworkBehaviour
컴포넌트에서 OnInput()
을 호출하여 클라이언트에 입력 권한
을 가집니다.
C#
public class InputProvider : SimulationBehaviour, INetworkRunnerCallbacks {
public void OnInput(NetworkRunner runner, NetworkInput input) {
var myInput = new MyInput();
myInput.Buttons.Set(MyButtons.Forward, Input.GetKey(KeyCode.W));
myInput.Buttons.Set(MyButtons.Backward, Input.GetKey(KeyCode.S));
myInput.Buttons.Set(MyButtons.Left, Input.GetKey(KeyCode.A));
myInput.Buttons.Set(MyButtons.Right, Input.GetKey(KeyCode.D));
myInput.Buttons.Set(MyButtons.Jump, Input.GetKey(KeyCode.Space));
input.Set(myInput);
}
}
MonoBehaviour 와 순수 CSharp
일반 CSharp 스크립트 또는 MonoBehaviour
에서 입력을 폴링 하려면 다음 단계를 수행합니다.
INetworkRunnerCallbacks
및OnInput()
을 구현하고- 여기에서
AddCallbacks()
을 호출하여NetworkRunner
를 가진 스크립트를 등록합니다.
C#
public class InputProvider : SimulationBehaviour, INetworkRunnerCallbacks {
public void OnEnable(){
var myNetworkRunner = FindObjectOfType<NetworkRunner>();
myNetworkRunner.AddCallbacks( this );
}
public void OnInput(NetworkRunner runner, NetworkInput input) {
// Same as in the snippet for SimulationBehaviour and NetworkBehaviour.
}
public void OnDisable(){
var myNetworkRunner = FindObjectOfType<NetworkRunner>();
myNetworkRunner.RemoveCallbacks( this );
}
}
입력 읽기
입력을 시뮬레이션으로 읽어 이전에 폴링 한 입력을 기반으로 기존 네트워크 상태를 현재 상태에서 새 상태로 수정할 수 있습니다. Fusion은 네트워크를 통해 입력 구조체를 동기화하고 입력 권한이 있는 클라이언트와 상태 권한(호스트)이 있는 클라이언트에서 시뮬레이션 중에 사용할 수 있도록 합니다.
폴링 입력과 달리 읽기 입력은 필요한 만큼 다양한 곳에서 수행될 수 있습니다.
주의: 플레이어 입력은 입력 권한 및 상태 권한이 있는 클라이언트만 사용할 수 있습니다. HostMode
및 ServerMode
에서는 플레이어 클라이언트와 호스트/서버를 의미하지만 SharedMode
에서는 하나의 동일한 클라이언트를 의미합니다.
한 클라이언트에서 다른 클라이언트에서 입력 내용을 읽을 수 없습니다. 따라서 입력에 의존하는 모든 변경사항은 다른 클라이언트에서 복제하기 위해 [Networked]
상태로 저장되어야 합니다.
GetInput()
입력 구조체를 가져오려면 확인하려는 객체(예: 플레이어의 이동을 제어하는 컴포넌트)에 대한 입력 권한을 가진 모든 NetworkBehaviour
의 FixedUpdateNetwork()
에서 GetInput(out T input)
을 호출하십시오. GetInput()
에 대한 호출은 이전에 OnInput()
에 입력되었던 것과 동일한 입력 구조체를 제공합니다.
GetInput()
호출이 false 를 리턴하는 경우:
- 클라이언트에 상태 권한 또는 입력 권한이 없습니다.
- 요청된 입력 유형이 시뮬레이션에 존재하지 않습니다.
C#
using Fusion;
using UnityEngine;
public class PlayerInputExample : NetworkBehaviour {
[Networked] public NetworkButtons ButtonsPrevious { get; set; }
public override void FixedUpdateNetwork() {
NetworkButtons buttons = default;
if (GetInput<MyInput>(out var input)) {
buttons = input.Buttons;
}
// compute pressed/released state
var pressed = buttons.GetPressed(ButtonsPrevious);
var released = buttons.GetReleased(ButtonsPrevious);
// store latest input as 'previous' state we had
ButtonsPrevious = buttons;
// movement (check for down)
var vector = default(Vector3);
if (buttons.IsSet(MyButtons.Forward)) { vector.z += 1; }
if (buttons.IsSet(MyButtons.Backward)) { vector.z -= 1; }
if (buttons.IsSet(MyButtons.Left)) { vector.x -= 1; }
if (buttons.IsSet(MyButtons.Right)) { vector.x += 1; }
DoMove(vector);
// jump (check for pressed)
if (pressed.IsSet(MyButtons.Jump)) {
DoJump();
}
}
void DoMove(Vector3 vector) {
// dummy method with no logic in it
}
void DoJump() {
// dummy method with no logic in it
}
}
Runner.TryGetInputForPlayer()
NetworkRunner.TryGetInputForPlayer<T>(PlayerRef playerRef, out var input)
를 호출하여 NetworkBehaviour
외부에서 입력을 읽는 것이 가능합니다. INetworkInput
타입 외에도 입력을 검색할 플레이어를 지정해야 합니다. 주의: GetInput()
에 대한 제한 사항이 적용됩니다. 즉, 입력 권한이 있는 클라이언트나 서버/호스트가 지정된 플레이어에 대한 입력을 가져올 수 있습니다.
var myNetworkRunner = FindObjectOfType<NetworkRunner>();
// Example for local player if script runs only on the client
if(myNetworkRunner.TryGetInputForPlayer<MyInput>(myNetworkRunner.LocalPlayer, out var input)){
// do logic
}
권한에 대한 노트
전체 시뮬레이션 권한을 보장하려면 입력 구조체를 채울 때 OnInput()
에서만 입력 값을 수집하는 것이 중요합니다. 입력을 기반으로 실행되는 논리는 GetInput()
에서 완전히 수행되어야 합니다.
예를 들어, 다음과 같은 분리가 탄환 발사에 사용됩니다.
OnInput()
: 플레이어의 발사 버튼 값을 저장합니다.GetInput()
: 발사 버튼을 눌렀는지 확인하고 눌렸으면 쏩니다.