Создать эффективный пул компонентов для системных циклов
Я устанавливаю очень маленький пример Entity-Component-System и имею некоторые проблемы с пулами компонентов.
В настоящее время мои сущности - это просто идентификаторы (GUID), и это нормально.
Каждая система должна реализовывать ISystem
интерфейс
internal interface ISystem
{
void Run();
}
и хранится в KeyedByTypeCollection
, Эта коллекция гарантирует, что каждая система уникальна.
Каждый компонент должен реализовать IComponent
интерфейс.
internal interface IComponent
{
}
Таким образом я могу сохранить все различные типы компонентов в их пуле соответствующих компонентов. Каждый бассейн Dictionary<Guid, IComponent>
, Ключ представляет собой идентификатор объекта, а значение является компонентом этого объекта. Каждый пул хранится в KeyedByTypeCollection
чтобы убедиться, что пул компонентов уникален.
В настоящее время мой EntityManager
класс содержит ядро логики. Я не знаю, требуется ли, чтобы он был статичным, но в настоящее время мои системы должны получать от него некоторую информацию.
Важными основными методами работы с пулами компонентов являются:
internal static class EntityManager
{
public static List<Guid> activeEntities = new List<Guid>();
private static KeyedByTypeCollection<Dictionary<Guid, IComponent>> componentPools = new KeyedByTypeCollection<Dictionary<Guid, IComponent>>();
public static KeyedByTypeCollection<ISystem> systems = new KeyedByTypeCollection<ISystem>(); // Hold unique Systems
public static Guid CreateEntity() // Add a new GUID and return it
{
Guid entityId = Guid.NewGuid();
activeEntities.Add(entityId);
return entityId;
}
public static void DestroyEntity(Guid entityId) // Remove the entity from every component pool
{
activeEntities.Remove(entityId);
for (int i = 0; i < componentPools.Count; i++)
{
componentPools[i].Remove(entityId);
}
}
public static Dictionary<Guid, TComponent> GetComponentPool<TComponent>() where TComponent : IComponent // get a component pool by its component type
{
return componentPools[typeof(TComponent)];
}
public static void AddComponentPool<TComponent>() where TComponent : IComponent // add a new pool by its component type
{
componentPools.Add(new Dictionary<Guid, TComponent>());
}
public static void RemoveComponentPool<TComponent>() where TComponent : IComponent // remove a pool by its component type
{
componentPools.Remove(typeof(TComponent));
}
public static void AddComponentToEntity(Guid entityId, IComponent component) // add a component to an entity by the component type
{
componentPools[component.GetType()].Add(entityId, component);
}
public static void RemoveComponentFromEntity<TComponent>(Guid entityId) where TComponent : IComponent // remove a component from an entity by the component type
{
componentPools[typeof(TComponent)].Remove(entityId);
}
}
Я создал небольшую систему движений для целей тестирования:
internal class Movement : ISystem
{
public void Run()
{
for (int i = 0; i < EntityManager.activeEntities.Count; i++)
{
Guid entityId = EntityManager.activeEntities[i];
if (EntityManager.GetComponentPool<Position>().TryGetValue(entityId, out Position positionComponent)) // Get the position component
{
positionComponent.X++; // Move one to the right and one up ...
positionComponent.Y++;
}
}
}
}
Что должно быть хорошо из-за поиска в словаре. Я не уверен, что смогу оптимизировать его, но думаю, что сначала мне нужно пройтись по всем сущностям.
И вот проблема:
Я не умею пользоваться KeyedByTypeCollection<Dictionary<Guid, IComponent>>
потому что мне нужно что-то вроде
KeyedByTypeCollection<Dictionary<Guid, Type where Type : IComponent>>
мой GetComponentPool
а также AddComponentPool
методы выдают ошибки.
GetComponentPool: невозможно неявное преобразование IComponent в TComponent
При звонке GetComponentPool
Я должен был бы привести значения словаря из IComponent
в TComponent
,
AddComponentPool: невозможно преобразовать TComponent в IComponent
При звонке AddComponentPool
Я должен был бы бросить TComponent
в IComponent
,
Я не думаю, что кастинг это вариант, потому что это, кажется, снижает производительность.
Может ли кто-нибудь помочь мне решить проблему типа?
Обновить:
При отладке я использую этот код для тестирования всего ECS
internal class Program
{
private static void Main(string[] args)
{
EntityManager.AddComponentPool<Position>();
EntityManager.AddComponentPool<MovementSpeed>();
EntityManager.Systems.Add(new Movement());
Guid playerId = EntityManager.CreateEntity();
EntityManager.AddComponentToEntity(playerId, new Position());
EntityManager.AddComponentToEntity(playerId, new MovementSpeed(1));
foreach (ISystem system in EntityManager.Systems)
{
system.Run();
}
}
}
Я уже показал Movement
Система, вот недостающие компоненты
internal class Position : IComponent
{
public Position(float x = 0, float y = 0)
{
X = x;
Y = y;
}
public float X { get; set; }
public float Y { get; set; }
}
internal class MovementSpeed : IComponent
{
public MovementSpeed(float value = 0)
{
Value = value;
}
public float Value { get; set; }
}
1 ответ
Суть проблемы в том, что Dictionary<Guid, TComponent>
а также Dictionary<Guid, IComponent>
совершенно не связанные типы. Они не конвертируемы друг в друга. Это потому, что словарь можно читать и писать. Если бы вы могли преобразовать List<Giraffe>
к List<Animal>
Вы могли бы затем сохранить Tiger
что нарушит безопасность типа. Если бы вы могли конвертировать наоборот, вы могли бы прочитать Tiger
экземпляры и относиться к ним как Giraffe
что снова нарушило бы безопасность типов. Вы можете искать в Интернете для .net covariance contravariance
чтобы найти больше информации.
Простое решение состоит в том, чтобы изменить
KeyedByTypeCollection<Dictionary<Guid, IComponent>> componentPools
в
Dictionary<Type, object> componentPools
,
Это позволяет вам хранить Dictionary<Guid, TComponent>
в коллекции, которая была невозможна раньше. Недостатком является то, что вам нужно сыграть сейчас. Но вам нужно только привести экземпляр словаря, который довольно дешев. Вам не нужно конвертировать весь словарь в O(N) времени.
Аксессоры:
public static Dictionary<Guid, TComponent> GetComponentPool<TComponent>() where TComponent : IComponent // get a component pool by its component type
{
return (Dictionary<Guid, TComponent>)componentPools[typeof(TComponent)]; //cast inserted
}
public static void AddComponentPool<TComponent>() where TComponent : IComponent // add a new pool by its component type
{
componentPools.Add(typeof(TComponent), (object)new Dictionary<Guid, TComponent>()); //unneeded cast inserted for clarity
}
Может быть, вам не нравится, что нам нужно сыграть на всех. Я не вижу возможности обойти это. Практический опыт показывает, что, когда вы начинаете разрабатывать систему типов (обобщения, дисперсии), вы получаете худшее решение. Так что не беспокойся об этом. Делай то, что работает.