通过编辑器插件缩小九宫格图片,简化美术的工作

对于一些可进行九宫格缩放的图片,比如UI背景,按钮等

image

像上面这张背景图,如果美术切一个完整的背景,显然会对合成图集造成不必要的浪费,一般要求美术切一个小图,然后在编辑器中设置图片九宫格缩放。

那么问题来了
1.美术切一个九宫格小图需要花额外的时间。
2.美术给了九宫格小图之后,在编辑UI时,保持图片原始大小会产生额外的操作,要么美术告诉你原图尺寸,要么用程序自己量,当然美术也可以把大图也一起切出来,总之就会产生额外的工作量
3.另外有些使用插件将psd转成ui的应用场景,psd切的图肯定是完整大图,加入让美术另外提供九宫格小图未免太麻烦

我想要的效果:
美术切出原图,在编辑器中设置好九宫格参数,然后通过插件自动生成九宫格小图,并且自动替换UI里的spriteframe。

image
原图 256 * 256

image
添加插件菜单

image
生成小图104 * 103

以上只是举例,当然也可以直接对场景或者预设体中的的节点Sprite进行替换操作。

接下来就是实现思路

1.对大图设置九宫格参数,见下图
image

2.读取图片的九宫格参数,这一步可以直接解析图片对应的meta文件获得,一个json结构,获取很简单

3.切片并且生成小图
这一步是重点,用到了第三方库node_images
https://github.com/zhangyuanwei/node-images
npm install images

思路就是先按九宫格参数生成9个切片的尺寸,当然不一定是9个切片,如果宫格参数只设置了左右拉伸,那么就只需要处理左中右3个切片,同理,如果只设置了上下拉伸,那么就只有上中下三个切片

获取到切片尺寸后,通过images库创建一个小图,然后把原图中各切片的像素复制到小图中。

贴代码

//缩小九宫格图片,

//nodeUuid如果有uuid,那么是处理场景中的节点Sprite图片,不传的话,默认读取assets选中的图片

//overwrite表示保存图片时是否覆盖原图

