Taking the best out of something and merging it with the best out of something else usually results in interesting outcomes, this is one of those cases.
Inheritance is such a powerful tool, the beauty of inheritance is that allows us to create new objects from already existing objects, they encapsulate all the old goodness (sometimes not so good) plus the new functionality. This makes it easy when sorting classes from their bases, such a list of base classes, but in reality is a list of a lot of different classes that derive from one, in my opinion, virtual functions are fantastic when we wanted to modify behaviors, or when giving special instructions to derived classes, etc, all this was the beauty of Inheritance, but it soon became a mess when adding more classes on top of classes, going from base to base modifying things in order to allow some general implementation.
Still there was plenty of good in the Inheritance pattern, and this is why while coding this new Entity system engine I decided to think over if I wanted to do labels entities or object entities.
When it comes to memory usage, making entities as labels sounds like the way to go, simply because there was no need to allocate memory for a new object to be placed, making objects that hold data from data that is already in memory might sound silly, at least in this case.
An Entity label is just a tag, nothing almost, but it requires some management, to live in some sort of collection, and this sometimes could be hard to debug because you are meant to have many labels with many different components in them, maybe this is not the case when it comes to MMOs but I can't speculate since I have no experience on them.
I am taking all this and putting it into the things I do, making game engines for local usage. Making an Entity object allows you to do simple things such as "register" it by itself to your self managed Factory, also, entities will have the advantage of being able to have minimal but useful implementation, such as cloning themselves, resetting values to default(why this is a good idea, coming up), etc.
Single Inheritance (double in C#, since everything inherits from "object" by default) was the pretty balance I wanted, objects that require a single base class to work with anything, nothing else, no more complicated hierarchies, just one base, multiple outcomes, another base, other multiple outcomes.
Looking at the core design of the Entity System, we have... labels (Entities) which are not objects, systems (meddle with the components), and the components themselves, besides some managers making sure things work smooth, such as the game manager, system manager, component managers maybe? etc.
To not have Inheritance made me feel like I couldn't be flexible when coding and linking all these elements together, I started to ponder if this was because I was used to OOP or because I didn't quite understand just yet what an Entity system was, soon after I decided to not really pay attention to this and just went and said "what's good in OOP, and what's good in ES".
In with inheritance, I made base classes for my most basics objects, base entities, base systems, base components, I could finally start doing some more than just thinking about the design and why I wanted to stay away from inheritance and just embraced what felt good and natural.
so for the Entities, I wanted to give it a meaning to be an object instead of just a label, so I made sure that this object wasn't just a waste of time, but rather a useful way to manage all my entities and to make it easy to work on them instead of just creating more and more, here's how my entity class looks for now:
public class Entity { #region fields ListBeing able to make my entity into an object allowed me all of this very basic functionality, something that maybe a manager would have to do if it was just a label, regardless, this class is meant to be inherited as you can tell, that the type of entity is specified in the constructor, take for example this little player class I made for testing which inherits from Entity:m_Components = new List (); List m_AffectingSystems = new List (); Type m_ThisType = null; #endregion #region properties public List Components { get { return m_Components; } set { m_Components = value; } } public Type GetThisType { get { return m_ThisType; } } #endregion #region constructor public Entity(Type thisType) { m_ThisType = thisType; } #endregion #region methods /// /// adds the components into the list, only if the list doesn't contain them already. /// /// public void AddComponent(params BaseComponent[] components) { for (int i = 0; i < components.Count(); i++) { bool toAdd = true; for (int a = 0; a < m_Components.Count; a++) { if (m_Components[a].ID == components[i].ID) { toAdd = false; } } if (toAdd) { components[i].Owner = this; m_Components.Add(components[i]); } } } //mutual reference, between entity and systems. public void AddSystem(params BaseSystem[] systems) { for (int i = 0; i < systems.Count(); i++) { if (!m_AffectingSystems.Contains(systems[i])) { m_AffectingSystems.Add(systems[i]); systems[i].AddEntity(this); } } } ////// Perform your component resetting here. /// public virtual void ResetParameters() { } ////// once the entity dies, or is not required anymore, it will go back into the pool it came from, that way it can be used again /// public void ReturnToFactory() { ResetParameters(); for (int i = 0; i < m_AffectingSystems.Count; i++) { m_AffectingSystems[i].RemoveEntity(this); } EntityFactory.Return(this); } #endregion }
public class Player : Entity { #region fields #endregion #region properties #endregion #region constructors public Player() : base(typeof(Player)) { Initialize(); } #endregion #region methods public void Initialize() { if (this.Components.Count > 0) { this.Components.Clear(); } else { //animation portion AnimationCO animation = new AnimationCO(); animation.Texture = TexturePool.GetTexture("Player"); animation.AddNewAnimation(42, 64, 42, 0, 5, "WalkingDown", 200.0f, 200.0f, 200.0f, 200.0f, 200.0f); animation.AddNewAnimation(42, 64, 0, 0, 1, "Idle", 200.0f); animation.AddNewAnimation(42, 64, 42, 64, 5, "WalkingLeft", 200.0f, 200.0f, 200.0f, 200.0f, 200.0f); animation.AddNewAnimation(42, 64, 42, 128, 5, "WalkingUp", 200.0f, 200.0f, 200.0f, 200.0f, 200.0f); animation.AddNewAnimation(42, 64, 42, 192, 5, "WalkingRight", 200.0f, 200.0f, 200.0f, 200.0f, 200.0f); //position portion PositionCO position = new PositionCO(); position.Position2D = new Vector2(0.0f, 0.0f); //input portion InputCO input = new InputCO(); input.Index = PlayerIndex.One; input.AddInput(Buttons.LeftStick, 0.1, PlayerIndex.One, Global.TheActions.MOVE, true); input.AddInput(Buttons.A, 0.0, PlayerIndex.One, Global.TheActions.DASH, false, 300.0); input.AddInput(Buttons.X, 0.0, PlayerIndex.One, Global.TheActions.RUN, true); //action portion ActionCO action = new ActionCO(); this.AddComponent(animation, position, input, action); } } public override void ResetParameters() { Initialize(); } #endregion }Take in consideration that this Player class is just for testing, but also take a look at some of the things going on, you can tell some of what's happening in the engine by just looking at how my entities are being made, also a quick note, "CO" is what I use for mention "Component". Going back to the simple implementation, thanks to the "ResetParams" we can simply reset the parameters back to it's original form, isn't that useful for re usability? The Initialization can be ignored really, what's important here is that the constructor passes it's own type to the base entity, thanks to this we are going to be able to make new instances of players very easily. "Why..?" Here's the other pretty part of having the Entities as objects, The Entity Factory:
public class EntityFactory { #region fields static EntityFactory m_EntityFactory = null; static Dictionary> m_RegisteredEntities = new Dictionary >(); static ComponentGetter m_PositionGetter = new ComponentGetter (); #endregion #region constructors private EntityFactory() { } #endregion #region methods public static EntityFactory Initialize() { if (m_EntityFactory == null) { m_EntityFactory = new EntityFactory(); } return m_EntityFactory; } #endregion #region creational methods /// /// creates an entity into the world. /// /// the type of entity, this type needs to be within the namespace./// where in the world you want to create this new entity (applicable only to entities that require position)/// which systems are going to affect the entity.
public static void CreateEntity(Type entityType,Vector3 position, params BaseSystem[] theSystems) { //checks if the current type of entity exists in the pool. if (m_RegisteredEntities.ContainsKey(entityType)) { //exists in the pool, use it. if (m_RegisteredEntities[entityType].Count > 0) { Entity CreationalVessel = m_RegisteredEntities[entityType][0]; CreationalVessel.AddSystem(theSystems); FixPosition(CreationalVessel, position); m_RegisteredEntities[entityType].Remove(CreationalVessel); } //doesn't exist in the pool, make a new one and use it. else { Entity CreationalVessel = (Entity)Activator.CreateInstance(entityType); CreationalVessel.AddSystem(theSystems); FixPosition(CreationalVessel, position); } } else //if the type of entity doesn't exists, then it will register it and try again. { Entity vessel = (Entity)Activator.CreateInstance(entityType); m_RegisteredEntities.Add(vessel.GetThisType, new List ()); m_RegisteredEntities[vessel.GetThisType].Add(vessel); CreateEntity(entityType, position, theSystems); } } /// /// re registers or registers for the first time e /// /// public static void Return(Entity returnee) { if (m_RegisteredEntities.ContainsKey(returnee.GetThisType)) { m_RegisteredEntities[returnee.GetThisType].Add(returnee); } else { //throw EntityCreatedOutOfWorldException(); } } ////// fixes the position of the Entity only if it contains a position component. /// /// /// public static void FixPosition(Entity entity, Vector3 position) { PositionCO vessel = m_PositionGetter.GetComponent(entity); if (vessel != null) { vessel.Position3D = position; } } #endregion }
As you maybe noticed in the Entity base class, there was this function "return to factory", this is a flyweight design pattern, entities are going to be created only if they need to, at some point you will not require to reallocate memory to make new entities, you will just start using the ones that had been made and use before, even though this is a work in progress (cause at some point we want to limit the amount of objects instantiated at once) it is very functional.
with this design pattern, we don't even need to concern ourselves in run time to allocate the new entities we are using, we just need to create them, give them their rules (the systems) and let them be. if you have to do this "Entity myEntity = new Entity();" somewhere outside the engine, then it will give you problems, or it will simply be ignored, depends on you.
so to the bottom line, for the purpose to have a data driven component engine, I have decided to embrace the minimum basics of object oriented programing, inheritance to it's minimal, so powerful.
Later on I will talk about the base systems and components.
No comments:
Post a Comment