如何使用node开发一款图集打包工具

技术如何使用node开发一款图集打包工具这篇文章主要为大家展示了“如何使用node开发一款图集打包工具”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“如何使用node开发一款图

本文主要展示“如何使用node开发一个地图集打包工具”。内容简单易懂,条理清晰,希望能帮你解开疑惑。让边肖带领大家学习《如何使用node开发地图集打包工具》一文。

比如把下面的几张图片合成一张.

如何使用node开发一款图集打包工具

如何使用node开发一款图集打包工具

这个地图集是我用本文介绍的工具打包合成的。

合成的图片质量还是很高的。

00-1010

为什么需要使用图集

在web开发中,每次在浏览器中显示图片时,都需要请求服务器资源。

例如,3次请求每次4k一次请求12k,之间有着本质的区别,而且更多的时候,请求不是来自3 * 4k.

使用atlas可以优化资源加载,提高网站性能。

web开发

在游戏开发中,图册的使用非常重要。无论是一般的帧动画还是svga这样的动画解决方案,每一张图片都不会要求资源。

更多时候,我们把它们打包到atlas中,atlas打包工具texturepacker更受欢迎。

其次,因为游戏场景太多,我们通常需要一步一步加载资源。有时候,一个动画模型涉及的图片少则十几张,多则一百张。

图集的使用不可或缺.

让我们来看看如何编写一个地图集打包工具。

游戏开发

开发地图集打包工具脚本需要哪些技巧?

node.js的编程能力

二维矩形布局算法

然后我们考虑如何打包地图集。

我们需要找到需要打包的文件夹。可能有多个文件夹或嵌套文件夹。

这本地图集由几张零散的图片组成。

地图集的大小需要配置。

尽可能压缩地图集空间,使每张图片紧密贴合。

每个文件夹都打包成一个地图集,图片太多了,没办法考虑。

可能需要生成atlas所需的json文件来记录图片的位置信息。

工具设计

开始编写脚本

,这是我的设计。

首先,我们需要一个打包对象实例MySpritePackTool,它也支持写配置参数选项。

/* *阿特拉斯包装对象*/

constMySpritePackTool=函数(opt){ 0

this . options={ 0

//文件夹中的图片太多或太长。最大递归次数

maxCount:opt.maxCount||2,

//图册的文件路径需要打包。

assetsPath:opt.assetsPath,

//输出文件路径

outPutPath:opt.outPutPath,

//一个地图包的最大尺寸

maxSize:nb

sp;{ 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的下边.

如何使用node开发一款图集打包工具

然后我们继续放入矩形B, 可以先右再下, 然后基于矩形B又有两块空白空间.

如何使用node开发一款图集打包工具

依次类推, 我们就可以将合适的矩形全部放入.

举个例子

把左边的散装矩形放入右边的矩形框中, 可以得到:

如何使用node开发一款图集打包工具

可以看到, 我们节省了很多空间, 矩形排列紧凑.

如果用代码实现, 是怎么样的呢?

/** 
 * 确定宽高 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]);
        }
    }
}

这样子, 我们就大功告成了,

运行测试一下, 可以得到如下的图集:

如何使用node开发一款图集打包工具

如何使用node开发一款图集打包工具

效果还不错.

如何使用

安装

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开发一款图集打包工具”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注行业资讯频道!

内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/126265.html

(0)

相关推荐

  • java怎么判断是否为数组参数(java分位数算法)

    技术Java分位点计算方法是什么本篇内容介绍了“Java分位点计算方法是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成

    攻略 2021年12月16日
  • 太极拳入门基本功,初学太极拳最好的方法有哪几种

    技术太极拳入门基本功,初学太极拳最好的方法有哪几种初学太极拳最好的方法有哪几种太极拳入门基本功?(原创)这个问题我来回答题主。我从五十岁开始学太极拳,到现在己经有二十三年了。根据我学习太极拳的经历来回答这个问题。初学大极

    生活 2021年10月22日
  • Go语言中什么是互斥锁

    技术Go语言中什么是互斥锁这篇文章将为大家详细讲解有关Go语言中什么是互斥锁,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。1、互斥锁Mutex1.1 Mutex介绍Go

    攻略 2021年10月21日
  • php如何获取指定日期是一周的第几天

    技术php如何获取指定日期是一周的第几天这篇文章主要讲解了“php如何获取指定日期是一周的第几天”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“php如何获取指定日期是一周

    攻略 2021年11月30日
  • 雄伟的反义词,气势宏大雄伟的意思写词语

    技术雄伟的反义词,气势宏大雄伟的意思写词语气势磅礴雄伟的反义词、气吞山河、一泻千里、地动山摇、波澜壮阔、排山倒海、气贯长虹、浩浩荡荡、声势浩大、汹涌澎湃、千军万马、气吞虹霓。1、一泻千里【解释】:泻:水往下直注。形容江河

    生活 2021年10月26日
  • Redis中缓存过期、内存被缓存占用要如何处理

    技术Redis中缓存过期、内存被缓存占用要如何处理这篇文章主要讲解了“Redis中缓存过期、内存被缓存占用要如何处理”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Redi

    攻略 2021年11月29日