This document is about: FUSION 2
SWITCH TO

고급 스폰

개요

Fusion에서의 스폰은 Runner.Spawn(), Runner.SpawnAsync(), 또는 Runner.TrySpawn()을 호출하는 것으로 이루어집니다. 이 호출은 다른 모든 피어에 복제되는 네트워크 엔티티를 생성합니다.

  • 서버/클라이언트 모드(서버/호스트/싱글)에서는 서버만 오브젝트를 스폰 할 수 있으며, 해당 스폰은 모든 클라이언트에 복제됩니다.
  • 공유 모드에서는 모든 클라이언트가 Spawn()을 호출할 수 있지만, 이 호출은 공유 서버에 메시지를 전송하여 실제 스폰을 시작하게 합니다.

Spawn()이 호출되거나 복제되면, 응답으로 네트워크 오브젝트(즉, NetworkObject 컴포넌트가 있는 유니티의 GameObject)가 생성됩니다. 해당 게임 오브젝트가 어떻게 생성되는지는 NetworkRunner에 연결된 Object Provider에 의해 결정됩니다.

오브젝트 제공자

오브젝트 제공자는 Runner.Spawn()Runner.Despawn() 작업이 유니티 GameObject를 생성하고 소멸하는 방식을 정의하는 INetworkObjectProvider 구현체입니다. 모든 활성 NetworkRunner는 관련된 INetworkObjectProvider 인스턴스를 가지고 있으며, 기본적으로는 NetworkObjectProviderDefault 인스턴스가 사용됩니다.

사용자 정의 오브젝트 제공자 사용 사례

사용자 맞춤형 INetworkObjectProvider 클래스를 생성하면 스폰 되는 실제 GameObject의 생명주기를 정밀하게 제어할 수 있으며, 다음과 같은 경우에 유용할 수 있습니다;

  • 풀링(Pooling)
  • 런타임 중에 네트워크 오브젝트를 동적으로 생성
  • 싱글 플레이어 모드로의 전환 및 그 반대의 고급 처리

오브젝트 제공자 할당

네트워크 러너에 오브젝트 제공자를 설정하는 방법에는 여러 가지가 있습니다.

  • Runner.GameStart()를 호출할 때 INetworkObjectProvider 인스턴스를 전달합니다.

C#

runner.StartGame(new StartGameArgs(){ ObjectProvider = new MyObjectProvider()});
  • 또는, Network Runner 게임 오브젝트에 INetworkObjectProvider를 구현하는 컴포넌트를 추가합니다.
  • 또는, 둘 다 하지 않으면 러너는 자동으로 자신의 게임 오브젝트에 NetworkObjectProviderDefault를 추가합니다.

INetworkObjectProvider 구현

INetworkObjectProvider 인터페이스는 다음 두 가지 메서드의 구현을 요구합니다:

  • AcquireInstance(): NetworkRunner.Spawn()이 호출되었을 때 풀에서 객체를 획득하는 데 사용됩니다.
  • ReleaseInstance(): NetworkRunner.Destroy()가 호출되었을 때 풀에서 객체를 해제하고 반환하는 데 사용됩니다.

NetworkObjectProviderDefault

NetworkObjectProviderDefaultINetworkObjectProvider의 기본 대체 구현체입니다. Runner.StartGame()에 전달된 StartArgs에서 제공자를 지정하지 않으면, 러너는 자신의 게임 오브젝트에서 INetworkObjectProvider를 구현하는 컴포넌트를 검색합니다. 만약 구현체가 없다면, 러너는 자동으로 자신의 게임 오브젝트에 NetworkObjectProviderDefault 컴포넌트를 추가하여 이를 오브젝트 제공자로 사용합니다.

NetworkObjectProviderDefault는 매우 기본적인 구현체로, 풀링 없이 전달된 프리팹의 복사본을 인스턴스화하여 Spawn()에 응답합니다. 반면, Despawn() 호출은 Destroy()를 사용하여 처리됩니다.

사용자 정의 풀링

객체 풀링은 메모리 단편화를 최소화하고 CPU 및 가비지 컬렉터에 가해지는 부담을 줄이기 위해 사용되는 일반적인 패턴입니다.

