Everything is an object
Everything comes to life in the form of an object.

- Martin
- 5 min read

Object
I like working with objects, you can put all the information you need later in it. Lua does not have a class system like I know from other languages. What I needed was something with which classes could be overwritten or extended at a later point. And then I mean from the perspective of the game that uses engine3.
The underlying idea is that engine3 can be released so that everyone can use it. The game itself can then be kept private. The two then exist side by side and complement each other. After some testing this worked and objects can now be created using classes.
An example class engine3/core/camera.lua (limited version).
local Callback = require "lib.engine3.src.framework.core.callback"
---@class Camera: Object
---@field config Config
---@field gameCanvas Canvas
---@field x integer
---@field y integer
---@field width integer
---@field height integer
---@field followSpeed integer
---@field targetX integer
---@field targetY integer
---@field bound boolean
---@field bounds_min_x integer
---@field bounds_min_y integer
---@field bounds_max_x integer
---@field bounds_max_y integer
---@field callbackAfterStop function
---@field callback Callback
---@field super self
---@overload fun(data: CameraOptions):Camera
local Camera = Object:extend("Camera")
Camera:implement(Callback)
---@class CameraOptions
---@field x? integer Default 0
---@field y? integer Default 0
---@field width? integer Default canvas width
---@field height? integer Default canvas height
---@field followSpeed integer Default 1
function Camera:new(data)
if data == nil then
data = {}
end
self.config = entityManager:findOne(nil, "Config")
self.gameCanvas = entityManager:findOne({name = "gameCanvas"}, "Canvas")
self.x = data.x or 0
self.y = data.y or 0
self.width = data.width or self.gameCanvas:getWidth()
self.height = data.height or self.gameCanvas:getHeight()
self.followSpeed = data.followSpeed or 1
self.targetX = nil
self.targetY = nil
self.bound = true
self.bounds_min_x = nil
self.bounds_min_y = nil
self.bounds_max_x = nil
self.bounds_max_y = nil
self.callbackAfterStop = {}
entityManager:add(self)
end
---@param x integer
---@param y integer
---@param w integer
---@param h integer
function Camera:setBounds(x, y, w, h)
self.bound = true
self.bounds_min_x = x
self.bounds_min_y = y
self.bounds_max_x = x + w
self.bounds_max_y = y + h
end
---@param dt number
function Camera:update(dt)
-- core things
end
function Camera:setBounds(x, y, w, h)
self.bound = true
self.bounds_min_x = x
self.bounds_min_y = y
self.bounds_max_x = x + w
self.bounds_max_y = y + h
end
Suppose the camera in the game needs to behave slightly differently than the default camera of engine3, then I create the file game/framework/core/camera.lua and point to the engine3 camera class. Then I make my adjustments.
---@class Camera: Object
---@field followEntity Actor|Animation
---@overload fun():Camera
local Camera = require("lib.engine3.src.framework.core.camera"):extend("Camera")
-- extra new function
---@return Actor|Animation
function Camera:getFollowEntity()
-- do things
return self.followEntity
end
-- overwrite the original
---@param dt number
function Camera:update(dt)
if self.followEntity then
-- do something
end
-- optional but possible, call the original camera update
Camera.super.update(self, dt)
end
Entity Manager
All those different objects are nice (I’ll call an object entity from now on), but they have to be found/used throughout the code. So something had to be made to manage everything.
EntityManager:implement(Callback)
function EntityManager:new()
self.entities = {}
self.lastEntityId = 0
self.callbackAfterAdd = {}
self.callbackAfterDelete = {}
end
function EntityManager:createId()
end
function EntityManager:add(entities)
end
function EntityManager:del(entity, args)
...
if entity.release then
entity:release(args)
end
...
end
function EntityManager:delAll(args)
end
function EntityManager:getEntityIndex(entity)
end
function EntityManager:find(filter, className, enitities)
end
function EntityManager:findOne(filter, className, entities)
end
function EntityManager:filter(data, filter)
end
This is actually the heart of the game or one of the many important ones, if something goes wrong here you have a problem…
When an entity is created it will be added to the Entity Manger and possibly also removed. In the camera class above you could already see that this goes with the rule below.
entityManager:add(self)
In every class I write that line is there so it will add itself via the entityManger.
In the class you can also create a release function, this will be executed automatically when the entityManger removes the object. This is very important to keep memory under control. You don’t want to keep all assets like images but at some point remove them from memory.
function Camera:release()
-- do things when the entityManger delete this object
end
In use
When I have a new class somewhere and I need an entity there, for example the camera, then that can be done in different ways.
local cameras = entityManager:find(nil,"Camera")
However, this will return a table with all found Entities of type Camera (class), this is not useful as in this case it is always 1 camera, and I want to be 100% sure that the Camera is also present.
local camera = entityManager:findOne(nil, "Camera")
function EntityManager:findOne(filter, className, entities)
local objects = self:find(filter, className, entities)
if #objects == 1 then
return objects[1]
elseif #objects > 1 then
error("Cannot get exactly one object, because it was found (" .. #objects .. ") times !")
end
if className ~= nil then
error("findOne() object does not exist :" .. className)
else
dump(filter)
error("findOne() Cannot find object")
end
end
This is a wrapper around find , and returns an error if the object is not present.
Some other examples are
local entities = entityManager:find({name = "bird", visible = true}, "Frameset")
local entities = entityManager:find({name = "bird", visible = true}, "Frameset", customEntities)
-- special magic
local entities = entityManager:find({createdBy = Item}, "Frameset")
-- special magic
local entities = entityManager:find({collidableA = "CONTAINS_DATA"}, "Frameset")
-- special magic
local entities = entityManager:find({isObject = require "lib.engine3.src.framework.core.canvas", excludeEntities = self.canvases})
Finally
All my points that I was looking for regarding classes and objects are now covered
- classes
- classes are extensible
- classes are overwritable
- entities are under the control of a manager
- entities I can use anywhere
It is very important to make that choice as soon as possible and assume that many changes will still take place in the final code. If you structure everything, you will benefit a lot from it later.
Suppose Lua can no longer handle the processing of the number of entities, then it is possible to convert this part to C!
Loading external comment system...