基於Quick-cocos2dx 2.2.3 的動態更新實現完整篇。(打包,服務器接口,模塊自更新)

先直接上代碼UpdateScene.lua: git

代碼位置爲:你的遊戲目錄/update/UpdateScene.lua web

require("config")
require("framework.init")
require("framework.shortcodes")
require("framework.cc.init")


local UpdateScene  = class("UpdateScene", function()
    return display.newScene("UpdateScene")
end)


local NEEDUPDATE   = true
local server       = "http://服務器ip:3001/"
local versionFile  = "version/?fileVersion="
local allFileList  = "version/?id="
local nowVersion   = CCUserDefault:sharedUserDefault():getStringForKey("current-version-code")
local bigVersion   = CCUserDefault:sharedUserDefault():getStringForKey("VERSION_BIG")


function UpdateScene:ctor(  )
    display.newSprite("kfs_background.png", display.cx, display.cy):addTo(self)
 self.path = device.writablePath.."kdfs/"
 
    self:createDownPath(self.path)
    self:createDownPath(self.path.."res/")
    self:createDownPath(self.path.."scripts/")


    if string.len(nowVersion) == 0 then
        nowVersion = bigVersion..".0"
    end
    local list      = string.split(nowVersion,".")
    self.nowId      = tonumber(list[3])
    self.versionBig = list[1].."."..list[2]


    --大版本變化了
    if self.versionBig ~= bigVersion then 
        self:delAllFilesInDirectory(self.path)
    end
        
    NEEDUPDATE          = CCUserDefault:sharedUserDefault():getBoolForKey("CanUpdate")
    --NEEDUPDATE          = true
    
    self.updateProgress = self:newProgressTimer( "kfs_jindutiaobox.png","kfs_jindutiao.png" ):pos(display.cx,120):addTo(self)
    self.progressLabel  = ui.newTTFLabel({text = "更新", size = 26, align = ui.TEXT_ALIGN_CENTER, color = display.COLOR_BLACK}):pos(display.cx,160):addTo(self) 
end


function UpdateScene:downIndexedVersion(  )
    if not self.nowDownIndex then self.nowDownIndex = 1 end
    local versionUrl = server..versionFile..self.needDownVersions[self.nowDownIndex].version.."."..self.needDownVersions[self.nowDownIndex].id
    local packageUrl = self.needDownVersions[self.nowDownIndex].fileUrl
    if self.nowDownIndex == 1 then 
        self.assetsManager = AssetsManager:new(packageUrl,versionUrl,self.path)  --資源包路徑,代碼號路徑,存儲路徑
        self.assetsManager:registerScriptHandler(handler(self, self.downHandler))
    else
        self.assetsManager:setVersionFileUrl(versionUrl)
        self.assetsManager:setPackageUrl(packageUrl)
    end


    if self.assetsManager:checkUpdate() then
        self.assetsManager:update()
    end
end


function UpdateScene:getNewestVersion(  )
    self.progressLabel:setString("正在獲取版本列表")


    if not NEEDUPDATE then
        print("當前版本爲DEBUG版本,不須要更新!")
        self.progressLabel:setString("當前版本爲DEBUG版本,不須要更新")
        self:performWithDelay(function (  )
            self:noUpdateStart()
        end,2)
        return
    end


    function callback(event)
        local ok = (event.name == "completed")
        local request = event.request
        if event.name then print("request event.name = " .. event.name) end
        if not ok then
            print("請求失敗 "..request:getErrorMessage())
            self.progressLabel:setString("版本更新列表請求網絡出錯")
            self:performWithDelay(function (  )
                self:noUpdateStart()
            end,2)
            return
        end
        local code = request:getResponseStatusCode()
        if code ~= 200 then
            print("請求錯誤,代碼 "..request:getResponseStatusCode())
            self.progressLabel:setString("版本更新列表請求網絡出錯"..request:getResponseStatusCode())
            self:performWithDelay(function (  )
                self:noUpdateStart()
            end,2)
            return
        end
        if json.decode(request:getResponseString()) then
            print(request:getResponseString())
            local needDownVersions = json.decode(request:getResponseString())
            if needDownVersions.code == 200 then
                self.needDownVersions = needDownVersions.list
                for i,v in ipairs(self.needDownVersions) do
                    if v.needRestart > 0 then
                        self.needRestart = true
                    end
                end
                if #self.needDownVersions > 0 then
                    self:downIndexedVersion()
                end
            else
                self.progressLabel:setString("當前版本已是最新版本")
                self.updateProgress.progressTimer:setPercentage(100)
                self:performWithDelay(function (  )
                    self:noUpdateStart()
                end,2)
            end
        end
    end
     
    local request = network.createHTTPRequest(callback, server..allFileList..self.nowId.."&versionBig="..self.versionBig, "GET")
    request:setTimeout(10)
    request:start()
