存档

‘cocos2d-lua’ 分类的存档

Quick-Cocos2d-X 捋一捋框架流程

2014年3月24日 没有评论

猴子原创,欢迎转载。转载请注明: 转载自Cocos2D开发网–Cocos2Dev.com,谢谢!

原文地址: http://www.cocos2dev.com/?p=535

一直比较关注Quick Lua,但是项目中一直使用的公司自有的Lua框架,所以一直没机会在实际中使用下Quick Lua。看到群里很多人都在用这个,我在这里梳理下开始使用的流程吧,我主要是说下实际使用上的流程问题。

比如很多学习者甚至不知道enterScene("MainScene") 为什么里面可以是个字符串?当然如果你已经很熟悉框架了,这篇文章就可以跳过了,呵呵。

下面开始吧!

一、前置准备
1、安装下载之类的,官方论坛写的很清楚了,我就不说了。http://wiki.quick-x.com/doku.php?id=zh_cn:get_started_create_new_project
2、关于IDE,我使用的IEDA,配置导出的api代码提示,还是挺方便的。http://wiki.quick-x.com/doku.php?id=zh_cn:get_started_install_intellij_idea

二、新建一个工程

新建之后,你首先看到的main.lua启动到MyApp.lua。

require("app.MyApp").new():run()

看MyApp.lua文件:
1、require("app.MyApp")
这里执行的MyApp.lua的代码是:

local MyApp = class("MyApp", cc.mvc.AppBase)  -- 继承cc.mvc.AppBase
return MyApp

这时候,你得到了MyApp这个类(lua关于类的实现网上很多)。

2、require("app.MyApp").new()

MyApp.new()执行后,执行的代码是:

function MyApp:ctor()
    MyApp.super.ctor(self)
end

为什么new()了之后会执行MyApp:ctor()?请看function.lua下的function class(classname, super)方法:

function cls.new(...)
            local instance = cls.__create(...)
            -- copy fields from class to native object
            for k,v in pairs(cls) do instance[k] = v end
            instance.class = cls
            instance:ctor(...)
            return instance
end


可以看到,在class的实现方法里面,给每个创建的类声明了一个new()方法,方法里面调用了ctor()构造方法(ctor只是个名字,所以不是有些人认为的new了之后,当然会调用构造方法,lua没有类,只是我们模仿了类)

3、require("app.MyApp").new():run()

这时候调用了

function MyApp:run()
    CCFileUtils:sharedFileUtils():addSearchPath("res/")
    self:enterScene("MainScene")
end


所以进到了MainScene.lua。

对于MyApp.lua文件,如果我修改成下面的样子,是不是你就理解了上面所做的事情:
 

-- 声明类
MyApp = class("MyApp", cc.mvc.AppBase) 


--- 类构造方法
--
function MyApp:ctor()
    MyApp.super.ctor(self)
end


--- 对应cpp版的static create()方法
--
function MyApp:create()
    local myApp = MyApp.new()
    myApp:init()
end


--- 你自己的方法
-- @param self 
--
local function launchMainScene(self)
    CCFileUtils:sharedFileUtils():addSearchPath("res/")
    self:enterScene("MainScene")
end


--- init 方法
--
function MyApp:init()
    -- add code here
    launchMainScene(self)
end

对应的main.lua将原来的require("app.MyApp").new():run()

修改为:

require("app.MyApp")
MyApp:create()


这样你是不是更容易理解了,哈哈。

三、MainScene.lua
enterScene("MainScene") 为什么可以切换场景?
我们看下MyApp的父类AppBase里面:

function AppBase:enterScene(sceneName, args, transitionType, time, more)
    local scenePackageName = self. packageRoot .. ".scenes." .. sceneName
    local sceneClass = require(scenePackageName)
    local scene = sceneClass.new(unpack(totable(args)))
    display.replaceScene(scene, transitionType, time, more)
end

 

这样你能理解了为什么连require文件都没有就能调用MainScene,当然你要留意下,它require时候的文件路径,scene默认写的app/scenes文件夹

好了,其他的应该按照上面的思路基本都能知道为什么了。我就不一一列举了。

分类: cocos2d, cocos2d-lua 标签:

COCOS2D-X Lua面向对象编程

2013年4月22日 没有评论

猴子原创,欢迎转载。转载请注明: 转载自Cocos2D开发网–Cocos2Dev.com,谢谢!

原文地址: http://www.cocos2dev.com/?p=425

上次有个同学问我,说lua太简单了,没有什么结构,也不好做面向对象编程。其实可以这样说,Lua中的table就是一个对象。

下面我一点一点介绍Lua的面向对象编程。

一、对象的方法函数:

  Hero = {attack = 100}
  
  function Hero.skill(addAttack)
    Hero.attack = Hero.attack + addAttack
  end
  
  Hero.skill(20)
  print(Hero.attack)	--> 120

上面的先创建了一个函数,并调用该函数。

仔细的同学可能发现了,调用函数的时候使用了全局Hero。我在上一篇细节介绍中提过,Lua尽量减少全局变量的使用。而且这里也有风险,一不小心修改了Hero,可能Hero就不能正常工作了。

那么联想到上一篇写到的local temA =  A,有人可能想到了这样修改上面的函数调用:

  oneHero = Hero; 
  Hero = nil
  oneHero.skill(30)
  print(oneHero.attack) --> 错误,oneHero为nil

这样也是错误的。因为Hero已经为nil了。

正确的如何修改呢?我们可以使用this/self 来实现:

  Hero = {attack = 100}
  
  function Hero.skill(self,addAttack)
    self.attack = self.attack + addAttack
  end
  
  oneHero = Hero; 
  Hero = nil
  oneHero.skill(oneHero,30)
  print(oneHero.attack) 	--> 130

OK,这下可以了,但是self每次需要自己传,看着就麻烦,其实Lua也可以隐性调用self,我们在修改下:

  Hero = {attack = 100}
  
  function Hero:skill(addAttack)
    self.attack = self.attack + addAttack
  end
  
  oneHero = Hero; 
  Hero = nil
  oneHero:skill(30)
  print(oneHero.attack) --> 130

请注意上面 :  的使用,冒号可以在方法中添加一个额外的隐藏参数。上面其实也看到了Hero.skill() 和Hero:skill()的区别。

二、类,将table作为自己的元表
例如上面的Hero例子,我们修改下:

  Hero = {}
  
  function Hero:new(o)
    o = o or {}
    setmetatable(o,self)
    self.__index = self
    return o
  end
  
  function Hero:skill(addAttack)
    self.attack = self.attack + addAttack
  end
  
  oneHero = Hero:new{attack = 90}
  oneHero:skill(10)
  print(oneHero.attack) -->100

创建一个新英雄的时候,oneHero将Hero设置为自己的元表,当oneHero:skill(10)的时候,在table oneHero中查找skill,没有找到后,会进一步搜索元表的__index。

所以等价于:getmetatable(oneHero).__index.skill(oneHero,10)

而getmetatable(oneHero) 是Hero,Hero.__index还是Hero

所以等价于Hero.skill(oneHero,10)

三、继承
例如:

 Hero = {attack = 0}
  
  function Hero:new(o)
    o = o or {}
    setmetatable(o,self)
    self.__index = self
    return o
  end
  
  function Hero:skill(addAttack)
    self.attack = self.attack + addAttack
  end
  
  function Hero:injured(loseAttack)
    if loseAttack > self.attack then error"not engouth attack" end
    self.attack = self.attack - loseAttack
  end
  
  HumanHero = Hero:new()
  
  oneHero = HumanHero:new{attack = 100}
  oneHero:skill(10)
  
  print(oneHero.attack) -->110

每个对象没有的方法都会去父类中查找(这里理解为父类),所以如果某个对象需要一些新的属性方法,只需要在该对象中实现就可以了。

分类: cocos2d, cocos2d-lua 标签:

COCOS2D-X 快速熟悉LUA细节问题

2013年4月19日 没有评论

猴子原创,欢迎转载。转载请注明: 转载自Cocos2D开发网–Cocos2Dev.com,谢谢!

原文地址: http://www.cocos2dev.com/?p=423

这篇博文主要是接着《COCOS2D-X 快速熟悉LUA基本细节问题》往下说了下,所以如果是cocos2dx开始学习Lua的话,请先看下《COCOS2D-X 快速熟悉LUA基本细节问题 》,主要快速讲了一些Lua的特性,所以快速学习Lua的话,建议先看一遍。

一、{},构造式

{a = 1, b= 2} 等价于 {["a"] = 1,["b"] = 2} 

{"x","y","z"} 等价于 {[1] = "x",[2] = "y", [3] = "z"}


如果真的需要以0为数组的话:

a = {[0] = "x","y","z"}  — 不推荐这样

二、尽量使用局部变量,便于垃圾收集器释放内存

如果是全局变量,局部使用的时候,有时可以考虑:

local temA = A


使用局部变量保存全局变量的值,如果后面函数会改变A的值,可以考虑这样,也可以加快当前域的访问速度。



三、控制语句


1、if then else


2、while

while true or false do
-- todo
end

3、repeat-until

repeat
-- todo
until true or false

4、for


for 起始,变量, 步长(不写默认1) do

–todo

end


5、iterator

for k,v in ipairs(a) do
-- k是索引
-- v是对应的value
end

for k in pairs(a) do
-- k是索引
end

可以用来逆向table:

revA = {}
for k,v in ipairs(a) do
revA[v] = k
end

注意:其他的switch、do-while 没有

五、return、break ,只能放在一个块的结束语句(end、else、until)前面

function func()
local x = 0
x = 1
print(x)
return  -- 想在这里跳出,这样是错误的。
if(x>1) then
x = 10
end
end 

修改成:

function func()
local x = 0
x = 1
print(x)
do
return -- 这样才是正确的,可以执行return
end
if(x>1) then
x = 10
end
end 

六、function,函数可以有多个返回值

例如:

function func() 
return "a","b" --返回2个值
end


1、如果函数是表达式的最后一个,则函数保留尽可能多的返回值用来赋值变量

x = func() 		-- x = "a",返回值"b"不要
x,y = func()  		-- x = "a", y = "b"
x,y,z = 1,func()     -- x = 1, y = "a" , z = "b"  注意func是表达式的最后一个
x,y,z = func()	     	-- x = "a", y = "b" , z = nil  返回值不够用nil补充