'scale-9-grid':function(event,nodeUuid,overwrite){

    let assetUuid;

    let nodeSprite;

    //如果传入了nodeUuid,那么认为是处理场景里的Sprite节点,取它的图片进行九宫格缩小

    if(nodeUuid){

        let node = cc.engine.getInstanceById(nodeUuid);

        if(node == null)

            return;

       

        let sprite = node.getComponent(cc.Sprite);

        if (sprite && sprite.spriteFrame) {

            let url = Editor.remote.assetdb.uuidToUrl(sprite.spriteFrame._uuid);



            if(!url)

                return;

            let textureUrlSeperatorIndex = url.lastIndexOf("/");

            let textureUrl = url.substr(0,textureUrlSeperatorIndex);

            assetUuid = Editor.remote.assetdb.urlToUuid(textureUrl);

            if(sprite.type != cc.Sprite.Type.SLICED)

                sprite.type = cc.Sprite.Type.SLICED;

            if(sprite.sizeMode != cc.Sprite.SizeMode.CUSTOM)

                sprite.sizeMode = cc.Sprite.SizeMode.CUSTOM;

            nodeSprite = sprite;

        }

        else{

            Editor.warn(`节点${node.name}中没有关联图片资源`);

            return;

        }

    }

    else{

        let selection = Editor.Selection.curSelection("asset");

        if (selection == null || selection.length == 0) {

            Editor.warn("请选中资源目录的图片资源");

            return;

        }

        assetUuid = selection[0];

    }

   

    if(!assetUuid)

        return;

    Editor.assetdb.queryInfoByUuid(assetUuid,(err,info)=>{

        if (info == null) {

            return;

        }

        let projectPath = Editor.Project.path;

        let fs = Editor.require("fs");

        let path = Editor.require("path");

        let ext = path.extname(info.path);

        let name = path.basename(info.path,ext);

       

        if (ext != ".png") {

            Editor.warn("请选中资源目录的png图片资源");

            return;

        }

       

        Editor.log(info.url);

        let newImageName = overwrite ? name : (name + "_scale_9_grid");

        let newImagePath = path.join(path.dirname(info.path),newImageName + ext);

        let newImageUrl = path.dirname(info.url) + "/" + newImageName + ext;

        let metaPath = info.path + ".meta";

        let newImageMetaPath = newImagePath + ".meta";

        //Editor.log(newImagePath);

        //Editor.log(newImageUrl);

        if(!fs.existsSync(metaPath)){

            Editor.warn("文件不存在:" + metaPath);

            return;

        }

        if(overwrite){

            let result = Editor.Dialog.messageBox({

                buttons: ['ok',"cancel"],

                title: '缩小九宫格图片',

                message: `是否按九宫格缩小图片:${name},确定操作将会覆盖原图,是否继续?\n\n${info.url}`

              });

            if(result == 1){

                Editor.log("取消操作");

                return;

            }

        }



        if(!overwrite && fs.existsSync(newImagePath)){

            let result = Editor.Dialog.messageBox({

                buttons: ['ok',"cancel"],

                title: '缩小九宫格图片',

                message: `图片已存在:${newImageName},继续操作将会覆盖此文件,是否继续?\n\n${newImageUrl}`

              });

            if(result == 1){

                Editor.log("取消操作");

                return;

            }

        }

       

        let metaStr = fs.readFileSync(metaPath).toString();

        let metaObj = JSON.parse(metaStr);

        // Editor.log(metaObj);

        if(metaObj.type != "sprite"){

            Editor.warn("资源类型必须为sprite");

            return;

        }

        if(!metaObj.subMetas || !metaObj.subMetas[name]){

            Editor.warn("未读取到SpriteFrame:" + name);

            return;

        }

        let images;

        try {

            //https://github.com/zhangyuanwei/node-images

            images = cc.require(projectPath + "/node_modules/images/index");

        }

        catch (e) {

            Editor.log(e);

            Editor.failed("此操作依赖第三方库node_images,请先安装images模块到项目中");

            Editor.warn("npm install images --save");

            return;

        }

        let spriteframe = metaObj.subMetas[name];

        let width = spriteframe.rawWidth;

        let height = spriteframe.rawHeight;

        //图片九宫格边距

        let borderTop = spriteframe.borderTop | 0;

        let borderBottom = spriteframe.borderBottom | 0;

        let borderLeft = spriteframe.borderLeft | 0;

        let borderRight = spriteframe.borderRight | 0;

        Editor.log("图片九宫格参数:" + JSON.stringify({borderTop,borderBottom,borderLeft,borderRight}));

       

        if(borderTop == 0 && borderBottom == 0 && borderLeft == 0 && borderRight == 0){

            Editor.warn("请先设置图片九宫格参数:" + name);

            return;

        }

        let originImage = images(info.path);

        //9个格子的切图,有些可能是空的,取决于九宫格的设置,比如只设置了上下边距,那就只有上中下三个切片

        let topleft,top,topright,left,center,right,bottomleft,bottom,bottomright;

        let paddingWidth = 30;//截取中间可拉伸图片部分的宽度

        let paddingHeight = 30;//可缩放区域的高度

        //宽度不缩放

        if(borderLeft == 0 && borderRight == 0)

            paddingWidth = width;

       

        //高度不缩放

        if(borderTop == 0 && borderBottom == 0)

            paddingHeight = height;

        //缩小后的尺寸

        let scaleWidth = borderLeft + borderRight + Math.min(paddingWidth,width - borderLeft - borderRight) ;

        let scaleHeight = borderTop + borderBottom + Math.min(paddingHeight,height - borderTop - borderBottom) ;

        Editor.log(`按九宫格缩小图片尺寸:width=${scaleWidth},height=${scaleHeight}`);

        if(width - scaleWidth < 10 && height - scaleHeight < 10){

            Editor.warn(`九宫格缩小图片尺寸差距不大,放弃缩小图片`);

            return;

        }

        //从源图片中创建切片图

        if(borderTop > 0){

            top = images(originImage,borderLeft,0,Math.min(width - borderLeft - borderRight,paddingWidth),borderTop);

            if(borderLeft > 0)

                topleft = images(originImage,0,0,borderLeft,borderTop);

               

            if(borderRight)

                topright = images(originImage,width - borderRight,0,borderRight,borderTop);

        }

        if(borderBottom > 0){

            bottom = images(originImage,borderLeft,height - borderBottom,Math.min(width - borderLeft - borderRight,paddingWidth),borderBottom);

            if(borderLeft > 0)

                bottomleft = images(originImage,0,height - borderBottom,borderLeft,borderBottom);

               

            if(borderRight)

                bottomright = images(originImage,width - borderRight,height - borderBottom,borderRight,borderBottom);

        }

        if(borderLeft){

            left = images(originImage,0,borderTop,borderLeft,Math.min(height - borderTop - borderBottom,paddingHeight));

        }

        if(borderRight){

            right = images(originImage,width - borderRight,borderTop,borderRight,Math.min(height - borderTop - borderBottom,paddingHeight));

        }

        center = images(originImage,borderLeft,borderTop,Math.min(width - borderLeft - borderRight,paddingWidth),Math.min(height - borderTop - borderBottom,paddingHeight));

        let scale_9_grid_image = images(scaleWidth,scaleHeight);

        if(topleft)

            scale_9_grid_image.draw(topleft,0,0);

       

        if(top)

            scale_9_grid_image.draw(top,borderLeft,0);

        if(topright)

            scale_9_grid_image.draw(topright,scaleWidth - borderRight,0);

        if(left)

            scale_9_grid_image.draw(left,0,borderTop);

        //center

        scale_9_grid_image.draw(center,borderLeft,borderTop);

       

        if(right)

            scale_9_grid_image.draw(right,scaleWidth - borderRight,borderTop);

        if(bottomleft)

            scale_9_grid_image.draw(bottomleft,0,scaleHeight - borderBottom);

        if(bottom)

            scale_9_grid_image.draw(bottom,borderLeft,scaleHeight-borderBottom);

        if(bottomright)

            scale_9_grid_image.draw(bottomright,scaleWidth-borderRight,scaleHeight-borderBottom);

        //保存九宫格缩小图

        try{

            scale_9_grid_image.save(newImagePath);

        }

        catch(e){

            Editor.log(e);

            return;

        }

        if(!fs.existsSync(newImagePath))

            return;

       

        Editor.assetdb.refresh(newImageUrl, (err, results) => {

            if (err) {

                Editor.log(err)

                return;

            }

            Editor.log("生成新的缩小九宫格图片:" + newImageUrl);

            if(overwrite)

                return;

            let textureResult;

            let spriteframeResult;

            results.forEach((result) => {

                if (result.type == "sprite-frame") {

                    spriteframeResult = result;

                }

                else if (result.type == "texture") {

                    textureResult = result;

                }

            });

            if(spriteframeResult && textureResult){

                //Editor.log(spriteframeResult.uuid);

                //Editor.log(spriteframeResult.path);

                if (!fs.existsSync(newImageMetaPath)) {

                    Editor.warn("更新九宫格参数失败,文件不存在:" + newImageMetaPath);

                    return;

                }

                let metaStr = fs.readFileSync(newImageMetaPath).toString();

                let metaObj = JSON.parse(metaStr);

                //Editor.log(metaObj);

                if (metaObj.type != "sprite" || !metaObj.subMetas || !metaObj.subMetas[newImageName]) {

                    return;

                }

                let spriteframe = metaObj.subMetas[newImageName];

                spriteframe.borderTop  = borderTop;

                spriteframe.borderBottom = borderBottom;

                spriteframe.borderLeft = borderLeft;

                spriteframe.borderRight = borderRight;

                Editor.assetdb.saveMeta(textureResult.uuid,JSON.stringify(metaObj),(err,meta)=>{

                    Editor.log("同步设置九宫格参数成功:" + JSON.stringify({borderTop,borderBottom,borderLeft,borderRight}));

                });

            }

           

        });

    });

}

上面那一段是完整的插件代码,放到场景脚本中。
1.先通过npm install images安装图片处理库
2.新建一个插件或者已有插件也行,创建一个场景脚本,这一步请查看cocos文档
3.将上述代码复制的场景脚本中,调用场景脚本即可,以下是两种调用方式
a:处理场景中的节点Sprite图片,Editor.Scene.callSceneScript(‘xxx插件名称’, ‘scale-9-grid’,node_selection[0],true);
b:处理图片资源,Editor.Scene.callSceneScript(‘xxx插件名称’, ‘scale-9-grid’);
4.测试效果

再这里提醒一下,对脚本不熟悉的话,测试时最后复制图片进行验证,避免以外操作错误覆盖了图片。

为什么不直接上传一个插件到Store,主要怕麻烦,我主要用2.4.x的版本开发,如果提交插件势必要适配3.x版本,目前还没有项目用到3.x的版本,懒得去搞。

所以直接贴源码,有兴趣的朋友可以去试一下。

9赞