├── 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 [](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 | }
--------------------------------------------------------------------------------