Player Input - cheona-thousand-man/Unity-myBasics-Wiki GitHub Wiki
- Fusion์
- ๋งค Tick๋ง๋ค ํ๋ ์ด์ด ์ ๋ ฅ์ ์์ง
- ์ ๋ ฅ ๋ฐ์ดํฐ๋ฅผ ํ์คํ ๋ฆฌ ๋ฒํผ์ ์ ์ฅ
- ๋ฐ์ดํฐ๋ฅผ ์๋ฒ์ ์๋์ผ๋ก ๋ณต์ ํ๋ ๋ฉ์ปค๋์ฆ ์ ๊ณต
- ์ด ๋ฉ์ปค๋์ฆ์ ์ฃผ๋ก ํด๋ผ์ด์ธํธ ์ธก ์์ธก(client-side prediction)์ ๊ฐ๋ฅํ๊ฒ ํ๊ธฐ ์ํด ์ ๊ณต
- Tick ์ ๋ ฅ(Tick Inputs)์ Tick ์๋ฎฌ๋ ์ด์ (FixedUpdateNetwork())์์ ์ฌ์ฉ
- ์ ๋ ฅ ๊ถํ(HasInputAuthority)์ด ์๋ ํด๋ผ์ด์ธํธ์ ์๋ฒ ๋ชจ๋์์ ์ผ๊ด๋ ๊ฒฐ๊ณผ ์์ฑ
- ํด๋ผ์ด์ธํธ์ ํ์คํ ๋ฆฌ ๋ฒํผ๋ Tick์ ์ฌ์๋ฎฌ๋ ์ด์ ์ ์ฌ์ฉ
-
์ ์ฝ ์ฌํญ
- INetworkInput์ ์์
- ์์ ํ์ ๋ฐ ๊ตฌ์กฐ์ฒด๋ง ํฌํจ
- Input Struct ๋ฐ ํฌํจ๋ ๋ชจ๋ ๊ตฌ์กฐ์ฒด๋ ์ต์์ ๊ตฌ์กฐ์ฒด(์ฆ, ํด๋์ค์ ์ค์ฒฉX)
- boolean ๊ฐ์ bool ๋์ NetworkBool์ ์ฌ์ฉ
(C#์ ํ๋ซํผ ๊ฐ boolean์ ํฌ๊ธฐ์ ๋ํด ์ผ๊ด์ฑ์ ๋ณด์ฅํ์ง ์์ผ๋ฏ๋ก, NetworkBool์ ์ฌ์ฉํด ์ง๋ ฌํ)
public struct MyInput : INetworkedInput {
public Vector3 aimDirection;
}
๋ฒํผ ํด๋ฆญ์ 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๊ณผ ๋๊ธฐํ๋์ง ์์ผ๋ฏ๋ก ๋ฌด์๋ ์ ์์ต๋๋ค.
- Fusion์ ๋ก์ปฌ ํด๋ผ์ด์ธํธ์์ input pollingํ๊ณ , ๋ฏธ๋ฆฌ ์ ์๋ ์ ๋ ฅ ๊ตฌ์กฐ์ฒด์ ๋ฐ์ดํฐ ์ฝ์
- Fusion Runner๋ ๋จ์ผ ์
๋ ฅ ๊ตฌ์กฐ์ฒด๋ง ์ถ์ ํ๋ฏ๋ก, input polling์ ํ ๊ณณ์์ ๊ตฌํํ๋๋ก ๊ถ์ฅ
- Fusion Runner๋
INetworkRunnerCallbacks.OnInput()
๋ฉ์๋๋ฅผ ํธ์ถํ์ฌ input polling -
OnInput()
์ ๊ตฌํ์INetworkInput์ ์์๋ฐ๋ ๊ตฌ์กฐ์ฒด
์ ์ ํํ ๋ฐ์ดํฐ ์ฝ์ -
์ฑ์์ง ๊ตฌ์กฐ์ฒด
๋ ์ ๊ณต๋NetworkInput
์ ๋ํดSet() ํธ์ถ
์ ํตํด Fusion์ ๋ฐํ
- Fusion Runner๋
์ค์
- ์ฌ๋ฌ ํด๋ง site๊ฐ ์์ผ๋ฉด ๋ง์ง๋ง input struct ๋ฒ์ ๋ง ์ ์ง
- ์ ๋ ฅ์ ๋ก์ปฌ์์๋ง ํด๋ง(๋ชจ๋ ๋ชจ๋)
-
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);
}
}
}
-
์ผ๋ฐ 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);
}
}
- ํ๋ก์ธ์ค๋ ๋์ผํ์ง๋ง ์
๋ ฅ ์ก์
์์ ์ค๋ ์
๋ ฅ ์์ง
- ์ ๋ ฅ ์ก์ ์ ์์ฑํ๊ณ ์ํ๋ ๋ฒํผ์ ์ ์ํ ํ 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);
}
}
}
- 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์ ํจ๊ป ์
๋ ฅ์ ํด๋งํ ๋๋ ์์ ๋์ผํ ๋ก์ง ์ํ
- UI๋ฅผ ํตํด ํธ์ถ๋
๋ฉ์๋
์์NetworkButton
์ ์ค์ ํ๊ณ ,OnInput
์์ ์ฝ๊ณ ์ฌ์ค์
- UI๋ฅผ ํตํด ํธ์ถ๋
- ์ ๋ ฅ์ ์๋ฎฌ๋ ์ด์ ์ ์ํด ์ฝํ์ ํ์ฌ ๋คํธ์ํฌ ์ํ๋ฅผ ์ ์ํ๋ก ์์
- Fusion์ ์ ๋ ฅ ๊ตฌ์กฐ์ฒด๋ฅผ ๋คํธ์ํฌ๋ฅผ ํตํด ๋๊ธฐํ
- ์ ๋ ฅ ๊ถํ(Input Authority) ๋ฐ ์ํ ๊ถํ(State Authority)์ด ์๋ ํด๋ผ์ด์ธํธ ๋ฐ ํธ์คํธ์๊ฒ ์ฌ์ฉ ๊ฐ๋ฅ
์ฃผ์: ํ๋ ์ด์ด ์ ๋ ฅ์ ์ ๋ ฅ ๊ถํ๊ณผ ์ํ ๊ถํ์ด ์๋ ํด๋ผ์ด์ธํธ์ ํธ์คํธ์์๋ง ์ฌ์ฉ ๊ฐ๋ฅํฉ๋๋ค.
- [Host Mode/Server Mode] ์ ๋ ฅ์ด ํน์ ํ๋ ์ด์ด์ ํธ์คํธ/์๋ฒ ์๋ฎฌ๋ ์ด์ ์๋ง ์ฌ์ฉ ๊ฐ๋ฅ
- [Shared Mode] ๋ก์ปฌ ์๋ฎฌ๋ ์ด์ ๋ง ์ ๋ ฅ์ ์ฌ์ฉ ๊ฐ๋ฅ
- ์ ๋ ฅ์ ํด๋ผ์ด์ธํธ ๊ฐ์ (์๋) ๊ณต์ X
-
GetInput(out T input)
์ ํธ์ถํ์ฌ Input struct ๊ฐ์ ธ์ด -
์ ๋ ฅ ๊ถํ์ด ์๋ ๋คํธ์ํฌ ๋์
(์: ํ๋ ์ด์ด ์ด๋์ ์ ์ดํ๋ ๊ตฌ์ฑ ์์)์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() {
// ๋ก์ง์ด ์๋ ๋๋ฏธ ๋ฉ์๋
}
}
-
NetworkRunner.TryGetInputForPlayer<T>(PlayerRef playerRef, out var input)
์ ํธ์ถํ์ฌ ๋คํธ์ํฌ ๋์ ์ธ๋ถ์์ ์ ๋ ฅ ๊ตฌ๋ -
INetworkInput
์ ํ๊ณผ ํจ๊ป ํ๋ ์ด์ด ์ง์ ํ์
์ฃผ์: GetInput()๊ณผ ๋์ผํ ์ ํ ์ฌํญ์ด ์ ์ฉ๋ฉ๋๋ค. ์ฆ, ์ ๋ ฅ ๊ถํ์ด ์๋ ํด๋ผ์ด์ธํธ๋ ์๋ฒ/ํธ์คํธ๊ฐ ํน์ ํ๋ ์ด์ด์ ๋ํ ์ ๋ ฅ์ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค.
var myNetworkRunner = FindObjectOfType<NetworkRunner>();
if(myNetworkRunner.TryGetInputForPlayer<MyInput>(myNetworkRunner.LocalPlayer, out var input)){
// ๋ก์ง ์ํ
}
- ์ ์ฒด ์๋ฎฌ๋ ์ด์
๊ถํ์ ๋ณด์ฅํ๋ ค๋ฉด, ์
๋ ฅ ๊ตฌ์กฐ์ฒด๋ฅผ ์ฑ์ธ ๋
OnInput()
์์๋ง ์ ๋ ฅ ๊ฐ ์์ง - ์
๋ ฅ์ ๊ธฐ๋ฐ์ผ๋ก ์คํํ ๋ก์ง์
GetInput()
์์ ์์ ํ ์ฒ๋ฆฌ
- ์๋ฅผ ๋ค์ด, ์ด์์ ๋ฐ์ฌํ๋ ๊ฒฝ์ฐ:
- OnInput() ํ๋ ์ด์ด์ ๋ฐ์ฌ ๋ฒํผ ๊ฐ ์ ์ฅ
- GetInput() ๋ฐ์ฌ ๋ฒํผ์ด ๋๋ ธ๋์ง ํ์ธํ๊ณ , ๋๋ ธ๋ค๋ฉด ์ด์ ๋ฐ์ฌ
-
"์ํ", "๋ถํ ํ๋ฉด" ๋๋ "๋ก์ปฌ" ๋ฉํฐํ๋ ์ด์ด๋ก ์์ฃผ ๋ถ๋ฆฌ๋ ์ด ๊ฒฝ์ฐ,
- ํ๋์ 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);
}
}
}