2、如果函数不是表达式的最后一个,则只返回一个值

x,y,z = func(),1	-- x = "a", y = 1, z = nil  


3、函数在table构造式中,如果函数在构造式的最后一个位置,返回所有值,否则返回一个值

t = {func()} --t = {"a","b"}
t = {func(),2} --t = {"a",2}

4、函数在return之后,返回所有值


5、如果函数在()中,则只返回一个值

print(func()) --输出 a b
print((func())) --函数被放在了()中,输出 a

6、函数实参可以是变长参数,如果函数实参同时包含了固定参数和变长参数,变长参数必须放在固定参数后面。请注意…的用法

七、如何写带描述的函数实参

//OC代码:
- (void) createRect:(float)X y:(float)Y width:(float)Width height:(float)Height
{
//TODO...
}

有时候函数参数很多,想用oc这种带描述的参数。lua虽然不支持,但是可以用table实现。

function createRect(rectOptions)
if type(rectOptions.title) ~= "string" then
error "need title"
end
end

createRect({x = 0,y = 0 , width = 200,height = 300 , title = "hello sunny"})

提示:函数实参只有一个table时候,可以不要(),所以可以写成:

createRect{x = 0,y = 0 , width = 200,height = 300 , title = "hello sunny"}

分类: cocos2d, cocos2d-lua 标签:

cocos2d-x 快速熟悉Lua基本细节问题

2013年4月18日 没有评论

猴子原创,欢迎转载。转载请注明: 转载自Cocos2D开发网–Cocos2Dev.com,谢谢!
原文地址: http://www.cocos2dev.com/?p=416


一、程序块的写法:

a = 1 
b = a*2

a = 1;
b = a*2;

a= 1; b = a*2

a = 1 b = a*2

这四个程序块都是等价的。


二、Lua 变量区分大小,~= 是 不等于


三、注释符号

--[[
  这里是多行注释符号
  这里是多行注释符号
]]
print("Sunny")  -- 两个连接符就是单行注释符号

注意:多行注释符号一般这样写:

--[[
 print("Sunny")  --已经被注释了
--]]

优点:假如你想把注释去掉,直接在开头注释符号前面加一个连接符就可以了。(多行注释符号被改成了单行)

---[[
 print("Sunny")  --没有注释
--]]

四、全局变量不用声明,赋值就可以了,不用的时候赋值nil,未赋值的全局变量为nil


五、boolean,只有false和nil是假,其他值都是真(包括0和空字符串)


六、number ,表示双精度浮点数,Lua没有整数类型


七、string,可以赋值为任意二进制数据

1、注意Lua的字符串是不可变的值,不能像c++那样修改字符串里的字符,需要修改的话请创建一个新的字符创。

a = "hello world" --也可以是单引号,注意统一风格就可以了
b = string.gsub(a,"world","sunny")
print(a) --输出 hello world
print(b) --输出 hello sunny

2、在字符串上面进行算术操作,lua会将字符串转化成数字

print("1" + 1) --输出 2
print("a" + 1) --错误:a string value

3、tonumber(),字符串转数字

print(tonumber("123")) --输出数字 123
print(tonumber("a123")) --输出 nil

3、tostring(),数字转字符串(也可以使用连接符,123 .. "" )

print(type(tostring(123))) --输出 string
print(type(123 .. ""))	 --输出 string

4、#,长度操作符,获取字符串长度

a = "sunny"
print(#a)	--输出 5

八、table,一种关联数组,也就是一种有索引方式的数组,索引值可以是整数、字符串或其他类型。(nil不可以作为table索引值)

a = {} --创建table,将table的引用存到a
a["x"] = 1  --增加一个新条目 "x":1
b = "y"
a[b] = 2    --增加一个新条目 "y":2

c = a         --c和a引用了同一个table

print(a["x"]) --输出 1
print(a["y"]) --输出 2
print(c["x"]) --输出 1
print(c["y"]) --输出 2
print(c.y)     --输出 2

注意:c.y 等价与 c["y"],但是不同于c[y]


常用技巧:

a = {}
for i = 1,9 do
a[i] = i 
end

for i = 0,#a do --#a 返回a的最后索引值
print(a[i]) --利用#输出所有行
end

print(a[#a]) --输出最后一个值

a[#a] = nil --删除最后一个值

a[#a+1] = 10 --添加一个值到最后

注意:不推荐使用索引0为起始索引,大多数内建函数都假设数组索引开始于1,所以为0的索引数组可能会出现错误。

分类: cocos2d, cocos2d-lua 标签:

cocos2dx-lua中如何使用自定义类以及tolua++的使用

2013年4月9日 没有评论

猴子原创,欢迎转载。转载请注明: 转载自Cocos2D开发网–Cocos2Dev.com,谢谢!

原文地址: http://www.cocos2dev.com/?p=405


cocos2dx-lua中如何使用自定义类以及tolua++的使用


使用cocos2dx-lua开发,免不了自己定义类,但是如何使用自定义的类的?


先了解下lua如何调用c++的:

lua脚本代码->通过coocs2dx中间解析层代码->将其转换并调用cocos2dx c++的前端代码

coocs2dx中间解析层代码都在libs/lua/cocos2dx_support/LuaCocos2d.cpp 这个文件中,想了解的可以自己去看下这个文件。


也就是说,你自己定义了一个类,lua能够调用你自己定义的类,你的自定义类就必须在LuaCocos2d.cpp这个中间解析文件中申明。


看了LuaCocos2d.cpp这个文件,可能有的同学都晕了,不知道怎么在LuaCocos2d.cpp中申明自己的定义的类。不过,不用担心,cocos2dx已经提供了tolua++这个工具自动编译生成新的LuaCocos2d.cpp文件。


下面开始进入正题。

一、创建一个coocs2dx-lua 的Demo工程,然后在class中自己定义个类。

SNSprite.h

//
//  SNSprite.h
//  LuaDemo
//
//  Created by LiuYanghui on 13-4-8.
//
//

#ifndef __LuaDemo__SNSprite__
#define __LuaDemo__SNSprite__

#include "cocos2d.h"
USING_NS_CC;

class  SNSprite : public CCSprite{
public:
    static SNSprite* create(const char* name);
    
private:
    void initData();
};

#endif /* defined(__LuaDemo__SNSprite__) */


SNSprite.cpp

//
//  SNSprite.cpp
//  LuaDemo
//
//  Created by LiuYanghui on 13-4-8.
//
//

#include "SNSprite.h"

SNSprite* SNSprite::create(const char* name){
    SNSprite* sprite = new SNSprite();
    if(sprite && sprite->initWithFile(name)){
        sprite->initData();
        sprite->autorelease();
        return sprite;
    }
    CC_SAFE_DELETE(sprite);
    return NULL;
}

void SNSprite::initData(){
    CCActionInterval* action = CCSequence::createWithTwoActions(CCMoveTo::create(1.0, ccp(300,300)),
                                                                CCMoveTo::create(1.0, ccp(100,100))
                                                                );
    this->runAction(CCRepeatForever::create(action));
}


上面是我定义个一个类,很简单的功能。


二、使用tolua++将自定义的类添加的LuaCocos2d.cpp文件中。

tolua++工具在/tools/tolua++/目录下。

1、编写pkg文件

首先我们看下里面会发现有很多pkg文件,所以先为自定义的类编写pkg文件,文件的编写规则在README中,我简单说下编写规则:

Writing .pkg files
    1) enum keeps the same  //枚举类型保留不变
    2) remove CC_DLL for the class defines, pay attention to multi inherites  //删除cc_dll的类定义,改用多继承
    3) remove inline keyword for declaration and implementation  //删除内联关键字声明和实现
    4) remove public protect and private //删除访问限定词
    5) remove the decalration of class member variable  //删除成员变量
    6) keep static keyword  //保留静态关键词
    7) remove memeber functions that declared as private or protected //非public的函数都删除

以上是pkg文件的编写规则。

下面编写自定义文件的pkg文件。纯文本,后缀改成pkg

SNSprite.pkg

class  SNSprite : public CCSprite{
    static SNSprite* create(const char* name);
};


注意对比之前的头文件声明,可以进一步了解pkg文件的编写规则。将编写好的pkg文件放在tolua++中,和所有的pkg文件放在一起。


2、在Cocos2d.pkg文件中注册添加的pkg文件

用文本编辑器打开Cocos2d.pkg文件,在最后一行加入我们的注册声明:

$pfile "SNSprite.pkg"


3、配置build.sh编译脚本

在tolua++文件夹里面有个tolua++.Mac.zip,解压得到mac版的tolua++

打开build.sh文件,修改成下面的:

#!/bin/bash
#
# Invoked build.xml, overriding the lolua++ property


SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
TOLUA=/APP/cocos2d-2.1rc0-x-2.1.2/tools/tolua++/tolua++
if [ -z "${TOLUA}" ]; then
    TOLUA=`which tolua++5.1`
fi


if [ -z "${TOLUA}" ]; then
    echo "Unable to find tolua++ (or tolua++5.1) in your PATH."
    exit 1
fi


cd ${SCRIPT_DIR}
${TOLUA} -L basic.lua -o /Users/liuyanghui/Desktop/LuaDemo/LuaDemo/libs/lua/cocos2dx_support/LuaCocos2d.cpp Cocos2d.pkg


两个地方注意修改:

a、TOLUA=/APP/cocos2d-2.1rc0-x-2.1.2/tools/tolua++/tolua++  这个是你的mac版tolua++的地址,请修改成你自己的。

b、${TOLUA} -L basic.lua -o /Users/liuyanghui/Desktop/LuaDemo/LuaDemo/libs/lua/cocos2dx_support/LuaCocos2d.cpp Cocos2d.pkg  

参数-o 后面的 /Users/liuyanghui/Desktop/LuaDemo/LuaDemo/libs/lua/cocos2dx_support/LuaCocos2d.cpp 是生成新的LuaCocos2d.cpp的存放路径,请修改成你自己的。


4、执行编译脚本,生成LuaCocos2d.cpp文件

终端cd到build.sh目录。执行:

./build.sh

执行后之后,在你定义的输出目录就生成了新的LuaCocos2d.cpp文件。我们自己定义的类也添加进去了。


