수정중인 페이지 입니다.

Fusion 102 - 씬 설정하기

개요

Fusion 102에서는 기본적인 네트워크된 씬 설정 방법에 대해서 설명합니다.

이 섹션이 끝나면, 프로젝트는 다음 사항들이 포함되어 있게 됩니다:

  • 경기 시작 또는 참여를 하기 위한 기본 Fusion Runner 그리고
  • 움직일 수 있는 캐릭터

네트워크 입력의 상세한 설명에 대한 매뉴얼을 참고하세요

메인 화면으로

Fusion 실행하기

Fusion을 실행하기 위해서, StartGame 메소드가 Fusion NetworkRunner에서 호출되어야 하기 때문에 애플리케이션이 씬에서 이 컴포넌트를 반드시 가지고 있거나 코드에서 추가해야 합니다. 대부분의 네트워킹 로직에서는 이와 상관없이 어느 정도의 코드를 작성해야 하므로 이 튜토리얼은 코드 중심적입니다.

유니티에서 기본 씬을 오픈합니다.

  1. 새로운 빈 GameObject를 생성합니다.
  2. 여기에 새로운 스크립트 컴포넌트를 추가합니다.
  3. 스크립트를 BasicSpawner로 이름을 부여합니다.
  4. 스크립트를 열고, BasicSpawner에 모든 필요한 메소들의 스텁(sutb)과 INetworkRunnerCallbacks 인터페이스를 추가합니다:
public class BasicSpawner : MonoBehaviour, INetworkRunnerCallbacks 
{
  public void OnPlayerJoined(NetworkRunner runner, PlayerRef player)    {    }
  public void OnPlayerLeft(NetworkRunner runner, PlayerRef player)    {    }
  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) { }
  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 OnReliableDataReceived(NetworkRunner runner, PlayerRef player, ArraySegment<byte> data) { }
  public void OnSceneLoadDone(NetworkRunner runner) { }
  public void OnSceneLoadStart(NetworkRunner runner) { }
}

INetworkRunnerCallbacks 구현은 Fusions NetworkRunnerBasicSpawner 클리스와 상호작용하는 것을 허용하게 됩니다. NetworkRunner는 Fusion의 중심이고 실제 네트워크 시뮬레이션을 수행합니다.

NetworkRunner는 자동적으로 BasicSpawnerINetworkRunnerCallbacks를 구현한지를 감지하고, StartGame 메소드내의 동일한 게임 객체에 추가되기 때문에 메소드들을 호출합니다.

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;

  // Start or join (depends on gamemode) a session with a specific name
  await _runner.StartGame(new StartGameArgs()
  {
    GameMode = mode, 
    SessionName = "TestRoom", 
    Scene = SceneManager.GetActiveScene().buildIndex,
    SceneObjectProvider = gameObject.AddComponent<NetworkSceneManagerDefault>()
  });}

StartGame 메소드는 먼저 Fusion NetworkRunner를 만들고 이 클라이언트가 입력을 제공할 것임을 알려줍니다. 그런 다음 하드 코딩된 이름과 지정된 게임 모드(게임 모드에 대한 자세한 내용은 잠시 후에 확인)를 사용하여 새 세션을 시작합니다. 현재 씬 인덱스가 전달되지만 클라이언트가 호스트에서 지정한 씬을 사용하도록 강제되기 때문에 이는 호스트에만 관련됩니다. 마지막으로 양호한 측정을 위해 기본 SceneObjectProvider가 지정됩니다. SceneObjectProvider는 씬에 직접 배치된 NetworkObject의 인스턴스화를 처리하며, 엄밀히 말하면 이 예에는 이러한 객체가 없으므로 필요하지 않습니다.

Fusion은 몇 가지 다른 네트워크 토폴로지를 지원하지만, 이번 소개에서는 Hosted Mode에 초점을 맞출 것입니다. 호스트 모드에서는 하나의 네트워크 피어가 서버이자 클라이언트이며 네트워크 세션을 만드는 반면 나머지 피어는 기존 세션에 참여하는 클라이언트일 뿐입니다.

이를 수용하려면 사용자가 게임을 호스팅 할 것인지 아니면 기존 세션에 참여할 것인지를 선택할 수 있는 방법이 있어야 합니다. 단순화를 위해 다음 예제는 유니티 IMGUI를 사용합니다. 다음 메소드를 BasicSpawner.cs에 추가하세요:

