One of the common nuisances, for some, with maintaining and using a database is the utmost care and consideration for existing data, albeit old. You see, the database schemas should be structured in a way that is similar and as close to perfection as possible.
In this context, let us say that the following traits describe perfection:
Well, throw all of these out the window since we are using a non-relational database or NoSQL called MongoDB which gives developers the dilemma of making a very weird and off-putting decision to choose between the two computer science tradeoffs, performance vs. storage.
Do you want fast queries? Avoid lookups, which means embedding documents instead of references, which results in more storage being used.
Do you want no data duplication and easily contextualized data? Then suffer the slow queries.
Also, writing queries in JSON-similar syntax, with or without the aggregation pipeline, is so terrible. There is a reason—actually a ton of them—why Javascript is dreaded by a lot of good programmers.
INSANE!
]]>When hardware fails, the developers panic.
What do you do when you, as a developer, lose your companion? Good thing that you still have a smaller and more powerful magical rock of a device called a smartphone.
We have all seen recently that there are students who code using their smartphones. There are also fellow game developers in the löve discord community who went to great lengths to make their developer experience good in the mobile context.
Not surprisingly, setting up a Linux focused environment on mobile is very easy, thanks to the wonderful people who made Termux.
As a Linux (I USE ARCH BTW) and terminal developer, I find it very amusing and convenient for this app to exist. It has its own package manager, key shortcuts for ESC (I USE VIM BTW), and so on.
I managed to set up my neovim configuration in a breeze.
I found out that my zsh setup is not that easy to re-setup since apparently there is a limitation with zsh (or prezto) with the way Termux is setup. But what is a minor thing compared to not being able to code and work because there is no VSCode for mobile (yes I am aware of the web based version of vscode blah blah)
This blog post was written using mobile -> termux -> tmux -> neovim
]]>This post will get updated many times as to include more content
append
instruction is not loaded in the stackimport random, time
data = [random.randrange(100) for i in range(1000000)]
limit = 1000
def test1(): # 2 list comprehensions
total = 0
for _ in range(limit):
start = time.perf_counter()
_a = [i for i in data if i >= 50]
_b = [i for i in data if i < 50]
end = time.perf_counter()
total += (end - start)
print(f"Test 1. limit = {limit}. n = {total/limit}")
def test2(): # single-pass iteration
total = 0
for _ in range(limit):
start = time.perf_counter()
_a, _b = [], []
for i in data:
if i >= 50:
_a.append(i)
elif i < 50:
_b.append(i)
end = time.perf_counter()
total += (end - start)
print(f"Test 2. limit = {limit}. n = {total/limit}")
Test Run | 2 List Comprehensions | Singe-pass |
---|---|---|
1 | 0.18813625255553051 | 0.17321713546628598 |
2 | 0.17929821243864716 | 0.23278189841780114 |
3 | 0.1843499925005599 | 0.23758876197517384 |
import time
limit = 10000
def test1(): # List + Set
total = 0
for _ in range(limit):
start = time.perf_counter()
ids = {category.id for tree in categories for category in tree}
end = time.perf_counter()
total += (end - start)
print(f"Test 1. limit = {limit}. n = {total/limit}")
def test2(): # Dict
total = 0
for _ in range(limit):
start = time.perf_counter()
ids = list(set(category.id for tree in categories for category in tree))
end = time.perf_counter()
total += (end - start)
print(f"Test 2. limit = {limit}. n = {total/limit}")
Test Run | List + Set | Dict |
---|---|---|
1 | 1.0207993909716605e-09 | 6.737012881785631e-10 |
2 | 1.6323989257216453e-09 | 1.0202988050878048e-09 |
3 | 1.5440979041159153e-09 | 9.007984772324562e-10 |
# For-loop
qs = Model.objects.filter() # some filters
l: List = []
for i in qs:
# do some logic like append ID to `l`
qs = qs.filter(field__in=l) # final filter
# Complex ORM (maybe in this sample it's simple)
qs = (Model.objects
.filter() # some filters
.annotate(
# some annotations
# example of ORM logic
a=Case(
When(
Q(id__in=a),
then=Value(True)
),
default=Value(False)
),
b=Case(
When(
Q(id__in=b),
then=Value(False)
),
default=Value(True)
),
)
.exclude() # probably use the `a` annotation
.filter() # probably use the `b` annotation
)
I lost the actual test code :(
Test Run | For-Loop | ORM |
---|---|---|
1 | 0.07486701965187677 | 0.06850977081921883 |
2 | 0.08062036872766912 | 0.07487364947465248 |
3 | 0.07310039575556293 | 0.0669569082101807 |
4 | 0.08089369648019783 | 0.07472086069546639 |
Q
objects can be combined like with |
and &
Building List
code, you may want to use list comprehension to make
it faster, but in this case the context can be a much complex for-loop and
logic that can’t be done with list comprehensionBuilding List
performs worse in a
minor way is due to the append
instruction.Q
objects in Django does)# Building Q
q = Q()
for id in ids:
# complex logic to determine whether to add or not the `id`
q = q | Q(id=id)
qs.filter(q)
# vs
# Building List
ids: List = []
for id in some_list:
# complex logic to determine whether to add or not the `id`
ids.append(id)
qs.filter(id__in=ids)
Test Run | Building List | Building Q |
---|---|---|
1 | 0.0879093942514155 | 0.07333762165158987 |
2 | 0.0828009122532113 | 0.05329912134122331 |
# Aggregate
total: int = qs.aggregate(Sum("count")).get("count__sum") or 0
# vs
# For-Loop Increment
total: int = 0
for count in qs:
total += count
Test Run | For-Loop Increment | Aggregate |
---|---|---|
1 | 4.2209844104945657e-07 | 0.000729216638370417 |
2 | 9.428325574845075e-07 | 0.007795811454532668 |
import time
import random
count = 1000
def a():
orig = [1000] * count
total = 0
for i in range(count):
test = orig.copy()
start = time.perf_counter()
for t in test:
if (i % 2) == 0:
# NOTE: this is bad, do not remove element in a list while iterating
# this is just for this test case
test.pop(random.randint(0, len(test) - 1))
end = time.perf_counter()
total += (end - start)
print(f"a: {total / count}")
def b():
orig = [1000] * count
total = 0
for i in range(count):
test = orig.copy()
start = time.perf_counter()
_ = filter(lambda x: (i % 2) == 0, test)
end = time.perf_counter()
total += (end - start)
print(f"b: {total / count}")
# The order is also reversed at other runs to avoid caching.
a()
b()
Test Run | For-Loop with Removal | Filter |
---|---|---|
1 | 0.00023961162311024963 | 2.1601941552944483e-06 |
2 | 0.00022506073210388423 | 2.134903275873512e-06 |
3 | 0.00023982634584535846 | 2.482539915945381e-06 |
4 | 0.0002623469726240728 | 1.2303730705752969e-05 |
5 | 0.00022321201750310138 | 2.569707517977804e-06 |
CodeNect is a visual programming software aimed for beginners at programming. This is developed for my thesis. The source code is available
Windows pre-built binaries are available over at itchio
Initially during the planning, way before the development has started, I was really looking forward to finally using the Haxe programming language as I have been keeping an eye on it for a very long time but with no solid reason yet to use it. So then after deciding to try out Haxe, I have looked around for the tools that I could use to further ease the development, especially for the GUI and Nodes libraries as I do not want to spend a lot of unneeded time writing those myself. Then I found the Kha and the zui libraries.
So with the preparations completed. I begun the development and here are the experiences with Haxe:
(note that most of the pointers are my opinion and can be proven wrong with more time using it as I am a beginner at Haxe)
Suffice to say, that my first endeavor with Haxe is not a good one, but I am still interested in using it perhaps next time for game development. For now, I have to choose another stack for the software.
So why not Lua? I could really use Lua + LOVE framework for the project and for the following reasons:
But the main takeaway is that, I felt that I do not have time to write my own Nodes system with it. Also, I really do not know how to even start writing that complicated system. I know, kind of a silly excuse, but that is that. Oh, GUI is also not mature with LOVE (atleast with the libraries I know that exist).
I also found out many Nodes library that I can use with JS, but then again, I want to not use the browser/web technology as my laptop is old and has RAM limitations (3gb, yeah). Also the libraries I have found seem to have a lot of issues and are not actively maintained anymore?
NO WAY IN EARTH I WOULD SPEND MONTHS WRITING IN JAVA
Ah, C++, the most complicated programming language I have used in my life. It is very powerful but complicated. Unlike my defacto favorite which is Lua, even if I spend years and years using it, I will never be able to completely master it or wrap my head around it fully like with Lua (I mean, is there someone who knows C++ all throughout?).
So back again to C++, why C++?
Well, there is a lot of Nodes library for C++. There are atleast 3 different ImNodes named library. Also, I have a lot of experience with using DearImGui as GUI for software development (See my previous project Geo::Math). (Then again, while I am writing this post I realized that there are ImGui bindings for Lua and LOVE, so perhaps I could make ImNodes work for LOVE using cimgui? Oh well, no time for regret)
I think that the main reason that drove me to finally using C++ is that there is a part of me that really wants to learn more about C++. I am hopelessly a fan of it though I have a lot of complaints with it such as:
auto
in sample code for you C++ library makersauto
are equivalent toThere is no need to mention that most of the development time using C++ has been debugging why it segfaults and why are strings corrupted or missing. We all know that I am bound to experience problems with pointers and memory address.
If you will check the source code, you will frown upon me for not using any
smart pointers. I have my reasons why I wrote the entire software using raw
pointers and such but in summary, I want to learn C++, and that includes almost
manual memory management. Perhaps next time, I will resort to using smart pointers
and auto
to make life easier.
Also, when debugging, I always did the bunch of print statements
rather than
using a debugger, but I think there was this one time that I used lldb
to get a lead
on why it segfaults after hours of print
every other line of code. The cause of
the segfault? Haha, you know it, referencing a null pointer.
Another thing that I am worried that would get a lot of criticism in the source
code is the fact that I just wrote almost all of the singleton
(I maybe
technically wrong with my definition of singleton) structs as static
. I know
that there is a better way on how to make a struct singleton
but during the
development I just stuck with static
methods and properties to make them like
global
During the early development of CodeNect, I have used the Xmake
build system. I mean, I know Lua so this would be a better option that using Makefile
or something, and as much as possible, no to CMake
.
But Xmake has little number of users and so guides and references are sparse online. Their documentation is complete but there are things of course that I do not understand quite easily from there so Stackoverflow is a nice place to use as reference.
So after fiddling with Xmake, I have tried using Meson, Genie, and Premake but to no avail as I had many issues with those that I do not remember right now. So a friend over at discord helped me convert my Xmake build system to CMake. To be honest I still do not understand CMake that much to the point that I can easily add or modify the CMake script but at least it works across Linux and Windows.
You know what is also terrifying aside from compiling a C++ project? Linking.
I spent a lot of debugging and figuring out why I get linking error especially
with the part that has to do with using the TinyC Compiler
or
libtcc. LibTCC is a very useful tool that I utilized
for CodeNect so that my software will not rely on installing or requiring compilers
for the machine it is being ran. But yeah, linking it as well as making sure that
the necessary files can be found along the .exe
is present. This would not really
be a problem if there is an updated or maintained documentation, but sadly, even the
homepage of LibTCC lacks the documentation for users as the docs there are more
oriented towards developers (that will modify the libtcc source). Anyway, with the
help of other websites and people over at discord, I have managed to make it work
for both Linux and Windows in a very portable manner.
I used DearImGui for all the GUI in the software. They say that DearImGui is more suited as GUI for debugging/development and not for production, but for me DearImGui exceeds my GUI needs. Also, writing in Immediate Mode is way easier than making a lot of callbacks for events. I like the declarativeness that IM provides.
Using the immediate mode paradigm, it made the process for managing the logic
and behavior behind the Nodes system much easier. Instead of thinking how the
logic will flow and process every event like on connection
, on delete
connection
, on change connection
, and so on. I just need to think it
happening per frame. I just needed to manage the logic per kind of node.
I learned a lot of things writing the NodeLogic
system.
I will write/explain the in-depth of Codenect in future blog post
Of course with doing that approach, I have to make sure that pointers and references are handled correctly, but once I got the general idea and like pattern for writing each logic for the kind of node, it becomes very easy and in rhythm.
One of the hardest for me to evaluate was checking for cyclic nodes. I had to write a lot of recursive functions for that, but hey, at least I learned more about recursions. Also, recursion is way easier to write in static-typed languages compared to dynamic ones.
As for the other libraries/tools I have used, I want to give my gratitude for the developers for writing open-source and free libraries.
Here is a list of those that I have used:
]]>WELCOME TO THE 4th INSTALLMENT OF ECS VS. OOP WEEKLY SERIES!
Before proceeding, if you have not read the previous post and previous part, please check those first, this post will wait for you. You will need the information and guide provided in the previous posts for this one.
Done? Good! You may now resume your quest!
Source code is now available here
This post will not try to teach Lua coding or any programming logic. This is to focus primarily in OOP design and pattern.
If you want to learn basic programming and game development, let me know. I will make an in-depth guide about it for beginners if requested.
Last time we only made additions in the backend, so no changes in the visual/front end. Still, here is what we have so far:
Last time, we made a scene
base class and other gamestates/screens subclasses. I know,
the terms scene
, screen
, state
can be confusing, indeed they are, but for me, and
especially with this tutorial, they all refer to the same thing, well at least the usage
that is.
If you want to refactor the naming convention to be consistent, let me know and I shall do that.
But before we proceed, let us first go and add the following to the
classes/base/scene.lua
:
function Scene:update(dt) end
function Scene:draw() end
function Scene:mousepressed(mx, my, mb) end
function Scene:keypressed(key) end
function Scene:get_id() return self.id end
They will kind of act like virtual
methods.
There is a lot of thing we need to change with classes/title_screen.lua
,
so I suggest to completely erase every content of what we already have and paste
the following instead:
local Scene = require("classes.base.scene")
local TitleScreen = Scene:extend()
local title = "Shape Clicker"
local str_play = "Press enter or space to play"
local str_quit = "Press escape to quit"
local font, font2
local window_width, window_height
function TitleScreen:new(manager)
TitleScreen.super.new(self, "title_screen", manager)
font = love.graphics.newFont(64)
font2 = love.graphics.newFont(18)
window_width, window_height = love.graphics.getDimensions()
return self
end
function TitleScreen:draw()
love.graphics.setColor(1, 0, 0, 1)
love.graphics.setFont(font)
love.graphics.print(title,
window_width/2, window_height/2 - 32,
0, 1, 1,
font:getWidth(title)/2,
font:getHeight(title)/2)
love.graphics.setFont(font2)
love.graphics.print(str_play,
window_width/2, window_height/2 + 64,
0, 1, 1,
font2:getWidth(str_play)/2,
font2:getHeight(str_play)/2)
love.graphics.print(str_quit,
window_width/2, window_height/2 + 96,
0, 1, 1,
font2:getWidth(str_quit)/2,
font2:getHeight(str_quit)/2)
end
function TitleScreen:keypressed(key)
if key == "enter" or key == "space" then
local GameScreen = require("classes.game_screen")
self.manager.switch(GameScreen)
elseif key == "escape" then
love.event.quit()
end
end
return TitleScreen
Also, for all the subclasses of the scene, namely game_screen.lua
. gameover_screen.lua
,
pause_screen.lua
, title_screen.lua
, make the following additions:
function XXXXXX:new(manager)
--before the `end` keyword
return self --add this
end
Now, to test the gamestates we have done, let us make a gamestate manager module.
This manager module will be a singleton, meaning, we will not create any instance of
it, this is kind of like static
in C/C++ concepts.
Why do this? Because we do not need to make any instance of it. We only want one to use this as general as possible.
Now, there is a lot of gamestate manager library out there which are far better than what I am going to provide, what I am going to make is hacky as just fit for this tutorial.
My aim is not to teach the concept of gamestate management or how to properly do it, in my personal projects I have one that is complex and has a lot of features suitable for the kind of game I am making, abstracting it away from the project will be a hassle for now so for now, bear with this ugly gamestate manager I am going to show.
Create a file in the root of the project (not inside the classes/
folder)
named gamestate_manager.lua
and write the following:
local GamestateManager = {}
local states = {}
local current_state
local previous_state
function GamestateManager.init(state)
current_state = state:new(GamestateManager)
current_state:set_active(true)
table.insert(states, current_state)
end
function GamestateManager.switch(next_state)
previous_state = current_state
current_state = next_state:new(GamestateManager)
current_state:set_active(true)
table.insert(states, current_state, 1)
print("Switched from " .. previous_state:get_id() .. " to " .. current_state:get_id())
end
function GamestateManager.update(dt)
for _, state in ipairs(states) do
if state:get_active() then
state:update(dt)
end
end
end
function GamestateManager.draw()
for _, state in ipairs(states) do
if state:get_active() then
state:draw()
end
end
end
function GamestateManager.mousepressed(mx, my, mb)
for _, state in ipairs(states) do
if state:get_active() then
state:mousepressed(mx, my, mb)
end
end
end
function GamestateManager.keypressed(key)
for _, state in ipairs(states) do
if state:get_active() then
state:keypressed(key)
end
end
end
return GamestateManager
To test that that manager is indeed working, rename the main.lua
file into whatever you
like, in my case I renamed it to main_temp.lua
. After that, create another file called
main.lua
and put the following:
local GamestateManager = require("gamestate_manager")
function love.load()
love.graphics.setBackgroundColor(1, 1, 1, 0.8)
local title_screen = require("classes.title_screen")
GamestateManager.init(title_screen)
end
function love.update(dt)
GamestateManager.update(dt)
end
function love.draw()
GamestateManager.draw()
end
function love.mousepressed(mx, my, mb)
GamestateManager.mousepressed(mx, my, mb)
end
function love.keypressed(key)
GamestateManager.keypressed(key)
end
And then run the game, you will only see the title of our game in the center of the screen. Sweet!
Now press enter
or space
and voila! We just switched to a new state!
(see terminal/console for the printed output)
This is it for now! Again, pardon for the short post. Time and schedule is kinda tricky right now.
For the next post, we will continue to:
Stay tuned via RSS or follow me on Twitter
]]>WELCOME TO THE 4th INSTALLMENT OF ECS VS. OOP WEEKLY SERIES!
Before proceeding, if you have not read the previous post and previous part, please check those first, this post will wait for you. You will need the information and guide provided in the previous posts for this one.
Done? Good! You may now resume your quest!
Source code is now available here
This post will not try to teach Lua coding or any programming logic. This is to focus primarily in OOP design and pattern.
If you want to learn basic programming and game development, let me know. I will make an in-depth guide about it for beginners if requested.
Last time I promised for a media (image/video) to show the progress of what we are making right? Well of course I do not disappoint! So here you go:
Looks good, doesn’t it?
Okay so for now I want to make this post short as well due to time constraints. So let us implement game states!
What is a game state?
To put it simply, it is like a container
for the different states possible in our game.
The whole game should be separated according to its state
which as much as possible
should be in their own, no dependency with other state.
States can also be thought of as screens
. Some engines/frameworks use that. Now that
I remember, Godot use scene
, wait no, it kinda is, but at the same time even for the
concept of object
they use the term scene
. I digress.
We will use screen
and state
interchangably okay?
Here is a list of the states we will need:
Game screen
game screen
Game screen
Title screen
The basic thing we need of the screen base class are the following:
love
API that we will useSo we will need a base class for the screens/states
Make a new file named scene.lua
in the classes/base/
and put the following:
local Class = require("modules.classic")
local Scene = Class:extend()
function Scene:new(id, manager)
self.id = id
self.manager = manager
self.is_active = false
end
function Scene:set_active(bool)
self.is_active = bool
end
function Scene:get_active() return self.is_active end
return Scene
Quite simple right? But why do we have that is_active
flag? Well, it is
for allowing us to have multiple screens at the same time. This is needed
in the case for making the game screen
shown even when the pause screen
is shown on top of it.
The manager
field will be the gamestate manager we will implement next post. There are multiple ways to do this, but we will go for this approach as to avoid overcomplications with dependencies and whatnot.
Next we will implement the subclasses.
For classes/title_screen.lua
:
local Scene = require("classes.base.scene")
local TitleScreen = Scene:extend()
local title = "Shape Clicker"
local font
local window_width, window_height
local text_width, text_height
function TitleScreen:new(manager)
TitleScreen.super.new(self, "title_screen", manager)
font = love.graphics.newFont(32)
window_width, window_height = love.graphics.getDimensions()
text_width = font:getWidth(title)
text_height = font:getHeight(title)
end
function TitleScreen:draw()
love.graphics.setColor(1, 0, 0, 1)
love.graphics.setFont(font)
love.graphics.print(title,
window_width/2, window_height/2,
0, 1, 1,
text_width/2, text_height/2)
end
function TitleScreen:keypressed(key)
if key == "enter" or key == "space" then
self.manager:switch("game_screen")
elseif key == "escape" then
love.event.quit()
end
end
return TitleScreen
For classes/game_screen.lua
:
local Scene = require("classes.base.scene")
local GameScreen = Scene:extend()
function GameScreen:new(manager)
GameScreen.super.new(self, "game_screen", manager)
end
function GameScreen:keypressed(key)
local pause_screen = self.manager:get("pause_screen")
if key == "escape" then
pause_screen:set_active(not pause_screen:get_active())
end
end
return GameScreen
For classes/pause_screen.lua
:
local Scene = require("classes.base.scene")
local PauseScreen = Scene:extend()
function PauseScreen:new(manager)
PauseScreen.super.new(self, "pause_screen", manager)
end
function PauseScreen:keypressed(key)
local game_screen = self.manager:get("game_screen")
if key == "return" or key == "space" then
self:set_active(false)
game_screen:set_active(true)
elseif key == "escape" then
self:set_active(false)
self.manager:switch("title_screen")
end
end
return PauseScreen
For classes/gameover_screen.lua
:
local Scene = require("classes.base.scene")
local GameOverScreen = Scene:extend()
function GameOverScreen:new(manager)
GameOverScreen.super.new(self, "game_screen", manager)
end
function GameOverScreen:keypressed(key)
if key == "r" then
self.manager:switch("game_screen")
elseif key == "escape" then
self.manager:switch("title_screen")
end
end
return GameOverScreen
Phew, that was a lot! But if you can notice, there is a lot of issues with it.
First, when there are multiple screens active at the same time, doing a keypress will result in conflicting logic as multiple screens will capture that event.
Second, there is nothing yet in any state. We will move the content of
our existing game logic which is in main.lua
and move those to their
respective scenes.
We will fix those when we finally make our manager.
This is for now! Sorry if the posts are falling short. Time is kinda tight right now as well as some personal things going on.
For the next post, we will continue to:
Stay tuned via RSS or follow me on Twitter
]]>WELCOME TO THE 4th INSTALLMENT OF ECS VS. OOP WEEKLY SERIES!
Before proceeding, if you have not read the previous post and previous part, please check those first, this post will wait for you. You will need the information and guide provided in the previous posts for this one.
Done? Good! You may now resume your quest!
Source code is now available here
This post will not try to teach Lua coding or any programming logic. This is to focus primarily in OOP design and pattern.
If you want to learn basic programming and game development, let me know. I will make an in-depth guide about it for beginners if requested.
Last time we did a lot of things! Now our simple project is becoming more and more like an actual game, though very simple. But what is a game without score or achievement? What is a game if there is no goal or no way to end the game?
Surely we do not want the player to keep playing a session forever, we want some way to make the player stop like through the concept of virtual death or game over.
But before that, let us add a snippet to the classes we have previously made that will be used for the scoring, namely, we need to be able to get the shape and color field of the instance.
We could easily do this just by accessing the field since there is private
syntax in
Lua, we could implement that but that would be out of scope. So in the spirit of OOP,
we will do this using getters
.
For classes/base/shape.lua
:
function Shape:new(shape_type)
self.shape_type = shape_type
self.alive = true
end
function Shape:getShapeType() return self.shape_type end
function Shape:getColor() return self.color end
function Shape:isDestroyed() return self.alive == false end
function Shape:isClicked() return self.is_clicked end
function Shape:destroy()
self.alive = false
self.is_clicked = false
end
Also for the following subclasses:
--circle.lua
Circle.super.new(self, "circle") --in the Circle:new
--rectangle.lua
function Rectangle:new(width, height, shape_type)
Rectangle.super.new(self, shape_type or "rectangle")
end
--square.lua
Square.super.new(self, size, size, "square")
Taking a peak at this post, we stated the following for scoring when shapes are destroyed:
In OOP, it will be hard for an instance of a shape to have knowledge or reference about the other clicked instance. So we can not put that logic in the shape class and its subclasses. If you remember the spawner class which we used as part of the general gameplay logic, we will also make that logic a sepate class.
Make a new file called classes/score.lua
and write the following:
local Class = require("modules.classic")
local Score = Class:extend()
local function compare_color(a, b)
return a[1] == b[1] and
a[2] == b[2] and
a[3] == b[3]
end
function Score:new(color_score, shape_score, both_score)
self.color_score = color_score
self.shape_score = shape_score
self.both_score = both_score
self.score = 0
end
function Score:update_clicked(obj)
if self.clicked1 and self.clicked1 ~= obj then
self.clicked2 = obj
self:check_score()
else
self.clicked1 = obj
end
end
function Score:check_score()
if self.clicked1 and self.clicked2 then
local shape1 = self.clicked1:getShapeType()
local shape2 = self.clicked2:getShapeType()
local color1 = self.clicked1:getColor()
local color2 = self.clicked2:getColor()
local same_color = false
local same_shape = false
if compare_color(color1, color2) then
self.score = self.score + self.color_score
same_color = true
end
if shape1 == shape2 then
self.score = self.score + self.shape_score
same_shape = true
end
if same_color and same_shape then
self.score = self.score + self.both_score
end
self.clicked1:destroy()
self.clicked2:destroy()
self.clicked1 = nil
self.clicked2 = nil
end
end
function Score:draw()
love.graphics.setColor(1, 0, 0, 1)
love.graphics.print("Score: " .. self.score, 8, 8)
end
return Score
Now let us implement it into our main.lua
file (I will only show the new lines):
local Score = require("classes.score")
local score = Score(2, 4, 8)
function love.update(dt)
for i, obj in ipairs(shapes) do
if obj:isClicked() then
score:update_clicked(obj)
end
end
--checking for destroyed shapes
for i = #shapes, 1, -1 do
local shape = shapes[i]
if shape:isDestroyed() then
table.remove(shapes, i)
end
end
end
function love.draw()
score:draw()
end
Now test the game, you should see a score text in the upper left corner of the screen, that is just a prototype, we will use a proper UI class for that next time.
Clicking on two shapes will yield score, for now there is no feedback to the user about what just happened, again, we will improve it next time when we add those juicy effects.
I am sorry that this week’s post is very short! To make up for it, I will add images to the posts so readers can see what we are building and the progress so far.
Here are some screenshots to show what we have done so far.
For the next post, we will continue to:
Stay tuned via RSS or follow me on Twitter
]]>WELCOME TO THE 3RD INSTALLMENT OF ECS VS. OOP WEEKLY SERIES!
Before proceeding, if you have not read the previous post and previous part, please check those first, this post will wait for you. You will need the information and guide provided in the previous posts for this one.
Done? Good! You may now resume your quest!
Source code is now available here
This post will not try to teach Lua coding or any programming logic. This is to focus primarily in OOP design and pattern.
If you want to learn basic programming and game development, let me know. I will make an in-depth guide about it for beginners if requested.
So far, we implemented the basic classes, base and subclasses, that we need for the game. But, to properly see if all that we did is working, let us write a simple test to see if all the shapes are indeed working.
Write the following so that your main.lua
file will look like this:
local Shapes = {
Rectangle = require("classes.rectangle"),
Circle = require("classes.circle"),
Square = require("classes.square"),
}
local rectangle, square, circle
function love.load()
love.graphics.setBackgroundColor(1, 1, 1, 0.8)
rectangle = Shapes.Rectangle(128, 64)
square = Shapes.Square(128)
circle = Shapes.Circle(96)
end
function love.update(dt)
rectangle:update(dt)
square:update(dt)
circle:update(dt)
end
function love.draw()
rectangle:draw()
square:draw()
circle:draw()
end
function love.keypressed(key)
if key == "r" then
love.event.quit("restart")
end
end
With that code, you should see three shapes drawn on screen and they are moving.
Press r
to restart the game and you should see that they spawn in random place with
random color. Neat!
The above is just for testing the progress so far. You may now safely clean the file again as we are going to change a lot later on.
It would not be a game if there is no interaction, right? So let us implement mouseclick
.
In the previous post, we already did the logic for checking for mousepressed
event, now let
us do the highlighting part.
Why not implement the method in the base class (shape)?
That is possible and without question an okay thing to do, but my reasoning is that, the
circle
shape will not inherit that since the algorithm for checking point-to-box is
a little bit different than point-to-circle. So instead of the circle
shape modifying
the method of the base class, for me that is a waste, implement it directly in the circle
shape. The rectangle
and the square
classes would share the same method though.
The algorithm for the highlighting is:
is_clicked
is true
.true
, draw another shape behind the original shape with the following property:
This is a quick-and-dirty method to implement a somewhat outline look.
First of all, add this property to shape.lua
:
--inside the Shape:new() method
self.outline_color = {0, 0, 0, 1} --black color
self.offset = 4
For the rectangle.lua
:
function Rectangle:draw()
if self.is_clicked then
love.graphics.setColor(self.outline_color)
love.graphics.rectangle("fill",
self.x - self.offset,
self.y - self.offset,
self.width + self.offset * 2,
self.height + self.offset * 2)
end
love.graphics.setColor(self.color)
love.graphics.rectangle("fill", self.x, self.y, self.width, self.height)
end
For the circle.lua
:
function circle:draw()
if self.is_clicked then
love.graphics.setcolor(self.outline_color)
love.graphics.circle("fill",
self.x, self.y,
self.radius + self.offset,
self.radius)
end
love.graphics.setcolor(self.color)
love.graphics.circle("fill", self.x, self.y, self.radius, self.radius)
end
To test if it is working, your main.lua
(testing) should look like this:
local Shapes = {
Rectangle = require("classes.rectangle"),
Circle = require("classes.circle"),
Square = require("classes.square"),
}
local rectangle, square, circle
function love.load()
love.graphics.setBackgroundColor(1, 1, 1, 0.8)
rectangle = Shapes.Rectangle(128, 64)
square = Shapes.Square(128)
circle = Shapes.Circle(96)
end
function love.update(dt)
--stop moving so we can click on them
-- rectangle:update(dt)
-- square:update(dt)
-- circle:update(dt)
end
function love.draw()
rectangle:draw()
square:draw()
circle:draw()
end
function love.mousepressed(mx, my, mb)
rectangle:mousepressed(mx, my, mb)
square:mousepressed(mx, my, mb)
circle:mousepressed(mx, my, mb)
end
function love.keypressed(key)
if key == "r" then
love.event.quit("restart")
end
end
Click inside the shapes, they should be highlighted/outlined.
Awesome?!
So how do we un-outline the same when we click on them again (unclicking mechanics)?
Very simple! Go to the rectangle
and circle
classes and modify:
--mousepressed() method
if is_clicked then
self.is_clicked = not self.is_clicked --this
end
Basically it just flips the property when we clicked on it.
Now, we will go to the interesting part. It’s time to create another class called spawner
.
But what does the spawner
class do? Basically, it just runs an internal timer, every time
we hit the max timer, we reset it to zero, and then spawn a shape. That’s it!
So head over to the classes/
directory and create a file there named spawner.lua
and
code the following:
local class = require("modules.classic")
local Spawner = class:extend()
function Spawner:new(spawn_time, spawn)
self.spawn_time = spawn_time
self.spawn = spawn
self.timer = 0
end
function Spawner:update(dt)
self.timer = self.timer + dt
if self.timer >= self.spawn_time then
self.timer = 0
self:spawn()
end
end
return Spawner
Basically it is just an interface where we can put callbacks (spawn method) for each of the shape classes.
Here’s how we are going to use it:
local Shapes = {
Rectangle = require("classes.rectangle"),
Square = require("classes.square"),
Circle = require("classes.circle"),
}
local Spawner = require("classes.spawner")
local rect = Shapes.Rectangle(64, 64)
local rectangle_spawner = Spawner(3, function()
rect = Shapes.Rectangle(64, 64)
end)
local square = Shapes.Square(48)
local square_spawner = Spawner(3, function()
square = Shapes.Square(48)
end)
local circle = Shapes.Circle(32)
local circle_spawner = Spawner(3, function()
circle = Shapes.Circle(32)
end)
function love.load()
love.graphics.setBackgroundColor(1, 1, 1, 0.8)
end
function love.update(dt)
rectangle_spawner:update(dt)
square_spawner:update(dt)
circle_spawner:update(dt)
end
function love.draw()
rect:draw()
square:draw()
circle:draw()
end
Now when you run this game, wait for every 3
seconds, and then the shape instances should
move to another place randomly!
Ofcourse, we do not want to only have 3
instances, we also do not want to create a lot
of named variables and manually add them to the needed methods. To make everything dynamic,
we will implement containers for each shape using Lua tables.
Here’s the main.lua
file now:
local Shapes = {
Rectangle = require("classes.rectangle"),
Square = require("classes.square"),
Circle = require("classes.circle"),
}
local shapes = {}
local Spawner = require("classes.spawner")
local rectangle_spawner = Spawner(3, function()
local rectangle = Shapes.Rectangle(64, 64)
table.insert(shapes, rectangle)
end)
local square_spawner = Spawner(3, function()
local square = Shapes.Square(48)
table.insert(shapes, square)
end)
local circle_spawner = Spawner(3, function()
local circle = Shapes.Circle(32)
table.insert(shapes, circle)
end)
function love.load()
love.graphics.setBackgroundColor(1, 1, 1, 0.8)
end
function love.update(dt)
rectangle_spawner:update(dt)
square_spawner:update(dt)
circle_spawner:update(dt)
end
function love.draw()
for i, obj in ipairs(shapes) do
obj:draw()
end
end
As you can see, every 3 seconds, a rectangle, square, and circle shape instance will appear. But the problem is, there is no way right now to limit or have constraints to when should an instance be inserted to the container. So if you run the game for a very long time, it should eat a lot of memory of your machine and thus at a certain time will hang your machine.
Can we implement containers for spawner instances?
Sure! That will save a lot of lines and give us more flexibility.
Here’s how it should look now:
local Shapes = {
Rectangle = require("classes.rectangle"),
Square = require("classes.square"),
Circle = require("classes.circle"),
}
local Spawner = require("classes.spawner")
local spawners = {}
local shapes = {}
local rectangle_spawner = Spawner(3, function()
local rectangle = Shapes.Rectangle(64, 64)
table.insert(shapes, rectangle)
end)
local square_spawner = Spawner(3, function()
local square = Shapes.Square(48)
table.insert(shapes, square)
end)
local circle_spawner = Spawner(3, function()
local circle = Shapes.Circle(32)
table.insert(shapes, circle)
end)
table.insert(spawners, rectangle_spawner)
table.insert(spawners, square_spawner)
table.insert(spawners, circle_spawner)
function love.load()
love.graphics.setBackgroundColor(1, 1, 1, 0.8)
end
function love.update(dt)
for i, spawner in ipairs(spawners) do
spawner:update(dt)
end
end
function love.draw()
for i, obj in ipairs(shapes) do
obj:draw()
end
end
function love.mousepressed(mx, my, mb)
for i, obj in ipairs(shapes) do
obj:mousepressed(mx, my, mb)
end
end
function love.keypressed(key)
if key == "r" then
love.event.quit("restart")
end
end
For now it does not look clean, we will fix that soon when we have our gameplay logic implemented by using states.
For the next post, we will continue to:
So for next week we will do that as well.
Stay tuned via RSS or follow me on Twitter
]]>WELCOME TO THE 2ND INSTALLMENT OF ECS VS. OOP WEEKLY SERIES!
Before proceeding, if you have not read the previous post, please check that first, this post will wait for you. You will need the information and guide provided in the previous post for this one.
Done? Good! You may now resume your quest!
Source code is now available here
This post will not try to teach Lua coding or any programming logic. This is to focus primarily in OOP design and pattern.
If you want to learn basic programming and game development, let me know. I will make an in-depth guide about it for beginners if requested.
Why OOP first? Why not straightway go to ECS?
Most of you probably has already learned or has even a vague idea and understanding with the OOP paradigm as it is universally taught to beginners for reasons I dare not to mention for now, and without a single idea that it is possible to even program in a non-OOP way, in which case ECS would be a difficult topic to start with. My approach, be it unconventional, is to remind you dear readers the concept of OOP as clear as I possibly could by going back to the fundamentals and basics of OOP, and then applying such lessons in making a simple game.
And as you are reminded, we will go into details the limitations and problems that the OOP paradigm give us.
To be fair, I will not mention or make gesture regarding ECS like “Oh, ECS could easily do this and that”.
So what are we going to make?
I was thinking and looking around for a very simple and known game that could be done in
a single post that will showcase the “benefits” of OOP that will also be fitting for an
ECS approach. Pong
would in this case be too simple for our goal, so I have decided to
make a not-too-simple yet with an interesting mechanic worthy of the time we will spend
on it.
The game would be called Shape Clicker
, it has the following mechanics:
First thing we need to do of course is to plan
, this phase is very important in OOP
because we need to have a clear knowledge about the model or structure we are going
to pursue. If there is a mistake in the plan, the implementation is affected as well.
So let us begin the planning by making a model or structure of the classes we need. How
do we know what class
to make? Well, we need to know what objects
do we need to make
following the mechanics of the game we discussed earlier.
Next, we need to look into each of those and find what are common and of the same nature:
Here is the first abstraction:
Rectangle Shape
+ color
Square Shape
+ color
Circle Shape
+ color
UI
+ text
UI
+ button
After the first abstraction, we now have the following classes:
If we are still going to look deeper, we can see that Rectangle shape
, Square shape
,
and Circle shape
are all a subclass of shape
. So now the classes that we need are:
See? Planning trims down the things we need to do.
What else do we need as part of the plan? After determining the classes we need, we now need to determine the subclasses or the classes that will inherit from the base class.
I am confident that those are the only classes and subclasses that we need. Now let us next plan and determine the behaviors or properties of each class:
We need to determine what are the variables/properties that are most common to the class and to its subclass(es).
Hey, we do not need those shape subclasses, we can just pass a size argument and let each instance handle the parameters and so on.
Yes, that is also possible, but again, we are doing this in the most basic approach as
a beginner would think. There is nothing wrong with the method proposed but that will
involve dealing with method overriding
which will add complexity. For advanced OOP,
yeah sure go with that.
Square should be the same with Rectangle since the only difference is the size
Yes, that is true. Square is basically a Rectangle but with the same width and height or vice versa. Look further down because you will see this implemented.
Let us now start by creating the files we need and properly organizing them:
game_oop/
:
main.lua
conf.lua
game_oop/classes/base/
:
shape.lua
ui.lua
game_oop/classes/
:
rectangle.lua
square.lua
circle.lua
button.lua
text.lua
Let us now open and edit first the conf.lua
file using your preferred text editor:
(do not forget that you are free to change values, you do not have to follow blindly)
--conf.lua
function love.conf(t)
t.window.title = "Shape Clicker OOP"
t.window.width = 800
t.window.height = 640
end
Now run the game, you should see the game window with the title Shape Clicker OOP
and
with dimensions of 800x640
, you can play around those values if you wish to.
Open now main.lua
:
--main.lua
function love.load()
love.graphics.setBackgroundColor(1, 1, 1, 0.8)
end
function love.update(dt)
end
function love.draw()
end
Run the game again and instead of seeing a black-filled window, you should see it filled with the white color with a little bit of transparency.
For now let us focus on the Shape
class and its subclasses.
Now open game_oop/classes/base/shape.lua
:
local Class = require("modules.classic")
local Shape = Class:extend()
local colors = {
{1, 0, 0}, --red
{0, 1, 0}, --green
{0, 0, 1}, --blue
}
local function generate_random_color()
local r = love.math.random(1, #colors)
local color = colors[r]
return {unpack(color)}
end
function Shape:new()
local w, h = love.graphics.getDimensions()
self.x = love.math.random(96, w - 96)
self.y = love.math.random(96, h - 96)
--thanks @slime!
self.direction = love.math.random() > 0.5 and 1 or -1
self.speed = love.math.random(64, 320)
self.color = generate_random_color()
self.is_clicked = false
end
function Shape:move(dt)
--moving
local dx = self.x + self.speed * self.direction * dt
local dy = self.y + self.speed * self.direction * dt
self.x = dx
self.y = dy
end
return Shape
Now open game_oop/classes/rectangle.lua
:
local Shape = require("classes.base.shape")
local Rectangle = Shape:extend()
function Rectangle:new(width, height)
Rectangle.super.new(self)
self.width = width
self.height = height
end
function Rectangle:update(dt)
Rectangle.super.move(self, dt)
--check out of bounds
local oob_left = self.x + self.width < 0 --left edge
local oob_right = self.x > love.graphics.getWidth() --right edge
local oob_top = self.y + self.height < 0 --top edge
local oob_bottom = self.y > love.graphics.getHeight() --bottom edge
end
function Rectangle:mousepressed(mx, my, mb)
if not (mb == 1) then return end
local is_clicked = mx >= self.x and mx <= self.x + self.width and
my >= self.y and my <= self.y + self.height
if is_clicked then
self.is_clicked = true
end
end
function Rectangle:draw()
love.graphics.setColor(self.color)
love.graphics.rectangle("fill", self.x, self.y, self.width, self.height)
end
return Rectangle
Now open game_oop/classes/square.lua
:
local Shape = require("classes.rectangle")
local Square = Shape:extend()
function Square:new(size)
Square.super.new(self, size, size)
end
function Square:update(dt)
Square.super.move(self, dt)
end
function Square:mousepressed(mx, my, mb)
Square.super.mousepressed(self, mx, my, mb)
end
function Square:draw()
Square.super.draw(self)
end
return Square
See? we subclass from Rectangle
because logically, they can be implementd the same. Why we separate them? for the sake of mechanics and adding variations to the game.
Now open game_oop/classes/rectangle.lua
:
local Shape = require("classes.base.shape")
local Circle = Shape:extend()
function Circle:new(radius)
Circle.super.new(self)
self.radius = radius
end
function Circle:update(dt)
Circle.super.move(self, dt)
--check out of bounds
local oob_left = self.x + self.radius < 0 --left edge
local oob_right = self.x > love.graphics.getWidth() --right edge
local oob_top = self.y + self.radius < 0 --top edge
local oob_bottom = self.y > love.graphics.getHeight() --bottom edge
end
function Circle:mousepressed(mx, my, mb)
if not (mb == 1) then return end
local dx = mx - self.x
local dy = my - self.y
local d = math.sqrt((dx * dx) + (dy * dy))
local is_clicked = d <= self.radius
if is_clicked then
self.is_clicked = true
end
end
function Circle:draw()
love.graphics.setColor(self.color)
love.graphics.circle("fill", self.x, self.y, self.radius, self.radius)
end
return Circle
This is it for this week!
For the next post, we will continue to:
Another thing is that we can clearly that there are even common logics happening between the
Rectangle/Square
and Circle
classes like the checking for out of bounds
and the determining whether a point is inside the shape in mousepressed
,
those can be abstracted to the base Shape
class if you want.
So for next week we will do that as well.
Stay tuned via RSS or follow me on Twitter
]]>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.
Source code is now available here
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.
What is ECS? What is OOP?
Entity Component System
.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?).
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.
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:
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
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:
position
, radius
, speed
componentsentity A
the position
, radius
, and speed
componentsentity B
the position
, and radius
componentsdraw ball
, where it will draw all entities that have
the position
and radius
components (both entities).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:
ball
class need to have the properties: position
,
radius
, and speed
regardless if they will move or not which is expensive in memory.draw
and move
.
Even though one ball will not move, that ball object still needs to have that method in
memory.Here are the problems (actually not) with ECS:
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.
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)
love
pacman -S love
/home/user/game_oop
and/home/user/game_ecs
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.
Coming soon!
classic
and put the extracted classic/classic.lua
file
inside the game_oop/modules/
directory.classes
directory inside the game_oop
directory.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!
concord
and put the extracted concord/concord
directory
inside the game_ecs/modules/
directory.worlds
directory inside the game_ecs
directory.systems
directory inside the game_ecs
directory.components
directory inside the game_ecs
directory.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!
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.game_ecs
directory.Again, if you are on different platforms, making love
work is up to you (for now)
For next post, we will start making a simple game that follows the OOP paradigm.
Stay tuned via RSS or follow me on Twitter
]]>