Alles is een object
Alles komt tot leven in de vorm van een object.
- Martin
- 5 min read

Object
Ik werk graag met objecten, je kan er alle informatie in kwijt die je later nodig hebt. Lua heeft geen class systeem zoals ik ken van andere talen. Wat ik nodig had was iets waarmee op een later punt classes overschreven of uigebreid konden worden. En dan bedoel ik vanuit het spel gezien die gebruik maakt van engine3.
Het achterliggende idee is dat engine3 uitgebracht kan worden zodat iedereen dit kan gebruiken. Het spel zelf kun je dan private houden. De twee bestaan dan naast elkaar en vullen elkaar aan. Na wat testen is dit het gelukt en kunnen er nu objecten gemaakt worden aan de hand van classes.
Een voorbeeld class engine3/core/camera.lua (beperkte versie).
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
Stel dat de camera in het spel zich net iets anders moet gaan gedragen dan de default camera van engine3 dan maak ik het bestand game/framework/core/camera.lua en verwijs naar de engine3 camera class. Vervolgens maak ik mijn aanpassingen.
---@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
Leuk al die verschillende objecten (ik noem een object vanaf nu entity), maar ze moeten door de hele code gevonden/gebruikt gaan worden. Er moest dus iets gemaakt worden alles te kunnen managen.
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
Dit is eigenlijk het hart van het spel of een van de vele belangrijke, wanneer hier iets fout gaat heb je een probleem…
Wanneer een entity wordt gemaakt zal dit worden toegevoegd aan de Entity Manger en eventueel ook weer verwijderd. In de camera class hierboven kon je al zien dat dit gaat met de onderstaande regel.
entityManager:add(self)
In elke class die ik schrijf staat die regel zodat het zichzelf zal toevoegen via de entityManger.
In de class kun je ook een release functie maken, dit wordt automatisch uitgevoerd wanneer de enitityManger het object verwijderd. Dit is zeer belangrijk om geheugen onder controle te houden. Je wilt assets zoals afbeeldingen niet allemaal bewaren maar op een gegeven moment uit het geheugen halen.
function Camera:release()
-- do things when the entityManger delete this object
end
In gebruik
Wanneer ik ergens een nieuwe class heb en ik heb daar een entity nodig bijv. de camera, dan kan dat op verschillende manier.
local cameras = entityManager:find(nil,"Camera")
Dit zal echter een tabel teruggeven met alle gevonden Entities van het type Camera (class), dit is niet handig aangezien het in dit geval altijd 1 camera is, en ik wil er 100% zeker van zijn dat de Camera ook aanwezig is.
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
Dit is een wrapper om find heen, en en geeft een error wanneer het object niet aanwezig is.
Enkele andere voorbeelden zijn
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})
Tot slot
Al mijn punten die ik zocht aangaande classes en objecten zijn nu gedekt
- classes
- classes zijn uitbreidbaar
- classes zijn overschrijfbaar
- entities staan onder controle door een manager
- entities kan ik overal gebruiken
Het is zeer belangrijk om die keuze zo snel mogelijk te maken en er vanuit gaan dat er nog veel veranderingen gaan plaats vinden in de uiteindelijke code. Wanneer je alles gestructeerd uitzet heb je daar later veel profijt van.
Stel dat Lua de verwerking van het aantal entities niet meer aan kan, dan is het mogelijk om dit deel om te zetten naar C!


