Alt er et objekt
Alt får liv i form av et objekt.

Objekt
Jeg liker å jobbe med objekter, du kan legge all informasjon du trenger senere, i dem. Lua har ikke et klassesystem slik jeg kjenner det fra andre språk. Jeg trengte noe som gjorde det mulig å overskrive eller utvide klasser senere. Og da mener jeg sett fra spillet som bruker engine3.
Tanken bak er at engine3 kan gis ut slik at alle kan bruke det. Selve spillet kan du da holde privat. De to eksisterer side om side og utfyller hverandre. Etter litt testing lyktes det, og nå kan det lages objekter ut fra klasser.
Et eksempel på en klasse, engine3/core/camera.lua (begrenset versjon).
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
Si at kameraet i spillet skal oppføre seg litt annerledes enn standardkameraet i engine3. Da lager jeg filen game/framework/core/camera.lua og viser til kameraklassen i engine3. Deretter gjør jeg endringene mine.
---@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 med alle disse ulike objektene (fra nå av kaller jeg et objekt en entity), men de må kunne finnes og brukes i hele koden. Det måtte altså lages noe for å styre alt.
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
Dette er egentlig hjertet i spillet, eller en av de mange viktige delene. Går noe galt her, har du et problem…
Når en entity lages, legges den til i Entity Manager og fjernes eventuelt igjen. I kameraklassen over kunne du allerede se at dette skjer med linjen under.
entityManager:add(self)
Den linjen står i hver klasse jeg skriver, slik at den legger seg til selv via entityManager.
I klassen kan du også lage en release-funksjon, som kjøres automatisk når entityManager fjerner objektet. Det er svært viktig for å holde kontroll på minnet. Du vil ikke ta vare på alle ressurser som bilder, men fjerne dem fra minnet på et tidspunkt.
function Camera:release()
-- do things when the entityManger delete this object
end
I bruk
Når jeg har en ny klasse et sted og trenger en entity der, for eksempel kameraet, kan det gjøres på flere måter.
local cameras = entityManager:find(nil,"Camera")
Dette gir imidlertid tilbake en tabell med alle funnede Entities av typen Camera (klasse). Det er upraktisk, siden det i dette tilfellet alltid er 1 kamera, og jeg vil være 100 % sikker på at Camera finnes.
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
Dette er en wrapper rundt find og gir en feil når objektet ikke finnes.
Noen 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 slutt
Alle punktene jeg lette etter når det gjelder klasser og objekter, er nå dekket
- klasser
- klasser kan utvides
- klasser kan overskrives
- entities kontrolleres av en manager
- entities kan jeg bruke overalt
Det er svært viktig å ta dette valget så tidlig som mulig og regne med at den endelige koden fortsatt kommer til å endre seg mye. Setter du opp alt strukturert, har du stor nytte av det senere.
Hvis Lua ikke lenger skulle klare antallet entities, kan denne delen gjøres om til C!