Versus Series #1 - ECS vs OOP
WELCOME TO THE VERY FIRST VERSUS SERIES POST! VERSUS SERIES IS WHERE WE WILL ATTEMPT TO JOIN FLAME WARS THAT BURN AND DIVIDE THE INTERNET AND PROGRAMMERS UP UNTIL NOW.
UPDATES
Source code is now available here
FOREWORD
Some of you might be knowledgeable about these things and might argue with me that ECS and OOP are two very different things. I will not attempt to argue for I agree with that point.
Yes, OOP is a programming paradigm while ECS is an architectural pattern under the Data-Oriented Design (DOD). As a game developer, and as most context of my posts, I can only go to where my experiences has led me. So my case is that it is safe to tackle ECS in comparison to OOP because ECS is a well-known design that follows the DOD. ECS is what most game developers use as an alternative to OOP.
Lastly, even though this is a so-called versus series, I will not be discreet in promoting and showing my favor towards ECS.
With that possible misunderstanding out of the way and cleared out, let us proceed.
INTRODUCTION
What is ECS? What is OOP?
- ECS is an acronym for
Entity Component System
. - OOP is an acronym for
Object Oriented Programming
(Further definitions and details about the topics are up to the readers to research)
What about them? Why are they important?
ECS and OOP are only two of the many ways we as game developers follow to have a solid foundation and approach into establishing our code. Think of these as principles we ought to fully understand and live by in order to have a smooth, stable, and strict life. Perhaps a more appropriate term for it to compare is the word lifestyle.
Now, these two have a different concept and model so using them at the same time, like a hybrid usage, is not necessarily wrong but it can lead to faulty and messy behaviors and whatnot because the complexities of both will add together. There are cases where ECS would be a better fit to use while there are cases befitting OOP as well.
For game development, ECS would be the way to go. High-profile game developers greatly encourage the application and design following ECS instead of OOP as it provides more flexibility and better performance.
For the brevity, I hope, of this post, I will not expound on the design and differences of these two topics. A quick search in your favorite search engine or in YouTube will provide many talks and articles about it.
I encourage watching Mike Acton’s talk about ECS.
For this post, and the following weeks, we will be making a simple game that we will implement both in the style of ECS, and in the style of OOP. Afterwards, in great hopes, you will decide to go and start learn ECS (do you feel my bias towards ECS?).
Why Should I Read This?
Well, there is a high chance that you have learned and have OOP-oxygen running through your veins as it is taught widely. If you have Java sigh in your course, yeah, you are already breathing and living in the OOP-land (let’s proceed before I start this rant for my hatred for that language).
You should study more in-depth about this topic after reading this post. After that proceed to trying it out and implementing it even in the simplest form of games like pong
. There is nothing better than learning through experience.
Also, it is never too late to change from what you are really used to (if you are really used to one and want to move to the other). I can safely say that if you have a long time with OOP, you have for sure tasted the overwhelmingly disaster and complexity the paradigm gives. It is simple and basic when you are just beginning, but as the project grows on, it exponentially turns to a disaster.
A Little Background
ECS is a better way to learn and teach fundamental logic compared to OOP
Now this might be a bold claim, but I will stand by it. Most who are just starting out in learning programming find it difficult to express what they want to achieve with a computer for the lack of proper and fundamental approach to logic. Most have this very complex desire and control that breaking it down to simple step-by-step procedures is hard.
OOP claims that it is easier to think in terms of objects and inheritance as it is observable in our world and environment. Indeed, it is easier to understand things when you can observe it, but sometimes our observation is faulty and more of an assumption rather than basing on facts. So to make objects, you do inheritance. Think of it as top-to-bottom hierarchy.
ECS does this the other way around, it makes us focus and use pure data. ECS teaches us that we should prioritize composition over inheritance, so instead of thinking what behaviors and traits an entity (object in OOP) has in common with others, figure out what are the data present in each of the entity (which in ECS would be components).
For example, we want to make a moving ball and a stationary ball.
In OOP it would be:
- we need a ball class which has the following properties:
- position in the world
- radius (because the ball is round)
- speed (because they can move)
- we need the following behaviors/traits:
- draw the ball
- move the ball
- we will make two objects out of that ball class
ball A
will be moving, so we set the speed property of the ball to something like100
ball B
will not be moving, so we set the speed propery of the ball to0
- each object (ball) will be the one responsible for their own behavior like drawing and moving.
The ball class
in Lua would look something like this:
local BallClass = class()
function BallClass:new(x, y, radius, speed, can_move)
self.x = x
self.y = y
self.radius = radius
self.speed = speed
self.can_move = can_move
end
function BallClass:update(dt)
--handles the moving
if self.can_move then
self.x = self.x + self.speed * dt
end
end
function BallClass:draw()
--for drawing
love.graphics.circle("fill", self.x, self.y, self.radius)
end
return BallClass
And using such is like so:
local BallClass = require("ball_class")
local ball_a, ball_b
function love.load()
ball_a = BallClass:new(128, 128, 64, 100, true) --moving ball
ball_b = BallClass:neew(320, 320, 64, 0, false) --stationary
end
function love.update(dt)
ball_a:update(dt)
ball_b:update(dt)
end
function love.draw()
ball_a:draw()
ball_b:draw()
end
In ECS it would be:
- we need
position
,radius
,speed
components - we will give
entity A
theposition
,radius
, andspeed
components - we will give
entity B
theposition
, andradius
components - we will make a system called
draw ball
, where it will draw all entities that have theposition
andradius
components (both entities). - we will make a system called
move ball
, where it will move all entities that have theposition
andspeed
components (only one entity).
Here’s what it looks like in Lua:
--components
local position = component(function(e, x, y)
e.x = x
e.y = y
end)
local radius = component(function(e, radius) e.radius = radius end)
local speed = component(function(e, speed) e.speed = speed end)
--system
local pool_ball = {"position", "radius"}
function Draw:draw()
for _, e in ipairs(pool_ball) do
local pos = e.position
love.graphics.circle("fill", pos.x, pos.y, e.radius.radius)
end
end
local pool_move = {"position", "speed"}
function Move:update(dt) --not exclusive to Ball only
for _, e in ipairs(pool_move) do
local pos = e.position
pos.x = pos.x + e.speed.speed * dt
end
end
--usage
local world = new_world()
local draw_system = Draw()
local move_system = Move()
local ball_a, ball_b
function love.load()
world:register_system(draw_system)
world:register_system(move_system)
ball_a = entity()
:give("position", 128, 128)
:give("radius", 64)
:give("speed", 100)
ball_b = entity()
:give("position", 320, 320)
:give("radius", 64)
world:register_entities(ball_a, ball_b)
end
function love.update(dt) emit("update", dt) end
function love.draw() emit("draw") end
What? ECS has more code than OOP?
Yes, but which among the two is more flexible and is easier to manage? With OOP, you might need to turn of some properties in an object to change it behavior, like set its can_move
property to false to make it stop moving.
But with ECS, you just remove the speed
component then it will not be processed anymore by the Move
system. See? Way easier, just add and remove data (component) and let your systems do the work without having to change it everytime you want a new behavior.
Here are the problems with OOP:
- each object that inherits from the
ball
class need to have the properties:position
,radius
, andspeed
regardless if they will move or not which is expensive in memory. - each object has their own methods which need to be called by them
draw
andmove
. Even though one ball will not move, that ball object still needs to have that method in memory. - adding a new property or behavior will likely mess up the inheritance. For example, what if you want to make the ball fly? You will add more properties and you will add more behaviors, which will be exclusive to ball class only unless you superclass it as another class, which will start to show complexity.
Here are the problems (actually not) with ECS:
- you need to think purely in terms of data to make components. Remember, components contain data only, no logic!
- systems will deal with the logic by processing valid combination of pools.
- if you want to make a ball fly, just add a
fly
orgravity
(with negative value) component. Make afly system
(which could be be used by other flying entities, not exclusive for balls)
Now, what is easier to teach and learn? Making classes and blueprints and understanding inheritance or just be logical with facts and data?
If you are an aspiring game developer, I recommend starting with learning through the use of frameworks instead of engines. Why? Because engines somehow dictate you on how to structure and do things. Whereas with frameworks, you are at liberty to do what you want and to do it the way you want it to.
Frameworks are great introduction to game development as you will learn the internal workflow in a game.
You know what, I’ll keep this game engines vs game frameworks
topic for the next time.
Getting Started
For now, let us just start by setting up our development environment. Here are the things we will need:
(DISCLAIMER: I will be using GNU/Linux as my OS, if you are on other platforms, I will entrust doing the procedures to you for now)
(We will add more modules as we go on if we need)
Linux
- using your package manager, download and install
love
- For Arch-based, there is
pacman -S love
- For Arch-based, there is
- now go into whatever directory you want and create the following. In my case it will be:
/home/user/game_oop
and/home/user/game_ecs
- download the
concord
andclassic
modules/libraries somewhere in your system. Using thedownload as ZIP
option.
From now on, we will refer to them as game_oop
and game_ecs
respectively.
Windows
Coming soon!
OOP
- Extract
classic
and put the extractedclassic/classic.lua
file inside thegame_oop/modules/
directory. - Create a
classes
directory inside thegame_oop
directory. - Create
main.lua
andconf.lua
files inside thegame_oop
directory.
So now your game_oop
directory should look like this:
--game_oop/
----modules/
--------classic.lua
----classes/
--------base/
----main.lua
----conf.lua
That’s it for now!
ECS
- Extract
concord
and put the extractedconcord/concord
directory inside thegame_ecs/modules/
directory. - Create a
worlds
directory inside thegame_ecs
directory. - Create a
systems
directory inside thegame_ecs
directory. - Create a
components
directory inside thegame_ecs
directory. - Create
main.lua
andconf.lua
files inside thegame_ecs
directory.
So now your game_ecs
directory should look like this:
--game_ecs/
----modules/
--------concord/
-----------component.lua
-----------components.lua
-----------entity.lua
-----------init.lua
-----------list.lua
-----------pool.lua
-----------system.lua
-----------type.lua
-----------utils.lua
-----------world.lua
----worlds/
----systems/
----components/
----main.lua
----conf.lua
That’s it for now!
Testing
- Now to make sure everything is working, go to
game_oop
directory then try to launchlove
there with the commandlove .
, you should see the empty/no-game screen of love. No error means all is good. - Now do the same for the
game_ecs
directory.
Again, if you are on different platforms, making love
work is up to you (for now)
What To Expect?
For next post, we will start making a simple game that follows the OOP paradigm.
Stay tuned via RSS or follow me on Twitter