Cocos2d-x 之使用 TexurePacker+Cocos Studio 協同工作

寫在前面

Cocos Studio 是 cocos2d-x 一個比較好用的編輯器,我們常用它來搭建場景 UI,導出場景文件和資源,然後在代碼中直接加載場景文件。一般導出場景文件的時候會連同資源一起導出,場景文件保存了資源的讀取方式,遊戲啓動時會根據這個讀取方式正確找到資源並加載。將小圖片打包成一張大圖片,導出合圖和 plist 文件,然後在程序中加載 plist 文件是一種比較常見和可行的方法。Cocos Studio 提供了打包合圖的功能,能滿足基本的需求。這兩天使整個項目的圖片打包和資源管理工作變得腳本化,自動化,還想對圖片資源進行加密,Cocos Studio 已經滿足不了需求了。使用 TexturePacker 能很好地解決問題,首先 TexturePacker 有腳本工具,可以使用腳本來自動打包圖片;其次 TexturePacker 在打包圖片的時候可以進行加密。但是,使用 Cocos Studio 和 TexturePacker 協同工作,有些坑我們不得不注意。

解密 csd

Cocos Studio 中的場景和層源文件都是 csd 格式,導出的文件是 csb 格式,csb 是二進制文件,它是 csd 文件編譯後的結果,可以實現加密。我們重點關注的是 csd 文件的格式,使用文本編輯工具打開一個 csd 文件

<GameFile>
  <PropertyGroup Name="GameScene" Type="Scene" ID="8c1fcf53-2f4f-46bf-a636-8fc966e42aef" Version="3.10.0.0" />
  <Content ctype="GameProjectContent">
    <Content>
      <Animation Duration="0" Speed="1.0000" />
      <ObjectData Name="Scene" Tag="23" ctype="GameNodeObjectData">
        <Size X="800.0000" Y="600.0000" />
        <Children>
            <AbstractNodeData Name="user_text" ActionTag="593766283" Tag="15" IconVisible="False" LeftMargin="363.5000" RightMargin="363.5000" TopMargin="533.5000" BottomMargin="37.5000" FontSize="24" LabelText="的房子" ShadowOffsetX="2.0000" ShadowOffsetY="-2.0000" ctype="TextObjectData">
                <Size X="73.0000" Y="29.0000" />
                <AnchorPoint ScaleX="0.5000" ScaleY="0.5000" />
                <Position X="400.0000" Y="52.0000" />
                <Scale ScaleX="1.0000" ScaleY="1.0000" />
                <CColor A="255" R="255" G="255" B="255" />
                <PrePosition X="0.5000" Y="0.0867" />
                <PreSize X="0.0913" Y="0.0483" />
                <FontResource Type="Normal" Path="font/xiaoxin.ttf" Plist="" />
                <OutlineColor A="255" R="255" G="0" B="0" />
                <ShadowColor A="255" R="110" G="110" B="110" />
          </AbstractNodeData>
            ...
        </Children>
      </ObjectData>
    </Content>
  </Content>
</GameFile>

可以看到 csd 文件的內容其實就是使用 xml 來組織的,上面是一個場景 GameScene 的內容,它的數據從 ObjectData 標籤開始,Children 標籤下保存的就是每個 UI 的數據,AbstractNodeData 保存的就是一個 UI 的數據。上面代碼列出的是一個 Text 結點的數據,AbstractNodeData 標籤最後有個屬性 ctype 保存了該結點的類型,我們要關注的是 Sprite,Button 等用到圖片資源的結點,這些結點有個標籤標明瞭資源的加載方式,主要有兩種。

直接加載單張圖片

<AbstractNodeData Name="bg" ActionTag="-1266017908" Tag="13" IconVisible="False" ctype="SpriteObjectData">
    <Size X="800.0000" Y="600.0000" />
    <AnchorPoint ScaleX="0.5000" ScaleY="0.5000" />
    <Position X="400.0000" Y="300.0000" />
    <Scale ScaleX="1.3600" ScaleY="1.3600" />
    <CColor A="255" R="255" G="255" B="255" />
    <PrePosition X="0.5000" Y="0.5000" />
    <PreSize X="1.0000" Y="1.0000" />
    <FileData Type="Normal" Path="ui/aquarium1.jpg" Plist="" />
    <BlendFunc Src="770" Dst="771" />
</AbstractNodeData>

我們看到 AbstractNodeData 結點下面有個子結點 FileData,所有使用到圖片資源的結點都會有這個子結點,這個結點就標明瞭 UI 加載時如何讀取這張圖片。Type 標明瞭加載的方式,Normal 就是直接讀取這張圖片,圖片的路徑保存在 Path 中,這種方式不需要用 Plist 文件。

加載 Plist 文件

