« 一个游戏的点子 | 返回首页 | 一些星舰或太空站建设类游戏 »

监视 Lua 对象的修改

我正在制作的游戏 demo 中,所有对象逻辑上都存在于二维空间,但在 Ant Engine 中通过 3d 渲染方式绘制出来。

我希望有一组简便的 API 方便我控制这些对象的渲染,只是控制它们的位置以及在 Y 轴上的旋转量。Ant Engine 是用场景组件来控制 entity 渲染时的空间状态,但场景节点使用的是 3d 空间的 SRT 即缩放、旋转、位移。而我只需要控制其中的两个坐标轴上的空间位置以及一个旋转轴上的旋转量,直接修改 SRT 太不方便了。而且,使用引擎时,还需要每帧标记被修改过的场景组件对应的 entity ,这也很麻烦。

在 ECS 结构下,最简单的方式是为这些 entity 创建一个额外的组件,里面有 x y r 三个值。通过一个 system 把它们转换到场景节点在 3d 空间下的 SRT 组件中。但如果每帧都全部转换一次显得多余,毕竟大部分 entity 不是每帧都会发生变化的。

我用了一个简单的 Lua 技巧来方便开发,下面便是代码:

local monitor = {}

local function new_type()
    local changes = {}
    local function touch(obj, k, v)
        local raw = obj.__raw
        changes[raw] = true
        raw[k] = v
        obj.__newindex = raw
    end
    local function new (obj)
        obj.__index = obj
        obj.__newindex = touch
        changes[obj] = true
        return setmetatable({ __raw = obj }, obj)
    end
    local function next_raw(t, key)
        local nkey, nvalue = next(t, key)
        if nkey then
            nkey.__newindex = touch
            return nkey, nvalue
        end
    end
    local function pairs()
        if next(changes) then
            local t = changes
            changes = {}
            return next_raw, t
        else
            return next, changes
        end
    end
    return { pairs = pairs, new = new }
end

local types = setmetatable ({}, {
    __index = function(self, name)
        local t = new_type()
        self[name] = t
        return t
    end })

function monitor.new(typename)
    return types[typename].new
end

function monitor.pairs(typename)
    return types[typename].pairs()
end

TEST = true

if TEST then
    local a = monitor.new "test" { x = 1, y = 2 }
    local b = monitor.new "test" { x = 10, y = 20 }

    local function flush()
        print "====="
        for obj in monitor.pairs "test" do
            print(obj.x, obj.y)
        end
    end

    a.x = -1
    flush()
    b.y = -20
    flush()
    local c = monitor.new "test" { x = 0, y = 0 }
    flush()
else
    return monitor
end

从最后的 test 代码可见:我们可以通过 monitor.new "typename" {}创建一个逻辑上有 x y 坐标的 lua 对象,它并不需要是 ECS 的组件,在和 ecs 结合使用的时候,可以把 eid 也放进对象里(在后面遍历的时候,可以对应到 ecs 中的 entity )。当我们后续修改这些对象时,会把修改过的对象标记在内部一张表中。

通过 for obj in monitor.pairs "typename" 可以遍历所有最近修改过(及新创建)的对象。

Comments

@arnan

是不好,这里只是想用几行实例说明一下问题。我现在实际用的版本比这个区别较大。

优雅~

学到了,把 __newindex 指来指去来避免多次 write 的 metamethod 调用额外开销~

不过把状态重置这一 write 意味的操作整合到 pairs 这一 readonly 意味的操作里总觉得会不会不太好。
简单的例子里还好,应用场景复杂了担心会不会引起一些不必要的疑惑和容易引起 BUG。
或许单独提供一个独立的 API? (不能在遍历时顺便做会多一点额外开销)
或者改一个 API 名字叫 resolve 或 iter_and_resolve 之类的?

另外,可能不一定需要一个 global 的 types 来持有所有的 monitor 对象?
也可以直接返回 monitor 对象给调用者自己使用和管理

local monitor_obj = monitor.new "test"
monitor_obj.new { x=1, y= 2}
monitor_obj.release()

Post a comment

非这个主题相关的留言请到:留言本