CreateJS 新司机开车指南

avatar
公众号 @fliggyfe
原文链接: zhuanlan.zhihu.com

TL,TR

最近在对前端互动游戏技术一些学习和探索,恰逢上一个小游戏使用了 CreateJS 技术,借此文对其使用做一次备忘,同时让想用 CreateJS 写小游戏的同学可以快速上手。

笔记基于最新1.0版本,有不少地方和网上老教程不一致,同时也建使用最新版本,主要内容如下:

概况

简介

CreateJS是基于HTML5开发的一套模块化的库和工具,基于这些库,可以非常快捷地开发出基于HTML5的游戏、动画和交互应用。

历史可以追溯到7年前 flash 使用慢慢变少那个时代,gskinner 开发它作为“下一代”的富交互的工具库,Adobbe、微软、火狐官方资助的一个项目,API 在很多地方和 Flash 是很像,同时可以通过Adobe Animate CC(替代 flash)直接导出Canvas在Web中使用。

在 18年 终于发布了 1.0 版本,同时官方正在开发 2.0 版本,相信不就会有一个更现代化的 Createjs 呈现给开发者。

模块组成

  • CreateJS 提供的 4 个模块可以帮助处理游戏开发中常见操作:
    • EaselJS:用于位图、图形、Sprites、文本的绘制,还包含 Ticker 定时器
    • TweenJS:用来创建补间动画效果
    • PreloadJS:游戏资源的预加载和管理,包括图片、音频、json等资源
    • SoundJS:播放和处理音频

更好使用

目前官方版本的 createjs 不支持通过 npm 方式的使用,导致在 ES6 开发中,需要在 html 中手动引入一个 <script> 标签才可以使用,对模块化开发不方便。

createjs-npm 通过 imports-loader、exports-loader 对官方库加了一层胶水,让大家可以向使用 npm 包方式一样使用,具体为:

// 安装后和官方 createjs 1.0 使用一致
npm install createjs-npm -S

// 引入全部模块
import createjs from 'createjs-npm';

// 只引入单个模块(xx 可以是 easel、preload、tween、sound)
import createjs from 'createjs-npm/lib/xx';

EaselJS 的使用

EaselJS 在 Createjs 中承担 的能力,比如说你要画图片、画图形、画帧动画、画文字可以使用它的 API 实现。

步骤

比如说你需要在画一个红色的圆,必含步骤为创建舞台->创建对象->设置对象属性->添加对象到舞台->更新舞台呈现下一帧

//创建一个舞台,得到一个参考的画布
  const stage = new createjs.Stage("myCanvas");
  //创建一个形状的显示对象
  const circle = new createjs.Shape();
  circle.graphics.beginFill("red").drawCircle(0, 0, 40);
  //形状实例的设置位置
  circle.x = circle.y = 50;
  //添加形状实例到舞台显示列表
  stage.addChild(circle);
  //更新舞台将呈现下一帧
  stage.update();

上述代码的效果为:

Graphics 类

在上面Demo中,我们创建了一个 Shape,这里其实是创建了一块画布,同时假如需要在画布上面画东西,就需要使用Graphics类了。

可以在 Graphics 上面设置很多样式,或者调用绘图方法来画其他的图形,同时Graphics类里的所有绘图方法都会返回一个graphics实例,这里使用链式操作会很方便,使用具体可见【EaselJS API】

EaselJS 元素

上面说过,EaselJS 可以用来画图片、图形、动画帧、文字这类型的元素,对应是 Bitmap、Shape、Sprite、Text 这几个。具体使用为:

// 使用 Bitmap 来表示图片
const bitmap = new createjs.Bitmap("imagePath.jpg");

// 使用 Shape 结合 Graphics 来表示图形
 const shape = new createjs.Shape();
 shape.graphics.beginFill("#ff0000").drawRect(0, 0, 100, 100);

// 使用 Sprite 来表示精灵动画(后面详细介绍)
const instance = new createjs.Sprite(spriteSheet);
instance.gotoAndStop("frameName"); 