三、在lua中调用自定义类

在刚才新的cocos2dx-lua的Demo工程中,在hello.lua文件中添加调用自定义类的函数:

local function createSunnyLayer()
        local layerSunny = CCLayer:create()
 
        local labTips = CCLabelTTF:create("这个icon图标就是使用的自定义类", "Arial", 18)
        labTips:setPosition(ccp(240,280))
        layerSunny:addChild(labTips)
 
        local sp  = SNSprite:create("Icon.png")
        sp:setPosition(ccp(100,100))
        layerSunny:addChild(sp)
 
        return layerSunny
    end
    -- play background music, preload effec t


添加到scene中:

-- run
    local sceneGame = CCScene:create() -- 创建场景
    --sceneGame:addChild(createLayerFarm()) -- 将农场层加入场景
    --sceneGame:addChild(createLayerMenu()) -- 将菜单界面层加入场景
    sceneGame:addChild(createSunnyLayer())
    CCDirector:sharedDirector():runWithScene(sceneGame)


OK,xcode编译运行,就看到效果了。








分类: cocos2d, cocos2d-lua 标签:

Cocos2d-x HelloLua 介绍

2013年3月28日 没有评论

猴子原创,欢迎转载。转载请注明: 转载自Cocos2D开发网–Cocos2Dev.com,谢谢!

原文地址: http://www.cocos2dev.com/?p=393


Cocos2d-x HelloLua 介绍Cocos2d-x HelloLua 介绍

最近网游使用lua做更新比较火,最近也有人问我能否写下lua的文章。刚新建了一个lua工程,看了下代码,就干脆先介绍下HelloLua吧。边看代码边写的注释,应该基本都写了。


-- for CCLuaEngine traceback 输出绑定执行函数发生错误的信息
function __G__TRACKBACK__(msg)
    print("----------------------------------------")
    print("LUA ERROR: " .. tostring(msg) .. "/n")
    print(debug.traceback())
    print("----------------------------------------")
end

local function main()
    -- avoid memory leak 设置脚本内存回收参数 避免内存泄露
    collectgarbage("setpause", 100)
    collectgarbage("setstepmul", 5000)

    -- 就是local function cclog(...) 定义局部Log函数
    local cclog = function(...)
        print(string.format(...))
    end

    -- 类似c++的include,会检查是否重复引入
    require "hello2"

    -- 调用外部函数,在hello2.lua中
    cclog("result is " .. myadd(3, 5))

    ---------------

    -- 获取可视区域
    local visibleSize = CCDirector:sharedDirector():getVisibleSize()
    -- 可视原点坐标 OpenGL坐标系 左下角为原点
    local origin = CCDirector:sharedDirector():getVisibleOrigin()

    -- add the moving dog 创建小松鼠
    local function creatDog()

        -- 每一帧尺寸设置,local表示局部变量
        local frameWidth = 105
        local frameHeight = 95

        -- create dog animate 加载动画资源并创建精灵帧
        -- 加载精灵动画所在纹理
        local textureDog = CCTextureCache:sharedTextureCache():addImage("dog.png") 
        -- 设置第一帧帧区域
        local rect = CCRectMake(0, 0, frameWidth, frameHeight)
        -- 创建第一帧精灵Frame
        local frame0 = CCSpriteFrame:createWithTexture(textureDog, rect)
        -- 设置第二帧帧区域
        rect = CCRectMake(frameWidth, 0, frameWidth, frameHeight)
        -- 创建第二帧精灵Frame
        local frame1 = CCSpriteFrame:createWithTexture(textureDog, rect)

        -- 基于使用第一帧Frame创建Sprite对象
        local spriteDog = CCSprite:createWithSpriteFrame(frame0)
        spriteDog.isPaused = false
        spriteDog:setPosition(origin.x, origin.y + visibleSize.height / 4 * 3)

        -- 将上面创建的两帧生成一个帧数组(这个松鼠一共就2帧)
        local animFrames = CCArray:create()

        animFrames:addObject(frame0)
        animFrames:addObject(frame1)

        -- 根据帧序列数组创建一个动画animation。帧间隔时间delay等于0.5秒
        local animation = CCAnimation:createWithSpriteFrames(animFrames, 0.5)
        -- 根据动画animation创建动作实例
        local animate = CCAnimate:create(animation);
        -- 松鼠精灵执行该动作
        spriteDog:runAction(CCRepeatForever:create(animate))

        -- moving dog at every frame 用来更新松鼠的位置,后面会调用该函数
        local function tick()
            if spriteDog.isPaused then return end
            local x, y = spriteDog:getPosition()
            if x > origin.x + visibleSize.width then
                x = origin.x
            else
                x = x + 1
            end

            spriteDog:setPositionX(x)
        end

        -- 生成一个schedule,每帧执行tick函数
        CCDirector:sharedDirector():getScheduler():scheduleScriptFunc(tick, 0, false)

        return spriteDog
    end

    -- create farm 创建地面的农场
    local function createLayerFarm()
        -- 创建一个新的Layer用作农场管理
        local layerFarm = CCLayer:create()

        -- add in farm background 添加农场背景图
        local bg = CCSprite:create("farm.jpg")
        bg:setPosition(origin.x + visibleSize.width / 2 + 80, origin.y + visibleSize.height / 2)
        layerFarm:addChild(bg)

        -- add land sprite 添加地免砖块
        for i = 0, 3 do
            for j = 0, 1 do
                local spriteLand = CCSprite:create("land.png")
                spriteLand:setPosition(200 + j * 180 - i % 2 * 90, 10 + i * 95 / 2)
                layerFarm:addChild(spriteLand)
            end
        end

        -- add crop 添加庄稼,注意crop.png是多张图的合成贴图,所以只取了里面的部分贴图
        local frameCrop = CCSpriteFrame:create("crop.png", CCRectMake(0, 0, 105, 95))
        for i = 0, 3 do
            for j = 0, 1 do
                local spriteCrop = CCSprite:createWithSpriteFrame(frameCrop);
                spriteCrop:setPosition(10 + 200 + j * 180 - i % 2 * 90, 30 + 10 + i * 95 / 2)
                layerFarm:addChild(spriteCrop)
            end
        end

        -- add moving dog 调用上面的creatDog()方法,创建一个移动的松鼠
        local spriteDog = creatDog()
        layerFarm:addChild(spriteDog)

        -- handing touch events 手指触摸事件处理
        local touchBeginPoint = nil

        -- 手指点击开始
        local function onTouchBegan(x, y)
            cclog("onTouchBegan: %0.2f, %0.2f", x, y)
            touchBeginPoint = {x = x, y = y} -- 保存点击位置
            spriteDog.isPaused = true -- 将松鼠暂停移动
            -- CCTOUCHBEGAN event must return true 这里必须返回ture,否则后续touch事件无法接收
            return true
        end

        -- 手指按住移动
        local function onTouchMoved(x, y)
            cclog("onTouchMoved: %0.2f, %0.2f", x, y)
            if touchBeginPoint then
                -- 将整个农场层拖动,因为之前已经将农场里面所有对象加入在layerFarm
                local cx, cy = layerFarm:getPosition()
                layerFarm:setPosition(cx + x - touchBeginPoint.x,
                                      cy + y - touchBeginPoint.y)
                touchBeginPoint = {x = x, y = y}
            end
        end

        -- 手指离开
        local function onTouchEnded(x, y)
            cclog("onTouchEnded: %0.2f, %0.2f", x, y)
            touchBeginPoint = nil -- 点击位置数据清空
            spriteDog.isPaused = false -- 回复松鼠移动
        end

        -- touch事件的接收函数
        local function onTouch(eventType, x, y)
            if eventType == "began" then   
                return onTouchBegan(x, y)
            elseif eventType == "moved" then
                return onTouchMoved(x, y)
            else
                return onTouchEnded(x, y)
            end
        end

        -- 注册touch事件
        layerFarm:registerScriptTouchHandler(onTouch)
        layerFarm:setTouchEnabled(true)

        return layerFarm
    end


    -- create menu 创建界面菜单
    local function createLayerMenu()
        -- 创建一个新的CCLayer管理所有菜单
        local layerMenu = CCLayer:create()

        local menuPopup, menuTools, effectID

        -- 点击菜单回调函数
        local function menuCallbackClosePopup()
            -- stop test sound effect 关闭音效
            SimpleAudioEngine:sharedEngine():stopEffect(effectID)
            menuPopup:setVisible(false) -- 隐藏菜单
        end

        -- 点击菜单回调函数
        local function menuCallbackOpenPopup()
            -- loop test sound effect 打开音效
            local effectPath = CCFileUtils:sharedFileUtils():fullPathForFilename("effect1.wav")
            effectID = SimpleAudioEngine:sharedEngine():playEffect(effectPath)
            menuPopup:setVisible(true) -- 显示菜单
        end

        -- add a popup menu 创建弹出的菜单面板
        local menuPopupItem = CCMenuItemImage:create("menu2.png", "menu2.png")
        menuPopupItem:setPosition(0, 0)
        menuPopupItem:registerScriptTapHandler(menuCallbackClosePopup) -- 注册点击回调地址
        menuPopup = CCMenu:createWithItem(menuPopupItem)
        menuPopup:setPosition(origin.x + visibleSize.width / 2, origin.y + visibleSize.height / 2)
        menuPopup:setVisible(false)
        layerMenu:addChild(menuPopup)

        -- add the left-bottom "tools" menu to invoke menuPopup 左下角的工具按钮,用来弹出菜单面板
        local menuToolsItem = CCMenuItemImage:create("menu1.png", "menu1.png")
        menuToolsItem:setPosition(0, 0)
        menuToolsItem:registerScriptTapHandler(menuCallbackOpenPopup) -- 注册点击回调地址
        menuTools = CCMenu:createWithItem(menuToolsItem)
        local itemWidth = menuToolsItem:getContentSize().width
        local itemHeight = menuToolsItem:getContentSize().height
        menuTools:setPosition(origin.x + itemWidth/2, origin.y + itemHeight/2)
        layerMenu:addChild(menuTools)

        return layerMenu
    end

    -- play background music, preload effect 

    -- uncomment below for the BlackBerry version
    -- local bgMusicPath = CCFileUtils:sharedFileUtils():fullPathForFilename("background.ogg")
    local bgMusicPath = CCFileUtils:sharedFileUtils():fullPathForFilename("background.mp3")
    SimpleAudioEngine:sharedEngine():playBackgroundMusic(bgMusicPath, true) -- 播放背景音乐
    local effectPath = CCFileUtils:sharedFileUtils():fullPathForFilename("effect1.wav")
    SimpleAudioEngine:sharedEngine():preloadEffect(effectPath) -- 预加载音效

    -- run
    local sceneGame = CCScene:create() -- 创建场景
    sceneGame:addChild(createLayerFarm()) -- 将农场层加入场景
    sceneGame:addChild(createLayerMenu()) -- 将菜单界面层加入场景
    CCDirector:sharedDirector():runWithScene(sceneGame)
