Player Input - cheona-thousand-man/Unity-myBasics-Wiki GitHub Wiki

๊ฐœ์š”

  • Fusion์€
    • ๋งค Tick๋งˆ๋‹ค ํ”Œ๋ ˆ์ด์–ด ์ž…๋ ฅ์„ ์ˆ˜์ง‘
    • ์ž…๋ ฅ ๋ฐ์ดํ„ฐ๋ฅผ ํžˆ์Šคํ† ๋ฆฌ ๋ฒ„ํผ์— ์ €์žฅ
    • ๋ฐ์ดํ„ฐ๋ฅผ ์„œ๋ฒ„์— ์ž๋™์œผ๋กœ ๋ณต์ œํ•˜๋Š” ๋ฉ”์ปค๋‹ˆ์ฆ˜ ์ œ๊ณต
  • ์ด ๋ฉ”์ปค๋‹ˆ์ฆ˜์€ ์ฃผ๋กœ ํด๋ผ์ด์–ธํŠธ ์ธก ์˜ˆ์ธก(client-side prediction)์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด ์ œ๊ณต
    • Tick ์ž…๋ ฅ(Tick Inputs)์€ Tick ์‹œ๋ฎฌ๋ ˆ์ด์…˜(FixedUpdateNetwork())์—์„œ ์‚ฌ์šฉ
    • ์ž…๋ ฅ ๊ถŒํ•œ(HasInputAuthority)์ด ์žˆ๋Š” ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„ ๋ชจ๋‘์—์„œ ์ผ๊ด€๋œ ๊ฒฐ๊ณผ ์ƒ์„ฑ
    • ํด๋ผ์ด์–ธํŠธ์˜ ํžˆ์Šคํ† ๋ฆฌ ๋ฒ„ํผ๋Š” Tick์˜ ์žฌ์‹œ๋ฎฌ๋ ˆ์ด์…˜์— ์‚ฌ์šฉ

