Unity

InputSystem을 이용한 Photon Fusion 이동

zzondy 2025. 12. 9. 17:12

네트워크 게임에 대한 공부용으로 저장해두는 내용입니다.

sdk 설치 및 연동은 스킵!

 

입력 흐름: Unity Input System → Fusion OnInput → GetInput

이번 구조에서는 Unity Input System을 사용하고, 별도의 InputManager를 통해 Fusion에 입력을 전달하도록 구성했다.

1) InputManager: Unity 입력을 받아서 NetworkInputData로 래핑

using Fusion;
using UnityEngine;
using UnityEngine.InputSystem;

public class InputManager : MonoBehaviour, IManager
{
    private PlayerInput inputAsset;
    private Vector2 inputDirection;
    
    public void Init()
    {
        inputAsset = new PlayerInput();
        inputAsset.Enable();
        
        inputAsset.KeyboardInput.Move.performed += OnMove;
        inputAsset.KeyboardInput.Move.canceled += OnMoveCanceled;
    }

    private void OnMoveCanceled(InputAction.CallbackContext obj)
    {
        inputDirection = Vector2.zero;
    }

    private void OnMove(InputAction.CallbackContext context)
    {
        if (context.performed)
        {
            inputDirection = context.ReadValue<Vector2>();
        }
    }

    public void OnInput(NetworkRunner runner, NetworkInput input)
    {
        var data = new NetworkInputData(inputDirection);
        input.Set(data);
    }

    public void OnInputMissing(NetworkRunner runner, PlayerRef player, NetworkInput input)
    {
        input.Set(new NetworkInputData(Vector2.zero));
    }
}

public struct NetworkInputData : INetworkInput
{
    public Vector2 direction;

    public NetworkInputData(Vector2 direction)
    {
        this.direction = direction;
    }
}

 

핵심은 OnInput에서 현재 프레임의 방향 입력을 NetworkInputData에 담고, input.Set(data)로 Fusion에 넘겨주는 것이다.

2) NetworkManager에서 OnInput 콜백 연결

NetworkRunner에는 콜백을 등록해 두고, 거기서 GameManager.Input을 호출하도록 했다.

public class NetworkManager : MonoBehaviour, IManager, INetworkRunnerCallbacks
{
    private NetworkRunner _runner;
    public NetworkRunner Runner => _runner;

    public void Init()
    {
        _runner = gameObject.AddComponent<NetworkRunner>();
        _runner.AddCallbacks(this);
    }

    public void OnInput(NetworkRunner runner, NetworkInput input)
    {
        GameManager.Input.OnInput(runner, input);
    }

    public void OnInputMissing(NetworkRunner runner, PlayerRef player, NetworkInput input)
    {
        GameManager.Input.OnInputMissing(runner, player, input);
    }

    // … 생략 …
}

3) Player쪽에서 GetInput으로 입력 받기

using Fusion;
using Fusion.Addons.Physics;
using UnityEngine;

public class Player : NetworkBehaviour
{
    private NetworkRigidbody2D _rigidbody;
    public float moveSpeed = 5f;

    private void Awake()
    {
        _rigidbody = GetComponent<NetworkRigidbody2D>();
    }

    public override void FixedUpdateNetwork()
    {
        if (GetInput(out NetworkInputData data))
        {
         	data.direction.Normalize();
         	_rigidbody.RBPosition += Vector3.Lerp(Vector3.zero, new Vector3(data.direction.x, data.direction.y, 0), speed * Runner.DeltaTime);
        }
    }
}

현재 이동코드도 대충 추가했고, 모든 오브젝트가 같이 움직일것 같지만 그건 추후의 리팩토링으로 넘어가자.

여기서 중요한 점은 두 가지였다.

  1. GetInput이 true를 반환하려면
    • 해당 오브젝트에 InputAuthority가 있어야 하고
    • 그 Authority에 해당하는 플레이어가 OnInput에서 데이터를 넣어줘야 한다.
  2. 따라서 Spawn 시에 InputAuthority를 정확히 넘겨주지 않으면, GetInput은 계속 false를 반환하게 된다.

Spawn 시 InputAuthority를 넘기지 않아 생긴 문제

public void OnPlayerJoined(NetworkRunner runner, PlayerRef player)
{
    Debug.Log("[NetworkManager] 플레이어가 방에 접속하였습니다.");
    Player prefab = GameManager.Resource.LoadAsset<Player>("Prefab/player");

    // 잘못된 버전: InputAuthority를 넘기지 않았다.
    _runner.Spawn(prefab, Vector2.zero, Quaternion.identity);
    
    // 수정된 버전: inputAuthority에 player를 넘긴다.
    runner.Spawn(prefab, Vector2.zero, Quaternion.identity, player);
}

위처럼 하면 Player 오브젝트의 NetworkObject.InputAuthority가 None 상태로 남는다.
그 결과:

  • InputManager → OnInput → input.Set(data) 까지는 잘 호출되는데
  • Player.FixedUpdateNetwork()의 GetInput(out NetworkInputData data)는 항상 false
  • 로그도 안 찍히고, 이동도 안 한다

이 문제를 해결하기 위해 Spawn 시 네 번째 인자로 PlayerRef를 넘겨주었다.

 

PlayerRef vs Network Runner

network runner는 Fusion의 중앙 유니티 컴포넌트로, 하나의 네트워크 피어를 나타낸다고 한다.

그런데 왜 PlayerRef를 넘기는게 아닌 Runner를 인자로써 넘겨야 하는걸까? runner또한 한 플레이어가 하나의 runner를 가지고 있을것이라 생각했다.

 

fusion 공식 문서에서는 이 runner를 여러개 생성하여 하나의 유니티 인스턴스에서 멀티 피어모드를 활성화 하는 방법을 설명해 주었다. 그렇다면 playerRef는 무엇일까. PlayerRef는 일종의 ID로 플레이어의 식별을 위한 것이라고 한다. 따라서 PlayerRef를 넘기는것이 플레이어가 제어할 수 있는 모든 runner에 대해 제어할 수 있으므로 플레이어 식별을 위한것이라면 PlayerRef를 사용하는 것이다.