end

--[[
xpcall( 调用函数, 错误捕获函数 );
lua提供了xpcall来捕获异常
xpcall接受两个参数:调用函数、错误处理函数。
当错误发生时,Lua会在栈释放以前调用错误处理函数,因此可以使用debug库收集错误相关信息。
两个常用的debug处理函数:debug.debug和debug.traceback
前者给出Lua的提示符,你可以自己动手察看错误发生时的情况;
后者通过traceback创建更多的错误信息,也是控制台解释器用来构建错误信息的函数。
--]]
xpcall(main, __G__TRACKBACK__)

写得还算详细的,后面会慢慢介绍。

分类: cocos2d, cocos2d-lua 标签:

Cocos2d-x之LUA脚本引擎深入分析

2013年3月21日 没有评论
                Cocos2d-x之LUA脚本引擎深入分析

         大家好,又是一周过去了,这一周忙的有点焦头烂额,除了工作照例每天加班到九点外,工具箱又做了大幅改进,新的论坛游戏兔子game2z也上线了,Cocos2d-x的学习时间被压缩的很少了,现在是凌晨一点零六分,看着妻子睡熟的样子,我也只能告诉自已,坚持到底。

 

         好了,不说废话,本周奉上一篇初级入门教程博文,Cocos2d-x中的LUA引导与入门。

做为惯例,一切都是以HelloWorld的样例为准。我们今天学习用LUA来完成一版HelloWorld。

 

         大家既使没有看过我的“HelloWorld 深入分析”一文,想必也无数次运行过Cocos2d-x里的HelloCpp工程,对于运行的结果画面熟烂于心。我们回想一下,这个画面里有什么。嗯,一个背景图精灵,一个文字标签,一个关闭按钮。OK,咱们就做这么个东西。

 

         首先,我们要知道LUA是个什么东西,至于官方怎么说可以百度去查,但我想告诉你的是LUA就是一种可以在不必修改C++代码的情况下实现逻辑处理的手段。稍微讲的再明白一点,就是你用指定语法写一些逻辑处理函数然后保存成文本格式,这个文件称为脚本文件,可以被游戏执行。经过若干年的发展,现在在LUA中写逻辑,除了调用注册到LUA的静态C函数外,也已经可以方便的访问到C++工程中的类的成员函数。这是游戏开发史上最重要的技术之一。其改变了很多设计方案,使游戏变的灵活强大而极具扩展性。

 

         在Cocos2d-x中,有两个类来完成对于LUA脚本文件的处理。

 

1. CCLuaEngine:LUA脚本引擎

 

2. CCScriptEngineManager:脚本引擎管理器。

 

 

CCLuaEngine类的基类是一个接口类,叫做CCScriptEngineProtocol,它规定了所有LUA引擎的功能函数,它和CCScriptEngineManager都存放在libcocos2d下的script_support目录中的CCScriptSupport.h/cpp中。

 

首先我们来看一下CCScriptEngineProtocol:


 

class CC_DLL CCScriptEngineProtocol : public CCObject
{
public:
	//取得LUA的全局指针,所有的LUA函数都需要使用这个指针来做为参数进行调用。
    virtual lua_State* getLuaState(void) = 0;
    
	//通过LUA脚本ID移除对应的CCObject
    virtual void removeCCObjectByID(int nLuaID) = 0;
    
    //通过函数索引值移除对应的LUA函数。
    virtual void removeLuaHandler(int nHandler) = 0;
    
    //将一个目录中的LUA文件加入到LUA资源容器中。
    virtual void addSearchPath(const char* path) = 0;
    
    //执行一段LUA代码
    virtual int executeString(const char* codes) = 0;
    
    //执行一个LUA脚本文件。
    virtual int executeScriptFile(const char* filename) = 0;
    
    //调用一个全局函数。
    virtual int executeGlobalFunction(const char* functionName) = 0;
    
//通过句柄调用函数多种形态。
//通过句柄调用函数,参数二为参数数量。
virtual int executeFunctionByHandler(int nHandler, int numArgs = 0) = 0;
//通过句柄调用函数,参数二为整数数据。
virtual int executeFunctionWithIntegerData(int nHandler, int data) = 0;
//通过句柄调用函数,参数二为浮点数据。
virtual int executeFunctionWithFloatData(int nHandler, float data) = 0;
//通过句柄调用函数,参数二为布尔型数据。
virtual int executeFunctionWithBooleanData(int nHandler, bool data) = 0;
//通过句柄调用函数,参数二为CCObject指针数据和其类型名称。
virtual int executeFunctionWithCCObject(int nHandler, CCObject* pObject, const char* typeName) = 0;    

//将一个整数数值压栈做为参数。
virtual int pushIntegerToLuaStack(int data) = 0;
//将一个浮点数值压栈做为参数。
virtual int pushFloatToLuaStack(int data) = 0;
//将一个布尔数值压栈做为参数。
virtual int pushBooleanToLuaStack(int data) = 0;
//将一个CCObject指针和类型名压栈做为参数。
    virtual int pushCCObjectToLuaStack(CCObject* pObject, const char* typeName) = 0;
    
    // 执行单点触屏事件
virtual int executeTouchEvent(int nHandler, int eventType, CCTouch *pTouch) = 0;
//执行多点触屏事件。
    virtual int executeTouchesEvent(int nHandler, int eventType, CCSet *pTouches) = 0;
    // 执行一个回调函数。
    virtual int executeSchedule(int nHandler, float dt) = 0;
};

 

这个接口类的功能函数的具体实现,我们要参看CCLuaEngine类。

现在我们打开CCLuaEngine.h:

//加入lua的头文件,约定其中代码使用C风格
extern "C" {
#include "lua.h"
}

//相应的头文件。
#include "ccTypes.h"
#include "cocoa/CCObject.h"
#include "touch_dispatcher/CCTouch.h"
#include "cocoa/CCSet.h"
#include "base_nodes/CCNode.h"
#include "script_support/CCScriptSupport.h"

//使用Cocos2d命名空间
NS_CC_BEGIN

// 由CCScriptEngineProtocol派生的实际功能类。

class CCLuaEngine : public CCScriptEngineProtocol
{
public:
	//析构
    ~CCLuaEngine();
    
    //取得LUA的全局指针,所有的LUA函数都需要使用这个指针来做为参数进行调用。
    virtual lua_State* getLuaState(void) {
        return m_state;
    }
    
    …此处省略若干字。
    
    // 加入一个多线程加载LUA脚本的实时回调函数,此函数用于ANDROID
    virtual void addLuaLoader(lua_CFunction func);
    //取得当前单件实例指针
    static CCLuaEngine* engine();
    
private:
	//构造,单例,你懂的。
    CCLuaEngine(void)
    : m_state(NULL)
    {
    }
    //初始化函数。
bool init(void);
//将一个句柄压栈
    bool pushFunctionByHandler(int nHandler);
    //唯一的LUA指针
    lua_State* m_state;
};

NS_CC_END

 

分析其CPP实现:

//本类的头文件。
#include "CCLuaEngine.h"
//这里用到了tolua++库,tolua++库是一个专门处理LUA脚本的第三方库,可以很好的完成LUA访问C++类及成员函数的功能。如果没有tolua++,这块要处理起来可是麻烦死了。
#include "tolua++.h"

//加入lua库的相应头文件。
extern "C" {
#include "lualib.h"
#include "lauxlib.h"
#include "tolua_fix.h"
}

//加入Cocos2d-x所用的相应头文件。
#include "cocos2d.h"
#include "LuaCocos2d.h"
#include "cocoa/CCArray.h"
#include "CCScheduler.h"

//如果是ANDROID平台,加上对多线程加载LUA脚本的支持,使用相应的头文件。

#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
#include "Cocos2dxLuaLoader.h"
#endif

//开始Cocos2d-x命名空间。
NS_CC_BEGIN

//析构。
CCLuaEngine::~CCLuaEngine()
{
	//结束对LUA指针的使用,关闭LUA。
    lua_close(m_state);
}

//初始始。
bool CCLuaEngine::init(void)
{
	//开始对LUA的使用,创建一个LUA指针。
m_state = lua_open();
//打开相应的库。
luaL_openlibs(m_state);
//打开使用tolua封装的访问Cocos2d-x的库。
    tolua_Cocos2d_open(m_state);
tolua_prepare_ccobject_table(m_state);
//如果是ANDROID平台,也加上对LUA进行多线程加载的库支持。
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
    addLuaLoader(loader_Android);
#endif
    return true;
}

//取得单例指针。
CCLuaEngine* CCLuaEngine::engine()
{
    CCLuaEngine* pEngine = new CCLuaEngine();
    pEngine->init();
    pEngine->autorelease();
    return pEngine;
}