Input Struct ์ •์˜

  • ์ œ์•ฝ ์‚ฌํ•ญ
    • INetworkInput์„ ์ƒ์†
    • ์›์‹œ ํƒ€์ž… ๋ฐ ๊ตฌ์กฐ์ฒด๋งŒ ํฌํ•จ
    • Input Struct ๋ฐ ํฌํ•จ๋œ ๋ชจ๋“  ๊ตฌ์กฐ์ฒด๋Š” ์ตœ์ƒ์œ„ ๊ตฌ์กฐ์ฒด(์ฆ‰, ํด๋ž˜์Šค์— ์ค‘์ฒฉX)
    • boolean ๊ฐ’์€ bool ๋Œ€์‹  NetworkBool์„ ์‚ฌ์šฉ
      (C#์€ ํ”Œ๋žซํผ ๊ฐ„ boolean์˜ ํฌ๊ธฐ์— ๋Œ€ํ•ด ์ผ๊ด€์„ฑ์„ ๋ณด์žฅํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ, NetworkBool์„ ์‚ฌ์šฉํ•ด ์ง๋ ฌํ™”)
public struct MyInput : INetworkedInput {
    public Vector3 aimDirection;
}

Buttons

๋ฒ„ํŠผ ํด๋ฆญ์„ INetworkInput ๊ตฌ์กฐ์ฒด์— ์ €์žฅํ•˜๋Š” wrapper๋ฅผ ์ œ๊ณต

  • Input Struct์— ๋ฒ„ํŠผ ์ถ”๊ฐ€ ๋ฐฉ๋ฒ•
    • ๋ฒ„ํŠผ์„ ์œ„ํ•œ ์—ด๊ฑฐํ˜•(enum) ์ƒ์„ฑ (์ค‘์š”: ์—ด๊ฑฐํ˜•์€ ๋ช…์‹œ์ ์œผ๋กœ ์ •์˜๋˜์–ด์•ผ ํ•˜๋ฉฐ 0๋ถ€ํ„ฐ ์‹œ์ž‘)
    • INetworkedInput์— NetworkButtons ๋ณ€์ˆ˜ ์ถ”๊ฐ€
enum MyButtons {
    Forward = 0,
    Backward = 1,
    Left = 2,
    Right = 3,
    Jump = 4
}

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 ํƒ€์ž…์€ ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ๋ฒ„ํŠผ์˜ ์ด์ „ ์ƒํƒœ๋ฅผ ์ถ”์ ํ•˜๊ธฐ ์œ„ํ•ด [Networked] ๋ฒ„์ „์„ ๋งŒ๋“ค์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

public class PlayerInputConsumerExample : NetworkBehaviour {
    [Networked] public NetworkButtons ButtonsPrevious { get; set; }

    // ์ž์„ธํ•œ ์ฝ”๋“œ๋Š” ์•„๋ž˜์˜ GetInput() ์„น์…˜์„ ์ฐธ์กฐํ•˜์„ธ์š”.
}
  • NetworkButtons GetPressed(NetworkButtons previous) ๋ฐฉ๊ธˆ ๋ˆŒ๋ฆฐ ๋ฒ„ํŠผ์˜ ๊ฐ’ ๋ฐ˜ํ™˜
  • NetworkButtons GetReleased(NetworkButtons previous) ๋ฐฉ๊ธˆ ํ•ด์ œ๋œ ๋ฒ„ํŠผ์˜ ๊ฐ’ ๋ฐ˜ํ™˜
  • (NetworkButtons, NetworkButtons) GetPressedOrReleased(NetworkButtons previous) ๋ฐฉ๊ธˆ ๋ˆŒ๋ฆฐ ๋ฒ„ํŠผ๊ณผ ํ•ด์ œ๋œ ๋ฒ„ํŠผ์˜ ๊ฐ’์„ touple๋กœ ๋ฐ˜ํ™˜

์ค‘์š”: ๋ฒ„ํŠผ ๊ฐ’์„ ํ• ๋‹นํ•  ๋•Œ๋Š” Input.GetKey()๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Input.GetKeyDown() ๋˜๋Š” Input.GetKeyUp()๋Š” Fusion Tick๊ณผ ๋™๊ธฐํ™”๋˜์ง€ ์•Š์œผ๋ฏ€๋กœ ๋ฌด์‹œ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Poll Input

  • Fusion์€ ๋กœ์ปฌ ํด๋ผ์ด์–ธํŠธ์—์„œ input pollingํ•˜๊ณ , ๋ฏธ๋ฆฌ ์ •์˜๋œ ์ž…๋ ฅ ๊ตฌ์กฐ์ฒด์— ๋ฐ์ดํ„ฐ ์‚ฝ์ž…
  • Fusion Runner๋Š” ๋‹จ์ผ ์ž…๋ ฅ ๊ตฌ์กฐ์ฒด๋งŒ ์ถ”์ ํ•˜๋ฏ€๋กœ, input polling์„ ํ•œ ๊ณณ์—์„œ ๊ตฌํ˜„ํ•˜๋„๋ก ๊ถŒ์žฅ
    • Fusion Runner๋Š” INetworkRunnerCallbacks.OnInput() ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ input polling
    • OnInput()์˜ ๊ตฌํ˜„์€ INetworkInput์„ ์ƒ์†๋ฐ›๋Š” ๊ตฌ์กฐ์ฒด์— ์„ ํƒํ•œ ๋ฐ์ดํ„ฐ ์‚ฝ์ž…
    • ์ฑ„์›Œ์ง„ ๊ตฌ์กฐ์ฒด๋Š” ์ œ๊ณต๋œ NetworkInput์— ๋Œ€ํ•ด Set() ํ˜ธ์ถœ์„ ํ†ตํ•ด Fusion์— ๋ฐ˜ํ™˜

์ค‘์š”

  1. ์—ฌ๋Ÿฌ ํด๋ง site๊ฐ€ ์žˆ์œผ๋ฉด ๋งˆ์ง€๋ง‰ input struct ๋ฒ„์ „๋งŒ ์œ ์ง€
  2. ์ž…๋ ฅ์€ ๋กœ์ปฌ์—์„œ๋งŒ ํด๋ง(๋ชจ๋“  ๋ชจ๋“œ)

SimulationBehaviour / NetworkBehaviour

  • OnInput()์„ SimulationBehaviour ๋˜๋Š” NetworkBehaviour ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉํ•˜๋ ค๋ฉด,
    • INetworkRunnerCallbacks ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌํ˜„
    • NetworkRunner.AddCallbacks()๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์ฝœ๋ฐฑ์„ Loacl Runner์— ๋“ฑ๋ก
public class InputProvider : SimulationBehaviour, INetworkRunnerCallbacks {
    public void OnEnable(){
        if(Runner != null){
           Runner.AddCallbacks(this);
        }
    }

    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);
    }

    public void OnDisable(){
        if(Runner != null){
            Runner.RemoveCallbacks(this);
        }
    }
}