private NetworkRunner _runner;

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

이 애플리케이션\을 실행하면 사용자가 새 세션을 호스팅하고 추가 인스턴스가 세션에 참여할 수 있지만 상호 작용이 없고 데이터가 전송되지 않으므로 단일 플레이어 경험처럼 보입니다.

메인 화면으로

플레이어 아바타 생성

이것이 게임이 되기 위해서는 플레이어마다 입력을 제공하고 플레이어 아바타 등 세상에 존재감을 가질 수 있는 방법이 주어져야 합니다.

유니티 편집기에서,

  1. PlayerPrefab이라고 하는 빈 GameObject를 생성합니다
  2. 여기에 NetworkObject 컴포넌트를 추가합니다.

이렇게 하면 모든 피어가 참조할 수 있도록 네트워크 ID를 제공합니다. 사용자가 이 아바타를 제어할 것이므로 NetworkCharacterController도 필요합니다. 요구 사항은 아니지만 대부분의 플레이어 제어 객체에 좋은 시작점이 될 수 있습니다.

일반적으로 네트워크 개체와 시각적 표현을 분리하고 시각적 표현이 부드럽게 보간되는 동안 네트워크 개체가 네트워크 상태로 스냅 되도록 하는 것이 좋습니다.

이를 달성하기 위해서

  1. PlayerPrefab의 자식 객체로 표준 유니티 Cube를 추가합니다
  2. Body로 이름을 바꿔줍니다
  3. Collider를 제거합니다
  4. Body를 드래그하여 부모 NetworkCharacterControllerInterpolation Target 속성에 놓습니다.

마지막으로, 캐릭터 컨트롤러는 콜라이더가 필요하므로

  1. 추가적인 Cube 자식 객체를 추가합니다
  2. Collider로 이름을 바꿉니다
  3. MeshRenderer 그리고 MeshFilter 컴포넌트를 제거합니다
  4. 이것을 NetworkCharacterControllerCollider 속성으로 드래그합니다.

아바타 환경 구성은 다음과 같이 보여야 합니다:

Player Avatar Configuration
플레이어 아마타 환경 구성

프로젝트를 저장하여 Fusion이 새 네트워크 개체를 베이크하도록 한 다음 프로젝트 폴더로 끌어 아바타 프리팹을 만들고 객체를 씬에서 삭제합니다.

메인 화면으로

아바타 스폰 하기

게임이 호스트 모드로 실행되기 때문에 호스트만 새 개체를 생성할 수 있는 권한을 가집니다. 즉, 모든 플레이어 아바타는 세션에 참여할 때 호스트에 의해 생성되어야 합니다. 편리하게도 바로 이때 INetworkRunnerCallbacks 인터페이스의 OnPlayerJoined 메소드가 호출됩니다.

유사하게, 플레이어가 연결 해제할 때, OnPlayerLeft 메소드가 호출됩니다.

비어있는 OnPlayerJoinedOnPlayerLeft 스텁을 다음의 코드로 바꾸어 줍니다:

[SerializeField] private NetworkPrefabRef _playerPrefab;
private Dictionary<PlayerRef, NetworkObject> _spawnedCharacters = new Dictionary<PlayerRef, NetworkObject>();

public void OnPlayerJoined(NetworkRunner runner, PlayerRef player)
{
  // Create a unique position for the player
  Vector3 spawnPosition = new Vector3((player.RawEncoded%runner.Config.Simulation.DefaultPlayers)*3,1,0);
  NetworkObject networkPlayerObject = runner.Spawn(_playerPrefab, spawnPosition, Quaternion.identity, player);
  // Keep track of the player avatars so we can remove it when they disconnect
  _spawnedCharacters.Add(player, networkPlayerObject);
}

public void OnPlayerLeft(NetworkRunner runner, PlayerRef player)
{
  // Find and remove the players avatar
  if (_spawnedCharacters.TryGetValue(player, out NetworkObject networkObject))
  {
    runner.Despawn(networkObject);
    _spawnedCharacters.Remove(player);
  }
}

유니티의 Instantiate() 메소드를 마지막 매개 변수를 제외하고 유사한 매개 변수 집합을 사용하는 runner.Spawn()로 대체하기 때문에 이 모든 것이 익숙해 보일 것입니다. 마지막 매개 변수는 아바타에 대한 입력을 제공할 수 있는 플레이어에 대한 참조입니다. 이 매개 변수는 개체를 "소유" 하는 것과 같지 않습니다. 잠시 후 더 자세히 설명하겠습니다.