end


function UpdateScene:onEnter(  )
    self:getNewestVersion()
end


function UpdateScene:afterUpdateStart(  )
    if self.needRestart then
        print("提示須要從新啓動遊戲")
        require("game")
        game.exit()
        return
    end
    print("更新成功,啓動遊戲")
    package.loaded["config"] = nil
    CCLuaLoadChunksFromZIP("game.zip")
    require("game")
    game.startup()
end


function UpdateScene:noUpdateStart(  )
    print("沒有更新或者更新失敗啓動遊戲")
    require("game")
    CCLuaLoadChunksFromZIP("game.zip")
    game.startup()
end


function UpdateScene:downHandler( event )
    if event == "success" then 
        if self.nowDownIndex < #self.needDownVersions then 
            self.nowDownIndex = self.nowDownIndex +1
            self:downIndexedVersion()
        else
            self.progressLabel:setString("更新成功")
            self:performWithDelay(function (  )
                self:afterUpdateStart()
            end,2)
        end
    elseif string.startWith(event,"error") then
        local text = ""
        if event == "errorNetwork" then text = "網絡出錯!" end
        if event == "errorNoNewVersion" then self.updateProgress.progressTimer:setPercentage(100) text = "已經是最新" end
        if event == "errorUncompress" then text = "解壓出錯" end
        if event == "errorUnknown" then text = "未知錯誤" end
        self.progressLabel:setString(text)
        self:performWithDelay(function (  )
            self:noUpdateStart()
        end,2)
    else
        local alreadyDownPercent = (self.nowDownIndex - 1)*100/(#self.needDownVersions )
        print("alreadyDownPercent:",alreadyDownPercent)
        alreadyDownPercent = alreadyDownPercent + event/(#self.needDownVersions )
        alreadyDownPercent = math.floor(alreadyDownPercent)
        self.progressLabel:setString("已更新"..alreadyDownPercent.."%")
        self.updateProgress.progressTimer:setPercentage(alreadyDownPercent)
        self:progressTimerAction(self.updateProgress.progressTimer,self.updateProgress.progressTimer:getPercentage(),alreadyDownPercent)
    end
end


function UpdateScene:createDownPath( path )
    if not self:checkDirOK(path) then
        print("更新目錄建立失敗,直接開始遊戲")
        self:noUpdateStart()
        return
    else
    	-- print("更新目錄存在或建立成功")
    end
end


function UpdateScene:checkDirOK( path )
    require "lfs"
    local oldpath = lfs.currentdir()
    if lfs.chdir(path) then
        lfs.chdir(oldpath)
        return true
    end
    if lfs.mkdir(path) then
        return true
    end
end


function UpdateScene:newProgressTimer( bgBarImg,progressBarImg ) 
    local bg = display.newSprite(bgBarImg)
    local progressTimer = CCProgressTimer:create(display.newSprite(progressBarImg))
    bg.progressTimer = progressTimer
    progressTimer:setType(kCCProgressTimerTypeBar) 
    progressTimer:setPercentage(0) 
    progressTimer:setMidpoint(ccp(0,0)) 
    progressTimer:setBarChangeRate(ccp(1, 0)) 
    progressTimer:setPosition(ccp(bg:getContentSize().width/2,bg:getContentSize().height/2)) 
    bg:addChild(progressTimer, 140)
    return bg
end


function UpdateScene:progressTimerAction( progressTimer,fromPercentage,toPercentage,duration )
    if not duration then duration = 0.3 end
    local ac = CCProgressFromTo:create(duration,fromPercentage,toPercentage)
    progressTimer:runAction(ac)
end


function UpdateScene:delAllFilesInDirectory( path )
    for file in lfs.dir(path) do
      if file ~= "." and file ~= ".." then
          local f = path..'/'..file
          local attr = lfs.attributes (f)
          assert (type(attr) == "table")
          if attr.mode == "directory" then
              self:delAllFilesInDirectory (f)
          else
              os.remove(f)
          end
      end
    end
end


string.startWith = function(str,strStart)
    local a,_ = string.find(str,strStart)
    return a==1
end


string.split = function(s, p)
    local rt= {}
    string.gsub(s, '[^'..p..']+', function(w) table.insert(rt, w) end )
    return rt
end


return UpdateScene
代碼解釋:

NeedUpdate 這個變量表明自動更新開關,日常我們在debug模式下等等時候,不須要這個這個功能,能夠把它關掉。 shell

server,versionFile,allFileList 這3個變量組成你增量更新所須要的2個服務器接口。 json

server..versionFile 接受一個版本號做參數,返回的也是這個版本號,具體緣由參見代碼,主要是爲了配合AssetsManager的參數需求。 服務器

server..allFileList 接受一個當前的版本的小版本號。好比全版本是 1.0.1 的話,那小版本就是1。同理 1.0.5的話,小版本id就是5。 網絡

server..allFileList?id=0&bigversion=1.0 則返回如下格式接口: svn

{"code":200,"list":[{"id":1,"fileUrl":"http://服務器地址/vf/kdfs1.0.1.zip","version":"1.0","needRestart":0}]}
接口中有一個很重要的變量 needRestart 表示本次更新是否有須要從新啓動遊戲加載的更新包。具體緣由見後文。

其它代碼不一一贅述。 ui

以上就是服務器接口設計篇。 lua

接下來說講更新自更新模塊和打包。 spa

簡單講講實現方法。

前文的UpdateScene編譯成zip,而後和 framework_precompiled.zip 一塊兒在放到res目錄下,遊戲啓動的時候別加載這兩個文件。main.lua爲以下代碼:

function __G__TRACKBACK__(errorMessage)
    print("----------------------------------------")
    print("LUA ERROR: " .. tostring(errorMessage) .. "\n")
    print(debug.traceback("", 2))
    print("----------------------------------------")
end

local writablePath = CCFileUtils:sharedFileUtils():getWritablePath()

CCFileUtils:sharedFileUtils():addSearchPath(writablePath.."kdfs/".."res/")
CCFileUtils:sharedFileUtils():addSearchPath(writablePath.."kdfs/".."scripts/")
CCFileUtils:sharedFileUtils():addSearchPath("res/")
CCFileUtils:sharedFileUtils():addSearchPath("scripts/")

CCLuaLoadChunksFromZIP("framework_precompiled.zip")
CCLuaLoadChunksFromZIP("update.zip")

xpcall(function()
	CCDirector:sharedDirector():runWithScene(require("UpdateScene").new())
end, __G__TRACKBACK__)

而且在你的遊戲代碼裏排除掉 UpdateScene.lua 。目的是爲了保證遊戲啓動的時候加載的就是惟一的一份UpdateScene代碼。

sh quick的目錄/bin/compile_scripts.sh -i 遊戲的目錄/update -o update.zip

我沒有把UpdateScene.lua 放到 scripts目錄下,而是有個單獨的目錄update。這樣的話能夠單獨把這個目錄打包,而後啓動遊戲的時候加載。

能夠參考下面的腳本完整實現如下功能:

#!/bin/sh

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
APP_ROOT="遊戲目錄啦"

echo "$DIR"
echo "$APP_ROOT"

rm -rf *.zip

echo "開始編譯代碼"

sh Quick目錄/bin/compile_scripts.sh -i 遊戲目錄/scripts -o game.zip   #編譯遊戲代碼
sh Quick目錄/bin/compile_scripts.sh -i 遊戲目錄/update -o update.zip  #編譯自更新代碼

if [ -d "$DIR"/res ]; then
    rm -rf "$DIR"/res/*    #把當前目錄下的res目錄清空
fi

if [ -d "$DIR"/scripts ]; then
    rm -rf "$DIR"/scripts/* #把當前目錄下的res目錄清空
fi

chmod 777 "$DIR"/scripts
chmod 777 "$DIR"/res

echo "- copy scripts"
cp -rf -p game.zip "$DIR"/scripts #把編譯好的game.zip複製到scripts目錄下
cp -rf -p update.zip "$DIR"/../res/update.zip  #把編譯好的update.zip 放到遊戲目錄下的res目錄下
cp -rf -p update.zip "$DIR"/res   #把編譯好的update.zip放到當前目錄的res目錄下

echo "- copy resources"
cp -rf -p "$APP_ROOT"/res "$DIR"/  #把遊戲目錄下的res目錄全部的內容複製到當前目錄下的res目錄

find ./res -type f -mtime +15000 -exec rm -rf {} \;  #實現增量更新的關鍵,代碼含義爲 刪掉當前res目錄下 文件修改日期爲 多少天或者多少時或多少秒 以前的文件
find ./res -name ".DS_Store" | xargs rm -Rf     #清除一些冗餘文件,可不要

echo "打包res和scripts"
zip -r kdfs.zip res/* scripts/*   #將當前版本的增量更新的res資源和代碼game.zip 打包成一個文件
cp kdfs.zip kdfs$1.zip    #根據接受的參數從新命名本次更新文件

echo "開始上傳"
scp kdfs$1.zip root@服務器ip:/root/kdfs_server/web-server/public/versionFile/ #經過scp上傳到服務器
echo "上傳結束"


關於回滾代碼的需求,打個比方你如今版本爲1.0.10,你須要回到1.0.5,其實實現很簡單:

1,編譯你1.0.5的時候的game.zip,原理很簡單,你有你的代碼服務器svn或git。

2,打包res資源,你能夠打包一次從1.0.0-1.0.5的資源。

這樣的話,你的代碼和資源都是1.0.5的時候的了。

相關文章
相關標籤/搜索