MonoBehaviour ๋ฐ Pure C# ์Šคํฌ๋ฆฝํŠธ

  • ์ผ๋ฐ˜ C# ์Šคํฌ๋ฆฝํŠธ ๋˜๋Š” MonoBehaviour์—์„œ ์ž…๋ ฅ์„ ํด๋งํ•˜๋ ค๋ฉด,
    • INetworkRunnerCallbacks ๋ฐ OnInput() ๊ตฌํ˜„
    • NetworkRunner์— ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋“ฑ๋กํ•˜๊ณ  AddCallbacks() ํ˜ธ์ถœ
public class InputProvider : MonoBehaviour, INetworkRunnerCallbacks {
    public void OnEnable(){
        var myNetworkRunner = FindObjectOfType<NetworkRunner>();
        myNetworkRunner.AddCallbacks(this);
    }

    public void OnInput(NetworkRunner runner, NetworkInput input) {
        // ์œ„์˜ SimulationBehaviour ๋ฐ NetworkBehaviour์™€ ๋™์ผ
    }

    public void OnDisable(){
        var myNetworkRunner = FindObjectOfType<NetworkRunner>();
        myNetworkRunner.RemoveCallbacks(this);
    }
}

Unity New Input System

  • ํ”„๋กœ์„ธ์Šค๋Š” ๋™์ผํ•˜์ง€๋งŒ ์ž…๋ ฅ ์•ก์…˜์—์„œ ์˜ค๋Š” ์ž…๋ ฅ ์ˆ˜์ง‘
    • ์ž…๋ ฅ ์•ก์…˜์„ ์ƒ์„ฑํ•˜๊ณ  ์›ํ•˜๋Š” ๋ฒ„ํŠผ์„ ์ •์˜ํ•œ ํ›„ C# ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ฝ”๋“œ์—์„œ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑ
    • PlayerInput ํด๋ž˜์Šค์˜ ์ด๋ฒคํŠธ๋ฅผ ์‚ฌ์šฉ or
    • ์ž…๋ ฅ์„ ๋กœ์ปฌ ์บ์‹œ์— ์ €์žฅํ•˜์—ฌ OnInput()์—์„œ ์‚ฌ์šฉ
public class InputProvider : SimulationBehaviour, INetworkRunnerCallbacks {
    private PlayerActionMap _playerActionMap = new PlayerActionMap();

    public void OnEnable(){
        if(Runner != null){
            _playerActionMap.Player.Enable();
            Runner.AddCallbacks(this);
        }
    }

    public void OnInput(NetworkRunner runner, NetworkInput input) {
        var myInput = new MyInput();
        var playerActions = _playerActionMap.Player;

        myInput.buttons.Set(MyButtons.Jump, playerActions.Jump.IsPressed());

        input.Set(myInput);
    }

