由于前段时间比较忙所以拖更了,这里也十分抱歉,加上我这个人平时不经常写博客,所以会慢一点,以后这种连续的博客我不会拖更了。
那么进入正题,今天我们实现一个简单的小插件,其实呢这个小插件也没什么卵用,只是可以给更多需要的人提供一个思路,同样对我来说也是一个笔记,如果各位朋友感兴趣的可以继续往下看。
首先为什么要写编辑器呢?我个人的理解不过于就是方便我们在平时开发,写一些和引擎交互的东西,可以实现一键搞定重复的操作,比如说你要对一个文件夹下的所有预制体都添加一个脚本,那么我们一个一个添加也可以,但肯定会对于这个重复的操作感到无聊同样也会浪费开发时间,要是一键就能搞定,那会多么方便是吧,但是转念一想可能写的时间还不如自己一个一个拖拽了,所以这个问题的取舍当然也看项目大小,进度等等情况来看,找到一个合适的解决办法才是最好的解决办法。然后开始我们今天的这个小插件。
首先说下这个写这个插件的意义,也就是需求,假设需求是这样的:我想弄一个简单的比如关卡的存储管理,把场景下的物体在我搭完之后,一键可以把场景的里的物体的位置保存到一个json的配置表中,然后我在加载某一个关卡的时候,根据表中的数据信息把场景还原回来,当然有人也会问那直接做个预支体不好么?当然也可以,所以也根据项目来看。我突然想到这个也可以用来做一下单机游戏存档是吧。
好了,我们给自己的需求定好了,那么我们自己看下要怎么去实现这个插件,也就是逻辑。首先分为几个部分,1.也就是我们的保存的数据位置,我们需要一个固定的文件夹来存放每一个关卡的配置数据,对资源文件的操作部分2.我们需要一个面板也就是我们场景搭建完成后需要一键保存的一个面板,而面板中有什么功能呢,要有一个一键生成的存储路径,还要有根据路径生成的按钮,以及我删除这个配置数据的按钮,还可能有刷新这个资源下配置表的数据的按钮,当然最好还要有一个换下一关时,直接一键把之前搭建好的场景一键清空,换下一关的按钮,至少我觉得这些都要有吧,3.我们需要把场景的物体怎么通过面板上一键存到json配置表中呢,这里涉及到场景和面板的进程交互。所以总结下来这三点,那么按照逻辑我们一步一步来吧,开始前还是先看张效果图吧:
其实也并不是什么完整的关卡配置数据表,只是我取的一个名字因为不知道叫什么,看下操作流程:我在root节点下比如搭建好了一个关卡,我想把信息保存起来,点击tool下的按钮会弹出这个面板,如果路径不想改的话就用默认的,因为到时候还原数据的时候也要load加载,所以最好是在resources或者它的子文件夹下,点击生成配置表按钮会一键把root下面所有的子节点的信息保存到这个配置表中也就是level1那个json里,至于想保存什么信息可以自己在代码中设定,因为我之前生成了,所以在生成就是覆盖之前的文件了信息了,有的时候可能生成的数据在引擎没有没有及时刷新,点下刷新就好了,如果这一关不想要了,也可点击删除,完成一关后点击重置,会把root场景下的节点全部删除,好进行下一关配置。
接下来我们在工程的根目录下的packages文件夹下建一个名为level-configuration的文件夹,也可以自己换成自己想要的名字,我以我的举例子。至于为什么在packages下操作我前面博客有说。里面的文件结构如下:
main.js是这个插件的入口,panel下index.js是面板的代码,scene-obtain.js是操作场景的脚本,那package.json就不用说了,path暂时先没用到。看下工程里:
先看下package.json里的配置内容吧:
{
"name": "level-configuration",
"version": "0.0.1",
"description": "关卡配置表生成工具",
"author": "tm",
"main": "main.js",
"scene-script": "scene-obtain.js",
"main-menu": {
"Tool/LevelConfiguration": {
"message": "level-configuration:open-panel"
}
},
"panel": {
"main": "panel/index.js",
"type": "dockable",
"title": "LevelConfiguration",
"width": 400,
"height": 300
}
}
接下来是main.js里的内容,引擎启动后如果没有resources文件夹会自动创建一个文件夹,后面是一些对文件的操作在主进程中操作:
'use strict';
let fs = require('fs');
let path = require('path');
//默认关卡路径
let defaultLevelPath='assets/resources/';
module.exports={
load(){
this.init();
},
unload(){
Editor.log('卸载执行');
},
//初始化
init(){
this.createDirectory();
},
//创建初始文件夹
createDirectory(){
if(fs.existsSync(path.join(Editor.Project.path, defaultLevelPath))){
Editor.log('resources exists!');
}else {
// 插件加载后在项目根目录自动创建指定文件夹
fs.mkdirSync(path.join(Editor.Project.path, defaultLevelPath));
Editor.success('resources created!');
}
},
//创建关卡文件
createLevelFile(filePath){
let levelPath=filePath+".json";
if(fs.existsSync(path.join(Editor.Project.path, levelPath))){
//覆盖源文件
this.coverTargetFile(levelPath);
}else {
//新建文件
this.createNewFile(levelPath);
}
},
//覆盖源文件
coverTargetFile(filePath){
Editor.Scene.callSceneScript('level-configuration', 'get-scene-info', function (err, json) {
Editor.assetdb.saveExists( 'db://'+filePath, json, function ( err, meta ) {
if(err){
Editor.log("覆盖文件失败!!!");
return;
}
Editor.log("覆盖文件成功!!!");
});
});
},
//创建一个新的文件
createNewFile(filePath){
Editor.Scene.callSceneScript('level-configuration', 'get-scene-info', function (err, json) {
Editor.assetdb.create( 'db://'+filePath, json, function ( err, results ) {
if(err){
Editor.log("创建文件失败!!!");
return;
}
Editor.log("创建文件成功!!!");
});
});
},
//删除文件
deleteLevelFile(filePath){
let levelPath=filePath+".json";
if(fs.existsSync(path.join(Editor.Project.path, levelPath))){
Editor.assetdb.delete(['db://'+levelPath], function (err, results) {
results.forEach(function (result) {
if(err){
Editor.log("删除文件失败!!!");
return;
}
Editor.log("删除文件成功!!!");
});
});
}else {
Editor.log("没有可删除的文件!!!");
}
},
messages: {
//打开面板
'open-panel'() {
Editor.Panel.open('level-configuration');
},
//保存按钮点击
'save-click'(event, ...args){
const self=this;
self.createLevelFile(args[0]);
},
//设置路径
'set-path'(event,...args){
if(args[0] && args!=''){
defaultLevelPath=args[0];
this.createDirectory();
}
},
//面板加载完成
'panel-load-finish'(evnet,...args){
Editor.Ipc.sendToPanel('level-configuration','setDefaultPath', defaultLevelPath);
},
//删除按钮点击
'delete-click'(event,...args){
this.deleteLevelFile(args[0]);
}
},
}
然后看下面板index.js的代码,无非就是界面以及那几个按钮绑定的方法,和主进程的通信
let defaultLevelPath='assets/resources/';
let levelNameHeader="level_";
let levelName="";
Editor.Panel.extend({
style: `
:host { margin: 5px; }
h2 { color: #f90; }
.bottom {
height: 30px;
}
`,
template: `
关卡配置表生成器
FilePath:
LevelName:
生成配置表
删除配置表
刷新资源
重置场景
`,
$: {
//保存按钮
btn: '#btn',
//固定路径前label
label: '#label',
//关卡路径前label
levelLabel:'#levelLabel',
//固定路径
inputPath:'#inputPath',
//更改关卡
changeLevel:'#changeLevel',
//删除按钮
deleteBtn:'#deleteBtn',
//刷新按钮
updateBtn:'#updateBtn',
//重置
resetBtn:'#resetBtn'
},
ready () {
Editor.Ipc.sendToMain('level-configuration:panel-load-finish');
this.init();
this.saveBtnClick();
this.setLevelConfigurationDefaultPath();
this.changeLevelEvent();
this.deleteBtnClick();
this.updateBtnClick();
this.resetBtnClick();
},
init(){
this.$label.innerText = '(默认文件路径)';
this.$levelLabel.innerText='(关卡名称)'+levelNameHeader;
this.$changeLevel.value=1;
levelName=levelNameHeader+this.$changeLevel.value;
},
//更改关卡事件
changeLevelEvent(){
this.$changeLevel.addEventListener('change',()=>{
levelName=levelNameHeader+this.$changeLevel.value;
})
},
//重置场景按钮点击
resetBtnClick(){
this.$resetBtn.addEventListener('confirm',()=>{
Editor.Scene.callSceneScript('level-configuration', 'reset-scene', function (err, res) {
});
})
},
//刷新按钮点击事件
updateBtnClick(){
this.$updateBtn.addEventListener('confirm',()=>{
Editor.assetdb.refresh('db://'+defaultLevelPath, function (err, results) {
if(err){
Editor.log("刷新文件目录失败!!!")
return;
}
Editor.log("刷新文件目录成功!!!")
});
})
},
//删除按钮点击事件
deleteBtnClick(){
this.$deleteBtn.addEventListener('confirm',()=>{
Editor.Ipc.sendToMain('level-configuration:delete-click',defaultLevelPath+levelName);
})
},
//保存按钮点击事件
saveBtnClick(){
this.$btn.addEventListener('confirm', () => {
Editor.Ipc.sendToMain('level-configuration:save-click',defaultLevelPath+levelName);
});
},
//设置默认的配置路径
setLevelConfigurationDefaultPath(){
this.$inputPath.addEventListener('confirm',()=>{
defaultLevelPath=this.$inputPath.value;
Editor.Ipc.sendToMain('level-configuration:set-path',defaultLevelPath);
})
},
messages : {
'setDefaultPath':function (event,...agrs) {
if(agrs[0] && agrs[0]!=""){
defaultLevelPath=agrs[0];
this.$inputPath.value=agrs[0];
}
if (event.reply) {
//if no error, the first argument should be null
event.reply(null, 'Fine, thank you!');
}
}
}
});
然后看下scene-obtain.js里的内容吧,想保存什么信息可以自己对应修改setObjInfo这个方法里的内容
'use strict';
module.exports = {
//获取场景root节点下的配置信息
'get-scene-info': function (event) {
var can=cc.find('Canvas');
var root = cc.find('Canvas/root');
let objArray=[];
if(root){
for(let i=0;i
结尾想测试的话,千万别忘记了在你当前场景下的canvas下新建一个root节点哈,当然如果不想在root下可以自己把代码里的部分关于root部分自行去掉,然后就可以测试了呀,代码里的内容我就不详细说了,大概逻辑如开头所说,其实无非就是写各自的部分,操作场景的部分,面板的部分,操作资源文件的部分,然后把各个部分通过进程通信连接到一起。若喜欢的小伙伴或有想更深入研究的话,最后还是老样子,文档贴在下面,文档是个学习好东西啊,多看https://docs.cocos.com/creator/manual/zh/extension/。