上一节篇尾提到,地图要切成小图便于加载,但地图编辑器是个网页版工具,要在网页上实现切图不是很方便,经过考虑之后,决定使用 nodejs 实现。
要做一个图形化的切图界面,加上要能使用 nodejs,最先想到的就是 Electron。Electron 是一个 nodejs 的扩展库,可以使用 JavaScript,HTML 和 CSS 构建跨平台的桌面应用程序。若之前写过网页,上手比较简单。
界面完全基于 HTML 和 CSS 实现,切图只需两个简单功能,选择图片和生成切图,生成包含地图块和缩小图片,小图用于提前显示和作小地图。
下面是界面的代码:
<html>
<head>
<title>MapCuttertitle>
<meta charset="UTF-8">
<link href="css/global.css" rel="stylesheet" type="text/css" />
head>
<body>
<button id="btnLoad">打开文件...button>
<div id="divFilepath">div>
<img id="imgMap" width="100%"/>
<button id="btnCut">切图button>
body>
<script src="render/index.js">script>
html>
window.onload = function onload() {
var btnLoad = document.querySelector("#btnLoad")
btnLoad.onclick = function () {
dialog.showOpenDialog({
title: "选中地图图片文件...",
defaultPath: '*.jpg',
filters: [{
name: "image",
extensions: ["jpg", "png"],
}],
}).then(res => {
let filepath = res.filePaths[0];
if (!filepath) {
return;
}
var divFilepath = document.querySelector("#divFilepath")
divFilepath.innerHTML = filepath;
var imgMap = document.querySelector("#imgMap")
imgMap.setAttribute("src", filepath)
g_filePath = filepath;
}).catch(err => {
console.error(err);
});
}
var btnCut = document.querySelector("#btnCut")
btnCut.onclick = function () {
if (!g_filePath) {
return;
}
let fileObj = path.parse(g_filePath)
let outDir = path.join(fileObj.dir, fileObj.name)
if (fs.existsSync(outDir)) {
fs.rmdirSync(outDir, { recursive: true });
}
fs.mkdirSync(outDir)
let srcImg = images(g_filePath);
let size = srcImg.size();
let wCnt = Math.ceil(size.width / g_sizeTitle)
let hCnt = Math.ceil(size.height / g_sizeTitle)
let dstImg, filename, outPath, yFrom, w, h;
for (let x = 0; x < wCnt; x++) {
for (let y = 0; y < hCnt; y++) {
yFrom = size.height - (y + 1) * g_sizeTitle
if (yFrom < 0) {
h += yFrom
yFrom = 0;
} else{
h = g_sizeTitle
}
w = Math.min(g_sizeTitle, size.width - x * g_sizeTitle)
dstImg = images(srcImg, x * g_sizeTitle, yFrom, w, h)
filename = "tile_" + x + "_" + y + ".jpg"
outPath = path.join(outDir, filename);
dstImg.save(outPath)
}
}
// 小地图
outPath = path.join(outDir, g_filenameSmall);
srcImg.size(g_sizeSmall).save(outPath);
dialog.showMessageBox({
message: "完成切图",
buttons: ["打开目录"],
cancelId: 1,
}).then((res) => {
if (res.response === 0) {
// 自动打开切图导出目录
shell.showItemInFolder(outDir)
}
});
}
}
切图功能用到了一个 nodejs 的依赖库 images,可以实现图片简单的分割和缩放,刚好满足需求。运行效果:
图形化界面所见即所得,易于使用,方便非专业人员使用。但若是需要处理的地图很多,需要大量重复的操作,比较繁琐。下面是一个命令行的工具,在配置文件中,配置好需要导出的地图路径,执行导出脚本,一次性就能导出全部地图了。
const fs = require("fs")
const path = require("path")
const images = require("images");
const exec = require('child_process').exec;
// 配置文件
const config = require("./resources/config");
const g_sizeTitle = 1024;
const g_sizeSmall = 400;
const g_filenameSmall = "small.jpg";
var cutImage = function (imgpath) {
console.log("cut file: " + imgpath);
let fileObj = path.parse(imgpath)
let outDir = path.join(fileObj.dir, fileObj.name)
if (fs.existsSync(outDir)) {
fs.rmdirSync(outDir, { recursive: true });
}
fs.mkdirSync(outDir)
let srcImg = images(imgpath);
let size = srcImg.size();
let wCnt = Math.ceil(size.width / g_sizeTitle)
let hCnt = Math.ceil(size.height / g_sizeTitle)
let dstImg, filename, outPath, yFrom, w, h;
for (let x = 0; x < wCnt; x++) {
for (let y = 0; y < hCnt; y++) {
yFrom = size.height - (y + 1) * g_sizeTitle
if (yFrom < 0) {
h += yFrom
yFrom = 0;
} else{
h = g_sizeTitle
}
w = Math.min(g_sizeTitle, size.width - x * g_sizeTitle)
dstImg = images(srcImg, x * g_sizeTitle, yFrom, w, h)
filename = "tile_" + x + "_" + y + ".jpg"
outPath = path.join(outDir, filename);
dstImg.save(outPath)
}
}
// 小地图
outPath = path.join(outDir, g_filenameSmall);
srcImg.size(g_sizeSmall).save(outPath);
return outDir;
}
var main = function () {
let outDir;
for (let i = 0; i < config.length; i++) {
outDir = cutImage(config[i]);
}
let cmd = "start explorer \"" + outDir + "\\.." + "\""
console.log(cmd);
exec(cmd)
}
main()
其中配置文件 config,是一个简单的 json 文件,其中保存的是需要导出地图图片的数组,格式如下:
[
"c:\\x\\MapEditor\\assets\\resources\\map\\1001.jpg",
"c:\\x\\MapEditor\\assets\\resources\\map\\1002.jpg"
]
图形化界面和脚本都是一个单独的工具,可以单独使用。既然地图编辑器使用 cocos creator 开发的,能不能把工具也集成进来呢?前面提到,地图编辑器运行时是一个网页,不好实现,只能想办法把工具继承到 cocos creator 的编辑器之中,刚好 cocos creator 有提供扩展编辑器的接口——插件,将上面命令行工具,经过简单地修改,就可以集成到 cocos creator 编辑器中。插件的主要代码如下:
'use strict';
// 地图资源目录
const MAP_ROOT = "db://assets/resources/map"
// 切图尺寸
const g_sizeTitle = 1024;
// 小地图尺寸
const g_sizeSmall = 400;
const g_filenameSmall = "small.jpg";
var cutImage = function (imgpath) {
const fs = require("fs")
const path = require("path")
const images = require("images");
// 重新创建导出目录
let fileObj = path.parse(imgpath)
let outDir = path.join(fileObj.dir, fileObj.name)
if (fs.existsSync(outDir)) {
// 删除旧的导出目录
fs.readdirSync(outDir).forEach((file, index) => {
const curPath = path.join(outDir, file);
if (fs.lstatSync(curPath).isDirectory()) { // recurse
deleteFolderRecursive(curPath);
} else { // delete file
fs.unlinkSync(curPath);
}
});
fs.rmdirSync(outDir);
}
fs.mkdirSync(outDir)
// 切图
let srcImg = images(imgpath);
let size = srcImg.size();
let wCnt = Math.ceil(size.width / g_sizeTitle)
let hCnt = Math.ceil(size.height / g_sizeTitle)
let dstImg, filename, outPath, yFrom, w, h;
for (let x = 0; x < wCnt; x++) {
for (let y = 0; y < hCnt; y++) {
yFrom = size.height - (y + 1) * g_sizeTitle
if (yFrom < 0) {
h += yFrom
yFrom = 0;
} else{
h = g_sizeTitle
}
w = Math.min(g_sizeTitle, size.width - x * g_sizeTitle)
dstImg = images(srcImg, x * g_sizeTitle, yFrom, w, h)
filename = "tile_" + x + "_" + y + ".jpg"
outPath = path.join(outDir, filename);
dstImg.save(outPath)
}
}
// 小地图
outPath = path.join(outDir, g_filenameSmall);
srcImg.size(g_sizeSmall).save(outPath);
}
module.exports = {
messages: {
cut: function () {
Editor.assetdb.queryAssets(MAP_ROOT + "/*.*", 'texture', (err, assetInfos) => {
if (err) {
Editor.console.error(err.message || err);
return
}
for (let i = 0; i < assetInfos.length; ++i) {
Editor.log("cute url: " + assetInfos[i].url)
// 切图
cutImage(assetInfos[i].path)
}
// 更新导入资源
Editor.assetdb.refresh(MAP_ROOT);
});
}
},
};
切图部分的代码跟命令行版本基本上没有什么变化,主要要修改的是地图输入的方式;在命令行中,通过配置文件输入需要切的图片路径,在插件中可以免除配置文件这个过程,直接遍历地图资源目录,获取图片路径作为输入。在完成切图之后,刷新导入资源,就可以在编辑器中看到,新切除的图片。
这样,就把切图功能作为插件集成到编辑器中,避免来去切换工具。
回顾一下地图编辑器开发的整个流程,以下相关的全部博客:
加载地图
编辑地图
测试阻挡
导出
后续会考虑使用这个地图编辑器,做一个MMORPG的demo。