    public void OnDisable(){
        if(Runner != null){
            _playerActionMap.Player.Disable();
            Runner.RemoveCallbacks(this);
        }
    }
}

๋‚ฎ์€ Tick ์†๋„์—์„œ input polling

  • Unity์˜ Update ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌ์กฐ์ฒด์— ๊ธฐ๋ก๋œ ์ž…๋ ฅ์„ ๋ˆ„์ ํ•˜๊ณ  ๋‚˜์ค‘์— ์‚ฌ์šฉ ๊ฐ€๋Šฅ
  • OnInput์—์„œ ์ด ๊ตฌ์กฐ์ฒด๋ฅผ ์ฝ๊ณ  input.Set() ํ˜ธ์ถœ์„ ํ†ตํ•ด Fusion์— ์ ์ ˆํ•˜๊ฒŒ ์ „์†กํ•œ ๋‹ค์Œ, ๋‹ค์Œ Tick์„ ์œ„ํ•ด ๊ตฌ์กฐ์ฒด ์žฌ์„ค์ •
public class InputProvider : SimulationBehaviour, INetworkRunnerCallbacks {
    MyInput myInput = new MyInput();

    public void OnEnable() {
        if(Runner != null) {
            Runner.AddCallbacks(this);
        }
    }

    public void Update() {
        if (Input.GetMouseButtonDown(0)) {
            myInput.Buttons.Set(MyButtons.Attack, true);
        }

        if (Input.GetKeyDown(KeyCode.Space)) {
            myInput.Buttons.Set(MyButtons.Jump, true);
        }
    }

    public void OnInput(NetworkRunner runner, NetworkInput input) {
        input.Set(myInput);
        myInput = default;
    }
}

UI์™€ ํ•จ๊ป˜ input polling

  • UI์™€ ํ•จ๊ป˜ ์ž…๋ ฅ์„ ํด๋งํ•  ๋•Œ๋Š” ์œ„์™€ ๋™์ผํ•œ ๋กœ์ง ์ˆ˜ํ–‰
    • UI๋ฅผ ํ†ตํ•ด ํ˜ธ์ถœ๋œ ๋ฉ”์„œ๋“œ์—์„œ NetworkButton์„ ์„ค์ •ํ•˜๊ณ , OnInput์—์„œ ์ฝ๊ณ  ์žฌ์„ค์ •

Read Input

  • ์ž…๋ ฅ์€ ์‹œ๋ฎฌ๋ ˆ์ด์…˜์— ์˜ํ•ด ์ฝํ˜€์„œ ํ˜„์žฌ ๋„คํŠธ์›Œํฌ ์ƒํƒœ๋ฅผ ์ƒˆ ์ƒํƒœ๋กœ ์ˆ˜์ •
  • Fusion์€ ์ž…๋ ฅ ๊ตฌ์กฐ์ฒด๋ฅผ ๋„คํŠธ์›Œํฌ๋ฅผ ํ†ตํ•ด ๋™๊ธฐํ™”
  • ์ž…๋ ฅ ๊ถŒํ•œ(Input Authority) ๋ฐ ์ƒํƒœ ๊ถŒํ•œ(State Authority)์ด ์žˆ๋Š” ํด๋ผ์ด์–ธํŠธ ๋ฐ ํ˜ธ์ŠคํŠธ์—๊ฒŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

์ฃผ์˜: ํ”Œ๋ ˆ์ด์–ด ์ž…๋ ฅ์€ ์ž…๋ ฅ ๊ถŒํ•œ๊ณผ ์ƒํƒœ ๊ถŒํ•œ์ด ์žˆ๋Š” ํด๋ผ์ด์–ธํŠธ์™€ ํ˜ธ์ŠคํŠธ์—์„œ๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

  • [Host Mode/Server Mode] ์ž…๋ ฅ์ด ํŠน์ • ํ”Œ๋ ˆ์ด์–ด์™€ ํ˜ธ์ŠคํŠธ/์„œ๋ฒ„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜์—๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
  • [Shared Mode] ๋กœ์ปฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜๋งŒ ์ž…๋ ฅ์„ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
  • ์ž…๋ ฅ์€ ํด๋ผ์ด์–ธํŠธ ๊ฐ„์— (์ž๋™) ๊ณต์œ X