// 使用 Text 来表示文字
const text = new createjs.Text("How are you", "20px Arial", "#000000");

精灵动画

这个特性会让第一次接触的同学高兴起来,很方便使用,同时实现效果很棒,比如说需要实现一个绅士走路的样子:

【详细实现可见 codepen】,其中 spriteSheet 逻辑如下:

const spriteSheet = new createjs.SpriteSheet({
    images: ["https://img.alicdn.com/tfs/TB1vMy8EeuSBuNjy1XcXXcYjFXa-2048-512.png"],
    frames: {
        height: 256,
        width: 128,
        regX: 0,
        regY: 0,
        count: 26
    },
    animations: {
        walk: [0, 25],
    },
});
const sprite = new createjs.Sprite(spriteSheet);
sprite.gotoAndPlay("walk");

【技巧】此处雪碧图的生成可以借助 TexturePacker,其中有一个导出为 EaselJS 的选项,将值直接替换 SpriteSheet即可。

上述代码应该能够很好的表达意思,可以试试animations中多设置几个动作,让其更有趣,更详细的使用可以参考 SpriteSheet Class

Ticker 刷新

主要作用

上述人物的走动,就是靠 Ticker 来实时刷新舞台的,同时可以通过addEventListener来监听tick的刷新,并在里面”做些事情”。

