最新文章
Cocos2d-x游戏开发实例详解7:对象释放时机
03-25 13:59
Cocos2d-x游戏开发实例详解6:自动释放池
03-25 13:55
Cocos2d-x游戏开发实例详解5:神奇的自动释放
03-25 13:49
Cocos2d-x游戏开发实例详解4:游戏主循环
03-25 13:44
Cocos2d-x游戏开发实例详解3:无限滚动地图
03-25 13:37
Cocos2d-x游戏开发实例详解2:开始菜单续
03-25 13:32
学习Cocos2d-x Lua:模块编写与module函数
在这个系列中,我们将深入学习Cocos2d-x Lua,总结Lua开发过程中涉及的知识点,以及如何在开发中使用Cocos Code IDE。本文将着重讲解如何编写Lua的模块以及module函数的使用。
1. 编写一个简单的模块
在Lua中,模块通常可以理解为一个table,这个table里包含一些变量和函数。这和我们熟悉的类很相似(实际上很难明确它们之间的区别)。
下面我们来创建一个简单的模块,新建一个名为game.lua的文件,代码如下:
game = {}
function game.play()
print("那么,开始吧")
end
function game.quit()
print("你走吧,我保证你不会出事的,呵,呵呵")
end
return game
在这段代码中,我们定义了一个tablegame,并为其添加了两个字段,这两个字段的值是函数。
要使用这个模块,我们需要用到require函数。在main函数里可以这样使用:
local function main()
cc.FileUtils:getInstance():addSearchPath("src")
game = require("game")
game.play()
end
需要注意的是,在使用require加载其他文件时,要确保文件路径设置正确,否则会找不到文件。由于我使用的是Cocos Code IDE,直接调用addSearchPath函数即可,这里我的game.lua文件位于src目录下。
运行代码,结果如下:
[LUA-print] 那么,开始吧
这就是一个简单的模块。如果习惯了Java、C++等面向对象语言,可以简单地将模块理解为类。
2. 为以后的自己偷懒——避免修改每个函数中的模块名
假设我们要将刚刚的game模块改名为eatDaddyGame,需要做两件事:
- 修改
game.lua的文件名。 - 修改
game.lua的内容,将所有的game替换为eatDaddyGame。
目前game.lua中的函数较少,只有两个,但实际上一个模块的函数数量通常不会少。手动修改这些函数会很繁琐,而批量修改又容易出错。
我们可以采用以下方法偷懒:
game = {}
local M = game
function M.play()
print("那么,开始吧")
end
function M.quit()
print("你走吧,我保证你不会出事的,呵,呵呵")
end
return M
我们使用一个局部变量M来代替game,这样以后只需要修改前面的game即可,函数部分的内容无需修改。这种方法在某些情况下很有用,修改越少,出错的可能性就越低。
3. 更进一步的偷懒——模块名参数
实际上,我们还可以更偷懒,以后修改模块名时,只需要修改模块的文件名,文件内容无需改动。具体实现如下:
local M = {}
local modelName = ...
_G[modelName] = M
function M.play()
print("那么,开始吧")
end
function M.quit()
print("你走吧,我保证你不会出事的,呵,呵呵")
end
return M
这里的local modelName = ...中的...表示传递给模块的模块名,在这个例子中就是"game"这个字符串。
接着,我们使用全局环境_G,以"game"作为字段名,将M添加到_G这个table中。这样,当我们直接调用game时,实际上就是在调用_G["game"],也就是这里的M。
4. 利用非全局环境制作更简洁和安全的模块
如果认为前面的方法已经很偷懒了,那你就太天真了。我们还可以利用非全局环境setfenv函数来进一步简化代码。
以下是示例代码:
local M = {}
local modelName = ...
_G[modelName] = M
setfenv(1, M)
function play()
print("那么,开始吧")
end
function quit()
print("你走吧,我保证你不会出事的,呵,呵呵")
end
return M
我们将game.lua模块的全局环境设置为M,这样在定义函数时就不需要再添加M前缀。因为此时的全局环境就是M,不带前缀定义的变量会作为全局变量保存在M中,所以play和quit函数仍然位于M这个table里。
这种方法让代码更加简洁,但如果直接运行代码会报错,因为全局环境改变后,print函数等原全局变量将无法找到。下面我们将介绍解决这个问题的方法。
5. 解决原全局变量的无法找到的问题——方案1
第一种方法是使用继承,代码如下:
local M = {}
local modelName = ...
_G[modelName] = M
-- 方法1:使用继承
setmetatable(M, {__index = _G})
setfenv(1, M)
function play()
print("那么,开始吧")
end
function quit()
print("你走吧,我保证你不会出事的,呵,呵呵")
end
return M
通过使用__index元方法,当找不到print等函数时,会去原来的_G里查找,从而解决了原全局变量无法找到的问题。
6. 解决原全局变量的无法找到的问题——方案2
第二种方法更简单,使用一个局部变量保存原来的_G,代码如下:
local M = {}
local modelName = ...
_G[modelName] = M
-- 方法2:使用局部变量保存_G
local _G = _G
setfenv(1, M)
function play()
_G.print("那么,开始吧")
end
function quit()
_G.print("你走吧,我保证你不会出事的,呵,呵呵")
end
return M
这种方法的缺点是,每次调用print等函数时都需要使用_G前缀。
7. 解决原全局变量的无法找到的问题——方案3
第三种方法比较繁琐,使用局部变量将需要用到的其他模块保存起来,代码如下:
local M = {}
local modelName = ...
_G[modelName] = M
-- 方法3:保存需要使用到的模块
local print = print
setfenv(1, M)
function play()
print("那么,开始吧")
end
function quit()
print("你走吧,我保证你不会出事的,呵,呵呵")
end
return M
这种方法的缺点更为明显,所有用到的模块都需要用局部变量声明一次。不过,从速度上来说,第三种方案比第二种方案快,第二种方法又比第一种快,但具体快多少还需要进一步测试。
8. 你就笑吧,但,我还想更加偷懒——module函数
前面介绍的技巧已经很方便了,但Lua为我们提供了更强大的功能——module函数,它可以自动完成前面的一系列操作。
回顾一下前面为了偷懒而写的代码:
local M = {}
local modelName = ...
_G[modelName] = M
setmetatable(M, {__index = _G})
setfenv(1, M)
使用module函数,我们可以简化代码,修改game.lua的内容如下:
module(..., package.seeall)
function play()
print("那么,开始吧")
end
function quit()
print("你走吧,我保证你不会出事的,呵,呵呵")
end
可以看到,前面的几行代码都被省略了,只留下了module函数的调用。module函数的调用相当于执行了前面的那些代码。
package.seeall参数的作用是让原来的_G依然生效,相当于调用了setmetatable(M, {__index = _G})。
另外,代码末尾的return M也不见了,因为module函数的存在,不需要我们主动返回这个模块的table。
9. 结束
这篇文章的内容较多,但实际上我还省略了一些内容,比如package.loaded、Lua路径查找的规则等。这些内容Cocos Code IDE或Cocos2d-x Lua已经帮我们处理好了,我们无需过多关注。