이와 같은 이유로, 필수는 아니지만 INetworkObjectProvider 구현에서 NetworkObject의 풀링을 구현하는 것이 권장됩니다.

게임 세션에 사용될 객체 풀은 INetworkObjectPool을 구현해야 합니다. 또한, 게임이 시작되기 전에 이를 알고 있어야 하며, NetworkRunner.StartGame()에 전달되는 StartGameArgs.ObjectPool 매개변수에 할당되어야 합니다. 만약 객체 풀이 지정되지 않으면, NetworkObjectPoolDefault가 사용됩니다.

런타임에서 사용자 정의 객체 제공

프리팹을 사용하여 스폰 하는 대신 런타임에서 게임 오브젝트를 동적으로 생성하려면, 사용자 정의 INetworkObjectProvider 클래스를 생성하고 AcquirePrefabInstance() 메서드를 오버라이드 하여 다음을 수행해야 합니다:

  1. Instantiate(), new GameObject(), 또는 GameObject.CreatePrimitive()를 사용하여 게임 오브젝트를 생성합니다.
  2. 게임 오브젝트에 아직 NetworkObject 컴포넌트가 없다면 추가합니다.
  3. 원하는 자식 GameObjectNetworkBehaviour를 추가합니다.
  4. 네트워크 오브젝트 구조에 대한 모든 변경 후 마지막으로 NetworkObjectBaker.Bake()를 호출하여 네트워크 오브젝트를 베이크 합니다.
  5. 결과로 NetworkObject 인스턴스를 반환합니다.

자체 사용자 정의 INetworkObjectProvider 구현을 만들 때 기본 클래스로 NetworkObjectProviderDefault를 사용하는 것이 가능하며, 대부분의 경우 추천됩니다.

중요: 스폰 되어 부착된 후에는 네트워크 오브젝트에 네트워크 행동(Network Behaviour)을 추가하거나 제거할 수 없습니다. 네트워크 오브젝트의 네트워크 행동에 대한 모든 커스터마이징은 스폰 되기 전에 이루어져야 합니다. 물론 네트워크와 관련 없는 컴포넌트는 언제든지 추가하거나 제거할 수 있습니다.

사용자 정의 오브젝트 제공자 예제

이 예제 코드는 미리 만들어진 프리팹을 사용하여 스폰 하는 대신, 런타임에서 사용자 정의 네트워크 오브젝트를 생성하는 방법을 보여줍니다.

C#

public class BakingObjectProvider : NetworkObjectProviderDefault
{
  // For this sample, we are using very high flag values to indicate custom.
  // Other values will fall through the default instantiation handling.
  public const int CUSTOM_PREFAB_FLAG = 100000;

  // The NetworkObjectBaker class can be reused and is Runner independent.
  private static NetworkObjectBaker _baker;
  private static NetworkObjectBaker Baker => _baker ??= new NetworkObjectBaker();

  public override NetworkObjectAcquireResult AcquirePrefabInstance(NetworkRunner runner, in NetworkPrefabAcquireContext context, out NetworkObject result)
  {
    // Detect if this is a custom spawn by its high prefabID value we are passing.
    // The Spawn call will need to pass this value instead of a prefab.
    if (context.PrefabId.RawValue == CUSTOM_PREFAB_FLAG)
    {
      var go = GameObject.CreatePrimitive(PrimitiveType.Cube);
      var no = go.AddComponent<NetworkObject>();
      go.AddComponent<NetworkTransform>();
      go.name = $"Custom Object";

      // Baking is required for the NetworkObject to be valid for spawning.
      Baker.Bake(go);

      // Move the object to the applicable Runner Scene/PhysicsScene/DontDestroyOnLoad
      // These implementations exist in the INetworkSceneManager assigned to the runner.
      if (context.DontDestroyOnLoad)
      {
        runner.MakeDontDestroyOnLoad(go);
      }
      else
      {
        runner.MoveToRunnerScene(go);
      }

      // We are finished. Return the NetworkObject and report success.
      result = no;
      return NetworkObjectAcquireResult.Success;
    }

    // For all other spawns, use the default spawning.
    return base.AcquirePrefabInstance(runner, context, out result);
  }
}
Back to top