2 - 씬 설정하기
개요
Fusion 102는 기본적인 네트워크 씬을 설정하는 방법을 설명할 것입니다.
이 섹션의 마지막 부분에서 프로젝트에는 다음과 같은 내용이 포함됩니다:
- 경기를 시작하거나 참가할 수 있는 기본 Fusion Runner, 그리고
- 움직일 수 있는 캐릭터
네트워크 입력에 대한 자세한 설명은 설명서를 참조하십시오
Fusion 실행하기
Fusion을 시작하려면 StartGame
메소드를 Fusion NetworkRunner
에서 호출해야 하므로 애플리케이션은 이 컴포넌트를 씬에 포함하거나 코드에서 추가해야 합니다. 대부분의 네트워킹 로직은 상관없이 어느 정도의 코드를 작성해야 하기 때문에 이 자습서는 코드 중심적입니다.
유니티에서 기본 씬이 열린 상태에서
- 빈 게임 객체를 새로 만듭니다
- 새 스크립트 컴포넌트를 추가합니다.
- 스크립트 이름을
BasicSpawner
로 지정합니다. - 스크립트를 열고
BasicSpawner
클래스에INetworkRunnerCallbacks
인터페이스와 필요한 모든 메서드에 대한 스터브를 추가합니다:
C#
using UnityEngine;
using UnityEngine.SceneManagement;
using Fusion;
public class BasicSpawner : MonoBehaviour, INetworkRunnerCallbacks
{
public void OnPlayerJoined(NetworkRunner runner, PlayerRef player) { }
public void OnPlayerLeft(NetworkRunner runner, PlayerRef player) { }
public void OnInput(NetworkRunner runner, NetworkInput input) { }
public void OnInputMissing(NetworkRunner runner, PlayerRef player, NetworkInput input) { }
public void OnShutdown(NetworkRunner runner, ShutdownReason shutdownReason) { }
public void OnConnectedToServer(NetworkRunner runner) { }
public void OnDisconnectedFromServer(NetworkRunner runner, NetDisconnectReason reason) { }
public void OnConnectRequest(NetworkRunner runner, NetworkRunnerCallbackArgs.ConnectRequest request, byte[] token) { }
public void OnConnectFailed(NetworkRunner runner, NetAddress remoteAddress, NetConnectFailedReason reason) { }
public void OnUserSimulationMessage(NetworkRunner runner, SimulationMessagePtr message) { }
public void OnSessionListUpdated(NetworkRunner runner, List<SessionInfo> sessionList) { }
public void OnCustomAuthenticationResponse(NetworkRunner runner, Dictionary<string, object> data) { }
public void OnHostMigration(NetworkRunner runner, HostMigrationToken hostMigrationToken) { }
public void OnSceneLoadDone(NetworkRunner runner) { }
public void OnSceneLoadStart(NetworkRunner runner) { }
public void OnObjectExitAOI(NetworkRunner runner, NetworkObject obj, PlayerRef player){ }
public void OnObjectEnterAOI(NetworkRunner runner, NetworkObject obj, PlayerRef player){ }
public void OnReliableDataReceived(NetworkRunner runner, PlayerRef player, ReliableKey key, ArraySegment<byte> data){ }
public void OnReliableDataProgress(NetworkRunner runner, PlayerRef player, ReliableKey key, float progress){ }
}
INetworkRunnerCallbacks
을 구현하면 Fusion의 NetworkRunner
가 BasicSpawner
클래스와 상호작용할 수 있습니다. NetworkRunner
는 Fusion의 심장이자 영혼으로 실제 네트워크 시뮬레이션을 실행합니다.
NetworkRunner
는 BasicSpawner
가 INetworkRunnerCallbacks
을 구현하는 것을 자동으로 감지하여 StartGame
메소드로 동일한 게임 객체에 추가되므로 해당 메소드를 호출합니다.
BasicSpawner
스크립트에 다음을 추가합니다:
C#
private NetworkRunner _runner;
async void StartGame(GameMode mode)
{
// Create the Fusion runner and let it know that we will be providing user input
_runner = gameObject.AddComponent<NetworkRunner>();
_runner.ProvideInput = true;
// Create the NetworkSceneInfo from the current scene
var scene = SceneRef.FromIndex(SceneManager.GetActiveScene().buildIndex);
var sceneInfo = new NetworkSceneInfo();
if (scene.IsValid) {
sceneInfo.AddSceneRef(scene, LoadSceneMode.Additive);
}
// Start or join (depends on gamemode) a session with a specific name
await _runner.StartGame(new StartGameArgs()
{
GameMode = mode,
SessionName = "TestRoom",
Scene = scene,
SceneManager = gameObject.AddComponent<NetworkSceneManagerDefault>()
});
}
StartGame
메소드는 먼저 Fusion NetworkRunner
를 만들고 이 클라이언트가 입력을 제공할 것임을 알려줍니다. 그런 다음 하드코딩된 이름과 지정된 게임 모드(게임 모드에 대한 자세한 내용은 잠시 후에)로 새 세션을 시작합니다. 현재 씬 인덱스는 전달되지만 클라이언트가 호스트에서 지정한 씬을 사용하도록 강제되므로 이는 호스트에만 관련됩니다. 마지막으로 적절한 조치를 위해 기본 SceneManager가 지정됩니다. SceneManager는 씬에 직접 배치되는 NetworkObjects의 인스턴스화를 처리하며 엄밀히 말하면 이러한 객체가 없기 때문에 이 예에서는 필요하지 않습니다.
Fusion은 여러 네트워크 토폴로지를 지원하지만 이번 소개는 호스팅 모드
에 초점을 맞춥니다. 호스팅 모드에서는 하나의 네트워크 피어가 서버와 클라이언트가 되어 네트워크 세션을 생성하고 나머지 피어는 기존 세션에 참여하는 클라이언트에 불과합니다.
이를 수용하기 위해서는 사용자가 게임을 진행할 것인지 아니면 기존 세션에 참여할 것인지 선택할 수 있는 방법이 필요합니다. 단순화를 위해 다음 예제에서는 유니티 IMGUI를 사용합니다. BasicSpawner.cs
에 다음 방법을 추가합니다:
C#
private void OnGUI()
{
if (_runner == null)
{
if (GUI.Button(new Rect(0,0,200,40), "Host"))
{
StartGame(GameMode.Host);
}
if (GUI.Button(new Rect(0,40,200,40), "Join"))
{
StartGame(GameMode.Client);
}
}
}
이 애플리케이션을 실행하면 사용자가 새 세션을 호스팅하고 추가 인스턴스가 해당 세션에 참여할 수 있지만 상호 작용이 없고 전송되는 데이터가 없기 때문에 단일 플레이어 경험처럼 보입니다.
플레이어 아바타 생성
게임이 되기 위해서는 각 플레이어에게 입력을 제공하고 플레이어 아바타와 같은 세계에서 존재감을 가질 수 있는 방법이 주어져야 합니다.
유니티 편집기에서,
PlayerPrefab
이라는 이름의 빈 게임 객체를 새로 만듭니다- 여기에
NetworkObject
컴포넌트를 추가합니다.
이렇게 하면 모든 피어가 참조할 수 있도록 네트워크 ID가 부여됩니다. 사용자가 이 아바타를 제어하기 때문에 NetworkCharacterController
도 필요합니다. 필수 조건은 아니지만 대부분의 플레이어가 제어하는 오브젝트에서 좋은 출발점이기 때문에 추가하십시오. NetworkCharacterController
는 기본 유니티 Character Controller
컴포넌트가 필요하며 이 컴포넌트는 자동으로 추가됩니다.
일반적으로 네트워크 객체를 시각적 표현에서 분리하는 것이 좋습니다. 이를 달성하기 위해,
PlayerPrefab
에 하위 개체로 표준 유니티Cube
추가- 이름을
Body
로 바꿉니다 Collider
를 제거합니다
아바타 구성은 다음과 같이 보여야 합니다:
프로젝트를 저장
하여 Fusion이 새 Network 객체를 베이크 한 다음 프로젝트 폴더로 끌어 아바타 프리팹을 만들고 해당 개체를 씬에서 삭제합니다.
아바타 스폰
게임은 호스팅 모드로 실행되기 때문에 새로운 객체를 생성할 권한은 호스트에게만 있습니다. 즉, 모든 플레이어 아바타가 세션에 참여할 때 호스트에 의해 생성되어야 합니다. 이때 편리하게도 INetworkRunnerCallbacks
인터페이스의 OnPlayerJoined
메소드가 호출됩니다.
마찬가지로 플레이어가 연결을 끊으면 OnPlayerLeft
메소드가 호출됩니다.
빈 OnPlayerJoined
및 OnPlayerLeft
스터브를 다음 코드로 바꿉니다:
C#
[SerializeField] private NetworkPrefabRef _playerPrefab;
private Dictionary<PlayerRef, NetworkObject> _spawnedCharacters = new Dictionary<PlayerRef, NetworkObject>();
public void OnPlayerJoined(NetworkRunner runner, PlayerRef player)
{
if (runner.IsServer)
{
// Create a unique position for the player
Vector3 spawnPosition = new Vector3((player.RawEncoded % runner.Config.Simulation.PlayerCount) * 3, 1, 0);
NetworkObject networkPlayerObject = runner.Spawn(_playerPrefab, spawnPosition, Quaternion.identity, player);
// Keep track of the player avatars for easy access
_spawnedCharacters.Add(player, networkPlayerObject);
}
}
public void OnPlayerLeft(NetworkRunner runner, PlayerRef player)
{
if (_spawnedCharacters.TryGetValue(player, out NetworkObject networkObject))
{
runner.Despawn(networkObject);
_spawnedCharacters.Remove(player);
}
}
이것은 본질적으로 유니티의 Instantiate()
메소드를 마지막 것을 제외하고는 비슷한 매개변수 집합을 사용하는 runner.Spawn()
메소드로 대체하기 때문에 매우 친숙하게 보일 것입니다. 마지막 매개변수는 아바타에 대한 입력을 제공할 수 있는 플레이어에 대한 참조입니다. 이것은 객체를 소유하는 것과 동일하지 않다는 점에 유의해야 합니다. 자세한 내용은 잠시 후에 확인하십시오.
유니티 편집기로 돌아가서 생성된 프리팹 아바타를 BasicSpawner
의 Player Prefab
필드로 드래그 앤 드롭하는 것을 잊지 마십시오.
입력 수집
입력 권한을 사용하면 클라이언트가 객체의 네트워크 상태를 직접 수정할 수 없습니다. 대신 호스트가 네트워크 상태를 업데이트하기 위해 해석할 입력 구조체를 제공할 수 있습니다.
클라이언트는 사용자에게 즉각적인 피드백을 제공하기 위해 입력을 로컬로 적용할 수도 있지만, 이는 호스트에 의해 무시될 수 있는 로컬 예측일 뿐입니다.
사용자로부터 입력을 수집하기 전에 입력을 유지하기 위한 데이터 구조체를 정의해야 합니다. 다음 구조체를 가진 NetworkInputData.cs
이라는 이름의 새 파일을 만듭니다:
C#
using Fusion;
using UnityEngine;
public struct NetworkInputData : INetworkInput
{
public Vector3 direction;
}
간단히 설명하기 위해 이 예제에서는 벡터를 사용하여 원하는 이동 방향을 나타내지만 대역폭 비용이 적게 드는 방법이 있음을 알아두십시오. 예를 들어 방향당 1비트가 있는 비트 필드를 들 수 있습니다. Fusion은 입력을 압축하고 실제로 변경되는 데이터만 전송하므로 섣불리 최적화하지 마십시오.
클라이언트는 OnInput 콜백에서 Fusion에 의해 폴링 될 때 사용자로부터 입력을 수집해야 하므로 BasicSpawner.cs
로 돌아가서 OnInput()
스터브를 다음과 같이 바꿉니다:
C#
public void OnInput(NetworkRunner runner, NetworkInput input)
{
var data = new NetworkInputData();
if (Input.GetKey(KeyCode.W))
data.direction += Vector3.forward;
if (Input.GetKey(KeyCode.S))
data.direction += Vector3.back;
if (Input.GetKey(KeyCode.A))
data.direction += Vector3.left;
if (Input.GetKey(KeyCode.D))
data.direction += Vector3.right;
input.Set(data);
}
다시 말하지만, 이것은 꽤 익숙할 것입니다. 핸들러가 표준 유니티 입력을 사용하여 이전에 정의된 구조체로 로컬 클라이언트의 입력을 수집하고 저장합니다. 이 메소드의 마지막 줄은 미리 채워진 입력 구조체를 Fusion에 전달하고, Fusion은 이 구조체를 호스트와 이 클라이언트가 입력 권한을 부여하는 모든 객체가 사용할 수 있도록 합니다.
입력 적용
마지막 단계는 수집된 입력 데이터를 플레이어 아바타에 적용하는 것입니다.
PlayerPrefab
을 선택합니다- 여기에
Player
라는 새로운 스크립트 컴포넌트를 추가합니다. - 새 스크립트를 열고 MonoBehaviour를 NetworkBehaviour로 바꿉니다
- 동작이 Fusion 시뮬레이션 루프에 참여할 수 있도록
FixedUpdateNetwork()
를 구현합니다:
C#
using Fusion;
public class Player : NetworkBehaviour
{
public override void FixedUpdateNetwork(){}
}
FixedUpdateNetwork는 모든 시뮬레이션 틱에 호출됩니다. Fusion이 오래된 확인된 네트워크 상태를 적용한 다음 해당 틱에서 현재(예측된) 로컬 틱까지 재시뮬레이션하기 때문에 렌더링 프레임당 여러 번 발생할 수 있습니다.
FixedUpdateNetwork
에 입력을 입력하면 각 틱에 맞는 입력이 되는지 확인할 수 있습니다. Fusion은 해당 틱에 대한 입력을 쉽게 얻을 수 있는 GetInput()
이라는 이름의 간단한 메소드를 제공합니다. 입력이 완료되면 NetworkCharacterController
를 호출하여 실제 움직임을 아바타 변환에 적용합니다.
전체 Player
클래스는 다음과 같습니다:
C#
using Fusion;
public class Player : NetworkBehaviour
{
private NetworkCharacterController _cc;
private void Awake()
{
_cc = GetComponent<NetworkCharacterController>();
}
public override void FixedUpdateNetwork()
{
if (GetInput(out NetworkInputData data))
{
data.direction.Normalize();
_cc.Move(5*data.direction*Runner.DeltaTime);
}
}
}
제공된 입력은 부정행위를 방지하기 위해 정규화되어 있습니다.
테스트
이제 게임에 이동 가능한 플레이어 아바타가 있는지 확인하기만 하면 됩니다. 하지만 먼저 생성된 객체가 시야 밖으로 떨어지지 않도록 씬에 바닥이 필요합니다.
정육면체 객체를 만들어 (0,-1,0)
에 놓고 X와 Z로 100씩 크기 조정합니다. 모든 것이 같은 색이 되지 않도록 새로운 머티리얼을 만들고 할당하는 것도 주의해 주세요.
애플리케이션을 만들고 여러 인스턴스를 시작합니다(또는 유니티 내에서 직접 실행). 클라이언트 중 한 명이 Host
버튼을 누르고 나머지 한 명이 Join
을 누르도록 합니다.
유니티 내에서 실행할 때 중요한 한 가지 주의 사항: 편집기 자체에서 가끔 렌더링을 해야 하기 때문에 편집기에서 게임을 실행할 때 프레임 속도가 매우 불규칙할 수 있습니다. 이는 타이밍을 정확하게 예측하는 Fusion 기능에 영향을 미치며 빌드 된 애플리케이션에서는 발생하지 않을 작은 양의 지터를 초래할 수 있습니다. 의문이 있는 경우 독립 실행형 빌드 두 개를 실행해 보십시오.
Back to top