현재 플레이어의 일정 거리 밖의 청크들은 비활성화되도록 해놓은 상태다.
청크만 비활성화 되는게 아니라, 몬스터, 상호작용 오브젝트 등등 다른 객체들도 비활성화 돼야한다.
비활성화된 청크 내부에 있는 오브젝트를 비활성화해야한다.
현재 객체의 위치값을 이용해서 간단하게 현재 어느 청크에 위치해있는지 알 수 있다.
public ChunkCoord ConvertChunkCoord(Vector3 pos)
{
ChunkCoord res = pos;
res.x /= VoxelData.ChunkSizeX;
res.z /= VoxelData.ChunkSizeZ;
return res;
}
오브젝트마다 비활성화할 요소가 다를 수 있다.
예를 들어, GameObject 자체를 꺼버린다던지, 렌더러만 끈다던지, 콜라이더만 끈다던지, 직접 작성한 Component (Behaviour)를 끈다던지.
각 객체마다 로직을 작성할 수도 있겠지만, 하나의 컴포넌트로 모듈화 할 수 있을 것 같았다.
public class ManagementedObject : MonoBehaviour
{
private ChunkCoord _coord;
private World _world;
private Transform _tranform;
private ObjectManager _manager;
public List<ObjectManagementProtocol> managedTargets = new();
private void Start()
{
_world = Managers.Game.World;
_manager = Managers.Game.ObjectManager;
_tranform = transform;
_coord = _world.ConvertChunkCoord(_tranform.position);
_world.OnWorldUpdated += SwitchEnabled;
SwitchEnabled();
}
public void SwitchEnabled()
{
_coord = _world.ConvertChunkCoord(_tranform.position);
if (_world.ChunkMap.TryGetValue(_coord, out var chunk))
{
bool enabled = chunk.IsActive;
foreach (var target in managedTargets)
_manager.ManageObject(target, enabled);
}
}
}
객체의 타입마다 활성화/비활성화 하는 법이 다르다.
GameObject는 SetActive() 메서드를 이용해 활성화/비활성화 할 수 있다.
Collider, Renderer, Behaviour 모두 Component를 상속받지만, Component에는 enabled 프로퍼티가 없고 파생 클래스에서 enabled 프로퍼티를 제공하고 있기 때문에 Collider, Renderer, Behaviour 각각 따로따로 enabled를 set하도록 해야한다.
그 결과, 각 타입을 Key로, 비활성화 여부 설정 방법을 수행하는 Action을 Value로 갖는 딕셔너리를 만들어서 사용해보기로 했다.
public class ObjectManager
{
private Dictionary<Type, Action<object, bool>> _process = new();
public ObjectManager()
{
_process.Add(typeof(Renderer[]), (x, enabled) =>
{
foreach (var e in x as Renderer[])
e.enabled = enabled;
});
_process.Add(typeof(Renderer), (x, enabled) =>
{
(x as Renderer).enabled = enabled;
});
_process.Add(typeof(GameObject[]), (x, enabled) =>
{
foreach (var e in x as GameObject[])
e.SetActive(enabled);
});
_process.Add(typeof(GameObject), (x, enabled) =>
{
(x as GameObject).SetActive(enabled);
});
_process.Add(typeof(Behaviour[]), (x, enabled) =>
{
foreach (var e in x as Behaviour[])
e.enabled = enabled;
});
_process.Add(typeof(Behaviour), (x, enabled) =>
{
(x as Behaviour).enabled = enabled;
});
_process.Add(typeof(Collider[]), (x, enabled) =>
{
foreach (var e in x as Collider[])
e.enabled = enabled;
});
_process.Add(typeof(Collider), (x, enabled) =>
{
(x as Collider).enabled = enabled;
});
}
public void ManageObject(ObjectManagementProtocol protocol, bool chunkEnabled)
{
_process[protocol.key]?.Invoke(protocol.target, chunkEnabled);
}
}
ManagementedObject
에는 어떤 객체를 관리할 것인지가 작성돼있고, ObjectManager
에는 관리될 객체의 타입 별 관리 방법이 작성돼있다.
이제 ManagementedObject
가 ObjectManager
에게서 관리 방법을 꺼내와 객체가 관리되도록 해야한다.
두 클래스가 통신할 수 있도록 Protocol 클래스를 만들어봤다.
public class ObjectManagementProtocol
{
public object target;
public Type key;
public ObjectManagementProtocol(object target, Type key)
{
this.target = target;
this.key = key;
}
}
몬스터는 비활성화 된 청크에 있다면 ...
private void Start()
{
...
SetManagementedObject();
}
public void SetManagementedObject()
{
var managedObject = gameObject.AddComponent<ManagementedObject>();
managedObject.managedTargets.Add(new(this, typeof(Behaviour)));
managedObject.managedTargets.Add(new(GetComponentsInChildren<Renderer>(), typeof(Renderer[])));
managedObject.managedTargets.Add(new(GetComponentsInChildren<Collider>(), typeof(Collider[])));
}
Monster 클래스 내부에서 자기 자신 (Monster 컴포넌트)를 관리하도록 this를 넘겨줬다.
직접 작성한 MonoBehaviour 컴포넌트는 프로토콜 클래스 생성 시, 타입(Key)를 Behaviour 타입으로 정해주면 된다.
Collider와 Renderer는 오브젝트에 달려있는 모든 Collider와 Renderer를 관리하기 위해 GetComponenetsInChildren을 이용해 넘겨줬다.
청크 경계면에서 왔다갔다 하면서 테스트해봤다.
Monster 컴포넌트, 콜라이더, 렌더러 모두 잘 활성화/비활성화된다.
이제 자원 오브젝트의 렌더러, 청크의 렌더러(현재는 청크 자체에서 직접 렌더러를 끄는 중)의 관리도 ManagementedObject
에게 맡길 수 있게 됐다.