Allt är ett objekt
Allt får liv i form av ett objekt.

Objekt
Jag gillar att arbeta med objekt, man kan lägga all information i dem som man behöver senare. Lua har inget klassystem som det jag känner till från andra språk. Jag behövde något som gjorde det möjligt att senare skriva över eller bygga ut klasser. Och då menar jag sett från spelet som använder engine3.
Tanken bakom är att engine3 ska kunna släppas så att alla kan använda det. Själva spelet kan man då hålla privat. De två finns sida vid sida och kompletterar varandra. Efter lite tester lyckades det, och nu kan objekt skapas utifrån klasser.
Ett exempel på en klass, engine3/core/camera.lua (förkortad 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
Säg att kameran i spelet ska bete sig lite annorlunda än standardkameran i engine3. Då skapar jag filen game/framework/core/camera.lua och hänvisar till engine3:s kameraklass. Sedan gör jag mina ändringar.
---@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
Trevligt med alla dessa olika objekt (från och med nu kallar jag ett objekt för entity), men de måste kunna hittas och användas i hela koden. Det behövdes alltså något för att hantera allt.
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 här är egentligen hjärtat i spelet, eller en av de många viktiga delarna. Om något går fel här har du ett problem…
När en entity skapas läggs den till i Entity Manager och tas eventuellt bort igen. I kameraklassen ovan kunde du redan se att det görs med raden nedan.
entityManager:add(self)
Den raden finns i varje klass jag skriver, så att den lägger till sig själv via entityManager.
I klassen kan man också skapa en release-funktion, som körs automatiskt när entityManager tar bort objektet. Det är mycket viktigt för att hålla minnet under kontroll. Man vill inte spara alla tillgångar som bilder, utan ta bort dem ur minnet vid något tillfälle.
function Camera:release()
-- do things when the entityManger delete this object
end
I bruk
När jag har en ny klass någonstans och behöver en entity där, till exempel kameran, kan det göras på olika sätt.
local cameras = entityManager:find(nil,"Camera")
Det här ger dock tillbaka en tabell med alla hittade Entities av typen Camera (klass). Det är opraktiskt eftersom det i det här fallet alltid finns 1 kamera, och jag vill vara 100 % säker på att Camera finns.
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 här är en wrapper runt find som ger ett fel när objektet inte finns.
Några andra exempel är
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})
Till sist
Alla punkter jag sökte när det gäller klasser och objekt är nu täckta
- klasser
- klasser kan byggas ut
- klasser kan skrivas över
- entities kontrolleras av en manager
- entities kan jag använda överallt
Det är mycket viktigt att göra det valet så tidigt som möjligt och räkna med att den slutliga koden kommer att ändras mycket. Om man lägger upp allt strukturerat har man stor nytta av det senare.
Om Lua inte längre skulle klara antalet entities går det att göra om den här delen till C!