cocos2dx lua AssetsManagerEx android実験


cocos2dx 3.5での実験


## やりたいこと

* ブラウザと同じぐらいに更新を行いやすくしたい。
* アップデートなしでロジックの修正をできるようにしたい。

以上２点の検証をAssetsManagerExをつかってどこまでできるのか
検証してみました。(androidのみ）



## クライアント側


http://www.kawaz.org/blogs/miiojp/2014/12/24/672/


こちらの記事をみてassetmanagerexすごい！！
luaコードもassetに含められるみたいなので実験してみました。
（iosは未確認）



```lua:src/main.lua

cc.FileUtils:getInstance():setPopupNotify(false)
cc.FileUtils:getInstance():addSearchPath("src/")
cc.FileUtils:getInstance():addSearchPath("res/")
cc.FileUtils:getInstance():addSearchPath(cc.FileUtils:getInstance():getWritablePath() .. "test/src/")
cc.FileUtils:getInstance():addSearchPath(cc.FileUtils:getInstance():getWritablePath() .. "test/res/")

require "config"
require "cocos.init"

local function main()
    require("app.MyApp"):create():run()
end

local status, msg = xpcall(main, __G__TRACKBACK__)
if not status then
    print(msg)
end
```

assetのダウンロード先をtestにしたのでそれにあわせてパスを通した


```lua:src/app/views/MainScene.lua

local MainScene = class("MainScene", cc.load("mvc").ViewBase)

MainScene.RESOURCE_FILENAME = "MainScene.csb"


MainScene.MANIFEST = "project.manifest"
MainScene.STRAGE_PATH = "test"

function MainScene:onCreate()

    local root = self:getResourceNode()
    local progress = root:getChildByName("Loading")
    local text = root:getChildByName("LoadingLabel")

    local am = cc.AssetsManagerEx:create(MainScene.MANIFEST, cc.FileUtils:getInstance():getWritablePath() .. MainScene.STRAGE_PATH)
    am:retain()

    if not am:getLocalManifest():isLoaded() then
        printInfo("Fail to update assets, step skipped.")
        self:getApp():enterScene("HomeScene")
    else
        local function onUpdateEvent(event)
            local eventCode = event:getEventCode()
            printInfo("AssetmanagerEx:EventCode:%d:%s", eventCode, event:getMessage())
            if eventCode == cc.EventAssetsManagerEx.EventCode.ERROR_NO_LOCAL_MANIFEST then
                print("No local manifest file found, skip assets update.")
            elseif  eventCode == cc.EventAssetsManagerEx.EventCode.UPDATE_PROGRESSION then
                local assetId = event:getAssetId()
                local percent = event:getPercent()
                local strInfo = ""

                if assetId == cc.AssetsManagerExStatic.VERSION_ID then
                    strInfo = string.format("Version file: %d%%", percent)
                elseif assetId == cc.AssetsManagerExStatic.MANIFEST_ID then
                    strInfo = string.format("Manifest file: %d%%", percent)
                else
                    strInfo = string.format("%d%%", percent)
                end
                printInfo("progress:%f", percent)
                text:setString(strInfo)
                progress:setPercent(percent)
            elseif eventCode == cc.EventAssetsManagerEx.EventCode.ERROR_DOWNLOAD_MANIFEST or 
                eventCode == cc.EventAssetsManagerEx.EventCode.ERROR_PARSE_MANIFEST then
                print("Fail to download manifest file, update skipped.")
            elseif eventCode == cc.EventAssetsManagerEx.EventCode.NEW_VERSION_FOUND then
                print("Fail to download new version nound")
            elseif eventCode == cc.EventAssetsManagerEx.EventCode.ASSET_UPDATED then
                print("asset updated")
            elseif eventCode == cc.EventAssetsManagerEx.EventCode.UPDATE_FAILED then
                print("update failed")
            elseif eventCode == cc.EventAssetsManagerEx.EventCode.ERROR_DECOMPRESS then
                print("error decompress")
            elseif eventCode == cc.EventAssetsManagerEx.EventCode.ALREADY_UP_TO_DATE or 
                eventCode == cc.EventAssetsManagerEx.EventCode.UPDATE_FINISHED then
                print("Update finished.")
                self:getApp():enterScene("TestScene")
            elseif eventCode == cc.EventAssetsManagerEx.EventCode.ERROR_UPDATING then
                print("Asset ", event:getAssetId(), ", ", event:getMessage())
            end
        end

        local listener = cc.EventListenerAssetsManagerEx:create(am, onUpdateEvent)
        cc.Director:getInstance():getEventDispatcher():addEventListenerWithFixedPriority(listener, 1)
        am:update()
    end




end


return MainScene
```

ここで実際にTestScene.luaはサーバ側にあります。

いろいろとeventcodeの条件分岐があるけど、manifestのパースエラーとか
versionのmanifestがないとかのエラーなのでとりあえずログをはくだけに

MainScene.cbsはcocos builderで作成した適当なページです。
ローディングの実験もしたかったのでプログレスバーを含めました。




```json:res/project.manifest 
{
        "packageUrl" : "http://cocos2dx.xperia.biz/packageUrl",
        "remoteManifestUrl" : "http://cocos2dx.xperia.biz/remoteManifestUrl.manifest",
        "remoteVersionUrl" : "http://cocos2dx.xperia.biz/remoteVersionUrl.manifest",
        "version" : "1.0.0",
        "engineVersion" : "3.5",
        "assets" : {
        },
    "searchPaths" : [
    ]
}

```

ローカルのマニフェストファイルはこんな感じに


## サーバ側

いよいよ問題のサーバ側の設定に

```
$ tree ./
./
├── cdn
│   ├── images
│   │   ├── Cute-Ball-Favorites-icon.png
│   │   └── ball.png
│   ├── res
│   │   └── TestScene.csb
│   ├── src
│   │   └── app
│   │       └── views
│   │           └── TestScene.lua
│   └── zip
│       └── compressed.zip
├── remoteManifestUrl.manifest
└── remoteVersionUrl.manifest

7 directories, 7 files
```

今回はmanifestべたで書いています。

```json:remoteManifestUrl.manifest 
{
    "packageUrl" : "http://cocos2dx.xperia.biz/cdn",
    "remoteManifestUrl" : "http://cocos2dx.xperia.biz/remoteManifestUrl.manifest",
    "remoteVersionUrl" : "http://cocos2dx.xperia.biz/remoteVersionUrl.manifest",
    "version" : "1.1.5", 
    "engineVersion" : "3.5", 
    "assets" : {
        "images/Cute-Ball-Favorites-icon.png" : { "md5" : "....." },
        "images/ball.png" : { "md5" : "..." }, 
        "zip/compressed.zip" : { "md5" : "...", "compressed" : true }, 
        "src/app/views/TestScene.lua" : { "md5" : "b84dfe61005304a7dba589896bf16c6e" },
        "res/TestScene.csb" : { "md5" : "..." }
    }, 
    "searchPaths" : [] 
}
```

versionの値は試行錯誤した回数ということでお察しを

```json:remoteVersionUrl.manifest 
{
    "packageUrl" : "http://cocos2dx.xperia.biz/packageUrl",
    "remoteManifestUrl" : "http://cocos2dx.xperia.biz/remoteManifestUrl.manifest",
    "remoteVersionUrl" : "http://cocos2dx.xperia.biz/remoteVersionUrl.manifest",
    "version" : "1.1.5", 
    "engineVersion" : "3.5"
}
```


```lua:cdn/src/app/views/TestScene.lua 

local TestScene = class("TestScene", cc.load("mvc").ViewBase)

TestScene.RESOURCE_FILENAME = "TestScene.csb"


function TestScene:onCreate()
    local root = self:getResourceNode()

    display.newSprite("images/Cute-Ball-Favorites-icon.png")
            :addTo(root)

    display.newSprite("zip/compressed/info-148099_1280.png")
            :addTo(root)
end


return TestScene
```


zip圧縮したものもちゃんと解凍して使えた。

そしてここで重要なのがversionをかえただけでは更新されなくて
md5の値に差があると更新してくれる作りらしい
md5の値がkeyとなっていると思ってよさそう。
TestScene.luaだけmd5ちゃんといれているのは
最初は他と同じで...として、versionだけ更新していたけど
更新されいと気づいたので


画像はいちおうgoogle検索でライセンス「改変後の再利用が許可された画像」
にして実験しました。

試していないけど、fontも音声もassetに含められそう。



androidでしか試してないけど何でも更新できるのではと感じるほど。



## 感想

なんでも更新できる。
luaを配信する際は難読化は必須かなと。
極論、インストール型ゲームプラットフォームアプリをつくれる気がする。