├── config.bat ├── SharpECS.Samples ├── Icon.ico ├── Content │ ├── Sprite.png │ ├── Sprite2.png │ └── Content.mgcb ├── app.config ├── Components │ ├── GraphicsComponent.cs │ ├── ControllerComponent.cs │ └── TransformComponent.cs ├── Launcher.cs ├── Systems │ ├── GraphicsSystem.cs │ └── ControllerSystem.cs ├── Properties │ └── AssemblyInfo.cs ├── SharpECS.Samples.csproj └── Game.cs ├── SharpECS ├── Source │ ├── IComponent.cs │ ├── Extensions.cs │ ├── Exceptions │ │ ├── ECSCacheException.cs │ │ ├── NoCompatibleEntitiesException.cs │ │ ├── NullEntityPoolException.cs │ │ ├── DuplicateEntityException.cs │ │ ├── EntityNotFoundException.cs │ │ ├── ComponentNotFoundException.cs │ │ ├── IndependentEntityException.cs │ │ └── ComponentAlreadyExistsException.cs │ ├── EntitySystem.cs │ ├── EntityPool.cs │ └── Entity.cs ├── Properties │ └── AssemblyInfo.cs └── SharpECS.csproj ├── compile.bat ├── SharpECS.Tests ├── App.config ├── BetterComponent.cs ├── EvenBetterComponent.cs ├── BetterSystem.cs ├── Program.cs ├── Properties │ └── AssemblyInfo.cs └── SharpECS.Tests.csproj ├── run.bat ├── LICENSE ├── SharpECS.sln ├── .gitattributes ├── .gitignore └── README.md /config.bat: -------------------------------------------------------------------------------- 1 | SET config=Release -------------------------------------------------------------------------------- /SharpECS.Samples/Icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthony-y/sharp-ecs/HEAD/SharpECS.Samples/Icon.ico -------------------------------------------------------------------------------- /SharpECS/Source/IComponent.cs: -------------------------------------------------------------------------------- 1 | namespace SharpECS 2 | { 3 | public interface IComponent 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /SharpECS.Samples/Content/Sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthony-y/sharp-ecs/HEAD/SharpECS.Samples/Content/Sprite.png -------------------------------------------------------------------------------- /SharpECS.Samples/Content/Sprite2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthony-y/sharp-ecs/HEAD/SharpECS.Samples/Content/Sprite2.png -------------------------------------------------------------------------------- /compile.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | call config.bat 4 | 5 | devenv /build %config% SharpECS.sln 6 | 7 | cls 8 | echo Build done! -------------------------------------------------------------------------------- /SharpECS.Samples/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /SharpECS.Tests/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /SharpECS/Source/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Collections.Generic; 4 | 5 | namespace SharpECS 6 | { 7 | public static class Extensions 8 | { 9 | public static bool IsComponent(this Type classType) => typeof(IComponent).IsAssignableFrom(classType); 10 | } 11 | } -------------------------------------------------------------------------------- /run.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | cls 4 | 5 | call config.bat 6 | 7 | echo Executing! 8 | echo( 9 | echo ================================ 10 | echo( 11 | 12 | cd Builds/SharpECS.Samples/%config% 13 | SharpECS.Samples.exe 14 | 15 | echo( 16 | echo ================================ 17 | echo( 18 | cd .. 19 | echo Done! 20 | echo( -------------------------------------------------------------------------------- /SharpECS.Tests/BetterComponent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | using SharpECS; 8 | 9 | namespace SharpECS.Tests 10 | { 11 | public class BetterComponent 12 | : IComponent 13 | { 14 | public Entity Owner { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /SharpECS.Tests/EvenBetterComponent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | using SharpECS; 8 | 9 | namespace SharpECS.Tests 10 | { 11 | public class EvenBetterComponent 12 | : IComponent 13 | { 14 | public Entity Owner { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /SharpECS/Source/Exceptions/ECSCacheException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SharpECS.Exceptions 8 | { 9 | class ECSCacheException : Exception 10 | { 11 | public ECSCacheException() 12 | : base("A blank (cached) Entity was found in an EntityPool.") 13 | { 14 | 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /SharpECS.Samples/Components/GraphicsComponent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | using SharpECS; 7 | 8 | using Microsoft.Xna.Framework.Graphics; 9 | 10 | namespace SharpECS.Samples.Components 11 | { 12 | internal class GraphicsComponent 13 | : IComponent 14 | { 15 | public Entity Owner { get; set; } 16 | public Texture2D Texture { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /SharpECS/Source/Exceptions/NoCompatibleEntitiesException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SharpECS.Exceptions 8 | { 9 | class NoCompatibleEntitiesException : Exception 10 | { 11 | public NoCompatibleEntitiesException() 12 | : base("No compatible entities found for system.") 13 | { 14 | 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /SharpECS/Source/Exceptions/NullEntityPoolException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SharpECS.Exceptions 8 | { 9 | class NullEntityPoolException : Exception 10 | { 11 | public NullEntityPoolException(EntityPool entityPool) 12 | : base($"EntityPool {entityPool.Id} was null.") 13 | { 14 | 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /SharpECS/Source/Exceptions/DuplicateEntityException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SharpECS.Exceptions 8 | { 9 | class DuplicateEntityException : Exception 10 | { 11 | public DuplicateEntityException(EntityPool pool) 12 | : base($"Two entities in pool {pool.Id} shared the same tag.") 13 | { 14 | 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /SharpECS/Source/Exceptions/EntityNotFoundException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SharpECS.Exceptions 8 | { 9 | class EntityNotFoundException : Exception 10 | { 11 | public EntityNotFoundException(EntityPool pool) 12 | : base($"Entity not found in pool \"{pool.Id}\".") 13 | { 14 | 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /SharpECS/Source/Exceptions/ComponentNotFoundException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SharpECS.Exceptions 8 | { 9 | class ComponentNotFoundException : Exception 10 | { 11 | public ComponentNotFoundException(Entity occuredIn) 12 | : base($"Component not found in Entity \"{occuredIn.Id}\".") 13 | { 14 | 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /SharpECS/Source/Exceptions/IndependentEntityException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SharpECS.Exceptions 8 | { 9 | class IndependentEntityException : Exception 10 | { 11 | public IndependentEntityException(Entity entity) 12 | : base($"Entity \"{entity.Id}\" does not belong to an EntityPool.") 13 | { 14 | 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /SharpECS/Source/Exceptions/ComponentAlreadyExistsException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SharpECS.Exceptions 8 | { 9 | class ComponentAlreadyExistsException : Exception 10 | { 11 | public ComponentAlreadyExistsException(Entity entity) 12 | : base("Component already exists on entity \"" + entity.Id + "\".") 13 | { 14 | 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /SharpECS.Samples/Launcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SharpECS.Samples 4 | { 5 | #if WINDOWS || LINUX 6 | /// 7 | /// The main class. 8 | /// 9 | internal static class Launcher 10 | { 11 | /// 12 | /// The main entry point for the application. 13 | /// 14 | [STAThread] 15 | static void Main() 16 | { 17 | using (var game = new Game()) 18 | game.Run(); 19 | } 20 | } 21 | #endif 22 | } 23 | -------------------------------------------------------------------------------- /SharpECS.Samples/Components/ControllerComponent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | using SharpECS; 8 | 9 | using Microsoft.Xna.Framework.Graphics; 10 | using Microsoft.Xna.Framework; 11 | 12 | namespace SharpECS.Samples.Components 13 | { 14 | internal class ControllerComponent 15 | : IComponent 16 | { 17 | public Entity Owner { get; set; } 18 | 19 | public float MoveSpeed { get; set; } = 700; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SharpECS.Tests/BetterSystem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | using SharpECS; 8 | 9 | namespace SharpECS.Tests 10 | { 11 | public class BetterSystem 12 | : EntitySystem 13 | { 14 | public BetterSystem(EntityPool entityPool) 15 | : base(entityPool, typeof(IComponent)) 16 | { 17 | Console.WriteLine("Hi from test system."); 18 | } 19 | 20 | public void Update() 21 | { 22 | foreach (var i in Compatible) 23 | { 24 | Console.WriteLine("Compatible Entity: " + i.Id); 25 | } 26 | } 27 | 28 | public void Draw() 29 | { 30 | 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Anthony Lewis Baynham 2 | 3 | This software is provided 'as-is', without any express or implied 4 | warranty. In no event will the authors be held liable for any damages 5 | arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute it 9 | freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not 12 | claim that you wrote the original software. If you use this software 13 | in a product, an acknowledgement in the product documentation is required. 14 | 2. Altered source versions must be plainly marked as such, and must not be 15 | misrepresented as being the original software. 16 | 3. This notice may not be removed or altered from any source distribution. -------------------------------------------------------------------------------- /SharpECS.Samples/Components/TransformComponent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | using SharpECS; 7 | 8 | using Microsoft.Xna.Framework.Graphics; 9 | using Microsoft.Xna.Framework; 10 | 11 | namespace SharpECS.Samples.Components 12 | { 13 | internal class TransformComponent 14 | : IComponent 15 | { 16 | public Entity Owner { get; set; } 17 | 18 | private Vector2 _position; 19 | 20 | public Vector2 Position 21 | { 22 | get { return _position; } 23 | set { _position = value; } 24 | } 25 | 26 | public Rectangle Rect { get; set; } 27 | 28 | public void SetX(float newX) => _position.X = newX; 29 | public void SetY(float newY) => _position.Y = newY; 30 | 31 | public TransformComponent() 32 | { 33 | Rect = new Rectangle(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /SharpECS.Samples/Content/Content.mgcb: -------------------------------------------------------------------------------- 1 | 2 | #----------------------------- Global Properties ----------------------------# 3 | 4 | /outputDir:bin/Windows 5 | /intermediateDir:obj/Windows 6 | /platform:Windows 7 | /config: 8 | /profile:Reach 9 | /compress:False 10 | 11 | #-------------------------------- References --------------------------------# 12 | 13 | 14 | #---------------------------------- Content ---------------------------------# 15 | 16 | #begin Sprite.png 17 | /importer:TextureImporter 18 | /processor:TextureProcessor 19 | /processorParam:ColorKeyColor=255,0,255,255 20 | /processorParam:ColorKeyEnabled=True 21 | /processorParam:GenerateMipmaps=False 22 | /processorParam:PremultiplyAlpha=True 23 | /processorParam:ResizeToPowerOfTwo=False 24 | /processorParam:MakeSquare=False 25 | /processorParam:TextureFormat=Color 26 | /build:Sprite.png 27 | 28 | #begin Sprite2.png 29 | /importer:TextureImporter 30 | /processor:TextureProcessor 31 | /processorParam:ColorKeyColor=255,0,255,255 32 | /processorParam:ColorKeyEnabled=True 33 | /processorParam:GenerateMipmaps=False 34 | /processorParam:PremultiplyAlpha=True 35 | /processorParam:ResizeToPowerOfTwo=False 36 | /processorParam:MakeSquare=False 37 | /processorParam:TextureFormat=Color 38 | /build:Sprite2.png 39 | 40 | -------------------------------------------------------------------------------- /SharpECS.Tests/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | using SharpECS; 8 | 9 | using System.Diagnostics; 10 | 11 | namespace SharpECS.Tests 12 | { 13 | class Program 14 | { 15 | static void Main(string[] args) 16 | { 17 | var pool = EntityPool.New("Pool1"); 18 | 19 | var timer = Stopwatch.StartNew(); 20 | 21 | var newEntity = pool.CreateEntity("newEntity"); 22 | 23 | timer.Stop(); 24 | 25 | Console.WriteLine("It took " + timer.Elapsed.TotalMilliseconds + "ms to create a new instance of Entity."); 26 | 27 | timer.Restart(); 28 | 29 | pool.DestroyEntity(ref newEntity); 30 | 31 | timer.Stop(); 32 | 33 | Console.WriteLine("It took " + timer.Elapsed.TotalMilliseconds + "ms to delete an Entity and move it into the cache!"); 34 | 35 | timer.Restart(); 36 | 37 | var cacheEntity = pool.CreateEntity("cacheEntity"); 38 | 39 | timer.Stop(); 40 | 41 | Console.WriteLine("It took " + timer.Elapsed.TotalMilliseconds + "ms to pull an Entity in from the cache!"); 42 | 43 | timer.Stop(); 44 | 45 | Console.ReadKey(); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /SharpECS.Samples/Systems/GraphicsSystem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Collections.Generic; 4 | 5 | using Microsoft.Xna.Framework; 6 | using Microsoft.Xna.Framework.Input; 7 | using Microsoft.Xna.Framework.Graphics; 8 | 9 | using SharpECS; 10 | using SharpECS.Samples.Components; 11 | 12 | namespace SharpECS.Samples.Systems 13 | { 14 | internal class GraphicsSystem 15 | : EntitySystem 16 | { 17 | public GraphicsSystem(EntityPool pool) 18 | : base(pool, typeof(GraphicsComponent), typeof(TransformComponent)) 19 | { } 20 | 21 | public void Draw(SpriteBatch spriteBatch) 22 | { 23 | for (int i = 0; i < Compatible.Count; i++) 24 | { 25 | var transform = Compatible[i].GetComponent(); 26 | var graphics = Compatible[i].GetComponent(); 27 | 28 | transform.Rect = new Rectangle((int)transform.Position.X, (int)transform.Position.Y, graphics.Texture.Width, graphics.Texture.Height); 29 | 30 | if (Compatible[i].State == EntityState.Active) 31 | { 32 | var texture = graphics.Texture; 33 | var position = transform.Position; 34 | 35 | spriteBatch.Draw 36 | ( 37 | texture, 38 | position, 39 | Color.White 40 | ); 41 | } 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /SharpECS/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SharpECS")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("SharpECS")] 13 | [assembly: AssemblyCopyright("Copyright © Anthony Baynham 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("085bc590-ea90-4ca6-8540-f61299c562ac")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /SharpECS.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SharpECS.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("SharpECS.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © Anthony Baynham 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("f80a42f0-faed-40a3-8c57-708ab64678ba")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /SharpECS.Samples/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SharpECS.Samples")] 9 | [assembly: AssemblyProduct("SharpECS.Samples")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyDescription("")] 12 | [assembly: AssemblyCompany("")] 13 | [assembly: AssemblyCopyright("Copyright © Anthony Baynham 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("7a62ece7-0a31-44ae-8fe4-cddcd26734fa")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("3.4.0.456")] 36 | [assembly: AssemblyFileVersion("3.4.0.456")] 37 | -------------------------------------------------------------------------------- /SharpECS.Samples/Systems/ControllerSystem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | using Microsoft.Xna.Framework; 5 | using Microsoft.Xna.Framework.Input; 6 | using Microsoft.Xna.Framework.Graphics; 7 | 8 | using SharpECS; 9 | using SharpECS.Samples.Components; 10 | 11 | namespace SharpECS.Samples.Systems 12 | { 13 | internal class ControllerSystem 14 | : EntitySystem 15 | { 16 | public ControllerSystem(EntityPool pool) 17 | : base(pool, typeof(ControllerComponent), typeof(TransformComponent)) 18 | { } 19 | 20 | public void Update(GameTime gameTime) 21 | { 22 | var delta = (float)gameTime.ElapsedGameTime.TotalSeconds; 23 | 24 | for (int i = 0; i < Compatible.Count; i++) 25 | { 26 | if (Compatible[i].State == EntityState.Active) 27 | { 28 | var transform = Compatible[i].GetComponent(); 29 | var moveSpeed = Compatible[i].GetComponent().MoveSpeed; 30 | 31 | if (Keyboard.GetState().IsKeyDown(Keys.D)) { transform.SetX(transform.Position.X + moveSpeed * delta); } 32 | if (Keyboard.GetState().IsKeyDown(Keys.A)) { transform.SetX(transform.Position.X - moveSpeed * delta); } 33 | if (Keyboard.GetState().IsKeyDown(Keys.W)) { transform.SetY(transform.Position.Y - moveSpeed * delta); } 34 | if (Keyboard.GetState().IsKeyDown(Keys.S)) { transform.SetY(transform.Position.Y + moveSpeed * delta); } 35 | } 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /SharpECS/Source/EntitySystem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace SharpECS 6 | { 7 | public abstract class EntitySystem 8 | { 9 | public EntityPool Pool { get; set; } 10 | public List Compatible { get; set; } 11 | 12 | protected List CompatibleTypes { get; private set; } 13 | 14 | public EntitySystem(EntityPool pool, params Type[] compatibleTypes) 15 | { 16 | if (compatibleTypes.Any(t => !t.IsComponent())) 17 | throw new Exception("Type passed into EntitySystem is not an IComponent!"); 18 | 19 | CompatibleTypes = new List(); 20 | CompatibleTypes.AddRange(compatibleTypes); 21 | 22 | Pool = pool; 23 | 24 | Compatible = GetCompatibleInPool(); 25 | 26 | Pool.EntityComponentAdded += OnPoolEntityChanged; 27 | Pool.EntityComponentRemoved += OnPoolEntityChanged; 28 | 29 | Pool.EntityAdded += OnPoolEntityChanged; 30 | Pool.EntityRemoved += OnPoolEntityChanged; 31 | } 32 | 33 | public void AddCompatibleType(Type type) 34 | { 35 | if (!type.IsComponent()) 36 | throw new Exception("Type passed into AddCompatibleType is not an IComponent!"); 37 | 38 | CompatibleTypes.Add(type); 39 | Compatible = GetCompatibleInPool(); 40 | } 41 | 42 | private void OnPoolEntityChanged(EntityPool pool, Entity entity) 43 | { 44 | Pool = pool; 45 | Compatible = GetCompatibleInPool(); 46 | } 47 | 48 | protected virtual List GetCompatibleInPool() 49 | { 50 | return Pool.Entities.Where(ent => ent.HasComponents(CompatibleTypes)).ToList(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /SharpECS.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25123.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpECS.Tests", "SharpECS.Tests\SharpECS.Tests.csproj", "{F80A42F0-FAED-40A3-8C57-708AB64678BA}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpECS", "SharpECS\SharpECS.csproj", "{085BC590-EA90-4CA6-8540-F61299C562AC}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpECS.Samples", "SharpECS.Samples\SharpECS.Samples.csproj", "{12C1AF6D-3B23-4F8F-8228-7852EE90E7EA}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {F80A42F0-FAED-40A3-8C57-708AB64678BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {F80A42F0-FAED-40A3-8C57-708AB64678BA}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {F80A42F0-FAED-40A3-8C57-708AB64678BA}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {F80A42F0-FAED-40A3-8C57-708AB64678BA}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {085BC590-EA90-4CA6-8540-F61299C562AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {085BC590-EA90-4CA6-8540-F61299C562AC}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {085BC590-EA90-4CA6-8540-F61299C562AC}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {085BC590-EA90-4CA6-8540-F61299C562AC}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {12C1AF6D-3B23-4F8F-8228-7852EE90E7EA}.Debug|Any CPU.ActiveCfg = Debug|x86 27 | {12C1AF6D-3B23-4F8F-8228-7852EE90E7EA}.Release|Any CPU.ActiveCfg = Release|x86 28 | EndGlobalSection 29 | GlobalSection(SolutionProperties) = preSolution 30 | HideSolutionNode = FALSE 31 | EndGlobalSection 32 | EndGlobal 33 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /SharpECS.Tests/SharpECS.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {F80A42F0-FAED-40A3-8C57-708AB64678BA} 8 | Exe 9 | Properties 10 | ECSIsBetter.Tests 11 | ECSIsBetter.Tests 12 | v4.5.2 13 | 512 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | ..\Builds\SharpECS.Tests\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | ..\Builds\SharpECS.Tests\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | {085bc590-ea90-4ca6-8540-f61299c562ac} 58 | ECSIsBetter 59 | 60 | 61 | 62 | 69 | -------------------------------------------------------------------------------- /SharpECS/SharpECS.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {085BC590-EA90-4CA6-8540-F61299C562AC} 8 | Library 9 | Properties 10 | SharpECS 11 | SharpECS 12 | v4.5.2 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | ..\Builds\SharpECS\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | ..\Builds\SharpECS\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 68 | -------------------------------------------------------------------------------- /SharpECS.Samples/SharpECS.Samples.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | x86 6 | 8.0.30703 7 | 2.0 8 | {12C1AF6D-3B23-4F8F-8228-7852EE90E7EA} 9 | Exe 10 | Properties 11 | SharpECS.Samples 12 | SharpECS.Samples 13 | 512 14 | Windows 15 | 16 | 17 | v4.5.2 18 | 19 | 20 | 21 | AnyCPU 22 | true 23 | full 24 | false 25 | ..\Builds\SharpECS.Samples\Debug\ 26 | DEBUG;TRACE;WINDOWS 27 | prompt 28 | 4 29 | false 30 | 31 | 32 | AnyCPU 33 | pdbonly 34 | true 35 | ..\Builds\SharpECS.Samples\Release\ 36 | TRACE;WINDOWS 37 | prompt 38 | 4 39 | false 40 | 41 | 42 | Icon.ico 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | $(MSBuildProgramFiles32)\MonoGame\v3.0\Assemblies\Windows\MonoGame.Framework.dll 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | {085bc590-ea90-4ca6-8540-f61299c562ac} 74 | ECSIsBetter 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 89 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | # DNX 42 | project.lock.json 43 | artifacts/ 44 | 45 | *_i.c 46 | *_p.c 47 | *_i.h 48 | *.ilk 49 | *.meta 50 | *.obj 51 | *.pch 52 | *.pdb 53 | *.pgc 54 | *.pgd 55 | *.rsp 56 | *.sbr 57 | *.tlb 58 | *.tli 59 | *.tlh 60 | *.tmp 61 | *.tmp_proj 62 | *.log 63 | *.vspscc 64 | *.vssscc 65 | .builds 66 | *.pidb 67 | *.svclog 68 | *.scc 69 | 70 | # Chutzpah Test files 71 | _Chutzpah* 72 | 73 | # Visual C++ cache files 74 | ipch/ 75 | *.aps 76 | *.ncb 77 | *.opensdf 78 | *.sdf 79 | *.cachefile 80 | 81 | # Visual Studio profiler 82 | *.psess 83 | *.vsp 84 | *.vspx 85 | 86 | # TFS 2012 Local Workspace 87 | $tf/ 88 | 89 | # Guidance Automation Toolkit 90 | *.gpState 91 | 92 | # ReSharper is a .NET coding add-in 93 | _ReSharper*/ 94 | *.[Rr]e[Ss]harper 95 | *.DotSettings.user 96 | 97 | # JustCode is a .NET coding add-in 98 | .JustCode 99 | 100 | # TeamCity is a build add-in 101 | _TeamCity* 102 | 103 | # DotCover is a Code Coverage Tool 104 | *.dotCover 105 | 106 | # NCrunch 107 | _NCrunch_* 108 | .*crunch*.local.xml 109 | 110 | # MightyMoose 111 | *.mm.* 112 | AutoTest.Net/ 113 | 114 | # Web workbench (sass) 115 | .sass-cache/ 116 | 117 | # Installshield output folder 118 | [Ee]xpress/ 119 | 120 | # DocProject is a documentation generator add-in 121 | DocProject/buildhelp/ 122 | DocProject/Help/*.HxT 123 | DocProject/Help/*.HxC 124 | DocProject/Help/*.hhc 125 | DocProject/Help/*.hhk 126 | DocProject/Help/*.hhp 127 | DocProject/Help/Html2 128 | DocProject/Help/html 129 | 130 | # Click-Once directory 131 | publish/ 132 | 133 | # Publish Web Output 134 | *.[Pp]ublish.xml 135 | *.azurePubxml 136 | ## TODO: Comment the next line if you want to checkin your 137 | ## web deploy settings but do note that will include unencrypted 138 | ## passwords 139 | #*.pubxml 140 | 141 | *.publishproj 142 | 143 | # NuGet Packages 144 | *.nupkg 145 | # The packages folder can be ignored because of Package Restore 146 | **/packages/* 147 | # except build/, which is used as an MSBuild target. 148 | !**/packages/build/ 149 | # Uncomment if necessary however generally it will be regenerated when needed 150 | #!**/packages/repositories.config 151 | 152 | # Windows Azure Build Output 153 | csx/ 154 | *.build.csdef 155 | 156 | # Windows Store app package directory 157 | AppPackages/ 158 | 159 | # Visual Studio cache files 160 | # files ending in .cache can be ignored 161 | *.[Cc]ache 162 | # but keep track of directories ending in .cache 163 | !*.[Cc]ache/ 164 | 165 | # Others 166 | ClientBin/ 167 | [Ss]tyle[Cc]op.* 168 | ~$* 169 | *~ 170 | *.dbmdl 171 | *.dbproj.schemaview 172 | *.pfx 173 | *.publishsettings 174 | node_modules/ 175 | orleans.codegen.cs 176 | 177 | # RIA/Silverlight projects 178 | Generated_Code/ 179 | 180 | # Backup & report files from converting an old project file 181 | # to a newer Visual Studio version. Backup files are not needed, 182 | # because we have git ;-) 183 | _UpgradeReport_Files/ 184 | Backup*/ 185 | UpgradeLog*.XML 186 | UpgradeLog*.htm 187 | 188 | # SQL Server files 189 | *.mdf 190 | *.ldf 191 | 192 | # Business Intelligence projects 193 | *.rdl.data 194 | *.bim.layout 195 | *.bim_*.settings 196 | 197 | # Microsoft Fakes 198 | FakesAssemblies/ 199 | 200 | # Node.js Tools for Visual Studio 201 | .ntvs_analysis.dat 202 | 203 | # Visual Studio 6 build log 204 | *.plg 205 | 206 | # Visual Studio 6 workspace options file 207 | *.opt 208 | 209 | # LightSwitch generated files 210 | GeneratedArtifacts/ 211 | _Pvt_Extensions/ 212 | ModelManifest.xml 213 | -------------------------------------------------------------------------------- /SharpECS.Samples/Game.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | using Microsoft.Xna.Framework; 5 | using Microsoft.Xna.Framework.Graphics; 6 | using Microsoft.Xna.Framework.Input; 7 | 8 | using SharpECS; 9 | using SharpECS.Samples.Components; 10 | using SharpECS.Samples.Systems; 11 | using System.Collections.Generic; 12 | 13 | namespace SharpECS.Samples 14 | { 15 | internal class Game 16 | : Microsoft.Xna.Framework.Game 17 | { 18 | GraphicsDeviceManager graphics; 19 | SpriteBatch spriteBatch; 20 | 21 | KeyboardState keyboard; 22 | KeyboardState previousKeyboard; 23 | 24 | MouseState mouse; 25 | MouseState previousMouse; 26 | 27 | EntityPool entityPool; 28 | 29 | GraphicsSystem graphicsSystem; 30 | ControllerSystem controllerSystem; 31 | 32 | Entity playerEntity; 33 | Entity hostileEntity; 34 | 35 | public Game() 36 | { 37 | graphics = new GraphicsDeviceManager(this); 38 | Content.RootDirectory = "Content"; 39 | 40 | IsMouseVisible = true; 41 | graphics.SynchronizeWithVerticalRetrace = false; 42 | IsFixedTimeStep = false; 43 | } 44 | 45 | protected override void Initialize() 46 | { 47 | entityPool = EntityPool.New("EntityPool"); 48 | 49 | playerEntity = entityPool.CreateEntity("Player"); 50 | hostileEntity = entityPool.CreateEntity("HostileEntity"); 51 | 52 | // Systems will refresh when new Entities have compatible components added to them. 53 | graphicsSystem = new GraphicsSystem(entityPool); 54 | controllerSystem = new ControllerSystem(entityPool); 55 | 56 | // One way of adding components. 57 | playerEntity += new TransformComponent() { Position = new Vector2(200, 364) }; 58 | playerEntity += new GraphicsComponent() { Texture = Content.Load("Sprite") }; 59 | playerEntity += new ControllerComponent() { MoveSpeed = 100 }; 60 | 61 | // Alternate way. 62 | hostileEntity.AddComponents 63 | ( 64 | new GraphicsComponent() { Texture = Content.Load("Sprite2") }, 65 | new TransformComponent() { Position = new Vector2(350, 200) } 66 | ); 67 | 68 | var newEntity = entityPool.CreateEntity("NewEntity"); 69 | newEntity.AddComponents(new TransformComponent(), new GraphicsComponent()); 70 | 71 | newEntity.GetComponent().Position = new Vector2(450, 320); 72 | newEntity.GetComponent().Texture = Content.Load("Sprite2"); 73 | 74 | Console.WriteLine("HostileEntity texture name: " + hostileEntity?.GetComponent()?.Texture?.Name); 75 | 76 | newEntity.CreateChild("ChildOfNew", false); 77 | 78 | newEntity.GetChild("ChildOfNew").CreateChild("GrandChildOfNew").CreateChild("GreatGrandChildOfNew"); 79 | 80 | var grandChild = entityPool.GetEntity("GreatGrandChildOfNew"); 81 | 82 | Console.WriteLine("Root entity of GrandChildOfNew (should be NewEntity): " + grandChild?.RootEntity?.Id); 83 | Console.WriteLine("Root entity of Player (should be Player): " + playerEntity.RootEntity.Id); 84 | 85 | var familyTree = newEntity.FamilyTree(); 86 | 87 | var position = new Vector2(10, 10); 88 | var random = new Random(); 89 | 90 | for (int i = 0; i < familyTree.Count(); i++) 91 | { 92 | var ent = familyTree.ElementAt(i); 93 | 94 | ent += new GraphicsComponent() { Texture = Content.Load("Sprite"), }; 95 | ent += new TransformComponent() { Position = new Vector2(position.X, position.Y) }; 96 | 97 | position.X += 128; 98 | position.Y += 272; 99 | } 100 | 101 | base.Initialize(); 102 | } 103 | 104 | protected override void LoadContent() 105 | { 106 | // Create a new SpriteBatch, which can be used to draw textures. 107 | spriteBatch = new SpriteBatch(GraphicsDevice); 108 | } 109 | 110 | protected override void UnloadContent() 111 | { 112 | Dispose(); 113 | } 114 | 115 | protected override void Update(GameTime gameTime) 116 | { 117 | keyboard = Keyboard.GetState(); 118 | mouse = Mouse.GetState(); 119 | 120 | if (keyboard.IsKeyDown(Keys.Escape)) Exit(); 121 | 122 | if (mouse.LeftButton == ButtonState.Pressed && previousMouse.LeftButton == ButtonState.Released 123 | && entityPool.DoesEntityExist(hostileEntity)) 124 | hostileEntity.GetComponent().Position = new Vector2(mouse.Position.X - 16, mouse.Position.Y - 16); 125 | 126 | if (mouse.RightButton == ButtonState.Pressed && previousMouse.RightButton == ButtonState.Released 127 | && entityPool.DoesEntityExist(hostileEntity)) 128 | { 129 | entityPool.DestroyEntity(ref hostileEntity); 130 | 131 | var fromTheCache = entityPool.CreateEntity("FromTheCache"); 132 | 133 | IComponent[] components = new IComponent[] 134 | { 135 | new TransformComponent() { Position = new Vector2(300, 256) }, 136 | new GraphicsComponent() { Texture = Content.Load("Sprite") } 137 | }; 138 | 139 | fromTheCache.AddComponents(components); 140 | } 141 | 142 | if (keyboard.IsKeyDown(Keys.R) && previousKeyboard.IsKeyUp(Keys.R) 143 | && entityPool.DoesEntityExist("Player")) 144 | playerEntity.Switch(); 145 | 146 | controllerSystem?.Update(gameTime); 147 | 148 | previousMouse = mouse; 149 | previousKeyboard = keyboard; 150 | 151 | base.Update(gameTime); 152 | } 153 | 154 | protected override void Draw(GameTime gameTime) 155 | { 156 | GraphicsDevice.Clear(Color.CornflowerBlue); 157 | 158 | spriteBatch.Begin(); 159 | graphicsSystem?.Draw(spriteBatch); 160 | spriteBatch.End(); 161 | 162 | base.Draw(gameTime); 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /SharpECS/Source/EntityPool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | using SharpECS.Exceptions; 8 | 9 | namespace SharpECS 10 | { 11 | /// 12 | /// The object that managed all your game Entities. 13 | /// 14 | public class EntityPool 15 | { 16 | public delegate void EntityChanged(EntityPool pool, Entity entity); 17 | 18 | public event EntityChanged EntityAdded; 19 | public event EntityChanged EntityRemoved; 20 | 21 | public event EntityChanged EntityComponentAdded; 22 | public event EntityChanged EntityComponentRemoved; 23 | 24 | private List _activeEntities; 25 | private Stack _cachedEntities; 26 | 27 | public List Entities 28 | { 29 | get { return _activeEntities; } 30 | private set { _activeEntities = value; } 31 | } 32 | 33 | public Stack CachedEntities 34 | { 35 | get { return _cachedEntities; } 36 | private set { _cachedEntities = value; } 37 | } 38 | 39 | public string Id { get; set; } 40 | 41 | // How many Entites the cache can store at a time. 42 | private readonly int MAX_CACHED_ENTITIES = 25; 43 | 44 | /// 45 | /// Creates and returns a new instance of EntityPool 46 | /// (it looks prettier than "var pool = new EntityPool("Id");" 47 | /// also less code) :3 48 | /// 49 | /// 50 | /// 51 | public static EntityPool New(string Id) 52 | { 53 | return new EntityPool(Id); 54 | } 55 | 56 | private EntityPool(string Id) 57 | { 58 | _activeEntities = new List(); 59 | _cachedEntities = new Stack(); 60 | 61 | if (Id != null) this.Id = Id; 62 | } 63 | 64 | /// 65 | /// Creates a new Entity with "entityId", adds it to active Entities and returns it. 66 | /// 67 | /// 68 | /// Final Entity 69 | public Entity CreateEntity(string entityId) 70 | { 71 | if (Entities.Any(ent => ent.Id == entityId)) 72 | throw new DuplicateEntityException(this); 73 | 74 | if (string.IsNullOrEmpty(entityId)) 75 | throw new Exception("The string you entered was blank or null."); 76 | 77 | Entity newEntity; 78 | 79 | if (_cachedEntities.Any()) 80 | { 81 | newEntity = _cachedEntities.Pop(); 82 | 83 | if (newEntity == null) 84 | throw new EntityNotFoundException(this); 85 | 86 | newEntity.Id = entityId; 87 | newEntity.OwnerPool = this; 88 | newEntity.State = EntityState.Active; 89 | 90 | _activeEntities.Add(newEntity); 91 | 92 | #if DEBUG 93 | Console.WriteLine($"Retrieved {newEntity.Id} from cache."); 94 | #endif 95 | } else 96 | { 97 | newEntity = new Entity(entityId, this); 98 | _activeEntities.Add(newEntity); 99 | 100 | #if DEBUG 101 | Console.WriteLine($"Created new instance for {newEntity.Id} because the cache was empty."); 102 | #endif 103 | } 104 | 105 | EntityAdded?.Invoke(this, newEntity); 106 | 107 | return newEntity; 108 | } 109 | 110 | internal void AddEntity(Entity entity) 111 | { 112 | if (entity.IsAvailable()) 113 | _activeEntities.Add(entity); 114 | 115 | else if (!entity.IsAvailable()) 116 | _cachedEntities.Push(entity); 117 | 118 | EntityAdded?.Invoke(this, entity); 119 | } 120 | 121 | public bool DoesEntityExist(string Id) 122 | { 123 | if (string.IsNullOrEmpty(Id)) 124 | return false; 125 | 126 | return Entities.Any(ent => ent.Id == Id && GetEntity(Id).IsAvailable()); 127 | } 128 | 129 | public bool DoesEntityExist(Entity entity) 130 | { 131 | if (entity == null || entity == default(Entity)) 132 | return false; 133 | 134 | if (string.IsNullOrEmpty(entity.Id)) 135 | return false; 136 | 137 | return Entities.Any(ent => ent == entity && entity.IsAvailable()); 138 | } 139 | 140 | public Entity GetEntity(string entityId) 141 | { 142 | var match = Entities.FirstOrDefault(ent => ent.Id == entityId); 143 | 144 | if (match != null) return match; 145 | 146 | throw new EntityNotFoundException(this); 147 | } 148 | 149 | /// 150 | /// Adds an Entity to the cache to be re-used if cachedEntities isn't full. 151 | /// If the cache is full, just remove completely. 152 | /// 153 | /// 154 | public void DestroyEntity(ref Entity entity) 155 | { 156 | if (!entity.IsAvailable()) 157 | return; 158 | 159 | if (!_activeEntities.Contains(entity)) 160 | throw new EntityNotFoundException(this); 161 | 162 | if (_cachedEntities.Count < MAX_CACHED_ENTITIES) 163 | { 164 | // Reset the Entity. 165 | entity.Reset(); 166 | _cachedEntities.Push(entity); 167 | } 168 | 169 | _activeEntities.Remove(entity); 170 | EntityRemoved?.Invoke(this, entity); 171 | 172 | // Set the entity that was passed in to null 173 | entity = null; 174 | } 175 | 176 | /// 177 | /// Clears the cached Entities stack. 178 | /// 179 | public void WipeCache() 180 | { 181 | _cachedEntities.Clear(); 182 | } 183 | 184 | /// 185 | /// Clears the active Entities list. 186 | /// 187 | public void WipeEntities() 188 | { 189 | _activeEntities.Clear(); 190 | } 191 | 192 | internal void ComponentAdded(Entity entity) 193 | { 194 | EntityComponentAdded?.Invoke(this, entity); 195 | } 196 | 197 | internal void ComponentRemoved(Entity entity) 198 | { 199 | EntityComponentRemoved?.Invoke(this, entity); 200 | } 201 | 202 | /// 203 | /// Operator overload to let you do "pool += entity" to add an Entity to the pool. 204 | /// 205 | /// 206 | /// 207 | /// 208 | public static EntityPool operator + (EntityPool pool, string id) 209 | { 210 | pool.CreateEntity(id); 211 | 212 | return pool; 213 | } 214 | 215 | /// 216 | /// Operator overload to let you do "pool -= entity" to remove an Entity from the pool. 217 | /// 218 | /// 219 | /// 220 | /// 221 | public static EntityPool operator - (EntityPool pool, Entity entity) 222 | { 223 | if (entity == null || !pool.DoesEntityExist(entity.Id)) 224 | { 225 | throw new EntityNotFoundException(pool); 226 | } 227 | 228 | pool.DestroyEntity(ref entity); 229 | return pool; 230 | } 231 | 232 | public static EntityPool operator - (EntityPool pool, string id) 233 | { 234 | if (string.IsNullOrEmpty(id) || !pool.DoesEntityExist(id)) 235 | { 236 | throw new EntityNotFoundException(pool); 237 | } 238 | 239 | var toDestroy = pool.GetEntity(id); 240 | pool.DestroyEntity(ref toDestroy); 241 | return pool; 242 | } 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # #ECS [![Build status](https://ci.appveyor.com/api/projects/status/icv0g4g8iok114l9)](https://ci.appveyor.com/project/anthony-y/sharp-ecs) 2 | 3 | An easy to use Entity Component System library for C#. 4 | 5 | # Getting Started 6 | 7 | ## Compiling 8 | 9 | First, you'll need to clone the project: 10 | 11 | ``` 12 | git clone http://www.github.com/anthony-y/sharp-ecs 13 | ``` 14 | 15 | Then you need to build the project, you can do this with the .bat file provided: 16 | 17 | ``` 18 | compile.bat 19 | ``` 20 | 21 | **Note: This requires Visual Studio 2015 or newer!** 22 | 23 | This will output new binaries into the Build folder. 24 | 25 | 3 projects will be built: 26 | 27 | - SharpECS: the main library. This is a .dll that you can link to when you compile your projects 28 | - SharpECS.Samples: a test program to show off the capabilities of the library 29 | - SharpECS.Tests: unit tests to check how fast some basic operations take 30 | 31 | ## Using the library 32 | 33 | The first thing you'll need to do is include the SharpECS namespace in the appropriate files: 34 | 35 | ```csharp 36 | using SharpECS; 37 | ``` 38 | 39 | Next, you'll need to make an instance of EntityPool which is where you'll store your Entities and where your Entity Systems will find your Entities. 40 | 41 | **Note: You can have multiple EntityPools to store different groups of Entities** 42 | 43 | ```csharp 44 | EntityPool entityPool = EntityPool.New("MyEntityPoolIdentifier"); 45 | ``` 46 | 47 | Creating your first Entity is as easy as: 48 | 49 | ```csharp 50 | Entity myEntity = entityPool.CreateEntity("MyEntity"); 51 | ``` 52 | 53 | In order for your Entities to be detected and manipulated by Entity Systems (which we'll cover shortly), you need to add Components to your Entities. 54 | Components also store data for that specific Entity which allows Systems to modify Entities independently. 55 | 56 | Here is an example of a simple transform component class which holds an X and Y position for any Entities which have it "attached" to them. 57 | 58 | ```csharp 59 | using SharpECS; 60 | 61 | public class TransformComponent 62 | : IComponent 63 | { 64 | public float X { get; set; } 65 | public float Y { get; set; } 66 | } 67 | ``` 68 | 69 | And now, to add this component to our previously created "MyEntity": 70 | 71 | ```csharp 72 | myEntity += new TransformComponent() { X = 123, Y = 123 }; // or whatever values you want 73 | ``` 74 | 75 | I will now write a "transform system" which will simply display the X and Y position of each Entity that has a TransformComponent every frame. 76 | You usually wouldn't actually write a component like this unless you were debugging or something but I just want to show the process of making components and systems and how the two interact as simply as possible. 77 | 78 | ```csharp 79 | using SharpECS; 80 | 81 | public class TransformSystem 82 | : EntitySystem 83 | { 84 | public TransformSystem(EntityPool entityPool) 85 | : base(entityPool, typeof(TransformComponent)) 86 | /* 87 | The above line: 88 | 1. Tells SharpECS to look for compatible Entities only in the EntityPool the user entered 89 | 2. Tells SharpECS that we only want to interact with Entities which have a TransformComponent on them. 90 | 91 | Note: you could obviously have the user pass the component types or hard-code the EntityPool, etc. 92 | it's totally up to you. 93 | */ 94 | { 95 | 96 | } 97 | 98 | /* 99 | This is not a built in method 100 | SharpECS aims to be platform independent so it doesn't matter what engine or framework(s) you use 101 | */ 102 | public void Update() 103 | { 104 | // Loop through every Entity which was found with a TransformComponent on it in the specified EntityPool 105 | foreach (var entity in Compatible) 106 | { 107 | float X = entity.GetComponent().X; 108 | float Y = entity.GetComponent().Y; 109 | 110 | System.Console.WriteLine($"Entity {entity.Id} found at {X}, {Y}!"); 111 | } 112 | 113 | /* 114 | Note: I recommend that you cache components to prevent slowdown grabbing the same components every frame. 115 | Currently SharpECS has no built in mechanism for this. 116 | */ 117 | } 118 | } 119 | ``` 120 | 121 | You can now create an instance of your system and call it as you wish! 122 | 123 | ```csharp 124 | TransformSystem transformSystem = new TransformSystem(entityPool); 125 | 126 | void Update() 127 | { 128 | transformSystem.Update(); 129 | } 130 | ``` 131 | 132 | Systems are very powerful because they can do anything, from rendering all your Entities to updating physics in the world. 133 | See the [samples](https://github.com/anthony-y/sharp-ecs/tree/master/SharpECS.Samples) for a full demo of System usage. 134 | 135 | Entities don't have to be made before Systems, nor do Components have to be added to Entities before Systems have been made! 136 | This is because Systems are notified internally when anything changes, and they will rescan for Entities, so I could have easily made the system just after creating the entity pool. 137 | 138 | ### What is the Entity Cache and how Does it Work? 139 | 140 | The EntityPool class is essentially what manages your Entities and gives both Systems, and you, a contained place to access them all. 141 | 142 | But there is a mechanism behind the scenes which greatly improves memory usage and, in some cases, performance by "caching" Entities instead of destroying them. 143 | 144 | When you destroy/delete/remove an Entity like so: 145 | 146 | ```csharp 147 | entityPool.DestroyEntity(ref myEntity); 148 | ``` 149 | 150 | It is stripped of all it's components, given an empty string as an Id and has all it's children removed. In this state it is called a blank or cached Entity. 151 | It is then placed into a Stack of Entities where it sits until a new Entity is requested (with ```CreateEntity```). When that happens, instead of contructing a new Entity with "new", the pool 152 | pops an Entity off the stack, fills it's information back in and returns it. 153 | 154 | It's important to know that if we do destroy "myEntity", the variable will be made null (which is why you must pass it as ```ref```). However, you can of course reassign it to a new Entity with ```CreateEntity```. 155 | 156 | ### Child Entities 157 | 158 | We can give "myEntity" a kid with just one method call! 159 | 160 | ```csharp 161 | myEntity.CreateChild("MyEntitysChild"); 162 | ``` 163 | 164 | I'm so proud! "myEntity" has now reproduced (asexually O_O) to make a beautiful new baby Entity. 165 | 166 | Now lets say "MyEntitysChild" is all grown up and of legal age and it wants to have kids too! Easy! 167 | 168 | ```csharp 169 | myEntity.GetChild("MyEntitysChild").CreateChild("MyEntitysGrandChild"); 170 | ``` 171 | 172 | There is an optional parameter for ```CreateChild``` called ```inheritComponents``` (which defaults to false). 173 | As you can imagine, if you pass this as true: 174 | 175 | ```csharp 176 | myEntity.CreateChild("TheLeastFavourite", true); 177 | ``` 178 | 179 | Then the new child will recieve all the components that it's parent has. 180 | 181 | Not sure why the least favourite got the inheritance, I'm sure "MyEntitysChild" will be having words. And maybe knock a few teeth out. Unless it doesn't have a component for that. 182 | 183 | ### Family tree 184 | 185 | You can walk the "family tree" of an Entity and do stuff with the family ( ͡° ͜ʖ ͡°) 186 | 187 | ```csharp 188 | foreach (var entity in myEntity.FamilyTree()) 189 | { 190 | System.Console.WriteLine($"Entity related to {myEntity.Id}: {entity.Id} (Parent: {entity.Parent.Id}"); 191 | } 192 | ``` 193 | 194 | ### Entity State 195 | 196 | An Entity can be in 1 of 3 states at a time: 197 | 198 | - Active 199 | - Inactive 200 | - Cached 201 | 202 | The state of an Entity is controlled internally by SharpECS but is accessible from the outside with: 203 | 204 | ```csharp 205 | EntityState myEntityState = myEntity.State; 206 | ``` 207 | 208 | SharpECS has a method for toggling an Entity between active and inactive: 209 | 210 | ```csharp 211 | myEntity.Switch(); 212 | ``` 213 | 214 | It's up to you to decide how your Systems act when Entities are in different states. 215 | 216 | Let's modify our previously created TransformSystem's update method to only print out the position of Entities which have an Active state. 217 | 218 | ```csharp 219 | public void Update() 220 | { 221 | // Loop through every Entity which was found with a TransformComponent on it in the specified EntityPool 222 | foreach (var entity in Compatible) 223 | { 224 | // Make the sure the Entity is active! 225 | if (entity.IsActive()) 226 | { 227 | float X = entity.GetComponent().X; 228 | float Y = entity.GetComponent().Y; 229 | 230 | System.Console.WriteLine($"Active Entity {entity.Id} found at {X}, {Y}!"); 231 | } 232 | } 233 | 234 | /* 235 | Note: I recommend that you cache components to prevent slowdown grabbing the same components every frame. 236 | Currently SharpECS has no built in mechanism for this. 237 | */ 238 | } 239 | ``` 240 | 241 | ### Removing Components 242 | 243 | Sometimes you might want to remove a Component from an Entity at runtime. You can do this with one method call: 244 | 245 | ```csharp 246 | myEntity.RemoveComponent(); 247 | ``` 248 | 249 | Easy. You can do it without a generic too using Runtime type checking, although I don't recommend you do this as it's much slower as the above method and, as I mentioned, uses Runtime type checking instead of compiletime type checking. 250 | 251 | ```csharp 252 | myEntity.RemoveComponent(typeof(TransformComponent)); 253 | ``` 254 | 255 | ### More Entity methods 256 | 257 | SharpECS gives you a lot of choice in how to do things, for example: 258 | 259 | Components can be retrieved in two ways: 260 | 261 | ```csharp 262 | var transformComponent = myEntity.GetComponent(typeof(TransformComponent)) as TransformComponent; 263 | ``` 264 | 265 | or 266 | 267 | ```csharp 268 | var transformComponent = myEntity.GetComponent(); 269 | ``` 270 | 271 | I've been using the latter in these demos and I recommend you do too as it is faster and more typesafe because the type is checked at compiletime whereas the former is checked at runtime. 272 | 273 | Components can also be added in a few ways: 274 | 275 | ```csharp 276 | myEntity += new MyComponent(); 277 | myEntity += new MyOtherComponent(); 278 | ``` 279 | 280 | or 281 | 282 | ```csharp 283 | myEntity.AddComponents 284 | ( 285 | new MyComponent(), 286 | new MyOtherComponent() 287 | ); 288 | ``` 289 | 290 | or 291 | 292 | ```csharp 293 | IComponent[] componentCollection = new IComponent[] 294 | { 295 | new MyComponent(), 296 | new MyOtherComponent() 297 | }; 298 | 299 | myEntity.AddComponents(componentCollection); 300 | ``` 301 | 302 | I encourage you to read the [code](https://github.com/anthony-y/sharp-ecs/tree/master/SharpECS/Source) for SharpECS in order to get an understanding of how it works and the methods you can use for each class. 303 | 304 | # 305 | 306 | And that's the basics! Check back here every once in a while or "watch" the project on GitHub to recieve updates on the library itself and this documentation. And don't forget to star :smiley: -------------------------------------------------------------------------------- /SharpECS/Source/Entity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | using SharpECS.Exceptions; 6 | 7 | namespace SharpECS 8 | { 9 | public enum EntityState 10 | { 11 | Active, 12 | Inactive, 13 | Cached, 14 | } 15 | 16 | public sealed class Entity 17 | { 18 | /// 19 | /// Delegate for ComponentAdded and ComponentRemoved. 20 | /// 21 | /// 22 | /// 23 | public delegate void EntityComponentChanged(Entity entity, IComponent component); 24 | 25 | /// 26 | /// Events 27 | /// 28 | public event EntityComponentChanged ComponentAdded; 29 | public event EntityComponentChanged ComponentRemoved; 30 | 31 | public string Id { get; set; } 32 | 33 | /// 34 | /// The pool which this Entity resides in. 35 | /// 36 | public EntityPool OwnerPool { get; set; } 37 | 38 | /// 39 | /// A list of this Entity's components. 40 | /// 41 | public List Components { get; set; } 42 | 43 | /// 44 | /// A list of this Entitys children Entities. 45 | /// 46 | public List Children { get; set; } 47 | 48 | /// 49 | /// The Entity which this Entity is a child of 50 | /// Is null if this Entity is root 51 | /// 52 | public Entity Parent { get; set; } 53 | 54 | /// 55 | /// Walks up all the parents of this Entity and returns the top one 56 | /// This Entity is called "root" 57 | /// 58 | public Entity RootEntity 59 | { 60 | get 61 | { 62 | if (this.Parent == null) 63 | return this; 64 | 65 | var parent = Parent; 66 | 67 | while (parent != null) 68 | { 69 | if (parent.Parent == null) 70 | return parent; 71 | 72 | parent = parent.Parent; 73 | } 74 | 75 | throw new Exception($"Entity \"{Id}\" has no Root!"); 76 | } 77 | } 78 | 79 | /// 80 | /// Holds the current state of this Entity 81 | /// 82 | public EntityState State { get; internal set; } 83 | 84 | internal Entity(string id, EntityPool pool) 85 | { 86 | Id = id; 87 | 88 | if (pool == null) throw new IndependentEntityException(this); 89 | 90 | OwnerPool = pool; 91 | 92 | Components = new List(); 93 | Children = new List(); 94 | 95 | State = EntityState.Active; 96 | } 97 | 98 | /// 99 | /// Set this Entity's state to active, providing it isn't in the cache 100 | /// 101 | public void Activate() 102 | { 103 | if (!IsAvailable()) 104 | return; 105 | 106 | State = EntityState.Active; 107 | } 108 | 109 | /// 110 | /// Set this Entity's state to inactive, providing it isn't in the cache 111 | /// 112 | public void Deactivate() 113 | { 114 | if (!IsAvailable()) 115 | return; 116 | 117 | State = EntityState.Inactive; 118 | } 119 | 120 | /// 121 | /// Toggles this Entity on or off 122 | /// 123 | public void Switch() 124 | { 125 | if (!IsAvailable()) 126 | return; 127 | 128 | State = (State == EntityState.Active ? EntityState.Inactive : EntityState.Active); 129 | } 130 | 131 | public bool IsActive() 132 | { 133 | if (!IsAvailable()) 134 | return false; 135 | 136 | return (State == EntityState.Active); 137 | } 138 | 139 | public bool IsInactive() 140 | { 141 | if (!IsAvailable()) 142 | return false; 143 | 144 | return (State == EntityState.Inactive); 145 | } 146 | 147 | /// 148 | /// Checks if this already has "component" 149 | /// If it does, ComponentAlreadyExistsException. 150 | /// Otherwise, add the component to "Components". 151 | /// And fire the ComponentAdded event (if it's also not null) 152 | /// 153 | /// 154 | /// 155 | private IComponent AddComponent(IComponent component) 156 | { 157 | if (!IsAvailable()) 158 | return null; 159 | 160 | // If it has a component of the same type as "component". 161 | if (HasComponent(component.GetType())) 162 | throw new ComponentAlreadyExistsException(this); 163 | 164 | Components.Add(component); 165 | ComponentAdded?.Invoke(this, component); 166 | OwnerPool.ComponentAdded(this); 167 | 168 | return component; 169 | } 170 | 171 | /// 172 | /// Remove a component by generic type parameter and notify the appropriate Entity pool and Systems 173 | /// 174 | /// 175 | public void RemoveComponent() 176 | where T : IComponent 177 | { 178 | if (!IsAvailable()) 179 | return; 180 | 181 | if (!this.HasComponent()) 182 | throw new ComponentNotFoundException(this); 183 | 184 | IComponent componentToRemove = GetComponent(); 185 | 186 | Components.Remove(componentToRemove); 187 | ComponentRemoved?.Invoke(this, componentToRemove); 188 | OwnerPool.ComponentRemoved(this); 189 | } 190 | 191 | /// 192 | /// Remove a Component by a Type parameter and notify the appropriate Entity pool and Systems 193 | /// Uses runtime type checking to make sure the type you passed implements IComponent 194 | /// 195 | /// 196 | public void RemoveComponent(Type componentType) 197 | { 198 | if (!IsAvailable()) 199 | return; 200 | 201 | if (!componentType.IsComponent()) 202 | throw new Exception("One or more of the types you passed were not IComponent children."); 203 | 204 | if (!HasComponent(componentType)) throw new ComponentNotFoundException(this); 205 | 206 | IComponent componentToRemove = GetComponent(componentType); 207 | 208 | Components.Remove(componentToRemove); 209 | ComponentRemoved?.Invoke(this, componentToRemove); 210 | OwnerPool.ComponentRemoved(this); 211 | } 212 | 213 | /// 214 | /// Checks through Components for a component of type "T" 215 | /// If it doesn't have one, throw ComponentNotFoundException. 216 | /// Otherwise returns the component. 217 | /// 218 | /// 219 | /// 220 | public T GetComponent() 221 | where T : IComponent 222 | { 223 | if (!IsAvailable()) 224 | return default(T); 225 | 226 | var match = Components.OfType().FirstOrDefault(); 227 | 228 | if (match == null) throw new ComponentNotFoundException(this); 229 | 230 | return match; 231 | } 232 | 233 | /// 234 | /// Checks through Components for a component of type "componentType" 235 | /// If it doesn't have one, throw ComponentNotFoundException. 236 | /// Otherwise return the component. 237 | /// Uses runtime type checking to make sure "componentType" implements IComponent 238 | /// 239 | /// 240 | /// 241 | public IComponent GetComponent(Type componentType) 242 | { 243 | if (!IsAvailable()) 244 | return null; 245 | 246 | if (!componentType.IsComponent()) 247 | throw new Exception("One or more of the types you passed were not IComponent children."); 248 | 249 | var match = Components.FirstOrDefault(c => c.GetType() == componentType); 250 | if (match != null) return match; 251 | 252 | throw new ComponentNotFoundException(this); 253 | } 254 | 255 | /// 256 | /// Moves a component between this and "destination". 257 | /// If destination or the component are null, throw a ComponentNotFoundException. 258 | /// Otherwise add the component to the destination and remove it from this. 259 | /// 260 | /// 261 | /// 262 | public void MoveComponent(IComponent component, Entity destination) 263 | { 264 | if (!IsAvailable()) 265 | return; 266 | 267 | // If the component itself isn't null and its actually on "this". 268 | if (component == null && !HasComponent(component.GetType())) 269 | throw new ComponentNotFoundException(this); 270 | 271 | destination.AddComponent(component); 272 | Components.Remove(component); 273 | } 274 | 275 | /// 276 | /// Checks if "this" contains a component of type "TComponent". 277 | /// If it does, return true. Otherwise, return false. 278 | /// 279 | /// 280 | /// 281 | public bool HasComponent() 282 | where TComponent : IComponent 283 | { 284 | if (!IsAvailable()) 285 | return false; 286 | 287 | var match = Components.Any(c => c.GetType() == typeof(TComponent)); 288 | 289 | if (match) return true; 290 | else return false; 291 | } 292 | 293 | /// 294 | /// Checks if "this" contains a component of type "TComponent". 295 | /// If it does, return true. Otherwise, return false. 296 | /// Uses runtime type checking to make sure "componentType" implements IComponent 297 | /// 298 | /// 299 | /// 300 | public bool HasComponent(Type componentType) 301 | { 302 | if (!IsAvailable()) 303 | return false; 304 | 305 | if (!componentType.IsComponent()) 306 | throw new Exception("One or more of the types you passed were not IComponent children."); 307 | 308 | var cMatch = Components.Any(c => c.GetType() == componentType); 309 | if (cMatch) return true; 310 | 311 | return false; 312 | } 313 | 314 | /// 315 | /// Check to see if this Entity has all the components in a collection 316 | /// If it does, return true, otherwise return false. 317 | /// 318 | /// 319 | /// 320 | public bool HasComponents(IEnumerable types) 321 | { 322 | if (!IsAvailable()) 323 | return false; 324 | 325 | foreach (var t in types) 326 | if (!HasComponent(t)) return false; 327 | 328 | return true; 329 | } 330 | 331 | /// 332 | /// Remove all components. 333 | /// 334 | public void RemoveAllComponents() 335 | { 336 | if (!IsAvailable()) 337 | return; 338 | 339 | for (int i = Components.Count - 1; i >= 0; i--) 340 | RemoveComponent(Components[i].GetType()); 341 | 342 | Components.Clear(); 343 | } 344 | 345 | /// 346 | /// RemoveAllComponents(), reset Id, OwnerPool. 347 | /// 348 | public void Reset() 349 | { 350 | if (!IsAvailable()) 351 | return; 352 | 353 | RemoveAllComponents(); 354 | 355 | for (int i = 0; i < Children.Count; i++) 356 | { 357 | var child = Children[i]; 358 | OwnerPool.DestroyEntity(ref child); 359 | } 360 | 361 | Children.Clear(); 362 | 363 | this.Id = string.Empty; 364 | this.OwnerPool = null; 365 | this.State = EntityState.Cached; 366 | } 367 | 368 | /// 369 | /// Add all the components in a collection to this Entity 370 | /// 371 | /// 372 | public void AddComponents(IEnumerable components) 373 | { 374 | if (!IsAvailable()) 375 | 376 | foreach (var c in components) 377 | AddComponent(c); 378 | } 379 | 380 | /// 381 | /// Allows an infinite(?) number of components as parameters and adds them all at once to "this". 382 | /// 383 | /// 384 | public void AddComponents(params IComponent[] components) 385 | { 386 | if (!IsAvailable()) 387 | return; 388 | 389 | foreach (var c in components) 390 | AddComponent(c); 391 | } 392 | 393 | /// 394 | /// Moves this Entity to another EntityPool (if it isn't null) 395 | /// 396 | /// 397 | public void MoveTo(EntityPool pool) 398 | { 399 | if (this == null) 400 | return; 401 | 402 | if (pool == null) 403 | throw new NullEntityPoolException(pool); 404 | 405 | pool.AddEntity(this); 406 | var me = this; 407 | OwnerPool.DestroyEntity(ref me); 408 | OwnerPool = pool; 409 | } 410 | 411 | /// 412 | /// Creates and returns a new Entity as a child under this Entity 413 | /// 414 | /// 415 | /// 416 | /// 417 | public Entity CreateChild(string childId, bool inheritComponents=false) 418 | { 419 | if (!IsAvailable()) 420 | return null; 421 | 422 | var child = OwnerPool.CreateEntity(childId); 423 | 424 | child.Parent = this; 425 | if (inheritComponents) { child.AddComponents(Components); } 426 | 427 | Children.Add(child); 428 | 429 | return child; 430 | } 431 | 432 | /// 433 | /// Adds an existing Entity as a child to this Entity 434 | /// 435 | /// 436 | /// 437 | public Entity AddChild(Entity entity) 438 | { 439 | if (!IsAvailable()) 440 | return null; 441 | 442 | entity.Parent = this; 443 | Children.Add(entity); 444 | 445 | return entity; 446 | } 447 | 448 | /// 449 | /// Get a child by ID 450 | /// 451 | /// 452 | /// 453 | public Entity GetChild(string childId) 454 | { 455 | if (!IsAvailable()) 456 | return null; 457 | 458 | return Children.FirstOrDefault(c => c.Id == childId); 459 | } 460 | 461 | /// 462 | /// Returns the whole "family tree" of this Entity (all children, "grandchildren", etc.) 463 | /// 464 | /// 465 | public IEnumerable FamilyTree() 466 | { 467 | // Thanks @deccer 468 | var childSelector = new Func>(ent => ent.Children); 469 | 470 | var stack = new Stack(Children); 471 | while (stack.Any()) 472 | { 473 | var next = stack.Pop(); 474 | yield return next; 475 | foreach (var child in childSelector(next)) 476 | stack.Push(child); 477 | } 478 | } 479 | 480 | /// 481 | /// Lets you add "component" to "entity" with a += 482 | /// 483 | /// 484 | /// 485 | /// 486 | public static Entity operator + (Entity entity, IComponent component) 487 | { 488 | if (!entity.IsAvailable()) 489 | return null; 490 | 491 | if (entity != null && component != null) 492 | { 493 | entity.AddComponent(component); 494 | return entity; 495 | } else 496 | { 497 | throw new NullReferenceException(); 498 | } 499 | } 500 | 501 | public bool IsAvailable() 502 | { 503 | return State != EntityState.Cached; 504 | } 505 | } 506 | } --------------------------------------------------------------------------------