使用Grunt实现资源自动化同步

同步美术、策划资源是日常开发中极为频繁的事情,shawn借用Web前端的一些思想和工具,将Grunt自动化框架引入Cocos Creator项目,可以实现相对高效地将图片、动画、配置、音效等游戏资源导入到客户端工程中。

grunt介绍

在开始之前先用简单介绍一下Grunt是什么:

使用Grunt实现资源自动化同步_第1张图片

为什么选择使用Grunt做自动化工具,简单总结以下几点:

  1. 使用JavaScript语言,与Cocos Creator开发使用相同的语言,减少学习成本

  2. 插件丰富,6000+(本篇文章只介绍两个grunt-sync和grunt-shell)

  3. 使用json配置插件完成任务,没有太多逻辑条件,使用简单容易上手,当配置好一个模块后,其它模块可以依葫芦画瓢,策划人员也可以上手配置

  4. Grunt基于Nodejs,可以借用大量插件与npm模块实现各种复杂需求

  5. 跨平台

安装grunt与插件

首先,使用npm安装全局grunt-cli工具:

>npm install grunt-cli -g

然后在项目根目录初始化npm的包管理文件package.json:

>npm init

输入npm init后一路回车,然后在项目中安装grunt npm模块:

>npm install grunt --save-dev

grunt只是一个自动化框架,我们这里还需要安装上面说的两个插件

>npm install grunt-sync --save-dev   //文件同步插件	
>npm install grunt-shell --save-dev  //shell插件

Grunt任务模块目录结构

安装好Grunt的命令行、插件后,在项目根目录创建Gruntfile.js文件,这是Grunt自动任务的入口文件。同时在根项目新建一个tools目录,用于存放各种与项目有关的工具或脚本,再添加一个grunt-task目录,用于存放具体的grunt任务配置脚本,请看下图:

使用Grunt实现资源自动化同步_第2张图片

上图中xxx-task.js就是各子模块的自动化任务。

Gruntfile

shawn在早期使用Grunt时,将所有任务都编写在Gruntfile.js文件,当模块越来越多,维护起来越来越困难,因此将不同模块的自动化任务独立开来,在Gruntfile.js进行统一加载和任务注册,下面看下Gruntfile文件的内容:

//引入rd模块读取文件	
let rd = require('rd');	
//获./tools/grunt-task目录下取所有文件	
let taskScripts = rd.readFileSync('./tools/grunt-task');	
module.exports = (grunt) => {	
    //我们这里使用了grunt-shel&grunt-sync插件	
    //下面shell与sync对象分别用于收集两种任务配置	
    let shell = {};	
    let sync = {};	
    //将grunt设置为全局变量	
    global.grunt = grunt;	
    //require所有任务模块,放入tasks数组	
    let tasks = [];	
    taskScripts.forEach((script) => {	
        let task = require(script);	
        if (task.init) {	
            //让task对象自己填充sync和shell内容	
            task.init(sync, shell);	
            tasks.push(task);	
        }	
    });	
    //配置sync、shell两大任务	
    grunt.initConfig({	
        sync,	
        shell,	
    });	
    //注册grunt-shell插件,用于执行外部shell命令	
    grunt.loadNpmTasks('grunt-shell');	
    //注册grunt-sync插件,用于本地文件同步	
    grunt.loadNpmTasks('grunt-sync');	
    //注册自定义的grunt任务	
    tasks.forEach(task => task.registerTask());	
};

简单说明一下:

  1. 加载tools/grunt-task下所有任务脚本

  2. 为每个任务对象传入sync、shell两个任务集合对象,各任务模块在sync、shell对象中配置自己的任务内容。

  3. 使用了两个grunt插件:grunt-shell、grunt-sync

  4. 最后调用所有task.registerTask()将命令注册到grunt命令行

在命令控制台上执行grunt --help会看到我们所编写的自动化任务,下图是shawn曾经项目中创建的Grunt任务:

使用Grunt实现资源自动化同步_第3张图片

文件同步任务

我们现在去执行 grunt--help还是空的,还没有注册具体的任务,前面讲过,Grunt是使用插件 + JSON配置的方式来创建任务,我们看一个美术资源为例:

使用Grunt实现资源自动化同步_第4张图片

