Alt er et objekt
Alt kommer til live i form af et objekt.

Objekt
Jeg kan godt lide at arbejde med objekter, man kan putte al den information i dem, man får brug for senere. Lua har ikke et klassesystem som dem, jeg kender fra andre sprog. Jeg havde brug for noget, som gjorde det muligt senere at overskrive eller udvide klasser. Og så mener jeg set fra spillet, som bruger engine3.
Tanken bag er, at engine3 kan udgives, så alle kan bruge det. Selve spillet kan man så holde privat. De to eksisterer side om side og supplerer hinanden. Efter nogle tests lykkedes det, og nu kan der laves objekter ud fra klasser.
Et eksempel på en klasse, engine3/core/camera.lua (begrænset 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
Lad os sige, at kameraet i spillet skal opføre sig lidt anderledes end standardkameraet i engine3. Så laver jeg filen game/framework/core/camera.lua og henviser til engine3’s camera-klasse. Derefter laver jeg mine ændringer.
---@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
Fint nok med alle de forskellige objekter (fra nu af kalder jeg et objekt en entity), men de skal kunne findes og bruges i hele koden. Der skulle altså laves noget til at styre det hele.
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
Det er faktisk hjertet i spillet, eller en af de mange vigtige dele. Går der noget galt her, har du et problem…
Når en entity bliver oprettet, bliver den tilføjet til Entity Manager og eventuelt fjernet igen. I camera-klassen ovenfor kunne du allerede se, at det sker med linjen herunder.
entityManager:add(self)
Den linje står i alle klasser, jeg skriver, så de selv bliver tilføjet via entityManager.
I klassen kan man også lave en release-funktion, som automatisk køres, når entityManager fjerner objektet. Det er meget vigtigt for at holde styr på hukommelsen. Man vil ikke gemme alle assets som billeder, men på et tidspunkt fjerne dem fra hukommelsen.
function Camera:release()
-- do things when the entityManger delete this object
end
I brug
Når jeg et sted har en ny klasse og skal bruge en entity, fx kameraet, kan det gøres på forskellige måder.
local cameras = entityManager:find(nil,"Camera")
Det giver dog en tabel tilbage med alle fundne Entities af typen Camera (klasse). Det er ikke praktisk, da der i det her tilfælde altid kun er 1 kamera, og jeg vil være 100 % sikker på, at Camera findes.
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
Det er en wrapper omkring find, som giver en fejl, når objektet ikke findes.
Nogle andre eksempler er
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})
Til sidst
Alle de punkter, jeg søgte angående klasser og objekter, er nu dækket
- klasser
- klasser kan udvides
- klasser kan overskrives
- entities styres af en manager
- entities kan jeg bruge overalt
Det er meget vigtigt at træffe det valg så tidligt som muligt og regne med, at der stadig kommer mange ændringer i den endelige kode. Hvis man sætter det hele struktureret op, får man meget glæde af det senere.
Skulle Lua ikke længere kunne klare antallet af entities, kan den del laves om til C!