学习Cocos2d-x Lua:面向对象 - 类和继承

2015年03月25日 11:33 0 点赞 0 评论 更新于 2017-05-09 00:42

这个系列主要聚焦于学习Cocos2d-x Lua,总结Lua开发过程中涉及的知识点,以及探讨在开发过程中如何使用Cocos Code IDE。本篇将着重介绍Lua中的面向对象编程,主要讲解类和继承。

写在前面

终于要探讨Lua中的面向对象编程了。相信目前学习Lua的大部分人都是为了开发手机网游,并且基本都是冲着脚本语言的热更新特性去的,因此全脚本开发变得十分流行。对于普及程度不如C++、Java等主流语言的Lua而言,新手若要在短时间内上手开发游戏并非易事。所以大家更倾向于运用面向对象思想来学习Lua。

1. 类的对象

创建一个类其实很简单,在Lua中,类可以用一个table来表示。那么,如何使用这个类创建多个对象呢?可以借助元表和元方法来实现。

以下是示例代码:

TSprite = {
x = 0,
y = 0
}

function TSprite:setPosition(x, y)
self.x = x
self.y = y
end

function TSprite:new()
local o = {}
setmetatable(o, {__index = self})
return o
end

local who1 = TSprite:new()
local who2 = TSprite:new()
who1:setPosition(1, 2)
who2:setPosition(44, 6)
print("who1坐标(" .. who1.x .. "," .. who1.y .. ")")
print("who2坐标(" .. who2.x .. "," .. who2.y .. ")")

留意TSpritenew函数,该函数创建了一个新的table,并为其设置了一个元表,这个元表的__index元方法指向TSprite本身,最后返回这个新的table。因此,所有通过new函数生成的新table,都能够使用TSprite的函数和各个字段属性(因为__index的值是TSprite)。

通过new函数,我们创建了who1who2,并调用它们的setPosition函数。最终,who1who2xy值是不同的,这就是类的对象。

2. 类对象的__index都是同一个TSprite,为什么xy值可以不相同?

或许大家会有这样的疑惑:who1who2最终调用的都是setPosition函数,调用self.x时最终不也是调用了TSpritex值吗?为什么它们的xy值会不一样呢?下面来梳理一下:

  1. who1中不存在setPosition方法时,Lua会去__index元方法里查找,从而找到TSpritesetPosition函数。
  2. setPosition函数中,使用了self.x = x,此时的self就是who1。由于who1中原本不存在x字段,所以如果打印self.x的值,实际上打印的是TSpritex值。
  3. 但需要注意的是,__index元方法是用于调用的,而不是用于赋值的。因此,self.x = x这句话,实际上是给who1这个table的x字段赋值。who1本身不存在x字段,赋值后who1就有了x字段,以后who1就不会再去TSprite里查找x字段了。
  4. 所以,对who1who2xy字段进行赋值操作时,完全不会影响到TSprite

3. 节省资源——使用TSprite作为元表

仔细观察new函数,在给新table设置元表时,我们重新创建了一个元表:

setmetatable(o, {__index = self})

这样做的话,每次调用new函数创建一个新对象时,都会产生一个新的元表。虽然这个开销看似可以忽略不计,但对于有强迫症的开发者来说,可能会倾向于下面的代码:

function TSprite:new()
local o = {}
setmetatable(o, self)
self.__index = self
return o
end

在这段新的new函数中,直接使用self作为元表,并将self赋值给__index。下面来详细解释一下:

  1. 调用new函数时,self实际上就是TSprite本身。这里使用self是为了后续扩展考虑,当然也可以直接用TSprite代替。
  2. self.__index = self,其实就是设置元表的__index元方法,相当于TSprite.__index = TSprite
  3. TSprite作为一个table,是可以作为元表的,并且元表可以有__index元方法,这并没有问题。
  4. 通过这个小技巧,我们避免了每次调用new函数时都额外创建一个新的元表,从而节省了资源。

4. 继承

在Lua里实现继承其实很简单,但需要认真思考。以下是示例代码:

TSprite = {
x = 0,
y = 0
}

function TSprite:setPosition(x, y)
self.x = x
self.y = y
end

function TSprite:new()
local o = {}
setmetatable(o, self)
self.__index = self
return o
end

local MoneySprite = TSprite:new()

function MoneySprite:setPosition(x, y)
print("呵呵,我是富二代,根本不需要改变。")
end

local who = MoneySprite:new()
who:setPosition(44, 6)
print("who坐标(" .. who.x .. "," .. who.y .. ")")

TSprite的定义保持不变。按照之前的理解,MoneySpriteTSprite的一个对象,但实际上它仍然是一个table。此时,我们修改了MoneySpritesetPosition函数,因此调用MoneySpritesetPosition函数时,与TSprite无关。

关键在于后续的代码,我们再次调用MoneySpritenew函数创建了一个新对象who。这里的关键在于new函数里的代码,此时new函数里的self是谁呢?由于new函数是由MoneySprite调用的,所以self就是MoneySprite。于是,新对象who的元表就是MoneySprite,元表的__index也是MoneySprite

因此,调用whosetPosition函数时,实际上调用的是MoneySpritesetPosition函数。所以,whoMoneySprite的对象,而MoneySprite就是TSprite的子类。

以下是输出结果:

[LUA-print] 呵呵,我是富二代,根本不需要改变。
[LUA-print] who坐标(0,0)

怎么样,继承的实现方法是不是很简单?如果对元表、元方法、self的概念比较生疏,可能一时难以理解,没关系,多思考一下,或者隔天再回头思考,或许就会豁然开朗了。

作者信息

boke

boke

共发布了 1025 篇文章