Cocos2dx-- 资源热更新

前言:游戏上线后,我们常常还会需要更新,如新增玩法,活动等,这种动态的更新资源我们称为游戏的热更新。热更新一般只适用于脚本语言,因为脚本不需要编译,是一种解释性语言,而如C++语言是很难热更新的,其代码只要有改动就需要重新链接编译(接口统一,用动态库可以实现,不过太不灵活了)。
本章将讲讲用Cocos-lua引擎怎么实现热更新,其实Cocos自带也封装了热更新模块(AssetsManager,AssetsManagerEx),不过我没用自带的那套,自己封装了一套,其基本思路原理是一致的。

热更新基本思路

  1. 登入游戏先向服务端请求当前游戏版本号信息,与本地版本号比较,如果相同则说明没有资源需要更新直接进入游戏,而如果不相同,则说明有资源需要更新进入第2步。

  2. 向服务端请求当前所有资源的列表(资源名+MD5),与本地资源列表比较,找出需要更新的资源。

  3. 根据找出的需要更新资源,向服务端请求下载下来。(目前发现更新资源很多时,一个个循环向服务端请求可能中途会出错,所以最好是以zip包的形式一次性发送过来,客服端只请求一次)

热更新注意点

  • 1,程序加载某个文件原理:首先一个程序加载本地硬盘某一文件最终加载的路径都是绝对全路径。而我们之所以还可以写相对路径也能找到对应的文件是因为还有一个搜索路径,搜索路径是用一个容器存储的,相对路径是这样得到全路径的 = 搜索路径(全路径) + 相对路径。就是只要加入到这个搜索路径中的路径,以后要加载这里面的文件就只需给文件名就可以了,前面的路径它会自动去搜索路径循环遍历查找。所以程序里我们一般不写绝对路径,而是把前面的全路径加入到搜索路径,之后只需写后面的相对路径就能查找到了。
    2,手游安装到手机上后,其安装目录是只读属性,以后是不能修改的。所以热更新的资源是没法下载到以前目录的,那么就得自己创建一个手机上可读写的目录,并将资源更新到这个目录。接下来还一个问题就是更新目录与以前资源目录不一致了,那么游戏中是怎么优先使用更新目录里的资源的呢?其实只要将创建的可读写目录路径添加到搜索路径中并将其插入到最前面即可,代码里统一是绝对路径。
    文件的操作我们使用cocos封装的FileUtils类,其中一些相关函数如:fullPathForFilename:返回全路径,cocos加载文件最后都是通过它转换到全路径加载的,addSearchPath:添加到搜索路径,getWritablePath:返回一个可读写的路径。下面是Lua代码:
--创建可写目录与设置搜索路径
    self.writeRootPath = cc.FileUtils:getInstance():getWritablePath() .. "_ClientGame2015_"  
    if not (cc.FileUtils:isDirectoryExist(self.writeRootPath)) then         
        cc.FileUtils:createDirectory(self.writeRootPath)    
    end
    local searchPaths = cc.FileUtils:getSearchPaths() 
    table.insert(searchPaths,1,self.writeRootPath .. '/')  
    table.insert(searchPaths,102);">2,0);">'/res/')
    table.insert(searchPaths,102);">3,0);">'/src/')
    cc.FileUtils:setSearchPaths(searchPaths)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 我封装的这套热更新本地需要两个配置文件,一个记录版本信息与请求url,一个记录所有资源列表。这两个配置文件都是json格式,cocos自带json.lua解析库, json.decode(js):将js转lua表,json.encode(table):将lua表转js。配置表如下:

  • 还发现一个lua io文件操作的坑,local fp = io.open(fullPath,’r’);这些操作在ios可以但android上却不支持。所以热更新文件读写还得我们c++自己封装再tolua使用(扩展FileUtils类)。然而,c++与lua传递字符串时又有一个坑,c,c++的字符串,如果是const char* 这种,那么遇到二进制的字节0,就认为结束。如果是std::string与lua这种,则有一个单独的变量来表示长度,遇到二进制的字节0也不会结束。而图片数据里面很可能会有很多0字节,那么lua与c++交互是不能直接接收完整的。解决办法是:

    c++这边接收字符串这样接收:
    char *p = “abc\0def”;
    size_t len = 7;
    std::string str = std::string(p + len);

    lua这边修改tolua交互代码:
    lua接收c++返回字符串:使用lua_pushlstring(),我看它是用的lua_pushstring()这个,接收不完整遇0结束。
    c++接收lua返回字符串:使用lua_tolstring(),同上。