createjs.Ticker.framerate = 60;
createjs.Ticker.timingMode = createjs.Ticker.RAF_SYNCHED;
sprite.addEventListener("tick", ()=>{console.log("doing something"});

关注点

createjs.Ticker.framerate 表示 Ticker 帧频率,理想下是60FPS,这里在游戏体验优化时候可以设置到60。

createjs.Ticker.timingMode 表示 Ticker的渲染模式(setTimeout、requestAnimationFrame),可以这样选择:

  • TIMEOUT:默认模式,这种模式通过setTimeout方式来更新下一次,虽然提供了可预期的、灵活的帧率,但是在性能体验上比requestAnimationFrame方式要差很多,不建议使用
  • RAF:只单纯使用RAF,会将 framerate 设置的帧速率等值忽略,频率取决于当时运行环境和浏览器性能
  • RAF_SYNCHED:它会尝试去协调RAF和设置的framerate频率,相当于结合了setTimeout 和 RAF 的优点,也即这里可以设置framerate=60,优化让CreateJS 帮忙处理,建议使用这个模式

优化点

同时这里可以和Ticker.paused结合起来做性能优化,比如说我们在游戏暂停时候,可以这样防止不停刷新,类似这样:

createjs.Ticker.addEventListener('tick', (e) => {
      !e.paused && stage.update();
});
createjs.Ticker.paused = true;  //在任何地方调用这个,则会暂停tick里面的处理
createjs.Ticker.paused = false;  //在任何地方调用这个,则会恢复tick里面的处理

在性能调优过程中,可以通过 createjs.Ticker.getMeasuredFPS() 将实时的帧率输出到屏幕,当这个值和设置的 framerate 很接近,说明性能良好

TweenJS 的使用

从上面可以看到,EaselJS 其实可以处理 CreateJS 开发中很多的事情,其他三项可以当做辅助工具,让开发更便捷。

TweenJS 在这里充当着“动”的能力,同时它也可以单独使用,支持数字对象属性和CSS样式属性,是一个很强大的补间动画功能库。

案例

比如说以下这个很经典的动画例子:

详细实现代码可见 【动画 codepen】,其中 Tween 的逻辑为:

createjs.Tween.get(circle, { loop: true})
    .wait(300)
    .to({ x: canvas.width - circle.r*2},speed,createjs.Ease.circOut)
    .to({ y: canvas.height - circle.r*2},speed,createjs.Ease.circOut)
    .to({ x: 0},speed,createjs.Ease.circOut)
    .to({ y: 0},speed,createjs.Ease.circOut)
    .call(()=>{console.log('handleComplete')});

使用

可以看到链式处理让很多操作使用是很简单的,也即可以获取目标元素、设置等待时间、执行下一步动画、动画结束事件;

一般场景用上面完全够用,还有如下高级特性,可以通过【文档】来查询:

  • createjs.Ease:设置动画缓动效果,包含众多的动画Ease效果可用,如linear/bounceIn/circInOut/getBackIn/quadInOut
  • ColorPlugin:色值处理插件,可以处理任何 CSS 可支持的颜色
  • CSSPlugin:使用前需要createjs.CSSPlugin.install();注册,可以处理几大部分 CSS 中的样式
  • RotationPlugin:通过RotationPlugin.install(props);,用在处理角度问题
  • MotionGuidePlugin:引导动画效果,使用前需要createjs.MotionGuidePlugin.install(createjs.Tween);,通过设置一系列path来实现路径动画效果
  • Timeline: 用来处理多组动画效果,让他们可以一组一组地被控制

PreloadJS 的使用

PreloadJS是一个用来管理和协调相关资源加载的类库,它可以方便的帮助你预先加载相关资源。

它主要是使用 LoadQueue 类来管理加载内容的,在游戏中很常见的场景是预加载图片资源和游戏音乐。

案例

屏幕中可以看到处理后的图片加载出来,同时在播放着背景音乐:

【详细实现可见 codepen】,其中预加载逻辑如下:

const canvas = document.getElementById("canvas");
const stage = new createjs.Stage(canvas);

const queue = new createjs.LoadQueue(false);
queue.installPlugin(createjs.Sound);
createjs.Sound.alternateExtensions = ["mp3"];

queue.loadManifest([
  {
    id: "bgImg",
    src: "//img.alicdn.com/tfs/TB1EENFADtYBeNjy1XdXXXXyVXa-528-691.png"
  },
  {
    id: "bgMusic",
    src: "//gw.alipayobjects.com/os/rmsportal/YwASPsDMOPusAzZVdLuS.mp3"
  }
]);

queue.on("complete", handleComplete, this);

function handleComplete() {
  const bg = new createjs.Bitmap(queue.getResult("bgImg"));
  stage.addChild(bg);
  stage.update();
  createjs.Sound.play("bgMusic");
}

使用

加载方式

const queue = new createjs.LoadQueue(false);

里面传入布尔值用来表明是用XHR还是用HTML标签来加载,默认为true,就是使用 XHR 来加载,但是XHR 加载 cdn 常常会报一些错误,也即推荐使用false类型,通过标签来加载。

资源加载和使用

通过 loadManifest 类可以加载一组游戏中用到的所有资源,包括 图片、音频、json、svg 等文件,id 用来加载完使用的标识,同时通过queue.getResult(id)获取。

资源加载过程中,有complete、error、progress、fileload供订阅,完成后才可以对资源进行使用,progress和complete结合来显示进度条,fileload表示当个文件加载完毕

加载音乐

加载音乐时候需要注册 Sound 插件,也即queue.installPlugin(createjs.Sound);,同时指明音乐文件格式,比如说createjs.Sound.alternateExtensions = ["mp3"];

这里还有一个小坑,对于低版本的浏览器,可能会出现音乐不能播放情况,可以引入 HTMLAudioPlugin 解决:createjs.Sound.registerPlugins([createjs.HTMLAudioPlugin])

对于音乐播放,使用createjs.Sound.play(id);即可,详细使用后面 SoundJS 介绍。

SoundJS 的使用

一个小游戏要想互动性比较好,背景音乐是必不可少少的,可以选择一些轻松活泼的,需要使用SoundJS来“放音乐”,同时具备完善的兼容处理。

Sound 类

音频管理主要使用 Sound 类,有如下功能:

  • Sound.registerSound:注册播放的音频,registerSounds 支持多个音频,同时在音频播放前的需要提前注册(此处的加载其实也可用preload弄)
  • Sound.registerPlugins:注册音频播放插件,一般用于不兼容的低浏览器场景,可以注册来兼容 WebAudioPlugin 或者 HTMLAudioPlugin
  • Sound.play: 播放音频,一般是createjs.Sound.play(id)

AbstractSoundInstance 播放实例类

当我们调用 Sound.play API时候其实就创建了一个AbstractSoundInstance 类,创建好后,我们可以通过它来直接控制音频的播放或者其他操作,同时可以监听其播放状态:

const myInstance = createjs.Sound.play("myAssetPath/mySrcFile.mp3");

// 属性控制,实际中选择需要的属性设置就好
// 属性同时以对象方式可以加到play第二个参数
myInstance.volume = 0.5;
myInstance.paused = false; // 控制音乐暂停否
myInstance.loop = true;  // 循环播放
myInstance.duration = 30;  //播放时长

// 监听状态,常用于错误时候,重新播放
myInstance.on("complete", handleComplete);
myInstance.on("loop", handleLoop);
myInstance.on("failed", handleFailed);

性能体验优化

图片优化

  • 图片大小和正常 H5 相比更加影响性能,尽量使用合适尺寸和通过 tinypng 压缩后的图片
  • 对于精灵图或者很多小图场景,可用 TexturePacker工具将他们合成一张雪碧图
  • 对于帧动画的场景,可用gka 工具对图片进行压缩和直接生成动画文件

善用回收机制

  • 对于屏幕外或者不在舞台里面的需及时销毁
  • 对于复杂的对象使用cache来增加性能,简单的尽量就不要用了,会有反作用
  • SpriteSheetBuilder 的方式可以代替 cache,而且效果更好

多用位图少用矢量

  • 在 CreateJS 中矢量运算比较影响性能,奇怪是 Text shape都算矢量,建议此处直接将字和矢量图直接通过工具转成位图
  • 如果某些项目必须用矢量,滤镜,叠加模式,或者含有非常多子元件,可以通过使用SpriteSheetBuilder进行位图渲染,从而优化
  • animateCC 中的遮罩相当于把整个动画序列帧化,矢量化,所以也尽量少用,如果实在要用,操作方法跟处理上面矢量一样

滤镜和动画场景优化

  • 不要使用滤镜特效比如(阴影滤镜和发光滤镜)来做动画,因为这样会非常耗费性能,在移动端上性能不可控
  • 可以使用逐帧图片来代替相关滤镜特效来实现动画效果

减少嵌套和叠加

  • 在做动画或者其他场景时候,尽量不要有太多容器的嵌套,尽量打平层级(有点点像写 Weex,讲究平和薄)
  • 有些叠加的场景,可以直接用图片代替

帧率优化

  • 不用的帧率监听就及时取消,比如说 tick
  • 使用 createjs.Ticker.timingMode = createjs.Ticker.RAF_SYNCHED
  • createjs.Ticker.framerate 设置比预想高一点
  • 通过createjs.Ticker.getMeasuredFPS() 输出实时FPS到屏幕,进行优化

试试 StageGL 神器

  • StageGL 在目前压缩版本没有引入,需要额外引入
  • 使用起来和 Stage 差不多,相当于将原有的 Stage 改成 StageGL,但是相当于使用WebGl能力,性能会比原有的好太多,更多详细可见GETTING STARTED WITH STAGEGL
 // 常规的2D场景
  const stage = new createjs.Stage("canvasId");
// 使用 WebGl
  const stage = new createjs.StageGL("canvasId");

移动端点击事件优化

在我们游戏初始化时候,一般需要为之绑定点击事件,但是对于移动端来说加上这一句效果会好很多

var stage = new createjs.Stage("canvasId");
createjs.Touch.enable(stage);

结束

CreateJS 的特性应该不止上述这些,更多的需要在实际项目中使用,通过各种游戏场景可以发掘很多有趣的使用,假如你也对 H5 互动小游戏开发有见解,欢迎一起交流。