一切皆对象

一切都以对象的形式活了起来。

超多的对象

对象

我喜欢用对象来工作,你可以把以后需要的所有信息都放进去。Lua 没有我在其他语言里熟悉的那种类系统。我需要的是一种以后可以覆盖或扩展类的机制。这里说的是从使用 engine3 的游戏这一侧来看。

背后的想法是,engine3 可以发布出来,让所有人都能使用。游戏本身则可以保持私有。两者并存,互相补充。经过一些测试后,我做到了,现在可以基于类来创建对象。

一个示例类 engine3/core/camera.lua(精简版)。

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

假设游戏里的镜头需要和 engine3 的默认镜头稍有不同,那我就创建文件 game/framework/core/camera.lua,并引用 engine3 的 camera 类。然后再做我的修改。

---@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

有这么多不同的对象当然很好(从现在起我把对象称为 entity),但它们需要能在整个代码中被找到和使用。所以必须做一个东西来管理这一切。

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

这其实是游戏的心脏,或者说众多重要部分之一。如果这里出了问题,你就麻烦了…

创建 entity 时,它会被添加到 Entity Manager 中,需要时也会被移除。在上面的 camera 类里,你已经看到这是通过下面这一行实现的。

entityManager:add(self)

我写的每个类里都有这一行,这样它就会通过 entityManager 把自己加进去。

在类里你还可以写一个 release 函数,当 entityManager 移除这个对象时会自动执行。这对控制内存非常重要。你不会想把图片之类的素材全都留着,而是要在某个时刻把它们从内存中释放。

function Camera:release()
    -- do things when the entityManger delete this object
end

使用

当我在某处有一个新类,并且需要一个 entity,比如镜头,可以有几种不同的做法。

local cameras = entityManager:find(nil,"Camera")

不过这会返回一个表,里面是所有找到的 Camera 类型(类)的 Entity。这并不方便,因为在这种情况下镜头始终只有1个,而且我想百分之百确定 Camera 确实存在。

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

这是对 find 的封装,当对象不存在时会报错。

其他一些例子

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})

最后

我在类和对象方面想要的几点,现在都满足了

  • 类
  • 类可以扩展
  • 类可以覆盖
  • entity 由管理器统一管控
  • entity 可以在任何地方使用

尽早做出这个选择非常重要,同时要预料到最终代码还会有很多改动。如果你把一切都搭得有条理,以后会受益良多。

如果 Lua 处理不了这么多 entity,也可以把这部分改用 C 来实现!