Using C# and XNA 4, I've made the decision to go for an entity-component based design for my game after reading such posts as this and this, but I'm now struggling to find how to retrieve components once they've been added to an entity.
Some articles I've read say that when a component is created, register it with the system that will act upon it, using a messaging system. A drawback I see here is that if a system needs 2 components to act (say CollideComponent and PositionComponent) then this gets complicated (and I can't figure how it would work).
The other possibility, and the one that I'm currently pursuing, is to have a system ask whether an entity has a particular component when it needs it. However I've not seen any code examples, only theory on the net, so I've been trying to muddle it together. Some code here might explain how far I've got :
First a System
:
public class RenderingSystem : IGameSystem
{
public void PerformAction(IQueryable gameObjects)
{
foreach (var gameObject in gameObjects)
{
RenderableGameComponent renderableGameComponent = gameObject.GetComponent(ComponentType.Renderable);
if (renderableGameComponent != null)
{
this.spriteBatch.Draw(renderableGameComponent.Texture, renderableGameComponent.DrawablePosition, Color.White);
}
}
}
}
Now the actual GameObject
:
public class GameObject : IGameObject
{
private List gameComponents;
private ComponentType componentTypes;
public void AddComponent(IGameComponent component)
{
this.gameComponents.Add(component);
this.componentTypes = this.componentTypes | component.ComponentType;
}
public T GetComponent(ComponentType componentType)
{
return (T)this.gameComponents.FirstOrDefault(gc => gc.ComponentType == componentType);
}
}
And finally the Component
and ComponentType
enumeration :
public class RenderableGameComponent : IGameComponent
{
public Texture2D Texture { get; set; }
public Vector2 DrawablePosition { get; set; }
public ComponentType ComponentType
{
get { return ComponentType.Renderable; }
}
}
[Flags]
public enum ComponentType
{
Renderable = 1,
Updateable = 2,
Position = 4,
}
Now as you might be able to see, I'm trying to use bitwise operations on the ComponentType
enumeration that every Component
of each GameObject
has. The reason being I thought this would be a much cleaner approach when trying to get the required Component
out of the GameObject
when asked by a System
(also refer to link 1 above).
I'm trying to get away from passing in a type, and doing an iteration over all the Component
s of a GameObject
. eg:
foreach (var component in this.GameComponents)
{
if (component.GetType() == requestedType) { return component; }
}
This is because when there are a few hundred bullets on the screen and a CollidableSystem
is created, it will massively slow the game down checking every type. I've had experience with this when creating a previous game, but admittedly, that was using an inheritance based design system.
It worries me that for each System
that is created, it will have to iterate over every GameObject
every tick asking the same question every time, "Do you have the component I am looking for ?" and still doing a lot of casting and null checks.
Now my question is this. I want to know whether there is a de facto design pattern for getting Component
s from GameObject
s that don't require null checks or casting ? Someway a System
will say "Give me this component, I know that you have it" and not bother wasting time asking those GameObject
s that don't have the Component
. Tell, don't ask.
Answer
Systems maintain a list of the entities they're interested in.
Systems are just created once when the game is initialized. If you're creating systems with entities already in play, you're doing it wrong :).
All the systems your game will use are to be created before any entities are created. When a new entity is created, each system will check to see if it's interested in that entity for processing. This is where your bitwise check comes into play, very fast. When an entities components change, each system should check that entity again to see if it's still interested in processing it. See an example of this check in the Artemis Framework code with the check(Entity e)
function.
Now, when each system processes, it just iterates over the list of entities it has stored.
Retrieving components is done with nested hash maps. The first layer is a map of maps indexed by the component ID, likely the long used for bitwise identification would work well here. The second layer is a map of components indexed by the entity ID. This would look something like the following in tree format:
-Renderable (1)
|-- Entity 1 (1)
|-- Instance of Renderable
|-- Entity 2 (2)
|-- Instance of Renderable
-Updateable (2)
|-- Entity 1 (1)
|-- Instance of Updateable
-Position (4)
|-- Entity 1 (1)
|-- Instance of Position
|-- Entity 2 (2)
|-- Instance of Position
Where Entity 1 is an entity with Renderable, Updateable and Position components and Entity 2 is an entity with Renderable and Position components.
These hash maps have constant access times and are very easy to navigate.
So when processing entities you can easily retrieve a component with something like the following (similar to the Artemis method):
Component getComponent(Entity e, ComponentType type) {
HashMap components = componentsByType.get(type.getLongID());
if(components != null) {
return components.get(e.getID());
}
return null;
}
There's still a null check, but you can get rid of that if you fill all your componentsByType
maps with empty maps for each component. Enabling you to shorten the look up to:
Component getComponent(Entity e, ComponentType type) {
return componentsByType.get(type.getLongID()).get(e.getID());
}
It's still possible this will return null if the entity doesn't have that component type. It's up to you if you want to check for that, but if the system added the entity based on its components, that component should be there, so it's up to you if you want to take that risk.
No comments:
Post a Comment