GetInput()

  1. GetInput(out T input)์„ ํ˜ธ์ถœํ•˜์—ฌ Input struct ๊ฐ€์ ธ์˜ด
  2. ์ž…๋ ฅ ๊ถŒํ•œ์ด ์žˆ๋Š” ๋„คํŠธ์›Œํฌ ๋™์ž‘(์˜ˆ: ํ”Œ๋ ˆ์ด์–ด ์ด๋™์„ ์ œ์–ดํ•˜๋Š” ๊ตฌ์„ฑ ์š”์†Œ)์˜ FixedUpdateNetwork()์—์„œ ํ˜ธ์ถœ ํ•„์š”
  • GetInput() ํ˜ธ์ถœ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒฝ์šฐ false ๋ฐ˜ํ™˜

  • ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ƒํƒœ ๊ถŒํ•œ ๋˜๋Š” ์ž…๋ ฅ ๊ถŒํ•œ์ด ์—†๋Š” ๊ฒฝ์šฐ

  • ์‹œ๋ฎฌ๋ ˆ์ด์…˜์—์„œ ์š”์ฒญํ•œ ์ž…๋ ฅ ์œ ํ˜•์ด ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ

  • Game Mode ๋ณ„ ์ •๋ณด

    • [Host Mode/Server Mode] ์ฃผ์–ด์ง„ Tick์— ๋Œ€ํ•œ ์ž…๋ ฅ์ด ํŠน์ • ํ”Œ๋ ˆ์ด์–ด์™€ ํ˜ธ์ŠคํŠธ/์„œ๋ฒ„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜์—๋งŒ ์ œ๊ณต(ํ”Œ๋ ˆ์ด์–ด ๊ฐ„ ๊ณต์œ X)
    • [Shared Mode]
      • OnInput(), GetInput() ํŒจํ„ด ๊ถŒ์žฅ
      • ๋กœ์ปฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜๋งŒ ๋กœ์ปฌ ํ”Œ๋ ˆ์ด์–ด ์ž…๋ ฅ์— ์ ‘๊ทผ ๊ฐ€๋Šฅ(ํ”Œ๋ ˆ์ด์–ด๊ฐ„ ๊ณต์œ X)
using Fusion;
using UnityEngine;

public class PlayerInputConsumerExample : NetworkBehaviour {
    [Networked] public NetworkButtons ButtonsPrevious { get; set; }

    public override void FixedUpdateNetwork() {
        if (GetInput<MyInput>(out var input) == false) return;

        var pressed = input.Buttons.GetPressed(ButtonsPrevious);
        var released = input.Buttons.GetReleased(ButtonsPrevious);

        ButtonsPrevious = input.Buttons;

        var vector = default(Vector3);

        if (input.Buttons.IsSet(MyButtons.Forward)) { vector.z += 1; }
        if (input.Buttons.IsSet(MyButtons.Backward)) { vector.z -= 1; }

        if (input.Buttons.IsSet(MyButtons.Left)) { vector.x -= 1; }
        if (input.Buttons.IsSet(MyButtons.Right)) { vector.x += 1; }

        DoMove(vector);

        if (pressed.IsSet(MyButtons.Jump)) {
            DoJump();
        }
    }

    void DoMove(Vector3 vector) {
        // ๋กœ์ง์ด ์—†๋Š” ๋”๋ฏธ ๋ฉ”์„œ๋“œ
    }

