2

与我的示例并行的是,我正在构建一个游戏并且有一个名为 player.lua 的类。

几个星期前,当我真的不知道 Lua 是如何工作的时候,我编写了这个代码,所以我没有为玩家构建的表格。

我为玩家分配了各种属性,例如 self.speed 或 self.strength。我希望(并且它有效)这些属性都是玩家的。

我有一些问题,我似乎无法找到一个合乎逻辑的、直观的答案。

如果我在 player.lua 中声明了一个表 (player = {}),那么 player.speed 将引用播放器表的“速度”键。但是没有这样的表,我目前实际上在做什么?

如果我在 player.lua 中使用 player.speed 而不是 self.speed 会怎样?

如果将来我想在同一个游戏中同时拥有多个玩家怎么办?我如何像在 Java 中一样“实例化同一类的多个实例”,但在 Lua 中?这基本上会涉及拥有一个中央游戏 lua 文件,比如 main.lua 或 game.lua,然后构建一个玩家表,其中每个元素本身就是一个玩家表吗?

比如说, listofPlayers = {} 你会去: table.insert(listOfPlayers, player:new()) 其中 player:new() 会用玩家的所有默认属性实例化一个新表,然后返回那个表?

那么我什么时候使用元表呢?

4

3 回答 3

4

元表

元表用于重载语言中的常见操作。这些操作可以包括加法、乘法、相等比较和(顾名思义)类似表的操作,例如通过键访问值table[key]

元表通常用于在 Lua 中实现面向对象的编程。驱动它的主要机制是使用__index. 这个例子将以最基本的形式说明这一点:

>>> parent = {parentID = 'Secret'}
>>> child = {}
>>> setmetatable(child,{__index=parent})
>>> =child.parentID
Secret

密钥parentID实际上并不存在于 child 中,因此child 中没有类似以下内容:

child = {
        parentID = 'Secret'
    }

相反,我们已经做到了,当有人在寻找不存在的键时child,我们去查看parent,这是在我们分配给 table in的元表中设置的:

>>> setmetatable(child,{__index=parent})

因此,当我们要求时,以编程方式的事件流child.parentID是:

  1. 是否child包含一个键值对,哪个键是"parentID"?不,因此转到 2。
  2. 是否child__index在它的元表中定义?是的,转到 3。
  3. 查看引用的表__index以检查密钥"parentID"
  4. 在父母身上找到!返回的值parent["parentID"]

所以这允许我们在表之间创建关系。我们可以使用__indexmetatable 方法在代表所有玩家信息的表格和每个玩家自己之间建立关系,如下所示:

Player = { }
    Player_metatable = {
        __index = Player --look for the missing key in the Player table
    }

    function Player.new(name)
        aPlayer = { name = name } 
        setmetatable(aPlayer,Player_metatable)
        return aPlayer
    end 

    function Player:rotate()
        print("I'M ROTATING",tostring(self))
    end 

    henry =  Player.new("Henry")
    henry:rotate()

当我们调用时,Player.new("Henry")我们创建一个表,并将其元表设置为Player_metatable,就像child在第一个示例中设置元表一样。但是,我们只是在函数内部而不是直接在 bat 中执行,没有区别!

当我们调用上面概述的情况时,我们会在 中henry:rotate()查找一个键,但没有找到,所以我们查找(因为那是指向我们元表中的表)。我们有一个与该键关联的函数。因此,我们然后调用该函数,由于语法而将自己传入。"rotate"henryPlayer__indext:function

要创建一个类的实例,您只需要分配一个表,元表指向您拒绝类行为的表。所以我们可以创建任意数量的玩家:

my_player_name = Player.new(...)

修改Player表中的值,将反映在具有关联元表的所有表中。

于 2013-06-23T13:50:15.270 回答
2

HennyH 很好地描述了如何使用元表以及它们为您提供了什么,但要直接回答您的问题,假设您的 Player 表包含一种方法name

local Player = {}
local name
function Player:name()
  return self.name -- #1
  return Player.name -- #2
  return name -- #3
end
  • #1 使用表name的字段self,这是您大部分时间使用的,因为它允许使用元表来实现类继承。self将引用调用的任何表方法(如果通过引用找到该方法,则name可能不是)Player__index
  • #2 使用表name的字段,忽略对实际对象Player的任何引用。self如果您想要一些不受继承类影响的特定于玩家的数据,这可能很有用。
  • #3 使用局部变量。它在许多方面Player.name与一个显着的区别相似:它允许您实现“类”的私有元素。请注意,一些使用Player类的代码可以随意访问和修改Player.name。当此方法用作name局部变量时的唯一访问方式是通过将此变量作为上值的闭包时,情况并非如此。如果您不提供修改它的方法,它将是只读的。

每个选项都有自己的用途,但 #1 可能是您最常看到的。

于 2013-06-23T20:16:48.500 回答
1

从不同类继承的另一种方法是复制所有方法,可能通过“新”方法。在这种情况下,您可以混合和匹配许多课程。Lua 只需复制对该方法的引用,因此内存开销最小

prototype={}
--add some methods to prototype
function prototype:new()
 local newtable={}
 for k,v in pairs(self) do
   newtabke[k]=v
 end
 return newtable
end
于 2013-06-23T18:59:57.190 回答