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

像上面这张背景图,如果美术切一个完整的背景,显然会对合成图集造成不必要的浪费,一般要求美术切一个小图,然后在编辑器中设置图片九宫格缩放。
那么问题来了
1.美术切一个九宫格小图需要花额外的时间。
2.美术给了九宫格小图之后,在编辑UI时,保持图片原始大小会产生额外的操作,要么美术告诉你原图尺寸,要么用程序自己量,当然美术也可以把大图也一起切出来,总之就会产生额外的工作量
3.另外有些使用插件将psd转成ui的应用场景,psd切的图肯定是完整大图,加入让美术另外提供九宫格小图未免太麻烦
我想要的效果:
美术切出原图,在编辑器中设置好九宫格参数,然后通过插件自动生成九宫格小图,并且自动替换UI里的spriteframe。

原图 256 * 256

添加插件菜单

生成小图104 * 103
以上只是举例,当然也可以直接对场景或者预设体中的的节点Sprite进行替换操作。
接下来就是实现思路
1.对大图设置九宫格参数,见下图

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的版本,懒得去搞。
所以直接贴源码,有兴趣的朋友可以去试一下。