    void DoJump() {
        // ๋กœ์ง์ด ์—†๋Š” ๋”๋ฏธ ๋ฉ”์„œ๋“œ
    }
}

Runner.TryGetInputForPlayer()

  • NetworkRunner.TryGetInputForPlayer<T>(PlayerRef playerRef, out var input)์„ ํ˜ธ์ถœํ•˜์—ฌ ๋„คํŠธ์›Œํฌ ๋™์ž‘ ์™ธ๋ถ€์—์„œ ์ž…๋ ฅ ๊ตฌ๋…
  • INetworkInput ์œ ํ˜•๊ณผ ํ•จ๊ป˜ ํ”Œ๋ ˆ์ด์–ด ์ง€์ • ํ•„์š”

์ฃผ์˜: GetInput()๊ณผ ๋™์ผํ•œ ์ œํ•œ ์‚ฌํ•ญ์ด ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ฆ‰, ์ž…๋ ฅ ๊ถŒํ•œ์ด ์žˆ๋Š” ํด๋ผ์ด์–ธํŠธ๋‚˜ ์„œ๋ฒ„/ํ˜ธ์ŠคํŠธ๊ฐ€ ํŠน์ • ํ”Œ๋ ˆ์ด์–ด์— ๋Œ€ํ•œ ์ž…๋ ฅ์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

var myNetworkRunner = FindObjectOfType<NetworkRunner>();

if(myNetworkRunner.TryGetInputForPlayer<MyInput>(myNetworkRunner.LocalPlayer, out var input)){
    // ๋กœ์ง ์ˆ˜ํ–‰
}

๊ถŒํ•œ์— ๋Œ€ํ•œ ์ฐธ๊ณ  ์‚ฌํ•ญ

  1. ์ „์ฒด ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๊ถŒํ•œ์„ ๋ณด์žฅํ•˜๋ ค๋ฉด, ์ž…๋ ฅ ๊ตฌ์กฐ์ฒด๋ฅผ ์ฑ„์šธ ๋•Œ OnInput()์—์„œ๋งŒ ์ž…๋ ฅ ๊ฐ’ ์ˆ˜์ง‘
  2. ์ž…๋ ฅ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์‹คํ–‰ํ•  ๋กœ์ง์€ GetInput()์—์„œ ์™„์ „ํžˆ ์ฒ˜๋ฆฌ
  • ์˜ˆ๋ฅผ ๋“ค์–ด, ์ด์•Œ์„ ๋ฐœ์‚ฌํ•˜๋Š” ๊ฒฝ์šฐ:
    • OnInput() ํ”Œ๋ ˆ์ด์–ด์˜ ๋ฐœ์‚ฌ ๋ฒ„ํŠผ ๊ฐ’ ์ €์žฅ
    • GetInput() ๋ฐœ์‚ฌ ๋ฒ„ํŠผ์ด ๋ˆŒ๋ ธ๋Š”์ง€ ํ™•์ธํ•˜๊ณ , ๋ˆŒ๋ ธ๋‹ค๋ฉด ์ด์•Œ ๋ฐœ์‚ฌ