热更新源代码

分为逻辑层与UI层,UI层是异步加载的,所以不能把这个模块当场景切换,用addChild添加到已有场景上就是。

逻辑层:

require('common.json')
local UpdateLogicLayer = class("UpdateLogicLayer",cc.Node)

function UpdateLogicLayer:create(callback)
    local view = UpdateLogicLayer.new()
    local function onNodeEvent(eventType)
        if eventType == "enter" then
            view:onEnter()
        elseif eventType == "exit" then
            view:onExit()
        end
    end
    view:registerScriptHandler(onNodeEvent)
    view:init(callback)
    return view
end


function UpdateLogicLayer:ctor()
    self.writeRootPath = nil               --手机可写路径
    self.manifest = nil                       --配置表信息(json->table)
    self.resConfigInfo = nil                --资源列表(json->table)
    self.updateResTable = nil            --需要更新资源表
    self.updateResProgress =1          --更新进度
    self.updateResPath = nil              --当前更新资源路径

    self.EventType = {
        None    = 0,--初始化状态
        StartGame  = --开始游戏
        StartUpdate = --开始更新
        AssetsProgress = --资源更新中
        AssetsFinish = 4,0);">--资源更新完成
    }

    self.callback = nil                             --外部回调
    self.status = self.EventType.None 
function UpdateLogicLayer:onEnter()

function UpdateLogicLayer:onExit()

function UpdateLogicLayer:init(callback)
    self.callback = callback

    --创建可写目录与设置搜索路径
    self.writeRootPath = cc.FileUtils:getInstance():getWritablePath() .. if not (cc.FileUtils:getInstance():isDirectoryExist(self.writeRootPath)) then         
        cc.FileUtils:getInstance():createDirectory(self.writeRootPath)    
    end
    local searchPaths = cc.FileUtils:getInstance():getSearchPaths() 
    table.insert(searchPaths,self.writeRootPath .. '/src/')
    cc.FileUtils:getInstance():setSearchPaths(searchPaths)

    --配置信息初始化
    local fullPath = cc.FileUtils:getInstance():fullPathForFilename('project.manifest') 
    local fp = io.open(fullPath,'r')
    if fp then
        local js = fp:read('*a')
        io.close(fp)
        self.manifest = json.decode(js)
    else    
        print('project.manifest read error!')
    end

    --版本比较
    self:cmpVersions()

end


--版本比较
function UpdateLogicLayer:cmpVersions() 
    --Post
    local xhr = cc.XMLHttpRequest:new()     
    xhr.responseType = 4   --json类型
    xhr:open("POST",self.manifest.versionUrl)  

    function onReadyStateChange() 
        if xhr.readyState == 4 and (xhr.status >= 200 and xhr.status < 207) then
            local localversion = self.manifest.version
            self.manifest = json.decode(xhr.response)
            if self.manifest.version == localversion then
                --开始游戏
                self.status = self.EventType.StartGame
                self:noticeEvent()
                print('11开始游戏啊啊啊啊啊啊啊啊啊啊啊啊啊啊!!!!!!')

            else
                --查找需要更新的资源并下载
                self.status = self.EventType.StartUpdate
                self:noticeEvent()
                self:findUpdateRes() 
            end

        else 
            print("cmpVersions = xhr.readyState is:",xhr.readyState,0);">"xhr.status is: ",xhr.status)
        end
    xhr:registerScriptHandler(onReadyStateChange) 
    xhr:send() 
--查找更新资源
function UpdateLogicLayer:findUpdateRes()
    new()   
    xhr.responseType = 4    
    xhr:then
            self.resConfigInfo = json.decode(xhr.response) 
            self.updateResTable = self:findUpdateResTable()
            self:downloadRes() 

        else
            print("findUpdateRes = xhr.readyState is:",136);">end
    xhr:registerScriptHandler(onReadyStateChange)   
    xhr:send('filename=/res_config.lua')  
--查找需要更新资源表(更新与新增,没考虑删除)
function UpdateLogicLayer:findUpdateResTable()
    local clientResTable = nil
    local serverResTable = self.resConfigInfo
    'resConfig.json') 
    '*a')
        fp:close(fp)
        clientResTable = json.decode(js)
    else
        print('resConfig.json read error!')
    end

    local addResTable = {}
    local isUpdate = true  

    if clientResTable and serverResTable then
        for key1,var1 in ipairs(serverResTable) do
            isUpdate = true
            for key2,var2 in ipairs(clientResTable) do
                if var2.name == var1.name then
                    if var2.md5 == var1.md5 then
                        isUpdate = false
                    end
                    break
                end
            end
            if isUpdate == true then
                table.insert(addResTable,var1.name)
            end
        end

    'local configFile error!(res_config_local or res_config_server)')
    end

    return addResTable
