CocosCreator编辑器扩展4-自己实现一个简单的小插件

由于前段时间比较忙所以拖更了,这里也十分抱歉,加上我这个人平时不经常写博客,所以会慢一点,以后这种连续的博客我不会拖更了。
那么进入正题,今天我们实现一个简单的小插件,其实呢这个小插件也没什么卵用,只是可以给更多需要的人提供一个思路,同样对我来说也是一个笔记,如果各位朋友感兴趣的可以继续往下看。
首先为什么要写编辑器呢?我个人的理解不过于就是方便我们在平时开发,写一些和引擎交互的东西,可以实现一键搞定重复的操作,比如说你要对一个文件夹下的所有预制体都添加一个脚本,那么我们一个一个添加也可以,但肯定会对于这个重复的操作感到无聊同样也会浪费开发时间,要是一键就能搞定,那会多么方便是吧,但是转念一想可能写的时间还不如自己一个一个拖拽了,所以这个问题的取舍当然也看项目大小,进度等等情况来看,找到一个合适的解决办法才是最好的解决办法。然后开始我们今天的这个小插件。
首先说下这个写这个插件的意义,也就是需求,假设需求是这样的:我想弄一个简单的比如关卡的存储管理,把场景下的物体在我搭完之后,一键可以把场景的里的物体的位置保存到一个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: `
    <h2 style="text-align:center">关卡配置表生成器</h2>
    <hr />
         <div>
              FilePath: <span id="label"></span>    
              <ui-input value="path" id="inputPath"}></ui-input>
         </div>
    <hr />
         <div>
              LevelName: <span id="levelLabel"></span>
              <ui-num-input style="width: 130px;" step=1 min=1 id="changeLevel"></ui-num-input>
         </div>
    <hr />
         <div style="text-align:right">
             <ui-button id="btn" class="green">生成配置表</ui-button>
             <ui-button id="deleteBtn" class="red">删除配置表</ui-button>
             <ui-button id="updateBtn" class="blue">刷新资源</ui-button>
             <ui-button id="resetBtn">重置场景</ui-button>
         </div>      
  `,

    $: {
        //保存按钮
        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<parseInt(root.children.length) ;i++){
                let singleInfo= this.setObjInfo(root.children[i],i,can);
                objArray.push(singleInfo);
            }
        }else {
            Editor.log("没有root节点");
        }
        let json=JSON.stringify(objArray);
        if (event.reply) {
            event.reply(null, json);
        }
    },
    //重置场景
    'reset-scene':function (event) {
        var root = cc.find('Canvas/root');
        if(root){
            for(let i=0;i<root.children.length;i++){
                root.children[i].destroy();
            }
        }
        if (event.reply) {
            event.reply(null, "");
        }
    },
    //设置每个json信息
    setObjInfo(gameObject,tag,can){
        let name="obj"+tag;
        let posX=gameObject.position.x;
        let posY=gameObject.position.y;
        let scaleX=gameObject.scaleX;
        let scaleY=gameObject.scaleY;
        let rotation=gameObject.rotation;
        let sprite=gameObject.getComponent(cc.Sprite);
        let imgName="";
        if(sprite && sprite.spriteFrame){
            imgName=sprite.spriteFrame.name;
        }
        var objInfo={
            name:name,
            posX:posX,
            posY:posY,
            scaleX:scaleX,
            scaleY:scaleY,
            rotation:rotation,
            canvas_w:can.width,
            canvas_h:can.height,
            imgName:imgName
        }
        return objInfo;
    }

};

结尾想测试的话,千万别忘记了在你当前场景下的canvas下新建一个root节点哈,当然如果不想在root下可以自己把代码里的部分关于root部分自行去掉,然后就可以测试了呀,代码里的内容我就不详细说了,大概逻辑如开头所说,其实无非就是写各自的部分,操作场景的部分,面板的部分,操作资源文件的部分,然后把各个部分通过进程通信连接到一起。若喜欢的小伙伴或有想更深入研究的话,最后还是老样子,文档贴在下面,文档是个学习好东西啊,多看https://docs.cocos.com/creator/manual/zh/extension/。

Cocos Creator编辑器提供了一套强大的功能,使开发者可以轻松地编写工具来增强开发流程。以下是Cocos Creator编辑器如何编写工具的步骤: 1.熟悉Cocos Creator编辑器:首先,开发者需要熟悉Cocos Creator编辑器的基本操作和功能。这包括了场景编辑、节点操作、组件配置等。 2.了解Cocos Creator插件机制:Cocos Creator提供了插件机制,开发者可以通过插件扩展编辑器的功能。开发者可以在官方文档中找到关于插件开发的详细说明。 3.确定工具的需求:开发者需要明确工具的需求和目标。这可以是一项新功能、自动化操作、场景批量处理等。确保明确工具的功能和预期效果。 4.开发插件:根据工具的需求,开发者可以通过插件机制来实现工具。插件可以包括自定义的编辑器窗口、面板、节点操作脚本等。 5.使用API扩展功能:Cocos Creator提供了丰富的API来扩展编辑器的功能。开发者可以使用这些API来实现工具的核心功能。 6.测试和调试:在编写工具过程中,开发者需要进行反复的测试和调试以确保工具的正确性和可靠性。这可以包括单元测试、集成测试和用户界面测试等。 7.发布和分享工具:完成工具开发后,开发者可以考虑将其发布和分享。开发者可以将工具打包成插件,供其他开发者使用和安装。 总之,使用Cocos Creator编辑器编写工具可以有效地提高开发效率和质量。通过插件机制和丰富的API,开发者可以根据自己的需求自定义和扩展编辑器的功能。希望这些步骤能对您编写Cocos Creator编辑器工具有所帮助。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值