Tutto è un oggetto
Tutto prende vita sotto forma di oggetto.

Oggetto
Mi piace lavorare con gli oggetti, ci puoi mettere tutte le informazioni che ti servono dopo. Lua non ha un sistema di classi come quello che conosco da altri linguaggi. Mi serviva qualcosa con cui, più avanti, sovrascrivere o estendere le classi. E intendo dal punto di vista del gioco che usa engine3.
L’idea di fondo è che engine3 possa essere pubblicato in modo che chiunque possa usarlo. Il gioco stesso invece puoi tenerlo privato. I due esistono uno accanto all’altro e si completano. Dopo qualche test ci sono riuscito, e ora si possono creare oggetti a partire da classi.
Un esempio di classe, engine3/core/camera.lua (versione ridotta).
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
Supponiamo che la camera nel gioco debba comportarsi in modo un po’ diverso dalla camera predefinita di engine3. Allora creo il file game/framework/core/camera.lua e faccio riferimento alla classe camera di engine3. Poi faccio le mie modifiche.
---@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
Belli tutti questi oggetti diversi (da qui in poi chiamo un oggetto entity), ma devono poter essere trovati e usati in tutto il codice. Serviva quindi qualcosa per gestire tutto.
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
Questo è in pratica il cuore del gioco, o uno dei tanti pezzi importanti. Se qui qualcosa va storto, hai un problema…
Quando viene creata un’entity, viene aggiunta all’Entity Manager ed eventualmente anche rimossa. Nella classe camera qui sopra hai già visto che succede con la riga qui sotto.
entityManager:add(self)
Ogni classe che scrivo contiene quella riga, così si aggiunge da sola tramite l’entityManager.
Nella classe puoi anche creare una funzione release, che viene eseguita automaticamente quando l’entityManager rimuove l’oggetto. È molto importante per tenere sotto controllo la memoria. Non vuoi tenere tutti gli asset come le immagini, ma a un certo punto toglierli dalla memoria.
function Camera:release()
-- do things when the entityManger delete this object
end
In uso
Quando ho una nuova classe da qualche parte e mi serve un’entity, per esempio la camera, posso farlo in diversi modi.
local cameras = entityManager:find(nil,"Camera")
Questo però restituisce una tabella con tutte le Entity trovate di tipo Camera (classe). Non è comodo, visto che in questo caso c’è sempre 1 sola camera, e voglio essere sicuro al 100% che la Camera ci sia.
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
Questo è un wrapper attorno a find, e dà un errore quando l’oggetto non c’è.
Alcuni altri esempi sono
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})
Per finire
Tutti i punti che cercavo per classi e oggetti ora sono coperti
- classi
- le classi sono estendibili
- le classi sono sovrascrivibili
- le entity sono sotto controllo grazie a un manager
- posso usare le entity ovunque
È molto importante fare questa scelta il prima possibile e mettere in conto che il codice finale cambierà ancora molto. Se imposti tutto in modo strutturato, dopo ne avrai un grande vantaggio.
Se Lua non riuscisse più a gestire il numero di entity, è possibile convertire questa parte in C!