diff options
Diffstat (limited to 'Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities')
19 files changed, 906 insertions, 0 deletions
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Aspect.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Aspect.cs new file mode 100644 index 0000000..e6fb606 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Aspect.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Specialized; + +namespace MonoGame.Extended.Entities +{ + public class Aspect + { + internal Aspect() + { + AllSet = new BitVector32(); + ExclusionSet = new BitVector32(); + OneSet = new BitVector32(); + } + + public BitVector32 AllSet; + public BitVector32 ExclusionSet; + public BitVector32 OneSet; + + public static AspectBuilder All(params Type[] types) + { + return new AspectBuilder().All(types); + } + + public static AspectBuilder One(params Type[] types) + { + return new AspectBuilder().One(types); + } + + public static AspectBuilder Exclude(params Type[] types) + { + return new AspectBuilder().Exclude(types); + } + + public bool IsInterested(BitVector32 componentBits) + { + if (AllSet.Data != 0 && (componentBits.Data & AllSet.Data) != AllSet.Data) + return false; + + if (ExclusionSet.Data != 0 && (componentBits.Data & ExclusionSet.Data) != 0) + return false; + + if (OneSet.Data != 0 && (componentBits.Data & OneSet.Data) == 0) + return false; + + return true; + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/AspectBuilder.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/AspectBuilder.cs new file mode 100644 index 0000000..d749795 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/AspectBuilder.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Specialized; +using MonoGame.Extended.Collections; + +namespace MonoGame.Extended.Entities +{ + public class AspectBuilder + { + public AspectBuilder() + { + AllTypes = new Bag<Type>(); + ExclusionTypes = new Bag<Type>(); + OneTypes = new Bag<Type>(); + } + + public Bag<Type> AllTypes { get; } + public Bag<Type> ExclusionTypes { get; } + public Bag<Type> OneTypes { get; } + + public AspectBuilder All(params Type[] types) + { + foreach (var type in types) + AllTypes.Add(type); + + return this; + } + + public AspectBuilder One(params Type[] types) + { + foreach (var type in types) + OneTypes.Add(type); + + return this; + } + + public AspectBuilder Exclude(params Type[] types) + { + foreach (var type in types) + ExclusionTypes.Add(type); + + return this; + } + + public Aspect Build(ComponentManager componentManager) + { + var aspect = new Aspect(); + Associate(componentManager, AllTypes, ref aspect.AllSet); + Associate(componentManager, OneTypes, ref aspect.OneSet); + Associate(componentManager, ExclusionTypes, ref aspect.ExclusionSet); + return aspect; + } + + // ReSharper disable once ParameterTypeCanBeEnumerable.Local + private static void Associate(ComponentManager componentManager, Bag<Type> types, ref BitVector32 bits) + { + foreach (var type in types) + { + var id = componentManager.GetComponentTypeId(type); + bits[1 << id] = true; + } + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/BitArrayExtensions.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/BitArrayExtensions.cs new file mode 100644 index 0000000..2ca0737 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/BitArrayExtensions.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections; + +namespace MonoGame.Extended.Entities +{ + public static class BitArrayExtensions + { + public static bool IsEmpty(this BitArray bitArray) + { + for (var i = 0; i < bitArray.Length; i++) + { + if (bitArray[i]) + return false; + } + + return true; + } + + public static bool ContainsAll(this BitArray bitArray, BitArray other) + { + var otherBitsLength = other.Length; + var bitsLength = bitArray.Length; + + for (var i = bitsLength; i < otherBitsLength; i++) + { + if (other[i]) + return false; + } + + var s = Math.Min(bitsLength, otherBitsLength); + + for (var i = 0; s > i; i++) + { + if ((bitArray[i] & other[i]) != other[i]) + return false; + } + + return true; + } + + public static bool Intersects(this BitArray bitArray, BitArray other) + { + var s = Math.Min(bitArray.Length, other.Length); + + for (var i = 0; s > i; i++) + { + if (bitArray[i] & other[i]) + return true; + } + + return false; + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/ComponentManager.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/ComponentManager.cs new file mode 100644 index 0000000..a59c87c --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/ComponentManager.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using Microsoft.Xna.Framework; +using MonoGame.Extended.Collections; +using MonoGame.Extended.Entities.Systems; + +namespace MonoGame.Extended.Entities +{ + public interface IComponentMapperService + { + ComponentMapper<T> GetMapper<T>() where T : class; + } + + public class ComponentManager : UpdateSystem, IComponentMapperService + { + public ComponentManager() + { + _componentMappers = new Bag<ComponentMapper>(); + _componentTypes = new Dictionary<Type, int>(); + } + + private readonly Bag<ComponentMapper> _componentMappers; + private readonly Dictionary<Type, int> _componentTypes; + + public Action<int> ComponentsChanged; + + private ComponentMapper<T> CreateMapperForType<T>(int componentTypeId) + where T : class + { + // TODO: We can probably do better than this without a huge performance penalty by creating our own bit vector that grows after the first 32 bits. + if (componentTypeId >= 32) + throw new InvalidOperationException("Component type limit exceeded. We currently only allow 32 component types for performance reasons."); + + var mapper = new ComponentMapper<T>(componentTypeId, ComponentsChanged); + _componentMappers[componentTypeId] = mapper; + return mapper; + } + + public ComponentMapper GetMapper(int componentTypeId) + { + return _componentMappers[componentTypeId]; + } + + public ComponentMapper<T> GetMapper<T>() + where T : class + { + var componentTypeId = GetComponentTypeId(typeof(T)); + + if (_componentMappers[componentTypeId] != null) + return _componentMappers[componentTypeId] as ComponentMapper<T>; + + return CreateMapperForType<T>(componentTypeId); + } + + public int GetComponentTypeId(Type type) + { + if (_componentTypes.TryGetValue(type, out var id)) + return id; + + id = _componentTypes.Count; + _componentTypes.Add(type, id); + return id; + } + + public BitVector32 CreateComponentBits(int entityId) + { + var componentBits = new BitVector32(); + var mask = BitVector32.CreateMask(); + + for (var componentId = 0; componentId < _componentMappers.Count; componentId++) + { + componentBits[mask] = _componentMappers[componentId]?.Has(entityId) ?? false; + mask = BitVector32.CreateMask(mask); + } + + return componentBits; + } + + public void Destroy(int entityId) + { + foreach (var componentMapper in _componentMappers) + componentMapper?.Delete(entityId); + } + + public override void Update(GameTime gameTime) + { + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/ComponentMapper.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/ComponentMapper.cs new file mode 100644 index 0000000..35b196c --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/ComponentMapper.cs @@ -0,0 +1,69 @@ +using System; +using MonoGame.Extended.Collections; + +namespace MonoGame.Extended.Entities +{ + public abstract class ComponentMapper + { + protected ComponentMapper(int id, Type componentType) + { + Id = id; + ComponentType = componentType; + } + + public int Id { get; } + public Type ComponentType { get; } + public abstract bool Has(int entityId); + public abstract void Delete(int entityId); + } + + public class ComponentMapper<T> : ComponentMapper + where T : class + { + public event Action<int> OnPut; + public event Action<int> OnDelete; + + private readonly Action<int> _onCompositionChanged; + + public ComponentMapper(int id, Action<int> onCompositionChanged) + : base(id, typeof(T)) + { + _onCompositionChanged = onCompositionChanged; + Components = new Bag<T>(); + } + + public Bag<T> Components { get; } + + public void Put(int entityId, T component) + { + Components[entityId] = component; + _onCompositionChanged(entityId); + OnPut?.Invoke(entityId); + } + + public T Get(Entity entity) + { + return Get(entity.Id); + } + + public T Get(int entityId) + { + return Components[entityId]; + } + + public override bool Has(int entityId) + { + if (entityId >= Components.Count) + return false; + + return Components[entityId] != null; + } + + public override void Delete(int entityId) + { + Components[entityId] = null; + _onCompositionChanged(entityId); + OnDelete?.Invoke(entityId); + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/ComponentType.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/ComponentType.cs new file mode 100644 index 0000000..ebef996 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/ComponentType.cs @@ -0,0 +1,46 @@ +using System; + +namespace MonoGame.Extended.Entities +{ + //public class ComponentType : IEquatable<ComponentType> + //{ + // public ComponentType(Type type, int id) + // { + // Type = type; + // Id = id; + // } + + // public Type Type { get; } + // public int Id { get; } + + // public bool Equals(ComponentType other) + // { + // if (ReferenceEquals(null, other)) return false; + // if (ReferenceEquals(this, other)) return true; + // return Id == other.Id; + // } + + // public override bool Equals(object obj) + // { + // if (ReferenceEquals(null, obj)) return false; + // if (ReferenceEquals(this, obj)) return true; + // if (obj.GetType() != GetType()) return false; + // return Equals((ComponentType) obj); + // } + + // public override int GetHashCode() + // { + // return Id; + // } + + // public static bool operator ==(ComponentType left, ComponentType right) + // { + // return Equals(left, right); + // } + + // public static bool operator !=(ComponentType left, ComponentType right) + // { + // return !Equals(left, right); + // } + //} +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Entity.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Entity.cs new file mode 100644 index 0000000..a56ccff --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Entity.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Specialized; + +namespace MonoGame.Extended.Entities +{ + public class Entity : IEquatable<Entity> + { + private readonly EntityManager _entityManager; + private readonly ComponentManager _componentManager; + + internal Entity(int id, EntityManager entityManager, ComponentManager componentManager) + { + Id = id; + + _entityManager = entityManager; + _componentManager = componentManager; + } + + public int Id { get; } + + public BitVector32 ComponentBits => _entityManager.GetComponentBits(Id); + + public void Attach<T>(T component) + where T : class + { + var mapper = _componentManager.GetMapper<T>(); + mapper.Put(Id, component); + } + + public void Detach<T>() + where T : class + { + var mapper = _componentManager.GetMapper<T>(); + mapper.Delete(Id); + } + + public T Get<T>() + where T : class + { + var mapper = _componentManager.GetMapper<T>(); + return mapper.Get(Id); + } + + + public bool Has<T>() + where T : class + { + return _componentManager.GetMapper<T>().Has(Id); + } + + public void Destroy() + { + _entityManager.Destroy(Id); + } + + public bool Equals(Entity other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Id == other.Id; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((Entity) obj); + } + + public override int GetHashCode() + { + // ReSharper disable once NonReadonlyMemberInGetHashCode + return Id; + } + + public static bool operator ==(Entity left, Entity right) + { + return Equals(left, right); + } + + public static bool operator !=(Entity left, Entity right) + { + return !Equals(left, right); + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/EntityManager.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/EntityManager.cs new file mode 100644 index 0000000..5e1d28a --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/EntityManager.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Diagnostics; +using System.Linq; +using Microsoft.Xna.Framework; +using MonoGame.Extended.Collections; +using MonoGame.Extended.Entities.Systems; + +namespace MonoGame.Extended.Entities +{ + public class EntityManager : UpdateSystem + { + private const int _defaultBagSize = 128; + + public EntityManager(ComponentManager componentManager) + { + _componentManager = componentManager; + _addedEntities = new Bag<int>(_defaultBagSize); + _removedEntities = new Bag<int>(_defaultBagSize); + _changedEntities = new Bag<int>(_defaultBagSize); + _entityToComponentBits = new Bag<BitVector32>(_defaultBagSize); + _componentManager.ComponentsChanged += OnComponentsChanged; + + _entityBag = new Bag<Entity>(_defaultBagSize); + _entityPool = new Pool<Entity>(() => new Entity(_nextId++, this, _componentManager), _defaultBagSize); + } + + private readonly ComponentManager _componentManager; + private int _nextId; + + public int Capacity => _entityBag.Capacity; + public IEnumerable<int> Entities => _entityBag.Where(e => e != null).Select(e => e.Id); + public int ActiveCount { get; private set; } + + private readonly Bag<Entity> _entityBag; + private readonly Pool<Entity> _entityPool; + private readonly Bag<int> _addedEntities; + private readonly Bag<int> _removedEntities; + private readonly Bag<int> _changedEntities; + private readonly Bag<BitVector32> _entityToComponentBits; + + public event Action<int> EntityAdded; + public event Action<int> EntityRemoved; + public event Action<int> EntityChanged; + + public Entity Create() + { + var entity = _entityPool.Obtain(); + var id = entity.Id; + Debug.Assert(_entityBag[id] == null); + _entityBag[id] = entity; + _addedEntities.Add(id); + _entityToComponentBits[id] = new BitVector32(0); + return entity; + } + + public void Destroy(int entityId) + { + if (!_removedEntities.Contains(entityId)) + _removedEntities.Add(entityId); + } + + public void Destroy(Entity entity) + { + Destroy(entity.Id); + } + + public Entity Get(int entityId) + { + return _entityBag[entityId]; + } + + public BitVector32 GetComponentBits(int entityId) + { + return _entityToComponentBits[entityId]; + } + + private void OnComponentsChanged(int entityId) + { + _changedEntities.Add(entityId); + _entityToComponentBits[entityId] = _componentManager.CreateComponentBits(entityId); + EntityChanged?.Invoke(entityId); + } + + public override void Update(GameTime gameTime) + { + foreach (var entityId in _addedEntities) + { + _entityToComponentBits[entityId] = _componentManager.CreateComponentBits(entityId); + ActiveCount++; + EntityAdded?.Invoke(entityId); + } + + foreach (var entityId in _changedEntities) + { + _entityToComponentBits[entityId] = _componentManager.CreateComponentBits(entityId); + EntityChanged?.Invoke(entityId); + } + + foreach (var entityId in _removedEntities) + { + // we must notify subscribers before removing it from the pool + // otherwise an entity system could still be using the entity when the same id is obtained. + EntityRemoved?.Invoke(entityId); + + var entity = _entityBag[entityId]; + _entityBag[entityId] = null; + _componentManager.Destroy(entityId); + _entityToComponentBits[entityId] = default(BitVector32); + ActiveCount--; + _entityPool.Free(entity); + } + + _addedEntities.Clear(); + _removedEntities.Clear(); + _changedEntities.Clear(); + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/EntitySubscription.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/EntitySubscription.cs new file mode 100644 index 0000000..6766b0f --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/EntitySubscription.cs @@ -0,0 +1,61 @@ +using System; +using MonoGame.Extended.Collections; + +namespace MonoGame.Extended.Entities +{ + internal class EntitySubscription : IDisposable + { + private readonly Bag<int> _activeEntities; + private readonly EntityManager _entityManager; + private readonly Aspect _aspect; + private bool _rebuildActives; + + internal EntitySubscription(EntityManager entityManager, Aspect aspect) + { + _entityManager = entityManager; + _aspect = aspect; + _activeEntities = new Bag<int>(entityManager.Capacity); + _rebuildActives = true; + + _entityManager.EntityAdded += OnEntityAdded; + _entityManager.EntityRemoved += OnEntityRemoved; + _entityManager.EntityChanged += OnEntityChanged; + } + + private void OnEntityAdded(int entityId) + { + if (_aspect.IsInterested(_entityManager.GetComponentBits(entityId))) + _activeEntities.Add(entityId); + } + + private void OnEntityRemoved(int entityId) => _rebuildActives = true; + private void OnEntityChanged(int entityId) => _rebuildActives = true; + + public void Dispose() + { + _entityManager.EntityAdded -= OnEntityAdded; + _entityManager.EntityRemoved -= OnEntityRemoved; + } + + public Bag<int> ActiveEntities + { + get + { + if (_rebuildActives) + RebuildActives(); + + return _activeEntities; + } + } + + private void RebuildActives() + { + _activeEntities.Clear(); + + foreach (var entity in _entityManager.Entities) + OnEntityAdded(entity); + + _rebuildActives = false; + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/MonoGame.Extended.Entities.csproj b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/MonoGame.Extended.Entities.csproj new file mode 100644 index 0000000..21ca693 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/MonoGame.Extended.Entities.csproj @@ -0,0 +1,12 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <Description>An Entity Component System to make MonoGame more awesome.</Description> + <PackageTags>monogame ecs entity component system</PackageTags> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="..\MonoGame.Extended\MonoGame.Extended.csproj" /> + </ItemGroup> + +</Project> diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/DrawSystem.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/DrawSystem.cs new file mode 100644 index 0000000..b9e63eb --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/DrawSystem.cs @@ -0,0 +1,16 @@ +using Microsoft.Xna.Framework; + +namespace MonoGame.Extended.Entities.Systems +{ + public interface IDrawSystem : ISystem + { + void Draw(GameTime gameTime); + } + + public abstract class DrawSystem : IDrawSystem + { + public virtual void Dispose() { } + public virtual void Initialize(World world) { } + public abstract void Draw(GameTime gameTime); + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/EntityDrawSystem.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/EntityDrawSystem.cs new file mode 100644 index 0000000..5a0d6b9 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/EntityDrawSystem.cs @@ -0,0 +1,14 @@ +using Microsoft.Xna.Framework; + +namespace MonoGame.Extended.Entities.Systems +{ + public abstract class EntityDrawSystem : EntitySystem, IDrawSystem + { + protected EntityDrawSystem(AspectBuilder aspect) + : base(aspect) + { + } + + public abstract void Draw(GameTime gameTime); + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/EntityProcessingSystem.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/EntityProcessingSystem.cs new file mode 100644 index 0000000..c4d9339 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/EntityProcessingSystem.cs @@ -0,0 +1,26 @@ +using Microsoft.Xna.Framework; + +namespace MonoGame.Extended.Entities.Systems +{ + public abstract class EntityProcessingSystem : EntityUpdateSystem + { + protected EntityProcessingSystem(AspectBuilder aspectBuilder) + : base(aspectBuilder) + { + } + + public override void Update(GameTime gameTime) + { + Begin(); + + foreach (var entityId in ActiveEntities) + Process(gameTime, entityId); + + End(); + } + + public virtual void Begin() { } + public abstract void Process(GameTime gameTime, int entityId); + public virtual void End() { } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/EntitySystem.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/EntitySystem.cs new file mode 100644 index 0000000..2c95107 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/EntitySystem.cs @@ -0,0 +1,51 @@ +using MonoGame.Extended.Collections; + +namespace MonoGame.Extended.Entities.Systems +{ + public abstract class EntitySystem : ISystem + { + protected EntitySystem(AspectBuilder aspectBuilder) + { + _aspectBuilder = aspectBuilder; + } + + public void Dispose() + { + if (_world != null) + { + _world.EntityManager.EntityAdded -= OnEntityAdded; + _world.EntityManager.EntityRemoved -= OnEntityRemoved; + } + } + + private readonly AspectBuilder _aspectBuilder; + private EntitySubscription _subscription; + + private World _world; + + protected virtual void OnEntityChanged(int entityId) { } + protected virtual void OnEntityAdded(int entityId) { } + protected virtual void OnEntityRemoved(int entityId) { } + + public Bag<int> ActiveEntities => _subscription.ActiveEntities; + + public virtual void Initialize(World world) + { + _world = world; + + var aspect = _aspectBuilder.Build(_world.ComponentManager); + _subscription = new EntitySubscription(_world.EntityManager, aspect); + _world.EntityManager.EntityAdded += OnEntityAdded; + _world.EntityManager.EntityRemoved += OnEntityRemoved; + _world.EntityManager.EntityChanged += OnEntityChanged; + + Initialize(world.ComponentManager); + } + + public abstract void Initialize(IComponentMapperService mapperService); + + protected void DestroyEntity(int entityId) => _world.DestroyEntity(entityId); + protected Entity CreateEntity() => _world.CreateEntity(); + protected Entity GetEntity(int entityId) => _world.GetEntity(entityId); + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/EntityUpdateSystem.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/EntityUpdateSystem.cs new file mode 100644 index 0000000..97a61d5 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/EntityUpdateSystem.cs @@ -0,0 +1,14 @@ +using Microsoft.Xna.Framework; + +namespace MonoGame.Extended.Entities.Systems +{ + public abstract class EntityUpdateSystem : EntitySystem, IUpdateSystem + { + protected EntityUpdateSystem(AspectBuilder aspectBuilder) + : base(aspectBuilder) + { + } + + public abstract void Update(GameTime gameTime); + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/ISystem.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/ISystem.cs new file mode 100644 index 0000000..d4511fc --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/ISystem.cs @@ -0,0 +1,9 @@ +using System; + +namespace MonoGame.Extended.Entities.Systems +{ + public interface ISystem : IDisposable + { + void Initialize(World world); + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/UpdateSystem.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/UpdateSystem.cs new file mode 100644 index 0000000..e85c964 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/Systems/UpdateSystem.cs @@ -0,0 +1,16 @@ +using Microsoft.Xna.Framework; + +namespace MonoGame.Extended.Entities.Systems +{ + public interface IUpdateSystem : ISystem + { + void Update(GameTime gameTime); + } + + public abstract class UpdateSystem : IUpdateSystem + { + public virtual void Dispose() { } + public virtual void Initialize(World world) { } + public abstract void Update(GameTime gameTime); + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/World.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/World.cs new file mode 100644 index 0000000..022d4b8 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/World.cs @@ -0,0 +1,84 @@ +using Microsoft.Xna.Framework; +using MonoGame.Extended.Collections; +using MonoGame.Extended.Entities.Systems; + +namespace MonoGame.Extended.Entities +{ + public class World : SimpleDrawableGameComponent + { + private readonly Bag<IUpdateSystem> _updateSystems; + private readonly Bag<IDrawSystem> _drawSystems; + + internal World() + { + _updateSystems = new Bag<IUpdateSystem>(); + _drawSystems = new Bag<IDrawSystem>(); + + RegisterSystem(ComponentManager = new ComponentManager()); + RegisterSystem(EntityManager = new EntityManager(ComponentManager)); + } + + public override void Dispose() + { + foreach (var updateSystem in _updateSystems) + updateSystem.Dispose(); + + foreach (var drawSystem in _drawSystems) + drawSystem.Dispose(); + + _updateSystems.Clear(); + _drawSystems.Clear(); + + base.Dispose(); + } + + internal EntityManager EntityManager { get; } + internal ComponentManager ComponentManager { get; } + + public int EntityCount => EntityManager.ActiveCount; + + internal void RegisterSystem(ISystem system) + { + // ReSharper disable once ConvertIfStatementToSwitchStatement + if (system is IUpdateSystem updateSystem) + _updateSystems.Add(updateSystem); + + if (system is IDrawSystem drawSystem) + _drawSystems.Add(drawSystem); + + system.Initialize(this); + } + + public Entity GetEntity(int entityId) + { + return EntityManager.Get(entityId); + } + + public Entity CreateEntity() + { + return EntityManager.Create(); + } + + public void DestroyEntity(int entityId) + { + EntityManager.Destroy(entityId); + } + + public void DestroyEntity(Entity entity) + { + EntityManager.Destroy(entity); + } + + public override void Update(GameTime gameTime) + { + foreach (var system in _updateSystems) + system.Update(gameTime); + } + + public override void Draw(GameTime gameTime) + { + foreach (var system in _drawSystems) + system.Draw(gameTime); + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/WorldBuilder.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/WorldBuilder.cs new file mode 100644 index 0000000..7f03323 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Entities/WorldBuilder.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using MonoGame.Extended.Entities.Systems; + +namespace MonoGame.Extended.Entities +{ + public class WorldBuilder + { + private readonly List<ISystem> _systems = new List<ISystem>(); + + public WorldBuilder AddSystem(ISystem system) + { + _systems.Add(system); + return this; + } + + public World Build() + { + var world = new World(); + + foreach (var system in _systems) + world.RegisterSystem(system); + + return world; + } + } +}
\ No newline at end of file |