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 like 100
    • ball B will not be moving, so we set the speed propery of the ball to 0
    • 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 the position, radius, and speed components
  • we will give entity B the position, and radius components
  • we will make a system called draw ball, where it will draw all entities that have the position and radius components (both entities).
  • we will make a system called move ball, where it will move all entities that have the position and speed 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, and speed 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 and move. 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 or gravity (with negative value) component. Make a fly 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
  • 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 and classic modules/libraries somewhere in your system. Using the download as ZIP option.

From now on, we will refer to them as game_oop and game_ecs respectively.

Windows

Coming soon!


OOP

  1. Extract classic and put the extracted classic/classic.lua file inside the game_oop/modules/ directory.
  2. Create a classes directory inside the game_oop directory.
  3. Create main.lua and conf.lua files inside the game_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

  1. Extract concord and put the extracted concord/concord directory inside the game_ecs/modules/ directory.
  2. Create a worlds directory inside the game_ecs directory.
  3. Create a systems directory inside the game_ecs directory.
  4. Create a components directory inside the game_ecs directory.
  5. Create main.lua and conf.lua files inside the game_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 launch love there with the command love ., 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