유니티 에디터로 돌아가서 생성된 프리팹 아바타를 BasicSpawner의 'Player Prefab 필드에 드래그 앤 드롭하는 것을 잊지 마십시오.

메인 화면으로

입력 수집

입력 권한이 있으면 클라이언트가 객체의 네트워크 상태를 직접 수정할 수 없습니다. 대신 호스트가 네트워크 상태를 업데이트하기 위해 해석할 입력 구조를 제공할 수 있습니다.

클라이언트는 사용자에게 즉각적인 피드백을 제공하기 위해 입력을 로컬에 적용할 수도 있지만 이는 호스트에 의해 무시될 수 있는 로컬 예측일 뿐입니다.

사용자로부터 입력을 수집하려면 먼저 입력을 보유할 데이터 구조체를 정의해야 합니다. 다음과 같은 구조로 NetworkInputData.cs이라는 새 파일을 만듭니다:

using Fusion;
using UnityEngine;

public struct NetworkInputData : INetworkInput
{
  public Vector3 direction;
}

이 예에서는 단순화를 위해 벡터를 사용하여 원하는 이동 방향을 나타내지만 대역폭 비용이 적게 드는 방법이 있다는 것을 알 수 있습니다. 예를 들어 방향당 1비트가 있는 비트 필드입니다. Fusion은 입력을 압축하고 실제로 변경되는 데이터만 전송하므로 너무 일찍 최적화하지 마십시오.

OnInput 콜백에서 Fusion에 의해 폴링 될 때 클라이언트는 사용자로부터 입력을 수집해야 합니다. 따라서 BasicSpawner.cs에서 OnInput() 스텁을 다음으로 바꾸십시오:

  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으로 전달하여 호스트와 이 클라이언트가 입력 권한을 가진 모든 객체에 사용할 수 있게 합니다.

메인 화면으로

입력 적용하기

마지막 단계는 수집한 데이터를 플레이어 아바타에게 적용하는 것입니다.

  1. PlayerPrefab를 선택합니다
  2. Player이라고 하는 새로운 스크립트 컴포넌트를 추가하고 프리팹에 붙여줍니다.
  3. 새로운 스크립트를 열고 MonoBehaviour를 NetworkBehaviour로 변경합니다
  4. FixedUpdateNetwork()를 구현하여 Fusion 시뮬레이션 루프에 참여할 수 있도록 합니다:
using Fusion;

public class Player : NetworkBehaviour
{
  public override void FixedUpdateNetwork(){}
}

FixedUpdateNetwork는 모든 시뮬레이션 틱에서 호출됩니다. Fusion이 이전에 확인된 네트워크 상태를 적용한 다음 해당 틱에서 현재(예측된) 로컬 틱까지 다시 시뮬레이션하기 때문에 렌더링 프레임당 이 문제가 여러 번 발생할 수 있습니다.

각 틱에 올바른 입력이 적용되도록 하려면 FixedUpdateNetwork에서 입력을 적용해야 합니다. Fusion은 GetInput()이라는 적절한 이름의 관련 틱에 대한 입력을 얻을 수 있는 간단한 메소드를 제공합니다. 입력을 획득하면 NetworkCharacterController가 호출되어 아바타 변환에 실제 움직임을 적용합니다.

완성된 Player 클래스는 다음과 같습니다:

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으로 스케일링합니다. 또한 새로운 머터리얼를 만들고 모든 것이 같은 색이 되지 않도록 주의하세요.

애플리케이션을 빌드하고 여러 인스턴스를 시작하거나 유니티 내에서 직접 인스턴스를 실행합니다. 클라이언트 중 한 명은 호스트 버튼을 누르고 나머지는 참여 버튼을 누르도록 하세요.

유니티 내에서 실행할 때 주의해야 할 사항: 편집기에서 게임을 실행할 때 프레임 속도가 매우 불규칙할 수 있습니다. 이는 타이밍을 올바르게 예측하는 Fusion 기능에 영향을 미치며 빌드 된 애플리케이션에서는 발생하지 않는 적은 양의 불안정성 발생할 수 있습니다. 확실하지 않은 경우 두 개의 독립 실행형 빌드를 실행해 보십시오.

기술문서 TOP으로 돌아가기