일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- MAC
- animation
- map design
- gameplay ability system
- gameplay tag
- Replication
- gas
- 게임 개발
- Unreal Engine
- Aegis
- rpc
- listen server
- 게임개발
- unity
- 유니티
- local prediction
- stride
- photon fusion2
- UI
- attribute
- 언리얼 엔진
- nanite
- ability task
- 언리얼엔진
- gameplay effect
- Multiplay
- CTF
- 보안
- os
- network object pooling
- Today
- Total
Replicated
[Fortress Craft] ** Network Object Pooling (Photon Fusion2, Shared Mode) ** 본문
[Fortress Craft] ** Network Object Pooling (Photon Fusion2, Shared Mode) **
라구넹 2025. 2. 5. 21:35Unit과 Arrow 등 많은 오브젝트에 오브젝트 풀링이 적용되어야 함
유니티 프로파일러 써보면 이거 없으면 오브젝트 만들 때마다 부하가 확 심해지는 걸 확인 가능
일단 네트워크 오브젝트는 일반적인 오브젝트 풀링으로 안된다
그 이유는 unactive해도 동기화가 안되고 그냥 자기 꺼에서만 꺼지고, 다른 클라이언트의 화면에는 그냥 있기 때문이다
즉, SetActive 등의 행위가 RPC로 행해져야 한다
https://doc.photonengine.com/fusion/current/manual/advanced/network-object-provider
Fusion 2 NetworkObjectProvider | Photon Engine
When Spawned is called, the INetworkObjectProvider that was passed in StartGame() StartArgs handles getting/creating the GameObject that will be attac
doc.photonengine.com
이거 보고 만드려고 했는데
공식 문서가 굉장히 불친절하다
정작 중요한 내부를 비워버리니 써먹기가 힘들다
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Fusion;
namespace Agit.FortressCraft
{
public class NetworkObjectPoolManager : NetworkObjectProviderDefault
{
public static NetworkObjectPoolManager Instance { get; set; }
private Dictionary<NetworkObjectTypeId, Stack<NetworkObject>> _poolTable = new();
private void Awake()
{
Instance = this;
}
public void AddPoolTable(NetworkObjectTypeId id)
{
if (!_poolTable.ContainsKey(id))
{
_poolTable.Add(id, new Stack<NetworkObject>());
}
}
protected override NetworkObject InstantiatePrefab(NetworkRunner runner, NetworkObject prefab)
{
NetworkObject instance = null;
instance = GetFromObjetPool(runner, prefab);
if (instance == null)
{
instance = runner.Spawn(prefab, (Vector2)transform.position, Quaternion.identity);
}
return instance;
}
private NetworkObject GetFromObjetPool(NetworkRunner runner, NetworkObject prefab)
{
NetworkObject instance = null;
if (_poolTable.TryGetValue(prefab.NetworkTypeId, out var pool) == true)
{
if (pool.TryPop(out var pooledObject) == true)
{
instance = pooledObject;
//Debug.Log("Pop! - " + instance.name);
}
}
if (instance == null)
{
instance = GetNewInstance(runner, prefab);
}
return instance;
}
private NetworkObject GetNewInstance(NetworkRunner runner, NetworkObject prefab)
{
NetworkObject instance = runner.Spawn(prefab, (Vector2)transform.position, Quaternion.identity);
if (_poolTable.TryGetValue(prefab.NetworkTypeId, out var stack) == false)
{
stack = new Stack<NetworkObject>();
_poolTable.Add(prefab.NetworkTypeId, stack);
}
return instance;
}
protected override void DestroyPrefabInstance(NetworkRunner runner, NetworkPrefabId prefabId, NetworkObject instance)
{
if (_poolTable.TryGetValue(prefabId, out var stack) == true)
{
Debug.Log("DestroyPrefabInstance - Pooling");
instance.transform.SetParent(null);
//instance.gameObject.SetActive(false);
if (instance.TryGetComponent<NormalUnitRigidBodyMovement>(out NormalUnitRigidBodyMovement normal))
{
normal.RPCSetUnactive();
//normal.gameObject.SetActive(false);
}
else if (instance.TryGetComponent<Arrow>(out Arrow arrow))
{
Debug.Log("Arrow unactive");
arrow.RPCSetUnactive();
}
else if( instance.TryGetComponent<ArcherArrow>( out ArcherArrow archerArrow ) )
{
archerArrow.RPCSetUnactive(archerArrow);
}
stack.Push(instance);
}
else
{
//Debug.Log("DestroyPrefabInstance - Destroy");
Destroy(instance.gameObject);
}
}
}
}
어찌저찌 코드를 여기저기서 뒤져가며 찾아서 내부를 만들었다
내부 poolTable은 딕셔너리로, Key로 NetworkObjectTypeId를 가지고, Value는 네트워크 오브젝트의 스택이다
NetworkObjectTypeId는 각각 네트워크 오브젝트마다 가지는 PrefabId인데, 이걸 얻으려면 Spawn해야 얻을 수 있다
보면 RPCSetUnactive를 하려고 else if 연결해둔게 보이는데,
지금 생각하면 interface 만들어서 해결하는게 훨씬 코드가 간결할 거라 본다
리팩토링 필요
NetworkObject temp = Runner.Spawn(UnitPrefab, (Vector2)transform.position, Quaternion.identity);
id = temp.NetworkTypeId.AsPrefabId;
Destroy(temp.gameObject);
poolManager.AddPoolTable(id);
Spawner의 Spawned() 함수 일부인데, 여기서 임시로 유닛을 스폰하고 Id를 얻어서 풀 테이블에 추가한다
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Fusion;
using Fusion.Sockets;
using System;
using UnityEngine.UI;
namespace Agit.FortressCraft
{
public class NormalUnitFire : MonoBehaviour
{
[SerializeField] private NetworkObject arrow;
public Transform TargetTranform { get; set; }
public string TargetUnit { get; set; }
public string SecondTargetUnit { get; set; }
private NormalUnitRigidBodyMovement normalUnit;
public NetworkObjectPoolManager poolManager;
public NetworkPrefabId id;
private void Awake()
{
poolManager = NetworkObjectPoolManager.Instance;
normalUnit = GetComponent<NormalUnitRigidBodyMovement>();
}
public void Fire(NetworkRunner runner)
{
if (normalUnit.Spawner == null) return;
Debug.Log("Fire");
id = normalUnit.Spawner.arrowId;
poolManager.AddPoolTable(id);
TargetUnit = normalUnit.TargetUnit;
if (TargetUnit.CompareTo("Unit_" + normalUnit.OwnType) == 0)
{
TargetUnit = SecondTargetUnit;
}
NetworkPrefabAcquireContext context = new NetworkPrefabAcquireContext(id);
NetworkObject networkObject;
poolManager.AcquirePrefabInstance(runner, context, out networkObject);
Debug.Log(networkObject);
//networkObject.transform.position = transform.position;
Arrow arrow = networkObject.GetComponent<Arrow>();
arrow.TargetTransform = TargetTranform;
arrow.ID = id;
arrow.Normal = normalUnit;
RPCSetActive(arrow, transform.position);
arrow.RPCSetActive();
arrow.ReserveRelease();
AttackCollider attackCollider = networkObject.GetComponentInChildren<AttackCollider>();
attackCollider.TargetUnit = TargetUnit;
attackCollider.OwnType = normalUnit.OwnType;
attackCollider.Damage = normalUnit.Damage;
}
[Rpc(RpcSources.All, RpcTargets.All)]
public void RPCSetActive(Arrow arrow, Vector3 pos)
{
arrow.gameObject.GetComponent<Rigidbody2D>().transform.position = pos;
}
}
}
그걸 유닛이 받아와서 Fire()에서 이용한다
그리고 NetworkPrefabAcquireContext를 만들어준다
NetworkPrefabAcquireContext가 자료가 정말 없어서 찾기 힘들었는데, 이게 위에서 찾은 Id를 넣어주는 구조체이다
이걸 AcquirePrefabInstance에 넘기면 풀 테이블에서 오브젝트를 꺼내온다
* SetActive의 경우 풀 매니저에서 해주지 않는다. Active하기 전에 미리 설정해야 하는 것들이 존재하기 때문이다. UnActive는 풀 매니저가 해준다
ReserveRelease는 일정 시간이 지난 뒤 풀링되도록 하는 함수로 만들었다
using UnityEngine;
using Fusion;
using NetworkRigidbody2D = Fusion.Addons.Physics.NetworkRigidbody2D;
using static UnityEngine.EventSystems.PointerEventData;
namespace Agit.FortressCraft
{
public class Arrow : NetworkBehaviour
{
public Transform TargetTransform { get; set; }
private NetworkRigidbody2D _rb;
[SerializeField] private float arrowSpeed = 5.0f;
public NetworkPrefabId ID { get; set; }
public NormalUnitRigidBodyMovement Normal { get; set; }
public bool Fired { get; set; }
private TickTimer destroyTimer;
public override void Spawned()
{
_rb = GetComponent<NetworkRigidbody2D>();
}
public override void FixedUpdateNetwork()
{
if (TargetTransform == null) return;
if( destroyTimer.Expired(Runner) )
{
if( gameObject.activeSelf == true )
{
Release();
}
}
if (Mathf.Abs(TargetTransform.position.x - transform.position.x) < 0.05f &&
Mathf.Abs(TargetTransform.position.y - transform.position.y) < 0.05f)
{
_rb.Rigidbody.velocity = Vector2.zero;
return;
}
Vector3 movDir = TargetTransform.position - transform.position;
Vector3 movDirNormalized = movDir.normalized;
_rb.Rigidbody.velocity = movDirNormalized * arrowSpeed;
float angle = Mathf.Atan2(movDir.y, movDir.x) * Mathf.Rad2Deg;
Quaternion targetRotation = Quaternion.AngleAxis(angle, Vector3.forward);
transform.rotation = targetRotation;
}
public void ReserveRelease()
{
destroyTimer = TickTimer.CreateFromSeconds(Runner, 1.3f);
//Invoke("DestroySelf", 1.3f);
}
public void Release()
{
destroyTimer = TickTimer.None;
NetworkObjectReleaseContext context = new NetworkObjectReleaseContext(Object, ID, false, false);
NetworkObjectPoolManager.Instance.ReleaseInstance(Runner, context);
}
[Rpc(RpcSources.All, RpcTargets.All)]
public void RPCSetActive()
{
gameObject.SetActive(true);
}
[Rpc(RpcSources.All, RpcTargets.All)]
public void RPCSetUnactive()
{
gameObject.SetActive(false);
}
}
}
Arrow 클래스이다
public void Release()
{
destroyTimer = TickTimer.None;
NetworkObjectReleaseContext context = new NetworkObjectReleaseContext(Object, ID, false, false);
NetworkObjectPoolManager.Instance.ReleaseInstance(Runner, context);
}
여기서 Release를 보면 풀 매니저로 릴리즈하는 걸 확인 가능하다
작동 확인 가능
'유니티 엔진 > Fortress Craft' 카테고리의 다른 글
[Fortress Craft] 골드, 경험치 (0) | 2025.02.05 |
---|---|
[Fortress Craft] Castle 생성 UI, 가격 설정 (0) | 2025.02.05 |
[Fortress Craft] Target Setting / Attack Enabled (0) | 2025.02.05 |
[Fortess Craft] Path Finding (0) | 2025.02.05 |
[Fortress Craft] Damage System (0) | 2025.02.05 |