Fusion 객체 폴링
개요
Fusion 객체 폴링
샘플은 NetworkRunner
가 생성하는 모든 NetworkObject
에 대해 새 프리팹을 인스턴스화하지 않고도 객체를 풀링 하는 방법을 보여줍니다. 이를 위해 맞춤형 INetworkObjectProvider
구현을 사용할 수 있습니다.
맞춤형 구현을 통해 객체를 추적하고 재사용할 수 있어 객체 생성 및 제거를 더 효율적으로 처리할 수 있습니다.
Download
INetworkObjectProvider 인터페이스
이 인터페이스는 NetworkRunner
의 Spawn()
및 Despawn()
작업에 대한 핸들러를 정의합니다. StartGameArgs.ObjectProvider
인자 값으로 이 인터페이스 인스턴스를 NetworkRunner.StartGame(Fusion.StartGameArgs)
에 전달하면 해당 인스턴스가 런너의 Spawn()
및 Despawn()
작업을 처리하게 됩니다.
프리팹 인스턴스 획득
AcquirePrefabInstance
메소드는 NetworkRunner
가 NetworkObject
를 생성할 때 호출되며, NetworkPrefabAcquireContext
를 인자로 받고 결과로 NetworkObject
를 반환합니다.
기본 구현의 동작은 새로운 GameObject
를 단순히 인스턴스화
하는 것입니다.
인스턴스 해제
ReleaseInstance
메소드는 NetworkRunner
가 NetworkObject
를 제거할 때 호출되며, 이 메소드는 런너 참조와 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