This document is about: FUSION 2
SWITCH TO

Fusion 객체 폴링

Level 4

개요

개요

Fusion 객체 폴링 샘플은 NetworkRunner가 생성하는 모든 NetworkObject에 대해 새 프리팹을 인스턴스화하지 않고도 객체를 풀링 하는 방법을 보여줍니다. 이를 위해 맞춤형 INetworkObjectProvider 구현을 사용할 수 있습니다.

맞춤형 구현을 통해 객체를 추적하고 재사용할 수 있어 객체 생성 및 제거를 더 효율적으로 처리할 수 있습니다.

Download

INetworkObjectProvider 인터페이스

이 인터페이스는 NetworkRunnerSpawn()Despawn() 작업에 대한 핸들러를 정의합니다. StartGameArgs.ObjectProvider 인자 값으로 이 인터페이스 인스턴스를 NetworkRunner.StartGame(Fusion.StartGameArgs)에 전달하면 해당 인스턴스가 런너의 Spawn()Despawn() 작업을 처리하게 됩니다.

프리팹 인스턴스 획득

AcquirePrefabInstance 메소드는 NetworkRunnerNetworkObject를 생성할 때 호출되며, NetworkPrefabAcquireContext를 인자로 받고 결과로 NetworkObject를 반환합니다.

기본 구현의 동작은 새로운 GameObject를 단순히 인스턴스화하는 것입니다.

인스턴스 해제

ReleaseInstance 메소드는 NetworkRunnerNetworkObject를 제거할 때 호출되며, 이 메소드는 런너 참조와 NetworkObjectReleaseContext를 제공하여 NetworkObject, NetworkObjectTypeId, 중첩된 객체 여부 등의 컨텍스트 정보를 제공합니다.

기본 구현의 동작은 GameObject를 단순히 Destroy하는 것입니다.

풀링 구현

이 샘플은 이전에 해제된 NetworkObject를 저장하고, 새로운 객체가 생성될 때 이미 인스턴스화된 프리팹을 재사용하는 맞춤형 구현을 사용합니다.

샘플 테스트 방법

FusionObjectPooling 씬을 열고 실행한 후 Start 버튼을 누르면 AutoHostOrClient 모드를 사용하는 새 세션이 시작됩니다.

연결된 후 호스트는 게임 내 버튼을 사용하여 두 가지 유형의 객체(큐브 및 구체)를 생성할 수 있습니다. Despawn All을 눌러 객체들이 파괴되는 대신 비활성화되는 것을 확인하세요. 이러한 객체들은 동일한 유형의 새로운 프리팹이 생성될 때 다시 사용할 준비가 된 것입니다.

Make Pooled Object Transparent 버튼을 누르면 풀링 된 객체가 게임 화면에서 투명하게 유지되고 물리적 충돌이 비활성화됩니다. 이는 단지 풀링 된 객체가 해제되었음을 쉽게 확인할 수 있도록 하기 위한 기능입니다.

또한 Max Pool Count 입력 필드를 변경하여 유형별로 풀링 된 객체의 최대 수를 설정할 수 있습니다. 0을 입력하면 풀링 된 객체 수에 제한이 없고, 양의 숫자를 입력하면 유형별로 저장할 수 있는 객체 수를 제한할 수 있습니다. 예를 들어, 10으로 설정하면 동일한 유형의 객체가 10개 해제된 후에 추가로 해제되는 NetworkObject는 파괴됩니다.

전체 코드 스니펫

다음은 이 샘플의 네트워크 객체 제공자에서 사용된 전체 코드 스니펫입니다. 다만, 몇 가지 특정 논리는 제거하여 보다 일반적인 용도로 사용할 수 있도록 만들었습니다.

C#

public class PoolObjectProvider : Fusion.Behaviour, INetworkObjectProvider
{
    /// <summary>
    /// If enabled, the provider will delay acquiring a prefab instance if the scene manager is busy.
    /// </summary>
    [InlineHelp]
    public bool DelayIfSceneManagerIsBusy = true;
    