//通过LUA脚本ID移除对应的CCObject 
void CCLuaEngine::removeCCObjectByID(int nLuaID)
{
    tolua_remove_ccobject_by_refid(m_state, nLuaID);
}
//通过函数索引值移除对应的LUA函数。
void CCLuaEngine::removeLuaHandler(int nHandler)
{
    tolua_remove_function_by_refid(m_state, nHandler);
}
//将一个目录中的LUA文件加入到LUA资源容器中。
void CCLuaEngine::addSearchPath(const char* path)
{
	//取得全局表package
lua_getglobal(m_state, "package");                              
//取得其中的path字段,压入栈顶。
lua_getfield(m_state, -1, "path");            
//取得当前的目录字符串。
const char* cur_path =  lua_tostring(m_state, -1);
//参数出栈,恢复堆栈。
lua_pop(m_state, 1);                                            
//将新路径字符串加入到路径串列中,压入栈顶。
lua_pushfstring(m_state, "%s;%s/?.lua", cur_path, path);
//设置path字段值路径
lua_setfield(m_state, -2, "path");      
//参数出栈,恢复堆栈。
 lua_pop(m_state, 1);                                            
}
//执行一段LUA代码
int CCLuaEngine::executeString(const char *codes)
{
	//执行一段LUA代码。返回值存放到nRet中。
int nRet =    luaL_dostring(m_state, codes);
//进行下拉圾收集。
    lua_gc(m_state, LUA_GCCOLLECT, 0);
	//如果出错,打印日志。
    if (nRet != 0)
    {
        CCLOG("[LUA ERROR] %s", lua_tostring(m_state, -1));
        lua_pop(m_state, 1);
        return nRet;
    }
    return 0;
}
//执行一个LUA脚本文件。

int CCLuaEngine::executeScriptFile(const char* filename)
{
	//执行一个LUA脚本文件。返回值存放到nRet中。
    int nRet = luaL_dofile(m_state, filename);
//    lua_gc(m_state, LUA_GCCOLLECT, 0);
	//如果出错,打印日志。
    if (nRet != 0)
    {
        CCLOG("[LUA ERROR] %s", lua_tostring(m_state, -1));
        lua_pop(m_state, 1);
        return nRet;
    }
    return 0;
}
//调用一个全局函数。
int    CCLuaEngine::executeGlobalFunction(const char* functionName)
{
	//将全局函数放在栈顶
lua_getglobal(m_state, functionName);  /* query function by name, stack: function */
//判断是否是函数。
    if (!lua_isfunction(m_state, -1))
    {
        CCLOG("[LUA ERROR] name '%s' does not represent a Lua function", functionName);
        lua_pop(m_state, 1);
        return 0;
    }
	//调用函数。
    int error = lua_pcall(m_state, 0, 1, 0);         /* call function, stack: ret */
//    lua_gc(m_state, LUA_GCCOLLECT, 0);

    if (error)
    {
        CCLOG("[LUA ERROR] %s", lua_tostring(m_state, - 1));
        lua_pop(m_state, 1); // clean error message
        return 0;
    }

    // get return value
	//如果取得的第一个参数不是数字,返回错误。
    if (!lua_isnumber(m_state, -1))
    {
        lua_pop(m_state, 1);
        return 0;
    }
	//取得数字的参数存放在ret中。
int ret = lua_tointeger(m_state, -1);
//参数出栈,恢复堆栈。
    lua_pop(m_state, 1);                                            /* stack: - */
    return ret;
}
//通过句柄调用函数多种形态。
//通过句柄调用函数,参数二为参数数量。
int CCLuaEngine::executeFunctionByHandler(int nHandler, int numArgs)
{
    if (pushFunctionByHandler(nHandler))
    {
        if (numArgs > 0)
        {
            lua_insert(m_state, -(numArgs + 1));                        /* stack: ... func arg1 arg2 ... */
        }

        int error = 0;
        // try
        // {
            error = lua_pcall(m_state, numArgs, 1, 0);                  /* stack: ... ret */
        // }
        // catch (exception& e)
        // {
        //     CCLOG("[LUA ERROR] lua_pcall(%d) catch C++ exception: %s", nHandler, e.what());
        //     lua_settop(m_state, 0);
        //     return 0;
        // }
        // catch (...)
        // {
        //     CCLOG("[LUA ERROR] lua_pcall(%d) catch C++ unknown exception.", nHandler);
        //     lua_settop(m_state, 0);
        //     return 0;
        // }
        if (error)
        {
            CCLOG("[LUA ERROR] %s", lua_tostring(m_state, - 1));
            lua_settop(m_state, 0);
            return 0;
        }

        // get return value
        int ret = 0;
		//如果返回参数是数字转为整数。
        if (lua_isnumber(m_state, -1))
        {
            ret = lua_tointeger(m_state, -1);
        }//如果是布尔型转为true或false
        else if (lua_isboolean(m_state, -1))
        {
            ret = lua_toboolean(m_state, -1);
        }
	//参数出栈,恢复堆栈。
        lua_pop(m_state, 1);
        return ret;
    }
    else
    {
        return 0;
    }
}

//通过句柄调用函数,参数二为整数数据。
int CCLuaEngine::executeFunctionWithIntegerData(int nHandler, int data)
{
    lua_pushinteger(m_state, data);
    return executeFunctionByHandler(nHandler, 1);
}
//通过句柄调用函数,参数二为浮点数据。
int CCLuaEngine::executeFunctionWithFloatData(int nHandler, float data)
{
    lua_pushnumber(m_state, data);
    return executeFunctionByHandler(nHandler, 1);
}
//通过句柄调用函数,参数二为布尔型数据。
int CCLuaEngine::executeFunctionWithBooleanData(int nHandler, bool data)
{
    lua_pushboolean(m_state, data);
    return executeFunctionByHandler(nHandler, 1);
}
//通过句柄调用函数,参数二为CCObject指针数据和其类型名称。
int CCLuaEngine::executeFunctionWithCCObject(int nHandler, CCObject* pObject, const char* typeName)
{
    tolua_pushusertype_ccobject(m_state, pObject->m_uID, &pObject->m_nLuaID, pObject, typeName);
    return executeFunctionByHandler(nHandler, 1);
}
//将一个整数数值压栈做为参数。
int CCLuaEngine::pushIntegerToLuaStack(int data)
{
	//将整数值压入堆栈
lua_pushinteger(m_state, data);
//返回参数的数量。
    return lua_gettop(m_state);
}
//将一个浮点数值压栈做为参数。
int CCLuaEngine::pushFloatToLuaStack(int data)
{
	//将数字值压入堆栈
lua_pushnumber(m_state, data);
//返回参数的数量。
    return lua_gettop(m_state);
}
//将一个布尔数值压栈做为参数。
int CCLuaEngine::pushBooleanToLuaStack(int data)
{
	//将boolean值压入堆栈
lua_pushboolean(m_state, data);
//返回参数的数量。
    return lua_gettop(m_state);
}
//将一个CCObject指针和类型名压栈做为参数。
int CCLuaEngine::pushCCObjectToLuaStack(CCObject* pObject, const char* typeName)
{
    tolua_pushusertype_ccobject(m_state, pObject->m_uID, &pObject->m_nLuaID, pObject, typeName);
    return lua_gettop(m_state);
}

// 执行单点触屏事件
int CCLuaEngine::executeTouchEvent(int nHandler, int eventType, CCTouch *pTouch)
{
CCPoint pt = CCDirector::sharedDirector()->convertToGL(pTouch->getLocationInView());
//将参数压栈后调用函数。
    lua_pushinteger(m_state, eventType);
    lua_pushnumber(m_state, pt.x);
    lua_pushnumber(m_state, pt.y);
    return executeFunctionByHandler(nHandler, 3);
}

//执行多点触屏事件。
int CCLuaEngine::executeTouchesEvent(int nHandler, int eventType, CCSet *pTouches)
{
//将类型参数压栈后调用函数。
lua_pushinteger(m_state, eventType);
//创建一个表
    lua_newtable(m_state);
//将多个触点信息参数放入表中。
    CCDirector* pDirector = CCDirector::sharedDirector();
    CCSetIterator it = pTouches->begin();
    CCTouch* pTouch;
    int n = 1;
    while (it != pTouches->end())
    {
        pTouch = (CCTouch*)*it;
        CCPoint pt = pDirector->convertToGL(pTouch->getLocationInView());
		//将位置x压入堆栈
        lua_pushnumber(m_state, pt.x);
		//将栈顶的数值放入到表中对应索引n的数值中
        lua_rawseti(m_state, -2, n++);
		//将位置x压入堆栈
        lua_pushnumber(m_state, pt.y);
		//将栈顶的数值放入到表中对应索引n的数值中
        lua_rawseti(m_state, -2, n++);
        ++it;
    }
	//以表做为第二参数压栈,调用函数。
    return executeFunctionByHandler(nHandler, 2);
}
//通过句柄调用函数,参数二为CCObject指针数据和其类型名称。
int CCLuaEngine::executeSchedule(int nHandler, float dt)
{
    return executeFunctionWithFloatData(nHandler, dt);
}
// 加入一个多线程加载LUA脚本的实时回调函数,此函数用于ANDROID
void CCLuaEngine::addLuaLoader(lua_CFunction func)
{
    if (!func) return;

//取得全局表
lua_getglobal(m_state, "package");                     
//取得全局表中的“loaders”表
    lua_getfield(m_state, -1, "loaders");                 
//将设定的函数和参数压栈
lua_pushcfunction(m_state, func);                     
//将参数压栈
    for (int i = lua_objlen(m_state, -2) + 1; i > 2; --i)
{
	//取得原"loaders"表第i-1个参数
        lua_rawgeti(m_state, -2, i - 1);                                                                              
		//将取出的值放到新"loaders"表中第i个数值
        lua_rawseti(m_state, -3, i);                      
}
//将函数设为新"loaders"表的第2个参数
    lua_rawseti(m_state, -2, 2);                           
//把“loaders” 表放到全局表中
    lua_setfield(m_state, -2, "loaders");                  
	//参数出栈,恢复堆栈。
    lua_pop(m_state, 1);
}
//将一个句柄压栈
bool CCLuaEngine::pushFunctionByHandler(int nHandler)
{
	//找出注册函数表的第nHandler个数值
lua_rawgeti(m_state, LUA_REGISTRYINDEX, nHandler);  /* stack: ... func */
//判断是否是函数。
    if (!lua_isfunction(m_state, -1))
    {
        CCLOG("[LUA ERROR] function refid '%d' does not reference a Lua function", nHandler);
        lua_pop(m_state, 1);
        return false;
    }
    return true;
}

 

 

然后我们来看一下CCScriptEngineManager,这个类被称为脚本引擎管理器,其实很简单,只是用来设定当前项目的唯一正在使用的脚本引擎。也许Cocos2d-x打算用它管理多种类型脚本引擎,比如python,js等。

 

