我的编程空间,编程开发者的网络收藏夹
学习永远不晚

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

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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

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

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

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

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

这一张图集就是我用本文介绍的工具打包合成的.

合成的图片品质依然十分高呢.

为什么需要使用图集

web开发

我们在web开发中, 每次在浏览器展示一张图片都需要请求一次服务器资源.

举个例子, 3次请求每次4k, 和一次请求12k还是有本质区别的, 然后更多的时候一次请求并不是3 * 4k.

使用图集能让我们优化资源加载, 提高网站的性能.

游戏开发

在游戏开发中, 图集的使用至关重要, 不管是一般帧动画还是svga等动画解决方案, 都不会每张图片去请求资源.

更多的时候, 我们都是打包成图集, 而图集打包工具texturepacker更是大行其道.

其次, 游戏场景过多, 我们一般都需要分步加载资源, 有的时候一个动画模型, 涉及的图片少则十来张, 多则近百张.

图集的使用不可或缺.

下面我们就来看下如何编写一款图集打包工具.

工具设计

开发一个图集打包工具脚本需要什么技能.

  • node.js编程能力

  • 二维矩形装箱算法

然后我们思考如何去打包一张图集.

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

  • 图集是多张散图拼合而成.

  • 图集的大小需要可配置

  • 尽可能的压缩图集空间, 使每张图紧密贴合

  • 每个文件夹打包成一个图集, 需要考虑图片过多的情况

  • 可能需要生成图集所需要的json文件, 记录图片位置信息

开始编写脚本

脚本IO

我这里是这样设计.

首先我们需要一个打包对象实例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的下边.

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

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

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

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

举个例子

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

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

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

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


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

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

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

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Qt如何使用windeployqt工具实现程序打包发布

这篇文章主要介绍了Qt如何使用windeployqt工具实现程序打包发布,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。Qt 官方开发环境使用的动态链接库方式,在发布生成的ex
2023-06-25

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录