Lua (CC) GUI class 将所有组件绘制在同一个 window 中,当被告知要在单独的 windows 中绘制它们时

Lua (CC) GUI class draws all components in the same window when told to draw them in separate windows

序曲

ComputerCraft 是 Minecraft (Forge) 的 mod,它在游戏中添加了一个基于 lua 的原始计算机。使用这台计算机,可以编写程序以各种方式与 Minecraft 世界交互。 ComputerCraft 问题是否适用于 Whosebug 之前已经在其他问题中进行过辩论,但我认为它 适用的,因为 mod 在很大程度上是关于编程的,虽然进行了一些 ComputerCraft 专有 API 调用,但这个问题中没有任何概念不适用于其他与 ComputerCraft 无关的 lua 程序(当然除非问题是由错误引起的在 ComputerCraft 本身)。使用的 API 的文档可以在 http://www.computercraft.info/wiki/Category:APIs 找到。

注意:如果您没有 ComputerCraft 经验,请不要惊慌;我相信这个问题可能与 ComputerCraft 完全无关,而是由我未能掌握的 lua 中 OOP 的一些复杂性引起的。在我认为有必要解释我正在进行的专有调用的最重要方面的地方,我对代码进行了注释。有什么不明白的可以评论,我会解释的。

如果您希望能够在没有 Minecraft 的情况下 运行 代码示例,可以使用名为 CCEmuRedux 的优秀 ComputerCraft 模拟器。我已经在实际的 ComputerCraft 和 CCEmuRedux 上测试了我的代码,结果相同,尽管 CCEmuRedux 似乎不支持监视器。需要 "Advanced" 电脑才能看到颜色。

问题

在 ComputerCraft 1.75(和 CCEmuRedux @ ComputerCraft 1.79)中,给出以下 class gui,以及一个尝试在每个中绘制基本按钮的测试程序两个不同的 windows 使用 gui class,两个按钮都在第二个 window 中绘制。从图形上看,guiTest.lua 的结果是 https://i.imgur.com/llFDlYI.png,而我希望在 Window 中绘制第一个(橙色)按钮。虽然我有一些关于它为什么会这样的理论,我没有必要的 lua 经验来弄清楚如何修复它。这是一个 MWE。

代码示例

gui.lua

--Meta class
gui = {t, vpx, vpy}

function gui:new(t, title) -- I'm aware this constructor is not in keeping with the referenced Tutorialspoint article, it is of no consequence in this example
    local o = o or {}
    setmetatable(o, self)
    self.__index = self
    self.t = t
    local sX, sY = self.t.getSize() -- get the size of the virtual terminal and save it to vpx, vpy
    self.vpx = sX
    self.vpy = sY
    self.t.setCursorPos(1, 1) -- put cursor at the start of the virtual terminal
    self.t.write(tostring(title)) -- note that this WORKS, it prints one title per Window as seen in the screenshot
    return o
end

function gui:drawButton(x, y, sX, sY, colour)
    self.t.setCursorPos(x, y) -- set the cursor to the button's first x- and y-coords
    self.t.setTextColor(colours.black) -- set text colour to black
    self.t.setBackgroundColor(colour) -- set background colour to the colour of the button
    for iY = 1, sY do 
        for iX = 1, sX do
            self.t.write("#") -- print hashtags to represent the button until we reach sX and sY
        end
        self.t.setCursorPos(x, y + iY) -- move cursor a line down, and back to button's first x-coord
    end
    self.t.setCursorPos(self.vpx, self.vpy) -- get cursor out of the way so the screenshot will be prettier
end

guiTest.lua

dofile('gui.lua')

local w1 = window.create(term.current(), 2, 2, 22, 15)
local w2 = window.create(term.current(), 26, 2, 22, 15) -- creates virtual windows in a terminal, acting as terminals of their own
-- window.create() arguments: terminal object to create window on, x position, y position, x size, y size

local g1 = gui:new(w1, "Window 1") -- create gui object for the first window
local g2 = gui:new(w2, "Window 2") -- create gui object for the second window

g1:drawButton(5, 3, 3, 2, colours.orange) -- should draw in w1, draws in w2
g2:drawButton(10, 8, 4, 4, colours.green) -- should draw in w2, draws in w2

尝试过的解决方案

为了它的价值,我一直在遵循 Lua OOP 秘诀 @ https://www.tutorialspoint.com/lua/lua_object_oriented.htm。这是我的第二个基于 lua 的程序,所以我希望它是一个 "easy" 问题。不过,我对 OOP 在其他几种语言(特别是 Java)中的工作原理有不止一个基本的了解,因此我的程序员的 "Spidey-Sense" 告诉我一些变量,例如 t,不是 "local enough"(同一个变量被两个 windows 使用),或者 gui 对象中的某个引用得到当一个新的 gui 对象被创建时被覆盖。

因此,我尝试将 table gui 设为本地,以确保它不会被覆盖:

local gui = {t, vpx, vpy}

...但它在 "gui.lua" (setmetatable(o, self)) 的第 6 行出现错误 attempt to index ?,所以我尝试了(意识到我将无法访问该函数来自外部 gui.lua,因为它是本地的):

local function gui:drawButton(x, y, sX, sY, colour)

... 结果是 guiTest.lua:1: bios.lua:14 [string "gui.lua"]:17:'(' expected。第 17 行是上面代码标签中 gui:drawButton() 的定义。在我有限的 ComputerCraft 经验中,这种格式错误的错误消息通常意味着 lua 解释器或 CraftOS 异常混乱™,但我认为它的要点是 "you can't make an object method local",因为我 can 使 other 以与我在此处尝试的方式类似的方式在本地运行。

一般来说,window.create() 或使用 window API 都不是问题,因为使用单独的监视器而不是单独的 windows 在同一台显示器上。本质上:

dofile('gui.lua')

local w = window.create(term.current(), 2, 2, 22, 15)
local m = peripheral.wrap('top') -- m becomes the Monitor physically on top of the ComputerCraft Computer

local gw = gui:new(w, "Window") -- create gui object for the Window
-- m is a terminal object, just like w, so we can still do
local gm = gui:new(m, "Monitor") -- create gui object for the Monitor

gw:drawButton(5, 3, 3, 2, colours.orange) -- should draw in w, draws in m
gm:drawButton(10, 8, 4, 6, colours.green) -- should draw in m, draws in m

也许有一种方法可以将函数存储为局部变量,类似于

local gui:printFoo = function() print("foo") end 
self:printFoo() -- prints "foo"...?

...或者更有可能的是,这个问题是我完全错过的。

结论

为了简化一个长问题,定义两个 gui 对象,每个对象对应两个虚拟控制台 windows,并尝试在每个对象上绘制一个按钮虚拟控制台 windows 使用它们各自的 gui 对象,导致两个按钮被绘制在同一个虚拟控制台 window 上。为什么?

是的,Lua 中的 OOP 对于 Lua 初学者来说很难,尽管他们非常了解 OOP 语言(例如 Java)。

--Meta class
gui = {}  -- class is a global variable, no default properties exist

function gui:new(t, title)   -- t = window, self = your class "gui"
    local o = {}   -- creating NEW object
    setmetatable(o, self)  -- link the object with the class
    self.__index = self
    o.t = t        -- save window into object (not into class)
    local sX, sY = t.getSize() -- get the size of the virtual terminal
    o.vpx = sX  -- save window's properties into object (not into class)
    o.vpy = sY
    t.setCursorPos(1, 1)
    t.write(tostring(title)) 
    return o
end

function gui:drawButton(x, y, sX, sY, colour)  -- self = object
    ....
end