<AbstractNodeData Name="shove_sp" ActionTag="-109570670" Tag="30" IconVisible="False" LeftMargin="12.0000" RightMargin="12.0000" TopMargin="13.0000" BottomMargin="13.0000" ctype="SpriteObjectData">
    <Size X="80.0000" Y="80.0000" />
    <AnchorPoint ScaleX="0.5000" ScaleY="0.5000" />
    <Position X="35.0000" Y="36.0000" />
    <Scale ScaleX="1.0000" ScaleY="1.0000" />
    <CColor A="255" R="255" G="255" B="255" />
    <PrePosition X="0.5000" Y="0.5000" />
    <PreSize X="0.6571" Y="0.6389" />
    <FileData Type="MarkedSubImage" Path="ui/Shovel.png" Plist="ui.plist" />
    <BlendFunc Src="1" Dst="771" />
</AbstractNodeData>

這種方式把小圖片打包成合圖,所以加載的方式是根據 plist 文件從合圖取到小圖片。MarkedSubImage 標明是加載 plist 的方式,此時會把 Plist 文件保存到 Plist 屬性中,而 Path 屬性保存的則是小圖片在合圖中的名字。

使用哪種方式

既然有兩種讀取資源的方式,那麼使用哪一種呢? csd 文件的內容是自動生成的,一般我們不會用文本工具打開 csd 文件來修改,所以選擇哪種方式是 Cocos Studio 來做的。但也不是說我們控制不了,Cocos Studio 也是根據我們對資源的處理方式來自動選擇讀取方式。默認情況下是使用第一種,即直接讀取單張圖片,這種方式我們在導出 csb 文件時得把圖片也導出,否則 csb 文件加載時找不到圖片就無法正常顯示 UI。前面說到 Cocos Studio 也可以打包圖片,如果我們在 Cocos Studio 把場景用到的圖片打包成合圖,這時 Cocos Studio 就能檢測到哪張圖片被併到合圖中,它就會選擇第二種方式。此時導出 csb 文件就得同時導出合圖和 Plist 文件。其實應該導出什麼資源並不需要我們手動控制,Cocos Studio 會智能地判斷並導出,我們要做的就是發佈時選擇 發佈資源和項目文件 選項即可。

發佈設置

使用 TexturePacker 會有什麼問題

其實只是單純地使用 Cocos Studio 來設計場景和管理資源是不會有什麼問題的,無論選擇哪一種資源加載方式,整個工作流程都不復雜,搭建完場景後一鍵發佈就完事了。但是,爲了使用腳本和加密功能,我們需要使用 TexturePacker 來管理資源;這時 Cocos Studio 發佈時就不能導出資源了,否則就會存在兩份資源,Cocos Studio 導出的一份,TexturePacker 生成的一份。所以發佈項目時要選擇 僅發佈項目文件

發佈設置

既然 Cocos Studio 不能發佈資源,那麼場景文件加載時只能使用 TexturePacker 發佈的資源,關鍵是 TexturePacker 發佈的資源場景文件能直接使用嗎?這裏要針對上面的兩種資源加載方式分別討論。

直接加載單張圖片

很顯然這種方式是不可行的,TexturePacker 是把圖片打包成合圖,所以單張小圖片已經不存在了,使用這種方式場景 UI 肯定是加載不出來的。

加載 Plist 文件

直接加載單張圖片的方式已經被 pass 掉了,那加載 plist 文件的方式可行嗎?毫無疑問,必須可行,否則這些文章就沒存在的意義了。這種方式可行的基礎是 Cocos Studio 和 TexturePacker 打包圖片的方式一樣,都是導出 plist 文件,當然合圖就可能不一樣了,Cocos Studio 只能導出 png 格式的合圖,而 TexturePacker 可以導出很多格式的合圖,所幸的是場景文件讀取資源靠的只是 plist 文件而與合圖無關(事實上,程序運行時先根據 plist 文件和合圖把所有小圖片加載到緩衝區,場景加載時是根據 plist 文件和小圖片的名稱從緩衝區中取到這張小圖片)。這種方式雖然可行,但還有兩個問題需要解決。

精靈幀緩衝區並沒有圖片

使用 TexturePacker 打包圖片,然後場景設計時用到的資源也使用合圖的方式,但遊戲運行時你還是會發現場景 UI 沒法加載出來;這時斷點看一個精靈幀緩衝區 SpriteFrameCache,你會發現它的內容是空的。原因很簡單,我們並沒有做任何工具把資源加載到緩衝區,緩衝區當然是空的,UI 也自然加載不出來。但爲什麼使用 Cocos Studio 導出的資源卻能在緩衝區找到呢,這應該是引擎的一個機制,會在遊戲啓動時把 Cocos Studio 場景用到的所有資源加載到緩衝區;雖然我不知道它是怎麼做的,在哪裏做的,但緩衝區中確確實實已經有了這些資源,否則 UI 無法正常顯示。我們不用管 Cocos Studio 這種機制,使用 Cocos Studio 來導出資源已經被放棄了,所以我們現在要考慮的是怎麼把 TexturePacker 導出的資源加載到緩衝區。很簡單,在第一次加載場景文件 csb 之前,把用到的所有資源事先加載到緩衝區。

cc.SpriteFrameCache:getInstance():addSpriteFrames("ui.plist", "ui.png")