--下载更新资源
function UpdateLogicLayer:downloadRes()
    local fileName = self.updateResTable[self.updateResProgress] 
    if fileName new()    
        xhr:function onReadyStateChange() 
            then
                self:localWriteRes(fileName,xhr.response)
            else
                print("downloadRes = xhr.readyState is:",xhr.status)
            end
        xhr:registerScriptHandler(onReadyStateChange)   
        xhr:'filename=' .. fileName) 

    else
        --资源更新完成
        open(self.writeRootPath .. '/res/project.manifest',0);">'w')  
        then 
            local js = json.encode(self.manifest)
            fp:write(js)
            io.close(fp)
        end

        '/res/resConfig.json',0);">'w')
        local js = json.encode(self.resConfigInfo)
            fp:end

        --更新完成开始游戏
        self.status = self.EventType.AssetsFinish
        self:noticeEvent()
        print('22开始游戏啊啊啊啊啊啊啊啊啊啊啊啊啊啊!!!!!!')

    end

--资源本地写入
function UpdateLogicLayer:localWriteRes(resName,resData)
    local lenthTable = {}
    local tempResName = resName
    local maxLength = string.len(tempResName)
    local tag = string.find(tempResName,0);">'/')
    while tag do
        if tag ~= 1 then
            table.insert(lenthTable,tag) 
        end
        tempResName = string.sub(tempResName,tag + '/')
    local sub = 0
    for key,var in ipairs(lenthTable) do
        sub = sub + var 
    end
    if sub ~= 0 local temp = string.sub(resName,sub + 1)
        local pathName = self.writeRootPath .. temp 
        if not (cc.FileUtils:getInstance():isDirectoryExist(pathName)) then         
            cc.FileUtils:getInstance():createDirectory(pathName)   
        end 

    self.updateResPath = self.writeRootPath .. resName
    open(self.updateResPath,0);">'w')
    then
        fp:write(resData)
        io.close(fp)

        self.status = self.EventType.AssetsProgress
        self:noticeEvent()
        print("countRes = ",self.updateResProgress,0);">"nameRes = ",resName)
        self.updateResProgress = self.updateResProgress + 1
        self:downloadRes() 
    'downloadRes write error!!')
    end
function UpdateLogicLayer:noticeEvent()
    if self.callback then
        self.callback(self,self.status)
    'callback is nil')
    end


return UpdateLogicLayer
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275

UI层:

--[[ 说明: 1,本地需求配置文件:project.manifest,resConfig.json 2,循环post请求,有时会出现闪退情况,最好改成只发一次zip压缩包形式 3,目前只支持ios,lua io库文件操作在andriod上不行,文件操作c实现(注意lua与c++交互对于char *遇/0结束问题,需要改lua绑定代码) ]]

local UpdateLogicLayer = 'app.views.Assets.UpdateLogicLayer')
local SelectSerAddrLayer = "app.views.Login.SelectSerAddrLayer")
local UpdateUILayer = class("UpdateUILayer",cc.Layer)

function UpdateUILayer:create()
    local view = UpdateUILayer.new()
    local function onNodeEvent(eventType)
        then
            view:onEnter()
        elseif eventType == then
            view:onExit()
        end
    end
    view:registerScriptHandler(onNodeEvent)
    view:init()
    return view
end


function UpdateUILayer:ctor()

function UpdateUILayer:onEnterfunction UpdateUILayer:onExitfunction UpdateUILayer:initlocal updateLogicLayer = UpdateLogicLayer:create(function(sender,eventType) self:onEventCallBack(sender,eventType) end)
    self:addChild(updateLogicLayer)

function UpdateUILayer:onEventCallBackif eventType == sender.EventType.StartGame print("startgame !!!")
        local view = SelectSerAddrLayer.new()
        self:addChild(view)

    elseif eventType == sender.EventType.StartUpdate "startupdate !!!")
        self:initAssetsUI()

    elseif eventType == sender.EventType.AssetsProgress "assetsprogress !!!")
        self:updateAssetsProgress(sender.updateResPath,sender.updateResTable,sender.updateResProgress)

    elseif eventType == sender.EventType.AssetsFinish "assetsfinish !!!")
        self:updateAssetsFinish(sender.writeRootPath)
    end
end


