[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的农场游戏。

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

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);// 设置显示FPSpDirector->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.cppstring 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,我们来分析一下:

[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 = 105local 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放入animFramesanimFrames: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 thenx = 0elsex = x + 1end--重新设置松鼠位置spriteDog:setPositionX(x)end--这里设置每帧调用上面的函数tickCCScheduler:sharedScheduler():scheduleScriptFunc(tick, 0, false)--返回松鼠精灵return spriteDog
end-- 创建农场
local function createLayerFram()--创建一个新的Layer实例对象,将指针返回给变量layerFarmlocal layerFarm = CCLayer:node()-- 由“farm.jpg”创建一个精灵实例,将指针返回给变量bglocal 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 dofor 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)endend-- 使用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 dofor j = 0, 1 dolocal spriteCrop = CCSprite:spriteWithSpriteFrame(frameCrop);spriteCrop:setPosition(10 + 200 + j * 180 - i % 2 * 90, 30 + 10 + i * 95 / 2)layerFarm:addChild(spriteCrop)endend-- 调用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--返回truereturn trueend--定义当保持按下屏幕进行移动时触发的函数
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}endend--当离开按下屏幕时
local function onTouchEnded(x, y)--打印位置信息cclog("onTouchEnded")--将变量touchBeginPoint设为未用touchBeginPoint = nil--将变量spriteDogspriteDog.isPaused = falseend--响应按下事件处理函数
local function onTouch(eventType, x, y)--如果是按下时,调用onTouchBeganif eventType == CCTOUCHBEGAN thenreturn onTouchBegan(x, y)--如果是按下并移动时,调用onTouchMovedelseif eventType == CCTOUCHMOVED thenreturn onTouchMoved(x, y)--松开时,调用onTouchEndedelsereturn onTouchEnded(x, y)endend
--调用layerFarm的registerScriptTouchHandler函数注册按下事件的响应函数
layerFarm:registerScriptTouchHandler(onTouch)
--调用layerFarm的setIsTouchEnabled使layerFarm能够响应屏幕按下事件layerFarm:setIsTouchEnabled(true)--返回layerFarmreturn layerFarm
end-- 定义创建菜单层函数
local function createLayerMenu()--创建一个新Layer,将其指针返回给变量layerMenulocal layerMenu = CCLayer:node()--定义三个本地变量local menuPopup, menuTools, effectID--定义本地函数menuCallbackClosePopuplocal function menuCallbackClosePopup()-- 通过参数effectID关闭指定声音SimpleAudioEngine:sharedEngine():stopEffect(effectID)--设置menuPopup不显示menuPopup:setIsVisible(false)end--定义本地函数menuCallbackOpenPopuplocal function menuCallbackOpenPopup()-- 循环播放声音文件“effect1.wav”,并返回对应的声音ID给变量effectIDeffectID = 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)--返回layerMenureturn 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);
}

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

我们姑切往后看。

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

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

看一下堆栈,可以发现是在显示设备析构时进入基类析构造成的。我们可以在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);}

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

仍然有一些泄漏,先来判断一下这些泄漏的出处吧,既然是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脚本调用。

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

我们可以看到在这个函数中能过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,我们在这里加入断点。然后再启动程序。当我们关闭程序时,会断在这里。

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

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

Cocos2d-x 的“HelloLua” 深入分析相关推荐

  1. Cocos2d-x中图字原理之深入分析

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

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

    FROM:http://www.2cto.com/kf/201303/197171.html 原文内容如下: 首先,我们要知道LUA是个什么东西,至于官方怎么说可以百度去查,但我想告诉你的是LUA就是 ...

  3. Cocos2d-x 2.0变速动画深入分析

    Cocos2d-x 2.0变速动画深入分析 另:本章所用Cocos2d-x版本为: 最红火的IOS开发群,还有工作推荐哦: 苹果开发群:140554286 手游精英群(IOS):13942819 变速 ...

  4. Cocos2d-x2.1.1-ClippingNodeTest 深入分析

    [Cocos2d-x相关教程来源于红孩儿的游戏编程之路CSDN博客地址:http://blog.csdn.net/honghaier 红孩儿Cocos2d-X学习园地QQ3群:205100149,47 ...

  5. Cocos2d-x 2.0 网格动画深入分析

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

  6. Cocos2d-x 2.0 变速动画深入分析

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

  7. Cocos2d-x 2.0 按键加速处理深入分析

    [Cocos2d-x相关教程来源于红孩儿的游戏编程之路CSDN博客地址:http://blog.csdn.net/honghaier 红孩儿Cocos2d-X学习园地QQ2群:44208467 Coc ...

  8. 深入分析Cocos2d-x 2.0中的“纹理”

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

  9. Cocos2d-x-Lua示例项目HelloLua

    Cocos2d-x-Lua示例项目HelloLua 本篇博客介绍Cocos2d-x中Lua的实例项目,就是使用Cocos2d-x创建的初始项目运行所呈现的农场,这里笔者取名为HelloLua.本篇博客 ...

最新文章

  1. BIBM系列论文阅读笔记
  2. 【Linux 内核 内存管理】Linux 内核内存布局 ④ ( ARM64 架构体系内存分布 | 内核启动源码 start_kernel | 内存初始化 mm_init | mem_init )
  3. JAVA_OA(bug篇)(一):SpringMVC的bug1
  4. MYSQL 如果把数据文件保存到其他磁盘里
  5. 使用gnucash查看任意时间段内的所有者权益变动表
  6. 权值线段树小结(hdu多校,普通平衡树,郁闷的出纳员)
  7. android new view参数,Android ViewModel附加参数
  8. Kylin 与 Spark SQL相比,有哪些差异和优势?
  9. 四川大学计算机与生命科学专业,四川大学生命科学学院2014年研招专业目录
  10. 考研计算机320分什么水平,考研320分算什么水平,能上211、985吗?很多人都答不上...
  11. IDEA+Gradle搭建Spring Boot项目
  12. mysql怎么放入图片_怎么将图片添加到mysql中
  13. 关于Word样式自动更新的详解
  14. python canvas画弧度_编程作战丨如何利用python绘制可爱皮卡丘?
  15. 新东方java开发面试经历---现场面试(2021年1月)
  16. Mac OS X,下载并安装ant
  17. Linux下Oracle 11g安装(3)—— Oracle安装篇
  18. 如何快速批量修改文件名
  19. 7. Laravel5学习笔记:如何定义自己的视图组件
  20. 业界最快的高转速马达,追觅科技V12无线吸尘器

热门文章

  1. 传智播客35期JavaEE工程师从基础到实战视频
  2. Matlab使用GUI工具箱实例(傻瓜式教程):温度转换器、画图。(wzl)
  3. 怎样快速删除Word中的空行
  4. springBoot最简单的配置https证书。首先你要又一个证书下载下来。
  5. 设计师要走怎样的职业之路
  6. 永久免费域名PP.UA最新注册指南
  7. 如何使用Authorware打造动态的抛物线
  8. 2021-2027全球与中国5G独立组网架构市场现状及未来发展趋势
  9. (6)六轴机械臂的运动学正、逆解
  10. AutoCAD 2013【32位/64位】官方简体中文版