[ IOS-Cocos2d-x 游戏开发] - Lua 开发之一(“HelloLua” 深入分析)
我们来看一下Cocos2d-x的HelloLua示例工程。首先编译运行一下这个工程,当然,因为我在HelloWorld工程中改动了CCApplication的run函数和initInstance函数,所以这里要修改一下,与HelloWorld保持一致才能编译成功。哇!一个很COOL的农场游戏。
这几乎是我见过的最令人激动的示例了。农场类游戏两年前可是非常火的!怎么做的,马上来看。
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。
点击打开hello.lua,我们来分析一下:
- -- 设定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脚本引擎的强大功能