Multiple Player per Peer

  • "์†ŒํŒŒ", "๋ถ„ํ•  ํ™”๋ฉด" ๋˜๋Š” "๋กœ์ปฌ" ๋ฉ€ํ‹ฐํ”Œ๋ ˆ์ด์–ด๋กœ ์ž์ฃผ ๋ถˆ๋ฆฌ๋Š” ์ด ๊ฒฝ์šฐ,

    • ํ•˜๋‚˜์˜ Peer(์˜ˆ: ์—ฌ๋Ÿฌ ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ ์žˆ๋Š” ๊ฒŒ์ž„ ์ฝ˜์†”)์—์„œ ์—ฌ๋Ÿฌ ์ธ๊ฐ„ ํ”Œ๋ ˆ์ด์–ด๊ฐ€ ์ž…๋ ฅ์„ ์ œ๊ณตํ•˜๋ฉด์„œ ์˜จ๋ผ์ธ ๋ฉ€ํ‹ฐํ”Œ๋ ˆ์ด์–ด ๊ฒŒ์ž„์— ์ฐธ์—ฌ ๊ฐ€๋Šฅ
    • Fusion์€ ๋ชจ๋“  ํ”Œ๋ ˆ์ด์–ด๋ฅผ ๋‹จ์ผ PlayerRef์˜ ์ผ๋ถ€๋กœ ๊ฐ„์ฃผ(๊ตฌ๋ถ„X)
    • ๋ฌด์—‡์ด "ํ”Œ๋ ˆ์ด์–ด"์ธ์ง€์™€ ๊ทธ๋“ค์ด ์ œ๊ณตํ•˜๋Š” ์ž…๋ ฅ์„ ๊ฒฐ์ •ํ•˜๋Š” ๊ฒƒ์€ ๊ฐœ๋ฐœ์ž ๋ชซ
  • ์ด ์‚ฌ์šฉ ์‚ฌ๋ก€๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ํ•œ ๊ฐ€์ง€ ์˜ˆ

๊ฐ ํ”Œ๋ ˆ์ด์–ด๋ฅผ ์œ„ํ•œ INetworkInput ๊ตฌ์กฐ์ฒด๋ฅผ ์ค‘์ฒฉ๋œ INetworkStruct๋กœ ์ •์˜

public struct PlayerInputs : INetworkStruct
{
  public Vector2 dir;
}

public struct CombinedPlayerInputs : INetworkInput
{
  public PlayerInputs PlayerA;
  public PlayerInputs PlayerB;
  public PlayerInputs PlayerC;
  public PlayerInputs PlayerD;

  public PlayerInputs this[int i]
  {
    get {
      switch (i) {
        case 0:  return PlayerA;
        case 1:  return PlayerB;
        case 2:  return PlayerC;
        case 3:  return PlayerD;
        default: return default;
      }
    }

    set {
      switch (i) {
        case 0:  PlayerA = value; return;
        case 1:  PlayerB = value; return;
        case 2:  PlayerC = value; return;
        case 3:  PlayerD = value; return;
        default: return;
      }
    }
  }
}

๋‹ค์ค‘ ํ”Œ๋ ˆ์ด์–ด ์ž…๋ ฅ ์ˆ˜์ง‘

public class CouchCoopInput : MonoBehaviour, INetworkRunnerCallbacks
{
  public void OnInput(NetworkRunner runner, NetworkInput input)
  {
    var myInput = new CombinedPlayerInputs();
    myInput[0] = new PlayerInputs() { dir = new Vector2( Input.GetAxis("Joy1_X"), Input.GetAxis("Joy1_Y")) };
    myInput[1] = new PlayerInputs() { dir = new Vector2( Input.GetAxis("Joy2_X"), Input.GetAxis("Joy2_Y")) };
    myInput[2] = new PlayerInputs() { dir = new Vector2( Input.GetAxis("Joy3_X"), Input.GetAxis("Joy3_Y")) };
    myInput[3] = new PlayerInputs() { dir = new Vector2( Input.GetAxis("Joy4_X"), Input.GetAxis("Joy4_Y")) };
    input.Set(myInput);
  }
}

์‹œ๋ฎฌ๋ ˆ์ด์…˜์„ ์œ„ํ•œ ์ž…๋ ฅ ๊ฐ€์ ธ์˜ค๊ธฐ

public class CouchCoopController : NetworkBehaviour
{
  private int _playerIndex;

  public override void FixedUpdateNetwork()
  {
    if (GetInput<CombinedPlayerInputs>(out var input))
    {
      var dir = input[_playerIndex].dir;
      float heading = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
      transform.rotation = Quaternion.Euler(0f, heading - 90, 0f);
    }
  }
}
โš ๏ธ **GitHub.com Fallback** โš ๏ธ