--UI界面初始化
function UpdateUILayer:initAssetsUIlocal assetsLayer = cc.CSLoader:createNode("csb/assetsUpdate_layer.csb") 
    local visibleSize = cc.Director:getInstance():getVisibleSize()
    assetsLayer:setAnchorPoint(cc.p(0.5,102);">0.5))
    assetsLayer:setPosition(visibleSize.width/2)
    self:addChild(assetsLayer) 
    self.rootPanel = assetsLayer:getChildByName("Panel_root")

    self.widgetTable = {
        LoadingBar_1 = ccui.Helper:seekWidgetByName(self.rootPanel,0);">"LoadingBar_1"),Text_loadProgress = ccui.Helper:seekWidgetByName(self.rootPanel,0);">"Text_loadProgress"),Text_loadResPath = ccui.Helper:seekWidgetByName(self.rootPanel,0);">"Text_loadResPath"),Image_tag = ccui.Helper:seekWidgetByName(self.rootPanel,0);">"Image_tag"),}
    self.widgetTable.Image_tag:setVisible(false)
    self.widgetTable.LoadingBar_1:setPercent(1)
    self.widgetTable.Text_loadProgress:setString('0%')
    self.widgetTable.Text_loadResPath:setString('准备更新...')
--资源更新完成
function UpdateUILayer:updateAssetsFinish(writePaht)
    self.widgetTable.Text_loadResPath:setString('资源更新完成...')
    self.widgetTable.Text_loadProgress:setString('100%')
    self:runAction(cc.Sequence:create(cc.DelayTime:create(1),cc.CallFunc:create(() 
            local view = SelectSerAddrLayer.new()
            self:addChild(view)
        end)
    ))
--资源更新中
function UpdateUILayer:updateAssetsProgress(resPath,updateResTable,updateResProgress)
    self.widgetTable.Text_loadResPath:setString(resPath)
    local percentMaxNum = #updateResTable
    local percentNum = math.floor((updateResProgress / percentMaxNum) * 100)
    self.widgetTable.LoadingBar_1:setPercent(percentNum) 
    self.widgetTable.Text_loadProgress:setString(percentNum .. '%')
end


return UpdateUILayer

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


    本文实践自 RayWenderlich、Ali Hafizji 的文章《How To Create Dynamic Textures with CCRenderTexture in Cocos2D 2.X》,文中使用Cocos2D,我在这里使用Cocos2D-x 2.1.4进行学习和移植。在这篇文章,将会学习到如何创建实时纹理、如何用Gimp创建无缝拼接纹
Cocos-code-ide使用入门学习地点:杭州滨江邮箱:appdevzw@163.com微信公众号:HopToad 欢迎转载,转载标注出处:http://blog.csdn.netotbaron/article/details/424343991.  软件准备 下载地址:http://cn.cocos2d-x.org/download 2.  简介2.1         引用C
第一次開始用手游引擎挺激动!!!进入正题。下载资源1:从Cocos2D-x官网上下载,进入网页http://www.cocos2d-x.org/download,点击Cocos2d-x以下的Download  v3.0,保存到自定义的文件夹2:从python官网上下载。进入网页https://www.python.org/downloads/,我当前下载的是3.4.0(当前最新
    Cocos2d-x是一款强大的基于OpenGLES的跨平台游戏开发引擎,易学易用,支持多种智能移动平台。官网地址:http://cocos2d-x.org/当前版本:2.0    有很多的学习资料,在这里我只做为自己的笔记记录下来,错误之处还请指出。在VisualStudio2008平台的编译:1.下载当前稳
1.  来源 QuickV3sample项目中的2048样例游戏,以及最近《最强大脑》娱乐节目。将2048改造成一款挑战玩家对数字记忆的小游戏。邮箱:appdevzw@163.com微信公众号:HopToadAPK下载地址:http://download.csdn.net/detailotbaron/8446223源码下载地址:http://download.csdn.net/
   Cocos2d-x3.x已经支持使用CMake来进行构建了,这里尝试以QtCreatorIDE来进行CMake构建。Cocos2d-x3.X地址:https://github.com/cocos2d/cocos2d-x1.打开QtCreator,菜单栏→"打开文件或项目...",打开cocos2d-x目录下的CMakeLists.txt文件;2.弹出CMake向导,如下图所示:设置
 下载地址:链接:https://pan.baidu.com/s/1IkQsMU6NoERAAQLcCUMcXQ提取码:p1pb下载完成后,解压进入build目录使用vs2013打开工程设置平台工具集,打开设置界面设置: 点击开始编译等待编译结束编译成功在build文件下会出现一个新文件夹Debug.win32,里面就是编译
