Abstract
ECS, Entity–component–system, is a structure that defines a way to easily manage the code hierarchy during the game development process.
In the game, every object is an Entity, including players, enemies, and obstacles. Each Entity can have multiple Components, which only contain feature data. For Systems, each system performs its specific tasks repeatedly and processes fixed tasks.
Entity
An Entity can be simply understood as an instance object. Generally speaking, each entity has its own unique ID to distinguish them from one another. For example, when you need to create 10 enemies, you can create an enemy entity and assign a different ID to each entity to differentiate between the individual enemies. In addition to the ID, the entity will store what this entity can do. Please note this "can," which means that this entity is capable of doing something; it does not imply that it will do it or how it will do things. These things that can be do are called Components.
For example:
The player has a position, attack, health points, and can be controlled by the user.
Enemy can have a position, attack, and health points.
Block can have a position
The structure of these three entities is as follows.
Player: position attack health controlled
Enemy: position attack health
Block: position
Component
Components play the role of a database in ECS. Each Component signifies for only one thing. The data needed to perform this task is stored within the component. A Component cannot do anything; it merely stores the data required for that task.
For example, the Position Component needs to store the x and y coordinate data. The HealthPoint Component needs to store the quantity of health points.
System
System is the part that handles the game logic in the game. System simply executes its responsible tasks repeatedly, doing things over and over again.
How do they work together?
I will first use a real-world example as an introduction. Suppose there is a person who is continuously hammering nails into a wooden board.
Then, I need to create a Component called "Hammering Nails into Wood," which stores two pieces of data: the position of the hammer and the position of the target nail.
Next, I will create an entity called "Person," who has a Component named "Hammering Nails into Wood."
After that, I need to write a system that, when an entity has this "Hammering Nails into Wood" Component, will read the positions of both the hammer and the target nail from the Component and perform the action of hammering nails into wood.
In this case, if there is another entity car, since the car does not hammer nails into wood, the car entity does not have a "Hammering Nails into Wood" Component. Therefore, when the system processes the car entity, it will not do anything because there is no "Hammering Nails into Wood" Component.
In general, Entity focuses on distinguishing different instance objects and what this object can do. Component focuses on recording the things needed to perform a certain action. System is responsible for do that action.
Let's build a simple ECS in MonoGame
This section defines the core ECS structure.
public class Entity
{
public int Id { get; private set; }
private Dictionary<Type, IComponent> components = new Dictionary<Type, IComponent>();
public Entity(int id)
{
Id = id;
}
public void AddComponent(IComponent component)
{
components[component.GetType()] = component;
}
public T GetComponent<T>() where T : IComponent
{
if (components.TryGetValue(typeof(T), out IComponent component))
{
return (T)component;
}
return null;
}
public bool HasComponent<T>() where T : IComponent
{
return components.ContainsKey(typeof(T));
}
}
public interface ISystem
{
public abstract void Update(GameTime gameTime, List<Entity> entities);
}
public interface IComponent
{
}
Here, a TransformComponent is defined, and entities that possess this component have the ability to define their own position.
public class TransformComponent : IComponent
{
public Vector2 Position;
public float Rotation;
public Vector2 Scale;
public TransformComponent(Vector2 position, float rotation, Vector2 scale)
{
Position = position;
Rotation = rotation;
Scale = scale;
}
}
MovementSystem
The system will continuously check whether each entity can move, that is, if it contains a TransformComponent. If it does, the movement operation will be executed.
public class MovementSystem : ISystem
{
public override void Update(GameTime gameTime, List<Entity> entities)
{
float deltaTime = (float)gameTime.ElapsedGameTime.TotalSeconds;
foreach (var entity in entities)
{
if (entity.HasComponent<TransformComponent>())
{
var transform = entity.GetComponent<TransformComponent>();
transform.Position += new Vector(1,1);
}
}
}
}
Load all systems in MonoGame and run each system during each cycle of MonoGame.
public class Game1 : Game
{
private GraphicsDeviceManager graphics;
private List<Entity> entities = new List<Entity>();
private List<SystemBase> systems = new List<SystemBase>();
private int entityCounter = 0;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}
protected override void Initialize()
{
// Load all system
systems.Add(new MovementSystem());
// Create Player entity and let it can move
Entity player = new Entity(entityCounter++);
player.AddComponent(new TransformComponent(new Vector2(100, 100), 0f, Vector2.One));
entities.Add(player);
base.Initialize();
}
protected override void Update(GameTime gameTime)
{
//run all system
foreach (var system in systems)
{
system.Update(gameTime, entities);
}
base.Update(gameTime);
}
}
Comments | NOTHING