    private Dictionary<NetworkPrefabId, Queue<NetworkObject>> _free = new Dictionary<NetworkPrefabId, Queue<NetworkObject>>();
    
    /// <summary>
    /// How many objects are going to be kept on the pools, 0 or negative means to pool all released objects.
    /// </summary>
    private int _maxPoolCount = 0;
    
    /// The base <see cref="NetworkObjectProviderDefault"/> by default simply instantiate a new game object.
    /// Let's create a method to use a custom logic that will pool objects.
    protected NetworkObject InstantiatePrefab(NetworkRunner runner, NetworkObject prefab,
        NetworkPrefabId contextPrefabId)
    {
        var result = default(NetworkObject);
        
        // Found free queue for prefab AND the queue is not empty. Return free object.
        if (_free.TryGetValue(contextPrefabId, out var freeQ))
        {
            if (freeQ.Count > 0)
            {
                result = freeQ.Dequeue();
                result.gameObject.SetActive(true);
                return result;
            }
        }
        else
        {
            _free.Add(contextPrefabId, new Queue<NetworkObject>());
        }
        
        // -- At this point a free queue was not yet created or was empty. Create new object.
        result = Instantiate(prefab);
        
        return result;
    }

    protected void DestroyPrefabInstance(NetworkRunner runner, NetworkPrefabId prefabId, NetworkObject instance)
    {
        if (_free.TryGetValue(prefabId, out var freeQ) == false)
        {
            // No free queue for this prefab. Should be destroyed.
            Destroy(instance.gameObject);
            return;
        } 
        else if (_maxPoolCount > 0 && freeQ.Count >= _maxPoolCount)
        {
            // The pool already have the max amount of object we defined. Should be destroyed.
            Destroy(instance.gameObject);
            return;
        }
        
        // Free queue found. Should cache.
        freeQ.Enqueue(instance);

        // Make objects inactive.
        instance.gameObject.SetActive(false);
    }

    public NetworkObjectAcquireResult AcquirePrefabInstance(NetworkRunner runner, in NetworkPrefabAcquireContext context,
        out NetworkObject instance)
    {
        
        instance = null;

        if (DelayIfSceneManagerIsBusy && runner.SceneManager.IsBusy) {
            return NetworkObjectAcquireResult.Retry;
        }

        NetworkObject prefab;
        try {
            prefab = runner.Prefabs.Load(context.PrefabId, isSynchronous: context.IsSynchronous);
        } catch (Exception ex) {
            Log.Error($"Failed to load prefab: {ex}");
            return NetworkObjectAcquireResult.Failed;
        }

        if (!prefab) {
            // this is ok, as long as Fusion does not require the prefab to be loaded immediately;
            // if an instance for this prefab is still needed, this method will be called again next update
            return NetworkObjectAcquireResult.Retry;
        }

        instance = InstantiatePrefab(runner, prefab, context.PrefabId);
        Assert.Check(instance);

        if (context.DontDestroyOnLoad) {
            runner.MakeDontDestroyOnLoad(instance.gameObject);
        } else {
            runner.MoveToRunnerScene(instance.gameObject);
        }

        runner.Prefabs.AddInstance(context.PrefabId);
        return NetworkObjectAcquireResult.Success;
    }

    public void ReleaseInstance(NetworkRunner runner, in NetworkObjectReleaseContext context)
    {
        var instance = context.Object;

        // Only pool prefabs.
        if (!context.IsBeingDestroyed) {
            if (context.TypeId.IsPrefab) {
                DestroyPrefabInstance(runner, context.TypeId.AsPrefabId, instance);
            }
            else
            {
                Destroy(instance.gameObject);
            }
        }

        if (context.TypeId.IsPrefab) {
            runner.Prefabs.RemoveInstance(context.TypeId.AsPrefabId);
        }
    }

    public void SetMaxPoolCount(int count)
    {
        _maxPoolCount = count;
    }
}
Back to top