您好,登錄后才能下訂單哦!
這篇文章主要為大家展示了“如何使用node開發一款圖集打包工具”,內容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領大家一起研究并學習一下“如何使用node開發一款圖集打包工具”這篇文章吧。
比如把下面的幾張圖片合成一張.
這一張圖集就是我用本文介紹的工具打包合成的.
合成的圖片品質依然十分高呢.
我們在web開發中, 每次在瀏覽器展示一張圖片都需要請求一次服務器資源.
舉個例子, 3次請求每次4k, 和一次請求12k還是有本質區別的, 然后更多的時候一次請求并不是3 * 4k.
使用圖集能讓我們優化資源加載, 提高網站的性能.
在游戲開發中, 圖集的使用至關重要, 不管是一般幀動畫還是svga等動畫解決方案, 都不會每張圖片去請求資源.
更多的時候, 我們都是打包成圖集, 而圖集打包工具texturepacker
更是大行其道.
其次, 游戲場景過多, 我們一般都需要分步加載資源, 有的時候一個動畫模型, 涉及的圖片少則十來張, 多則近百張.
圖集的使用不可或缺.
下面我們就來看下如何編寫一款圖集打包工具.
開發一個圖集打包工具腳本需要什么技能.
node.js編程能力
二維矩形裝箱算法
然后我們思考如何去打包一張圖集.
我們需要找到需要打包的文件夾, 可能有多個或者嵌套文件夾.
圖集是多張散圖拼合而成.
圖集的大小需要可配置
盡可能的壓縮圖集空間, 使每張圖緊密貼合
每個文件夾打包成一個圖集, 需要考慮圖片過多的情況
可能需要生成圖集所需要的json文件, 記錄圖片位置信息
我這里是這樣設計.
首先我們需要一個打包對象實例MySpritePackTool
, 同時支持寫入配置參數options
.
/** 圖集打包對象 */ const MySpritePackTool = function (opt) { this.options = { //一個文件夾圖片過多或者過長 遞歸最大次數 maxCount: opt.maxCount || 2, //需要打包圖集的文件路徑 assetsPath: opt.assetsPath, //輸出文件路徑 outPutPath: opt.outPutPath, //一張圖集打包最大size maxSize: { width: 2048, height: 2048 } } };
然后我們需要輸出這個對象, 可以被其他項目所引用.
module.exports = MySpritePackTool;
我們的輸入參數盡可能的少, 這樣就需要我們程序去遍歷文件夾.
例如, 我們有如下的目錄樹:
|--assets |--index |--img-3.png |--img-4.png |--login |--img-5.png |--img-1.png |--img-2.png
我們需要每個文件夾下打包出一張圖集.
思考: 需要什么樣的數據結構?
首先便于js解析, 我們約定一個對象,
每一層, 需要一個圖片信息容器assets
;
一個所包含的圖片標識keys
;
一個文件夾名字, 也方便我們后面為圖集命名name
;
然后每層文件夾前套相同對象;
結構如下:
{ assets: [ { id: 'assets/img-1.png', width: 190, height: 187 }, ... ], name: 'assets', keys: 'img-1.png,img-2.png,', index: { assets: [ { id: 'assets/index/img-3.png', width: 190, height: 187 }, ... ], name: 'index', keys: 'img-3.png,img-4.png,' }, login: { assets: [ { id: 'assets/login/img-5.png', width: 190, height: 187 } ], name: 'index', keys: 'img-5.png,' }, }
不難發現, 我們已經可以得到需要打包的所有文件和文件夾.
那么用程序如何實現呢?
主要用到nodejs的fs模塊來遞歸操作文件夾, 并輸出所需要的節點樹.
注意在書寫的時候需要判斷是圖片還是文件夾.
MySpritePackTool.prototype.findAllFiles = function (obj, rootPath) { let nodeFiles = []; if (fs.existsSync(rootPath)) { //獲取所有文件名 nodeFiles = fs.readdirSync(rootPath); //組裝對象 let nameArr = rootPath.split('/'); obj["assets"] = []; obj["name"] = nameArr[nameArr.length - 1]; obj["keys"] = ""; nodeFiles.forEach(item => { //判斷不是圖片路徑 if (!/(.png)|(.jpe?g)$/.test(item)) { let newPath = path.join(rootPath, item); //判斷存在文件 同時是文件夾系統 if (fs.existsSync(newPath) && fs.statSync(newPath).isDirectory()) { // console.log("獲得新的地址", newPath); obj[item] = {}; this.findAllFiles(obj[item], newPath); } else { console.log(`文件路徑: ${newPath}不存在!`); } } else { console.log(`圖片路徑: ${item}`); obj["keys"] += item + ","; let params = {}; params["id"] = path.resolve(rootPath, `./${item}`); //獲得圖片寬高 params["width"] = images(path.resolve(rootPath, `./${item}`)).width(); params["height"] = images(path.resolve(rootPath, `./${item}`)).height(); obj["assets"].push(params); } }) } else { console.log(`文件路徑: ${rootPath}不存在!`); } }
這樣子我們就可以得到我們所需要的節點樹了.
我們對文件夾的操作已經完成, 這時候我們就需要思考.
如何把這些零散的圖片打包到一張圖片上.
散圖有兩個信息, 一個width
和一個height
, 其實就是一個矩形.
我們現在所要做的就是把這些不同面積的矩形放到一個具有最大長寬的大矩形中.
跳開圖片, 從矩形放置入手
二維矩形裝箱算法有不少, 我這里選用一種比較簡單的.
首先得到一個具有最大長寬的矩形盒子.
我們先放入一個矩形A, 這樣子, 剩余區域就有兩塊: 矩形A的右邊和矩形A的下邊.
然后我們繼續放入矩形B, 可以先右再下, 然后基于矩形B又有兩塊空白空間.
依次類推, 我們就可以將合適的矩形全部放入.
舉個例子
把左邊的散裝矩形放入右邊的矩形框中, 可以得到:
可以看到, 我們節省了很多空間, 矩形排列緊湊.
如果用代碼實現, 是怎么樣的呢?
/** * 確定寬高 w h * 空白區域先放一個, 剩下的尋找右邊和下邊 * 是否有滿足右邊的, 有則 放入 無則 繼續遍歷 * 是否有滿足下邊的, 有則 放入 無則 繼續遍歷 */ const Packer = function (w, h) { this.root = { x: 0, y: 0, width: w, height: h }; // /** 匹配所有的方格 */ Packer.prototype.fit = function (blocks) { let node; for (let i = 0; i < blocks.length; i++) { let block = blocks[i]; node = this.findNode(this.root, block.width, block.height); if (node) { let fit = this.findEmptyNode(node, block.width, block.height); block.x = fit.x; block.y = fit.y; block.fit = fit; } } } /** 找到可以放入的節點 */ Packer.prototype.findNode = function (node, w, h) { if (node.used) { return this.findNode(node.rightArea, w, h) || this.findNode(node.downArea, w, h); } else if (node.width >= w && node.height >= h) { return node; } else { return null; } } /** 找到空位 */ Packer.prototype.findEmptyNode = function (node, w, h) { //已經使用過的 刪除 node.used = true; //右邊空間 node.rightArea = { x: node.x + w, y: node.y, width: node.width - w, height: h }; //下方空位 node.downArea = { x: node.x, y: node.y + h, width: node.width, height: node.height - h } return node; } }
使用遞歸, 代碼量很少, 但是功能強大.
但是有一個問題, 如果超出定長定寬, 或者一個矩形裝不完, 我們的算法是不會放入到大矩形中的.
這樣子就有點不滿足我們的圖集打包思路了.
所以我們還需要將這個算法改進一下;
加入兩個變量, 一個記錄使用的總的區域, 一個記錄未被裝入的矩形.
//記錄使用的總的區域 this.usedArea = { width: 0, height: 0 }; //記錄未被裝入的矩形 this.levelBlocks = [];
詳細代碼可以查看源碼中的packing
.
當然, 這里只是最簡單的一種二維裝箱算法
還有一種加強版的裝箱算法, 我放在源碼里了, 這里就不贅述了, 原理基本一致
現在, 我們已經可以將矩形合適的裝箱了, 那怎么使用去處理成圖集呢?
定義一個dealImgsPacking
方法, 繼續去處理我們的節點樹.
這里用到了我們的配置項maxCount
, 就是為了一張圖集裝不完, 多打出幾張圖集的作用.
然后我們打包出來的圖集命名使用文件夾 + 當前是第幾張的形式.
`${obj['name'] + (count ? "-" + count : '')}`
具體方法如下:
MySpritePackTool.prototype.dealImgsPacking = function (obj) { let count = 0; if (obj.hasOwnProperty("assets")) { let newBlocks = obj["assets"]; obj["assets"] = []; while (newBlocks.length > 0 && count < this.options.maxCount) { let packer1 = new Packer(this.options.maxSize.width, this.options.maxSize.height); packer1.fit(newBlocks); let sheets1 = { maxArea: packer1.usedArea, atlas: newBlocks, fileName: `${obj['name'] + (count ? "-" + count : '')}` }; newBlocks = packer1.levelBlocks; obj["assets"].push(sheets1); count++; } } for (let item in obj) { if (obj[item].hasOwnProperty("assets")) { this.dealImgsPacking(obj[item]); } } }
通過這個方法我們改造了之前的節點樹;
將之前節點樹中的assest
變為了一個數組, 每個數組元素代表一張圖集信息.
結構如下:
assets: [ { maxArea: { width: 180,height: 340 }, atlas: [ { id: 'assets/index/img-3.png', width: 190, height: 187, x: 0, y: 0 } ], fileName: 'assets' }, ... ]
我們可以清晰的得到, 打包之后的圖集, 最大寬高是maxArea
, 每張圖寬高位置信息是atlas
,以及圖集名稱fileName
.
接下來, 就是最后一步了, 繪制新的圖片, 并輸出圖片文件.
注意
我們在使用打包算法的時候, 可以先進行一下基于圖片大小的排序
這樣以來打包出來的圖集會留白更小
這里圖集的繪制和輸出均是使用了node-images
的API;
遍歷之前得到的節點樹, 首先繪制一張maxArea
大小的空白圖像.
images(item["maxArea"].width, item["maxArea"].height)
然后遍歷一張圖集所需要的圖片信息, 將每一張圖片繪制到空白圖像上.
//繪制空白圖像 let newSprites = images(item["maxArea"].width, item["maxArea"].height); //繪制圖集 imgObj.forEach(it => { newSprites.draw(images(it["id"]), it["x"], it["y"]); });
然后繪制完一張圖集輸出一張.
newSprites.save(`${this.options.outPutPath}/${item['fileName']}.png`);
最后對節點樹遞歸調用, 繪制出所有的圖集.
具體代碼如下:
MySpritePackTool.prototype.drawImages = function (obj) { let count = 0; if (obj.hasOwnProperty("assets")) { //打包出一個或者多個圖集 let imgsInfo = obj["assets"]; imgsInfo.forEach(item => { if (item.hasOwnProperty("atlas")) { let imgObj = item["atlas"]; // console.log("8888",imgObj) //繪制一張透明圖像 let newSprites = images(item["maxArea"].width, item["maxArea"].height); imgObj.forEach(it => { newSprites.draw(images(it["id"]), it["x"], it["y"]); }); newSprites.save(`${this.options.outPutPath}/${item['fileName']}.png`); count++; } }) } for (let item in obj) { if (obj[item].hasOwnProperty("assets")) { this.drawImages(obj[item]); } } }
這樣子, 我們就大功告成了,
運行測試一下, 可以得到如下的圖集:
效果還不錯.
安裝
npm i sprites-pack-tool
使用
const MySpritePackTool = require("sprites-pack-tool"); const path = require("path"); /** 打包最多遞歸次數 */ const MAX_COUNT = 2; //需要合成的圖集的路徑 const assetsPath = path.resolve(__dirname, "./assets"); /** 圖集打包工具配置*/ const mySpritePackTool = new MySpritePackTool({ //一個文件夾圖片過多或者過長 遞歸最大次數 maxCount: MAX_COUNT, //需要打包圖集的文件路徑 assetsPath: assetsPath, //輸出文件路徑 outPutPath: path.resolve(__dirname, "./res"), //一張圖集打包最大size maxSize: { width: 2048,height: 2048} }); /** 圖集打包 */ mySpritePackTool.Pack2Sprite();
以上是“如何使用node開發一款圖集打包工具”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。