class CC_DLL CCScriptEngineManager
{
public:
	//析构
    ~CCScriptEngineManager(void);
    //取得单例指针
    CCScriptEngineProtocol* getScriptEngine(void) {
        return m_pScriptEngine;
    }
    //设置使用的LUA管理器
void setScriptEngine(CCScriptEngineProtocol *pScriptEngine);
//移除使用的LUA管理器。
    void removeScriptEngine(void);
	//取得单例指针
static CCScriptEngineManager* sharedManager(void);
//销毁单例
    static void purgeSharedManager(void);

private:
	//构造,单例,你懂的。
    CCScriptEngineManager(void)
    : m_pScriptEngine(NULL)
    {
    }
    //使用的LUA脚本引擎
    CCScriptEngineProtocol *m_pScriptEngine;
};
其对应的CPP实现:
//全局唯一的
static CCScriptEngineManager* s_pSharedScriptEngineManager = NULL;
//析构
CCScriptEngineManager::~CCScriptEngineManager(void)
{
    removeScriptEngine();
}
//设置使用的LUA管理器
void CCScriptEngineManager::setScriptEngine(CCScriptEngineProtocol *pScriptEngine)
{
    removeScriptEngine();
    m_pScriptEngine = pScriptEngine;
    m_pScriptEngine->retain();
}
//移除使用的LUA管理器。
void CCScriptEngineManager::removeScriptEngine(void)
{
    if (m_pScriptEngine)
    {
        m_pScriptEngine->release();
        m_pScriptEngine = NULL;
    }
}

//取得单例指针
CCScriptEngineManager* CCScriptEngineManager::sharedManager(void)
{
    if (!s_pSharedScriptEngineManager)
    {
        s_pSharedScriptEngineManager = new CCScriptEngineManager();
    }
    return s_pSharedScriptEngineManager;
}
//销毁单例
void CCScriptEngineManager::purgeSharedManager(void)
{
    if (s_pSharedScriptEngineManager)
    {
        delete s_pSharedScriptEngineManager;
        s_pSharedScriptEngineManager = NULL;
    }
}

 

现在我们来实际操作一下。

打开HelloLua工程中的AppDelegate.cpp:

在AppDelegate::applicationDidFinishLaunching()函数中看这几行代码:

 //取得LUA脚本引擎
CCScriptEngineProtocol* pEngine = CCLuaEngine::engine();
//设置脚本引擎管理器使用新创建的LUA脚本引擎
    CCScriptEngineManager::sharedManager()->setScriptEngine(pEngine);
//如果是ANDROID平台,获hello.lua内存到字符串然后执行字符串
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
    CCString* pstrFileContent = CCString::createWithContentsOfFile("hello.lua");
    if (pstrFileContent)
    {
        pEngine->executeString(pstrFileContent->getCString());
    }
#else
	//如果不是ANDROID平台,取得hello.lua文件全路径并执行文件。
    std::string path = CCFileUtils::sharedFileUtils()->fullPathFromRelativePath("hello.lua");
    pEngine->addSearchPath(path.substr(0, path.find_last_of("/")).c_str());
    pEngine->executeScriptFile(path.c_str());
#endif 

 

 

就这样,hello.lua中的脚本就可以被执行了。

 

现在我们将HelloLua工程目录拷出一份来,将目录和工程命名为StudyLua,并在程序运行目录中加入相关资源图片。之后我们打开hello.lua:

 

 

-- 设置内存回收
collectgarbage("setpause", 100)
collectgarbage("setstepmul", 5000)


-- 取得窗口大小
local winSize = CCDirector:sharedDirector():getWinSize()
-- 将Hello背景图加入

local function createLayerHello()
    local layerHello = CCLayer:create()

    -- 加入背景图
    local bg = CCSprite:create("Hello.png")
    bg:setPosition(winSize.width / 2 , winSize.height / 2)
    layerHello:addChild(bg)
    
   	-- 创建HelloWorld
	local label = CCLabelTTF:create("Hello Cocos2d-x", "Arial", 50)
	label:setPosition(winSize.width / 2 ,60)
    label:setVisible(true)
    layerHello:addChild(label)
     
	return layerHello
end

--将关闭按钮菜单加入
local function createExitBtn()

    local layerMenu = CCLayer:create()

	--局部函数,用于退出
    local function menuCallbackExit()
		CCDirector:sharedDirector():endToLua()
    end

    -- 创建退出按钮
    local menuPopupItem = CCMenuItemImage:create("CloseNormal.png", "CloseSelected.png")
    -- 放在居上角附近
    menuPopupItem:setPosition(winSize.width - 50, winSize.height - 50)
    -- 注册退出函数
    menuPopupItem:registerScriptHandler(menuCallbackExit)
    -- 由菜单按钮项创建菜单
    local	menuClose = CCMenu:createWithItem(menuPopupItem)
    menuClose:setPosition(0, 0)
    menuClose:setVisible(true)
    -- 将菜单加入层中
    layerMenu:addChild(menuClose)

    return layerMenu
end

--创建场景
local sceneGame = CCScene:create()
--将Hello背景图加入
sceneGame:addChild(createLayerHello())
--将关闭按钮菜单加入
sceneGame:addChild(createExitBtn())
--运行场景
CCDirector:sharedDirector():runWithScene(sceneGame)

运行一下:

Cocos2d-x之LUA脚本引擎深入分析

我只能说,一切好极了,下课!

 

分类: cocos2d, cocos2d-lua 标签:

Cocos2d-x 的“HelloLua” 深入分析

2012年8月25日 没有评论