plist 文件中小圖片的命名方式不統一

把 TexturePacker 導出的資源加載到緩衝後,可能發現場景 UI 還是顯示不出來,這就是 TexturePacker 導出的 plist 文件和 Cocos Studio 導出的存在差異性造成的。兩者導出的 plist 文件格式雖然一樣,但小圖片的命名方式卻不一樣。Cocos Studio 的命名方式是路徑+文件名,而在 csd 文件我們可以看到小圖片的文件名也是路徑+文件名,所以場景文件可以根據小圖片的文件名正確從緩衝區找到它

<FileData Type="MarkedSubImage" Path="ui/Shovel.png" Plist="ui.plist" />
<!--ui.plist-->
<key>ui/Shovel.png</key>
<dict>
    <key>aliases</key>
    <array/>
    <key>spriteOffset</key>
    <string>{0,0}</string>
    <key>spriteSize</key>
    <string>{80,80}</string>
    <key>spriteSourceSize</key>
    <string>{80,80}</string>
    <key>textureRect</key>
    <string>{{431,0},{80,80}}</string>
    <key>textureRotated</key>
    <false/>
</dict>

比如這個例子,使用 Cocos Studio 導出的 ui.plist 文件中有個結點,它的 key 值爲 ui/Shovel.png,與場景文件中的 Path 屬性 ui/Shovel.png 一致,所以場景文件能夠通過文件 ui.plist 和圖片名稱 ui/Shovel.png 順利在緩衝區中找到圖片。但使用 TexturePacker 導出的 ui.plist 文件如下

<!--ui.plist-->
<key>Shovel.png</key>
<dict>
    <key>aliases</key>
    <array/>
    <key>spriteOffset</key>
    <string>{0,0}</string>
    <key>spriteSize</key>
    <string>{80,80}</string>
    <key>spriteSourceSize</key>
    <string>{80,80}</string>
    <key>textureRect</key>
    <string>{{431,0},{80,80}}</string>
    <key>textureRotated</key>
    <false/>
</dict>

在這個文件中,結點的名稱是 Shovel.png,加載之後緩衝區中會有個叫 Shovel.png 的 SpriteFrame,而沒有叫 ui/Shovel.png 的 SpriteFrame;所以場景文件通過文件 ui.plist 和圖片名稱 ui/Shovel.png 是無法在精靈幀緩衝區找到相應圖片的,場景 UI 也自然加載不出來。

解決這問題的辦法其實很簡單,只是我之前沒去嘗試白白苦惱了很久。TexturePacker 默認導出的 plist 文件中小圖片的文件名是不包括的路徑的,但並不代表它不能做到包含路徑。事實上,是否包含路徑是可選的,只是自己想當然地以爲不能。

導出文件名

第一個選項 Trim sprite names 選中時,導出的文件名不包括後綴名,比如上面的例子導出的結果就是 <key>Shove</key>。第二個選項 Prepend folder name 選中時,導出的文件名就包括了路徑,這就是我們想要的功能。比如上面的例子導出結果爲 <key>ui/Shove.png</key>,這就和 Cocos Studio 導出的一樣,問題也就迎刃而解了。如果這兩個選項都勾上,則結果爲 <key>ui/Shove</key>,我們可以根據需求靈活搭配。
使用命令行的方式則加上這兩個參數即可

--trim-sprite-names
--prepend-folder-name 

總結

Cocos Studio 和 TexturePacker 協同工作,Cocos Studio 負責搭建場景,使用圖片資源但不導出資源;TexturePacker 負責導出和管理圖片資源。爲了使用 Cocos Studio 搭建的場景能夠正確加載 TexturePacker 導出的資源,要遵循下面的步驟

  • 開始前,組織好資源的目錄結構,避免後期不斷修改;一般把“生”資源放在 cocostudio 目錄下,一旦確定了目錄結構,就不再修改;後期使用 Cocos Studio 搭建場景和使用 TexturePacker 腳本打包合圖,都會基於這個目錄結構

  • 第一步,使用 Cocos Studio 正常搭建場景

  • 第二步,在 Cocos Studio 新建合圖 csi 文件,把場景用到的所有資源打包成合圖(目的是爲了讓 Cocos Studio 知道我們的資源加載想用 plist 的方式而不是直接加載單張圖片)

  • 第三步,導出項目文件而不導出資源(在 Cocos Studio 新建合圖,導出項目文件會把合圖也導出,我們只需要使用 TexturePacker 打包的文件覆蓋即可)

  • 第四步,使用 TexturePacker 打包合圖,TexturePacker 管理所有的圖片資源,包括搭建場景沒用到的,我們可以使用腳本智能化地管理這些資源,但搭建場景用到的資源打包時的名稱必須與 Cocos Studio 使用的名稱完全對應,即要加上路徑

  • 第五步,在加載第一個場景文件之前把 TexturePacker 導出的所有合圖資源加載到緩衝區,可以使用配置文件的方式列出所有要加載的資源

發佈了100 篇原創文章 · 獲贊 59 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章