资源仓库客户端Assets按上图所示建立对应关系,其中绿色箭头是文件同步操作,其中headIcon目录中的图片,在项目中是动态加载的,需要同步到assets/resources/game1/texture目录下,这些操作我们可以使用grunt-sycn来完成。

其中比较特别是atlas目录,里面是经过分类需要合并图集的图片,文件合并后放到客户端项目assets/game1/texture/atlas目录,这个操作shawn是借用TexturePacker命令行工具 + Nodejs脚本来完成。

UI资源同步

梳理好了资源目录结构,现在我们将上流程编写成grunt同步任务,创建一个game1-task.js的文件,内容如下:

let path = require('path');	
//获取grunt参数,是否模拟执行,不真实复制文件	
let pretend = grunt.option('pretend');	
//获取资源根路径,资源路径是定义在单独的define.js文件	
let { UI_ROOT_PATH,ANI_ROOT_PATH } = require('./define');	
//定义模块名,也就是在assets下的目录名	
const moduleName = 'game1';	
//定义同步任务	
const syncTask = {	
    //同步UI任务:普通图片、背景图片	
    'sync-ui': {	
        files: [	
            //同步UI图片	
            {	
                //设置过滤器,排除atlas、headIcon、svn、隐藏文件	
                src: ['**', '!atlas/', '!headIcon/', '!**/.*', '!**/.svn', '!**/.svn/**'],  	
                dest: `./assets/${moduleName}/texture/ui`,  //目标路径,将文件同步到此处	
                cwd: path.join(UI_ROOT_PATH, moduleName, 'ui')  //美术UI资源目录	
            },	
            //同步headIcon	
            {	
                //排除 atlas 目录及下面的子目录、文件	
                src: ['**', '!**/.*', '!**/.svn', '!**/.svn/**'], 	
                //同步到resources/moduleName/texture目录下	
                dest: `./assets/resources/${moduleName}/texture`, 	
                cwd: path.join(UI_ROOT_PATH, moduleName, 'ui', 'headIcon')	
            },	
            //同步背景图片	
            {	
                src: ['**', '!**/.*', '!**/.svn', '!**/.svn/**'],	
                dest: `./assets/${moduleName}/texture/bg`,	
                cwd: path.join(UI_ROOT_PATH, moduleName, 'bg')	
            }	
        ],	
        verbose: true, // 显示日志	
        pretend: false, // 模拟输出	
        updateAndDelete: true, // 删除dst冗余文件	
        compareUsing: 'md5', // 可选'mtime/md5'	
        ignoreInDest: ['**/*.meta', '**/*.pac', '**/.svn/**'],  // 不删除.meta文件	
    },	
}

上面代码中sync-ui就是一个同步任务,其中files数组中配置同步目录,每一个数组元素包含三个字段:

  1. src: 文件过滤器

  2. dest: 目标路径,同步到那里去,以当前Gruntfile文件为相对路径

  3. cwd: 源路径,从那里去复制文件,同样以Gruntfile文件为相对路径

然后是同步选项:

  1. verbose: 打印日志输出,这对我们检查路径是否正确非常有用,建议设置为true

  2. pretend: 同步模拟,当值为真时,配合verbose使用只会显示要同步的文件,不会真实写入或删除文件目标文件

  3. updateAndDelete:删除冗余文件,比如你动随意放入一个图片到客户端ui目录,当执行ui同步资源,这个文件并未在资源仓库中,它会被同步删除掉

  4. compareUsing:文件比较策略,可选项"md5"与“mtime”,建议使用md5保证正确性

  5. ignoreInDest:指定同步时不删除那些文件,这个选项非常有用,我们都知道Cocos Creator会为每个文件生成同名.meta文件,这里一定要注意,不能被同步掉了,除了meta文件外,还有自动图集、svn等文件。下面是执行grunt-shell命令的效果:

⮀ grunt up-hall --pretend	
Running "sync:hall-ui" (sync) task	
Copying ../../../hall/ui/hall_btn_jlb.png -> assets/hall/texture/ui/hall_btn_jlb.png	
Copying ../../../hall/ui/hall_btn_kefu.png -> assets/hall/texture/ui/hall_btn_kefu.png	
Copying ../../../hall/ui/hall_btn_mail.png -> assets/hall/texture/ui/hall_btn_mail.png	
Copying ../../../hall/ui/hall_btn_set.png -> assets/hall/texture/ui/hall_btn_set.png	
Copying ../../../hall/ui/hall_btn_share.png -> assets/hall/texture/ui/hall_btn_share.png	
Copying ../../../hall/ui/hall_Btn_shop.png -> assets/hall/texture/ui/hall_Btn_shop.png	
Copying ../../../hall/ui/hall_btn_yxsj.png -> assets/hall/texture/ui/hall_btn_yxsj.png	
Copying ../../../hall/ui/hall_img_dianchi.png -> assets/hall/texture/ui/hall_img_dianchi.png	
Copying ../../../hall/ui/hall_img_dianliang.png -> assets/hall/texture/ui/hall_img_dianliang.png	
Copying ../../../hall/ui/hall_img_gold.png -> assets/hall/texture/ui/hall_img_gold.png	
Copying ../../../hall/ui/hall_img_head_00.png -> assets/hall/texture/ui/hall_img_head_00.png	
Copying ../../../hall/ui/hall_img_head_01.png -> assets/hall/texture/ui/hall_img_head_01.png	
Copying ../../../hall/ui/hall_img_laba.png -> assets/hall/texture/ui/hall_img_laba.png	
Copying ../../../hall/ui/hall_img_red.png -> assets/hall/texture/ui/hall_img_red.png	
Copying ../../../hall/ui/hall_img_xinhao.png -> assets/hall/texture/ui/hall_img_xinhao.png	
Copying ../../../hall/ui/hall_mask_bottom.png -> assets/hall/texture/ui/hall_mask_bottom.png	
Unlinking assets/resources/hall/texture/headimg/default.png because it was removed from src.	
Unlinking assets/resources/hall/texture/ui/ttz_bg_toast.png because it was removed from src.	
Removing dir assets/resources/hall/texture/ui because not longer in src.

上面可以看到以Copying开头的是文件复制信息,使用verbose参数,它显示了从那儿复制文件那儿,Unlinking是删除文件,同样显示了被删除的文件路径。

动画资源同步

上面讲了UI资源的同步, 对于动画资源我们处理方式有些不同,因此需要单独创建一个同步任务:

const syncTask = {	
    'sync-ui': { ... }	
    'sync-ani': {	
         files: [	
            //同步UI	
            {	
                src: ['**', '**/!.DS_Store', '!**/.svn', '!**/.svn/**', '!**/.gitignore'], //过滤器	
                dest: `./assets/{moduleName}/animations`,	
                cwd: path.join(ANI_ROOT_PATH, moduleName, 'animations')	
            },	
          ],	
          verbose: true, // 显示日志	
          pretend: pretend || false, // 模拟输出	
          updateAndDelete: true, // 删除dst冗余文件	
          compareUsing: 'md5', // 可选'mtime/md5'	
          ignoreInDest: ['**/.svn/**', '.DS_Store', '**/.gitignore'],  // 不删除.svn下文件	
    },    	
    }	
}

动画同步与UI同步最大的差别在于,ignoreInDes同步选项不能忽略meta文件。在shawn的项目中,动画是由美术人员在独立的Cocos Creator工程中编辑的,美术人员可以在动画工程中使用Cocos Creator动画编辑器或Spine、DragonBones等动画资源,使用Prefab进行整合,客户端主要依赖美术提供的动画prefab文件以及动画名字,动画同步任务需要将所有动画资源全部同步到客户端项目中,其中包括所有的meta文件。

这里可能会有一个小小的风险,就是动画工程中的meta文件与客户端界面中的meta文件发生UUID冲突,这种冲突的可能性是完全存在的,但在shawn一年半十多个子模块的动画项目中暂时还未遇到过,冲突的概率非常低。

图集合并同步

在UI目录中有一个类特殊的图片,需要做成图集提高游戏渲染性能,在一个游戏项目初期由于UI风格不稳定或使用临时图片,让美术同学经常去合并图集是一个效率较低的事情。因此shawn将需要合并图集的文件放入atlas的子目录中,由程序调用TexturePacker的命令行工具,以atlas子目录为单位生成图集,直接存入客户端模块atlas目录。

图集合并并完全是动态的,shawn编写了一个Node脚本,用于遍历atlas下的子目录文件,生成图集文件,然后再使用grunt-shell插件进行整合,看下面代码:

//TexturePacker图集合并工具	
let tpimg = require('../tpimage');	
//shell任务	
const shellTask = {	
    //TexturePacker合并图片	
    'tp-img': {	
        command() {	
            //工作路径,资源仓库atlas	
            let cwd = path.join(UI_ROOT_PATH, `${moduleName}/ui/atlas`);	
            //目标路径,图集文件保存到这里	
            let dst = `./assets/${moduleName}/texture/ui/atlas`;	
            //使用编写的Node脚本生成TexturePacker命令数组	
            let commands = tpimg.tpdirs({ cwd, dst });	
            //模拟输出命令	
            if (pretend) {	
                console.log(`cwd:${cwd}, dst:${dst}`);	
                console.log(commands.join('\n'));	
                return '';	
            }	
            //返回命令字符串,由grunt-shell插件执行	
            return commands.join('&&');	
        }	
    },	
}

下面是任务执行时的效果:

使用Grunt实现资源自动化同步_第5张图片两次执行,每一次执行时生成了icon.png、icon.plist,马上再次执行,提示未发生改变没有再重新生成图集,这比我们手动打图或使用Cocos Creator的自动图集效率要高。

资源仓库更新

上面介绍了美术UI、动画、图集等资源的同步,但一个完整的模块资源同步,还需要涉及到对资源仓库的更新,具体操作就是用git或svn将资源仓库更新到最新状态,下面看使用grunt-shell命令更新资源仓库:

//svn更动画资源	
'svn-ui': {	
    command: [	
        `svn up ${UI_ROOT_PATH}/${moduleName}/ui`,	
        `svn up ${UI_ROOT_PATH}/${moduleName}/bg`	
    ].json('&&')    	
},	
//svn更新动画资源	
'svn-ani': {	
    //如果使用nosvn参数,返回空字符串,跳过svn更新	
    command: nosvn ? '' : `svn up ${ANI_ROOT_PATH}/${moduleName}/animation`,	
}

有时会遇到svn暂时不可用,或有同学未安装svn命令行工具,会导致任务失败,可以shawn这里设置了一个nosvn的参数,用于控制是否跳过svn操作,直接返回了一个空字符串。

任务整合

一个子模块完整的资源同步任务大概需要经历下面几个步骤:

使用Grunt实现资源自动化同步_第6张图片

我们前面都建立的单个任务,使用grunt.registerTask可以将任意单个任务进行自由组合,看下图:

使用Grunt实现资源自动化同步_第7张图片

grunt.registerTask的参数包括:任务名、任务说明、子任务列表,子任务列表是一系列的任务或插件任务字符串的组合,上图中up-hall-svn任务,是由三个shell插件任务组成。

此时我们在项目根目录中,执行: >grunt up-hall即可享受到所有美术图片、动画、音效、配置等等资源的同步到我们的客户端hall模块。

我们经常会遇到这样一个场景:

美术同学:“xxx程序我增加了大厅商店道具张图片,你更新一下呢,我想看看效果”。

程序同学:“这几张图需要与策划配置文件配合才能生效,yyy策划你更新下商店配置”。

策划同学:“今天早上一来我就已经更好了,你直接取吧!”。

动画同学:“商店里几个动画特效我也更新了,你也放进去一下吧”。

程序同学打开终端,键入:grunt up-hall,屏幕一阵疯狂输闪... 1、2、3、4、5 .... 五秒过后,激活Cocos Creator窗口编译资源,观察控制台,一切ok!

程序同学:“你们使用我的IP:7456自己去看吧!”

美术、策划一脸惊讶地看着你,效率这么高...

曾经十几分钟都搞不定的事情,现在几秒就解决了,每天做10几遍都不会觉得累!也不需要做10遍,将grunt任务与Cocos Creator插件结合,嵌入到Cocos Creator界面菜单上面,让程序员多休息一会儿吧!

使用Grunt实现资源自动化同步_第8张图片

弓箭大冒险

静待作者分享开发中的心路历程!


使用Grunt实现资源自动化同步_第9张图片

奎特尔数字大冒险,点击进入

0.2新版本

优化手写识别、简单难度、可爱新角色、帅气狼头

当然还有满血复活广告!

欢迎关注「奎特尔星球」公众号,欢迎大家投稿,来我们一起成长!

使用Grunt实现资源自动化同步_第10张图片

「奎特尔星球」微信公众号

使用Grunt实现资源自动化同步_第11张图片

「奎特尔星球」博客网站,建设中...

你可能感兴趣的:(使用Grunt实现资源自动化同步)