[Cocos2d-x相关教程来源于红孩儿的游戏编程之路 CSDN博客地址:http://blog.csdn.net/honghaier]  

红孩儿Cocos2d-X学习园地QQ群:249941957 加群写:Cocos2d-x

本章为我的Cocos2d-x教程一书初稿。望各位看官多提建议!

转载请注明来源:红孩儿的游戏编程之路 CSDN博客


Cocos2d-x 的“HelloLua” 深入分析


注:本章需要一定Lua经验和内存管理编程相关经验

另:Cocos2d-x版本为http://cn.cocos2d-x.org/download:
cocos2d-1.0.1-x-0.12.0 @ Mar 05, 2012


     我们来看一下Cocos2d-x的HelloLua示例工程。首先编译运行一下这个工程,当然,因为我在HelloWorld工程中改动了CCApplication的run函数和initInstance函数,所以这里要修改一下,与HelloWorld保持一致才能编译成功。哇!一个很COOL的农场游戏。

                     Cocos2d-x 的“HelloLua” 深入分析

       这几乎是我见过的最令人激动的示例了。农场类游戏两年前可是非常火的!怎么做的,马上来看。

       main.h和main.cpp与HelloWorld无异。不理会了。打开AppDelegate.cpp,可以看到它与HelloWorld工程中的AppDelegate.cpp的明显不同是使用了声音引擎类SimpleAudioEngine和脚本引擎类CCScriptEngineManager。下面我们来分析一下源码。

应用程序启动时调用的函数
bool AppDelegate::applicationDidFinishLaunching()
{
	// 初始化显示设备
	CCDirector *pDirector = CCDirector::sharedDirector();
	// 设置显示设备使用initInstance函数创建的OpenGL视窗
    pDirector->setOpenGLView(&CCEGLView::sharedOpenGLView());
	//使用高清模式
    // pDirector->enableRetinaDisplay(true);

	// 设置显示FPS
	pDirector->setDisplayFPS(true);
	//设置设备的显示方向
	// pDirector->setDeviceOrientation(kCCDeviceOrientationLandscapeLeft);
	//设置FPS帧间隔时间
	pDirector->setAnimationInterval(1.0 / 60);

	// 通过CCLuaEngine的静态函数获取一个LUA脚本引擎实例对象指针
CCScriptEngineProtocol* pEngine = CCLuaEngine::engine();
// 通过CCScripEngineManager的静态函数sharedManager获取单件脚本引擎管理器的实例对象指针,并将上一句创建的LUA脚本引擎实例对象指针设为脚本引擎管理器当前进行管理的脚本引擎。
    CCScriptEngineManager::sharedManager()->setScriptEngine(pEngine);

#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
	//在ANDROID平台下,会通过文件处理API类CCFileUtils中的getFileData取得hello.lua文件并读取到char数组中。
	unsigned long size;
	char *pFileContent = (char*)CCFileUtils::getFileData("hello.lua", "r", &size);

	if (pFileContent)
	{
	    //将char数组数据放入到一个新的以’/0’结尾的char数组中形成一个LUA脚本字符串
	    char *pCodes = new char[size + 1];
	    pCodes[size] = '/0';
	    memcpy(pCodes, pFileContent, size);
	    delete[] pFileContent;
		//让脚本引擎执行这个LUA脚本字符串
	    pEngine->executeString(pCodes);
		//释放动态申请的char数组的内存
	    delete []pCodes;
	}
#endif

#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) || (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) || (CC_TARGET_PLATFORM == CC_PLATFORM_MARMALADE)
	//如果是Win32,IOS或MARMALADE平台,通过文件处理API类CCFileUtils中的fullPathFromRelativePath函数产生一个hello.lua在当前程序所在目录下的路径。具体实现可参看CCFileUtils_win32.cpp
	string path = CCFileUtils::fullPathFromRelativePath("hello.lua");
	//将这个路径的目录放入到脚本引擎的搜索目录
	//path.substr会将路径中的目录取出来。
pEngine->addSearchPath(path.substr(0, path.find_last_of("/")).c_str());
//执行这个路径所指向的LUA文件
    pEngine->executeScriptFile(path.c_str());
#endif 

	return true;
}


       我们没有在这里面发现任何关于农场或松鼠的只言片语。我们只知道程序执行了一下“hello.lua”文件。多COOL!

       我已经迫不急待的想要打开hello.lua文件一看究竟了。我们打开工程下的Resource目录,可以发现农场和松鼠的图片,还有一些声音文件,以及我们要找的lua文件,共有两个:hello.lua和hello2.lua。

Cocos2d-x 的“HelloLua” 深入分析

点击打开hello.lua,我们来分析一下:

[Cocos2d-x相关教程来源于红孩儿的游戏编程之路 CSDN博客地址:http://blog.csdn.net/honghaier] 
-- 设定LUAGC的拉圾回收参数
collectgarbage("setpause", 100)
collectgarbage("setstepmul", 5000)
--这里定义一个函数cclog,用来打印字符串参数
local cclog = function(...)
    print(string.format(...))
end
--将hello2.lua包含进来,hello2.lua定义了myadd函数的实现
require "hello2"
--这里调用cclog打印一个和的结果,并没什么实际用处,可略过
cclog("result is " .. myadd(3, 5))

---------------
--通过CCDirector:sharedDirector()来取得显示设备实例对象,并调用其getWinSize函数取得窗口大小给变量winSize
local winSize = CCDirector:sharedDirector():getWinSize()

-- 定义createDog函数创建松鼠
local function creatDog()
    --定义两个变量为每一帧图块的宽高
    local frameWidth = 105
    local frameHeight = 95

-- 创建松鼠的动画
-- 先使用CCTextureCache:sharedTextureCache()取得纹理块管理器,将dog.png放入纹理块管理器产生一张纹理返回给变量textureDog
local textureDog = CCTextureCache:sharedTextureCache():addImage("dog.png")
--创建一个矩形返回给变量rect 
local rect = CCRectMake(0, 0, frameWidth, frameHeight)
--由这个矩形从纹理上取出图块产生一个CCSpriteFrame指针返回给变量frame0
local frame0 = CCSpriteFrame:frameWithTexture(textureDog, rect)
--换一个新的位置的矩形返回给变量rect中
rect = CCRectMake(frameWidth, 0, frameWidth, frameHeight)
--由第二个矩形从纹理上取出图块产生一个CCSpriteFrame指针返回给变量frame1
local frame1 = CCSpriteFrame:frameWithTexture(textureDog, rect)
    --从frame0产生一个精灵返回给变量spriteDog(在C++中是CCSprite指针)
local spriteDog = CCSprite:spriteWithSpriteFrame(frame0)
--设置初始化时
spriteDog.isPaused = false
--设置精灵的位置在左上的位置
    spriteDog:setPosition(0, winSize.height / 4 * 3)

    --生成一个设定大小为2的CCMutableArray类的实例对象。用来存储CCSpriteFrame指针,将其指针返回给变量animFrames
local animFrames = CCMutableArray_CCSpriteFrame__:new(2)
--调用addObject将frame0和frame1放入animFrames
    animFrames:addObject(frame0)
    animFrames:addObject(frame1)
    --由容器类实例对象的指针animFrames创建一个动画帧信息对象,设定每0.5秒更新一帧,返回动画帧信息对象指针给变量animation
local animation = CCAnimation:animationWithFrames(animFrames, 0.5)
--由animation创建出一个动画动作,将这个动画动作的指针给变量animate
local animate = CCAnimate:actionWithAnimation(animation, false);
--设置精灵循环运行这个动作
    spriteDog:runAction(CCRepeatForever:actionWithAction(animate))

    -- 每帧移动松鼠
local function tick()
	   --如果松鼠停止动作,则返回
        if spriteDog.isPaused then return end
        --取得松鼠的位置
        local x, y = spriteDog:getPosition()
	    --如果松鼠的x值已经超出屏幕宽度将x位置变为0,否则加1,这样可以实现不断的往右移动,超出后就又回到最左边
        if x > winSize.width then
            x = 0
        else
            x = x + 1
        end
        --重新设置松鼠位置
        spriteDog:setPositionX(x)
    end
    --这里设置每帧调用上面的函数tick
    CCScheduler:sharedScheduler():scheduleScriptFunc(tick, 0, false)
    --返回松鼠精灵
    return spriteDog
end

-- 创建农场
local function createLayerFram()
	--创建一个新的Layer实例对象,将指针返回给变量layerFarm
    local layerFarm = CCLayer:node()

    -- 由“farm.jpg”创建一个精灵实例,将指针返回给变量bg
    local bg = CCSprite:spriteWithFile("farm.jpg")
---设置这个精灵实例的位置
bg:setPosition(winSize.width / 2 + 80, winSize.height / 2)
----将精灵放入新创建的Layer中
    layerFarm:addChild(bg)

    --在农场的背景图上的相应位置创建沙地块,在i从0至3,j从0至1的双重循环中,共创建了8块沙地块。
    for i = 0, 3 do
        for j = 0, 1 do
		   --创建沙地块的图片精灵
            local spriteLand = CCSprite:spriteWithFile("land.png")
		   --设置精灵的位置,在j的循环中每次向右每次增加180个位置点。在i的循环中每次会跟据i与2取模的结果向左移90个位置点,向上移95的一半数量的位置点。这样最先绘制最下面的两个沙地块,再绘制上面两个。再上面两个直至最上面两个。注意:这里的位置计算数值不必太纠结,如果是依照land.png的图片大小182x94,则这里改成spriteLand:setPosition(200 + j * 182 – (i % 2) * 182 / 2, 10 + i * 94 / 2)会更好理解一些。
            spriteLand:setPosition(200 + j * 180 - i % 2 * 90, 10 + i * 95 / 2)
		   --将沙地块的图片精录放入到新创建的Layer中
            layerFarm:addChild(spriteLand)
        end
    end

    -- 使用CCTextureCache:sharedTextureCache()取得纹理块管理器,将dog.png放入纹理块管理器产生一张纹理textureCrop
local textureCrop = CCTextureCache:sharedTextureCache():addImage("crop.png")
-- 由一个矩形从纹理取出一个图块frameCrop
local frameCrop = CCSpriteFrame:frameWithTexture(textureCrop, CCRectMake(0, 0, 105, 95))
-- 和刚才的沙地块一样,由图块创建出精灵并放在相应的位置上,这里不再赘述。
    for i = 0, 3 do
        for j = 0, 1 do
            local spriteCrop = CCSprite:spriteWithSpriteFrame(frameCrop);
            spriteCrop:setPosition(10 + 200 + j * 180 - i % 2 * 90, 30 + 10 + i * 95 / 2)
            layerFarm:addChild(spriteCrop)
        end
    end

    -- 调用createDog增加一个移动的松鼠精灵
local spriteDog = creatDog()
-- 将松鼠精录放入新创建的Layer中
    layerFarm:addChild(spriteDog)

    -- 定义变量touchBeginPoint,设为未使用
    local touchBeginPoint = nil
	--定义当按下屏幕时触发的函数
local function onTouchBegan(x, y)
	   --打印位置信息
        cclog("onTouchBegan: %0.2f, %0.2f", x, y)
        --将x,y存在变量touchBeginPoint中
        touchBeginPoint = {x = x, y = y}
	   --暂停精灵spriteDog的运动
        spriteDog.isPaused = true
        --返回true
        return true
    end
	--定义当保持按下屏幕进行移动时触发的函数
local function onTouchMoved(x, y)
	   --打印位置信息
        cclog("onTouchMoved: %0.2f, %0.2f", x, y)
        --如果touchBeginPoint有值
        if touchBeginPoint then
		   --取得layerFarm的位置,将返回结果存放在cx和cy中。
            local cx, cy = layerFarm:getPosition()
		   --设置layerFarm的位置受到按下移动的偏移影响
            layerFarm:setPosition(cx + x - touchBeginPoint.x,
                                  cy + y - touchBeginPoint.y)
		   --更新当前按下位置存放到变量touchBeginPoint中
            touchBeginPoint = {x = x, y = y}
        end
    end
    --当离开按下屏幕时
local function onTouchEnded(x, y)
	   --打印位置信息
        cclog("onTouchEnded")
        --将变量touchBeginPoint设为未用
        touchBeginPoint = nil
	   --将变量spriteDog
        spriteDog.isPaused = false
    end
    --响应按下事件处理函数
local function onTouch(eventType, x, y)
	    --如果是按下时,调用onTouchBegan
        if eventType == CCTOUCHBEGAN then
            return onTouchBegan(x, y)
        --如果是按下并移动时,调用onTouchMoved
        elseif eventType == CCTOUCHMOVED then
            return onTouchMoved(x, y)
        --松开时,调用onTouchEnded
        else
            return onTouchEnded(x, y)
        end
    end
--调用layerFarm的registerScriptTouchHandler函数注册按下事件的响应函数
layerFarm:registerScriptTouchHandler(onTouch)
--调用layerFarm的setIsTouchEnabled使layerFarm能够响应屏幕按下事件
    layerFarm:setIsTouchEnabled(true)
    --返回layerFarm
    return layerFarm
end


-- 定义创建菜单层函数
local function createLayerMenu()
    --创建一个新Layer,将其指针返回给变量layerMenu
    local layerMenu = CCLayer:node()
	--定义三个本地变量
    local menuPopup, menuTools, effectID
    --定义本地函数menuCallbackClosePopup
    local function menuCallbackClosePopup()
        -- 通过参数effectID关闭指定声音
        SimpleAudioEngine:sharedEngine():stopEffect(effectID)
        --设置menuPopup不显示
        menuPopup:setIsVisible(false)
    end
    --定义本地函数menuCallbackOpenPopup
    local function menuCallbackOpenPopup()
        -- 循环播放声音文件“effect1.wav”,并返回对应的声音ID给变量effectID
        effectID = SimpleAudioEngine:sharedEngine():playEffect("effect1.wav")
        -- 设置menuPopup显示
        menuPopup:setIsVisible(true)
    end

    -- 创建图片菜单按钮,设置其两个状态(普通和按下)的图片都相同是menu2.png,返回图片菜单按钮给menuPopupItem
local menuPopupItem = CCMenuItemImage:itemFromNormalImage("menu2.png", "menu2.png")
-- 设置图片菜单按钮的位置在0,0点
menuPopupItem:setPosition(0, 0)
-- 为图片菜单按钮注册响应函数menuCallbackClosePopup
menuPopupItem:registerScriptHandler(menuCallbackClosePopup)
-- 由图片菜单按钮menuPopupItem创建出菜单返回给变量menuPopup
menuPopup = CCMenu:menuWithItem(menuPopupItem)
-- 设置菜单menuPopup的位置为屏幕中央
    menuPopup:setPosition(winSize.width / 2, winSize.height / 2)
    --设置menuPopup不显示。
menuPopup:setIsVisible(false)
--将菜单放入layerMenu中
    layerMenu:addChild(menuPopup)

-- 下面几行代码创建左下角的图片菜单按钮menuToolsItem及菜单menuTools,与上面的代码基本相似,不再赘述。
local menuToolsItem = CCMenuItemImage:itemFromNormalImage("menu1.png", "menu1.png")
menuToolsItem:setPosition(0, 0)
menuToolsItem:registerScriptHandler(menuCallbackOpenPopup)
menuTools = CCMenu:menuWithItem(menuToolsItem)
menuTools:setPosition(30, 40)
    layerMenu:addChild(menuTools)
    --返回layerMenu
    return layerMenu
end


-- 注意:以上大部分都是函数的定义,以下才是真正的游戏逻辑。我在这里加个序号方便大家读懂。
-- 1。取得声音引擎的实例对象并调用其playBackgroundMusic函数加载并循环播放声音文件“background.mp3”。这里做为背景音乐
SimpleAudioEngine:sharedEngine():playBackgroundMusic("background.mp3", true);
-- 2。取得声音引擎的实例对象并调用其preloadEffect函数将声音文件“effect1.wav”预加载进内存。这里并不播放,预加载是为了在播放时不造成卡顿感。
SimpleAudioEngine:sharedEngine():preloadEffect("effect1.wav");

-- 3。创建一个场景返回给变量sceneGame
local sceneGame = CCScene:node()
-- 4。创建农场所用的Layer,并放入场景中
sceneGame:addChild(createLayerFram())
-- 5。创建菜单所用的Layer,并放入场景中
sceneGame:addChild(createLayerMenu())
-- 6。调用显示设备的单件实例对象的runWithScene函数运行场景sceneGame
CCDirector:sharedDirector():runWithScene(sceneGame)


       hello.lua读完了,我来做一下总结:

hello.lua定义了几个函数(1) . creatDog:创建松鼠动画精灵,并设置精灵每帧触发tick函数由左向右循环移动。 (2).createLayerFram:创建农场所用的Layer,然后使用双循环创建沙地块图片精灵和农作物图片精灵,并放在合适的位置构建出农场田园的景色。之后调用createDog。最后设定这个农场所用的Layer在接收到按下,按下移动和松开事件时响应的函数。(3).createLayerMenu:创建菜单所用的Layer及菜单,游戏共使用了两个菜单,第一个菜单是一个图片菜单按钮,处于屏幕中央,图片中是一些游戏工具按钮的内容,默认这个菜单不显示。定义这个菜单在按下时响应的函数是隐藏它自已。第二个菜单也是一个图片菜单按钮,处于屏幕左下角,默认这个菜单显示,定义这个菜单在按下时响应的函数功能为显示第一个菜单。游戏的逻辑在脚本文件的最后面。先加载播放背景音乐,之后创建场景并创建农场和菜单将各自返回的层放入场景,最后运行这个场景。

       我们已经知道,lua文件可以调用静态C函数,也可以通过tolua++访问类对象的成员函数进行游戏逻辑的处理。通过这个示例的分析,我们更加感受到了Lua脚本引擎的强大功能。

   

       做为一个负责任的程序员,虽然我很开心Cocos2d-x提供了Lua的脚本引擎,但是我发现在当前版本中有一个美中不足的问题。就是内存泄漏,如果你现在猛的一紧,哈哈,那就赶快来跟着我往下面进行吧。我们仍然回到HelloLua工程的AppDelegate.cpp:

AppDelegate::AppDelegate()
{
	// 引擎作者在这里屏蔽了CRT对于内存泄漏的检查
	//_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF|_CRTDBG_LEAK_CHECK_DF);
}

我们将前面的注释去掉,编译运行。可以发现在输出栏里有大量的内存泄漏:

Cocos2d-x 的“HelloLua” 深入分析

我们姑切往后看。

AppDelegate::~AppDelegate()
{
	//释放声音引擎
	SimpleAudioEngine::sharedEngine()->end();
	//这里本来是释放脚本引擎所用到的内存的,但奇怪的是作者把它注释掉了
        // CCScriptEngineManager::purgeSharedManager();
}


       我暂且大着胆子把释放脚本引擎这一句的注释去掉,编译运行吧。发现关闭程序后在CCObject.cpp中会崩溃掉。

Cocos2d-x 的“HelloLua” 深入分析


     看一下堆栈,可以发现是在显示设备析构时进入基类析构造成的。我们可以在CCDirector和AppDelegate的析构里加断点来运行一下程序,可以看到在关闭程序后,是先执行AppDelegate的析构,后执行CCDirector的折构。因为在AppDelegate的析构中我们执行了对脚本引擎的调用。所以在CCObject的析构中。CCScriptEngineManager::shareManager()->getScriptEngine()返回的是个空指针,而空指针再调用removeCCObjectByID,那可不必崩嘛!好了,知道原理了,修改吧。


   if (m_nLuaID)
    {
		CCScriptEngineManager*	tpScriptEngineManage = CCScriptEngineManager::sharedManager() ;
		if(tpScriptEngineManage)
		{
			CCScriptEngineProtocol*	tpEngineProtocol = tpScriptEngineManage->getScriptEngine();
			if(tpEngineProtocol)
			{
				tpEngineProtocol->removeCCObjectByID(m_nLuaID);
			}

		}
		
		//CCScriptEngineManager::sharedManager()->getScriptEngine()->removeCCObjectByID(m_nLuaID);
    }


再次运行程序,关闭后可以看到内存泄漏的数量大幅减少。

Cocos2d-x 的“HelloLua” 深入分析


      仍然有一些泄漏,先来判断一下这些泄漏的出处吧,既然是Lua引擎的使用示例,跟Lua多少可能有关,我们打开hello.lua,将文件开头用“- -[[” 结尾用“]] – -” 将整篇注释。之后运行程序,哈哈,在输出窗口没有提示任何内存泄漏。这就说明内存泄漏与Lua引擎有一定关系。好吧,我们一点一点打开hello.lua中的语句。

       将“- – [[”移到“local winSize”之前,运行,仍然没有任何内存泄漏,证明上面的语句都不会有问题,再移到下一行,运行。哦哦~,出现了!4字节的内存泄漏。

Delete CriticalSection spin count Detected memory leaks!
Dumping objects ->
{12911} normal block at 0x00481AD0, 4 bytes long.
 Data: <    > 00 00 00 00 
Object dump complete.
The program '[8684] HelloLua.exe: Native' has exited with code 0 (0x0).


可以肯定的是在这一行Lua脚本引擎一定申请了4字节的内存。

local winSize = CCDirector:sharedDirector():getWinSize()


       这里面调用了两个函数,第一个是sharedDirector(),第二个是getWinSize()。我们先来看getWinSize()是否有内存申请。打开CCDirector.cpp中找到getWinSize函数加入断点。运行程序,中断后我们观察其调用堆栈,正好是Lua脚本调用。

Cocos2d-x 的“HelloLua” 深入分析


       继续按F10运行,进入Lua调用的静态函数

Cocos2d-x 的“HelloLua” 深入分析


       我们可以看到在这个函数中能过Mtolua_new动态态建了一个CCSize实例对象,返回其指针给tolua_obj,我们进入Mtolua_new,就是new函数了,这里是确定申请了内存的。大小为8字节。我们继续运行,下一句是告知Lua这个数据指针的名称是CCSize,在Cocos2d-x引擎中,对于Lua脚本引擎是分别与Cocos2d-x中的各种类做了绑定。通过这个名称是可以识别到的。继续下一句tolua_register_gc(tolua_S,lua_gettop(tolua_S));这一行其实是将指针数据注册到lua的拉圾收集器,由拉圾收集器负责处理。我们现在打开LuaCocos2d.cpp的最上面,会发现有一些供lua调用的静态函数,它们均以tolua_collect做函数名称的前缀,其意义是为了释放相应类形的内存块。我们找到下面一段:

static int tolua_collect_CCSize (lua_State* tolua_S)
{
     CCSize* self = (CCSize*) tolua_tousertype(tolua_S,1,0);
	Mtolua_delete(self);
	return 0;
}


     顾名思义,这个函数对CCSize类型的指针进行delete,我们在这里加入断点。然后再启动程序。当我们关闭程序时,会断在这里。

 

Cocos2d-x 的“HelloLua” 深入分析


       看一下调用堆栈,很明显,在Lua脚本引擎析构时进行了拉圾清空处理,这里可以看一下tolua_tousertype(tolua_S,1,0)返回的self的值,没错,是我们使用的CCSize。所以这里可以确定getWinSize是没有问题的。那4字节的内存泄漏应该是sharedDirecotor()函数造成的。

       我们在CCDirector.cpp中找到sharedDirector()函数,加断点运行。略过前20次中断,在第21次中断时,我们可以看到函数调用堆栈的情况跟lua有关,也就是上一句Lua在调用C函数触发的。这里已经是相当底层了,做为入门教程,我并不打算再对外有的内存进行一步一步的跟踪排查了。我们还是寄希望作者会在后面的版本中修复内存泄漏的问题吧。好了。有点累了,玩去~


分类: cocos2d, cocos2d-lua 标签:

What is a good game engine that uses Lua? [closed]

2011年2月19日 没有评论

I know Love2D and Corona SDK (for mobile devices). Is there any other game engines that use Lua you recommend?

World of Warcraft’s engine seems all right, and it uses Lua. :)

Heroes of Might and Magic V used modified Silent Storm engine. I think you can find many good engines listed in wikipedia: Lua-scriptable game engines

List of game engines that use lua:

2D

  • Agen (Lua; Windows)
  • Blitwizard (Lua; Windows, Linux, Mac)
  • Cocos2d-x (C++/Lua/JS; Windows, Linux, Mac; iOS/Android/BlackBerry)
  • Corona (Lua; Windows, Mac; iOS/Android)
  • EGSL (Pascal/Lua; Windows, Linux, Mac, Haiku)
  • Grail Adventure Game Engine (C++/Lua; Windows, Linux, MacOS X (SDL))
  • LÖVE (Lua; Windows, Linux, Mac)
  • MOAI (C++/Lua; Windows, Linux, Mac; iOS, Android; Google Chrome (Native Client))

2.5D

3D

A few more:

There’s our IDE / engine called Codea.

The runtime is iOS only, but it’s open source. The development environment is iPad only at the moment.

I can second the previous posters enthusiasm for the Gideros Lua game engine, whilst focusing currently on Mobile (iOS and Android – Windows phone 8 is in the works), desktop support for Mac, PC (possibly Linux) is also planned for the not too distant future.

Google for “Gideros Mobile”