分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!http://www.captainbed.net前言上次用象棋演示了cocos2dx的基本用法,但是对cocos2dx并没有作深入的讨论,这次以超级马里奥的源代码为线索,我们一起来学习超级马里奥的实
1. 圆形音量button事实上作者的本意应该是叫做“电位计button”。可是我觉得它和我们的圆形音量button非常像,所以就这么叫它吧~先看效果:好了,不多解释,本篇到此为止。(旁白: 噗。就这样结束了?)啊才怪~我们来看看代码:[cpp] viewplaincopyprint?CCContro
原文链接:http://www.cnblogs.com/physwf/archive/2013/04/26/3043912.html为了进一步深入学习贯彻Cocos2d,我们将自己写一个场景类,但我们不会走的太远,凡是都要循序渐进,哪怕只前进一点点,那也至少是前进了,总比贪多嚼不烂一头雾水的好。在上一节中我们建
2019独角兽企业重金招聘Python工程师标准>>>cocos2d2.0之后加入了一种九宫格的实现,主要作用是用来拉伸图片,这样的好处在于保留图片四个角不变形的同时,对图片中间部分进行拉伸,来满足一些控件的自适应(PS: 比如包括按钮,对话框,最直观的形象就是ios里的短信气泡了),这就要求图
原文链接:http://www.cnblogs.com/linji/p/3599478.html1.环境和工具准备Win7VS2010/2012,至于2008v2版本之后似乎就不支持了。 2.安装pythonv.2.0版本之前是用vs模板创建工程的,到vs2.2之后就改用python创建了。到python官网下载版本2.7.5的,然后
环境:ubuntu14.04adt-bundle-linux-x86_64android-ndk-r9d-linux-x86_64cocos2d-x-3.0正式版apache-ant1.9.3python2.7(ubuntu自带)加入环境变量exportANDROID_SDK_ROOT=/home/yangming/adt-bundle-linux/sdkexportPATH=${PATH}:/$ANDROID_SDK_ROOTools/export
1开发背景游戏程序设计涉及了学科中的各个方面,鉴于目的在于学习与进步,本游戏《FlappyBird》采用了两个不同的开发方式来开发本款游戏,一类直接采用win32底层API来实现,另一类采用当前火热的cocos2d-x游戏引擎来开发本游戏。2需求分析2.1数据分析本项目要开发的是一款游
原文链接:http://www.cnblogs.com/linji/p/3599912.html//纯色色块控件(锚点默认左下角)CCLayerColor*ccc=CCLayerColor::create(ccc4(255,0,0,128),200,100);//渐变色块控件CCLayerGradient*ccc=CCLayerGradient::create(ccc4(255,0,0,
原文链接:http://www.cnblogs.com/linji/p/3599488.html//载入一张图片CCSprite*leftDoor=CCSprite::create("loading/door.png");leftDoor->setAnchorPoint(ccp(1,0.5));//设置锚点为右边中心点leftDoor->setPosition(ccp(240,160));/
为了答谢广大学员对智捷课堂以及关老师的支持,现购买51CTO学院关老师的Cocos2d-x课程之一可以送智捷课堂编写图书一本(专题可以送3本)。一、Cocos2d-x课程列表:1、Cocos2d-x入门与提高视频教程__Part22、Cocos2d-x数据持久化与网络通信__Part33、Cocos2d-x架构设计与性能优化内存优
Spawn让多个action同时执行。Spawn有多种不同的create方法,最终都调用了createWithTwoActions(FiniteTimeAction*action1,FiniteTimeAction*action2)方法。createWithTwoActions调用initWithTwoActions方法:对两个action变量初始化:_one=action1;_two=action2;如果两个a
需要环境:php,luajit.昨天在cygwin上安装php和luajit环境,这真特么是一个坑。建议不要用虚拟环境安装打包环境,否则可能会出现各种莫名问题。折腾了一下午,最终将环境转向linux。其中,luajit的安装脚本已经在quick-cocos2d-x-develop/bin/中,直接luajit_install.sh即可。我的lin
v3.0相对v2.2来说,最引人注意的。应该是对触摸层级的优化。和lambda回调函数的引入(嗯嗯,不枉我改了那么多类名。话说,每次cocos2dx大更新。总要改掉一堆类名函数名)。这些特性应该有不少人研究了,所以今天说点跟图片有关的东西。v3.0在载入图片方面也有了非常大改变,仅仅只是