# CreateJS 新司机开车指南

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

- 发布日期: 2018-07-28
- Author: Tw93
- HTML: https://tw93.fun/2018-07-28/createjs.html
- English translation: https://tw93.fun/en/2018-07-28/createjs.md
- 全部文章: https://tw93.fun/posts.md

---

<img src="https://gw.alipayobjects.com/zos/rmsportal/egMrezneoXbjpgPnjxNj.jpg" class="img-zoom" loading="lazy" decoding="async"/>

<small style="text-align:center">拍于沙巴红树林萤火虫</small>

## TL,TR

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

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

<img src="https://img.alicdn.com/tfs/TB17tF4EKGSBuNjSspbXXciipXa-2228-1008.png" width="700"/>

## 概况

### 简介

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](https://github.com/tw93/createjs-npm) 通过 imports-loader、exports-loader 对官方库加了一层胶水，让大家可以向使用 npm 包方式一样使用，具体为：

```bash
// 安装后和官方 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 实现。

### 步骤

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

```js
  //创建一个舞台，得到一个参考的画布
  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();
```

上述代码的效果为：

{::nomarkdown}
<p data-height="265" data-theme-id="dark" data-slug-hash="qyVprj" data-default-tab="result" data-user="tw93" data-pen-title="Demo" class="codepen">See the Pen <a href="https://codepen.io/tw93/pen/qyVprj/">Demo</a> by Tw93 (<a href="https://codepen.io/tw93">@tw93</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>
{:/nomarkdown}

### Graphics 类

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

可以在 Graphics 上面设置很多样式，或者调用绘图方法来画其他的图形，同时Graphics类里的所有绘图方法都会返回一个graphics实例，这里使用链式操作会很方便，使用具体可见[【EaselJS API】](https://createjs.com/docs/easeljs/classes/Graphics.html)。

### EaselJS 元素

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

```js
// 使用 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");
```

### 精灵动画

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

{::nomarkdown}
<p data-height="309" data-theme-id="dark" data-slug-hash="yqavrz" data-default-tab="js,result" data-user="tw93" data-pen-title="Sprite" class="codepen">See the Pen <a href="https://codepen.io/tw93/pen/yqavrz/">Sprite</a> by Tw93 (<a href="https://codepen.io/tw93">@tw93</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>
{:/nomarkdown}

【技巧】此处雪碧图的生成可以借助 [TexturePacker](https://www.codeandweb.com/texturepacker)，其中有一个导出为 EaselJS 的选项，将值直接替换 SpriteSheet即可。

上述代码应该能够很好的表达意思，可以试试animations中多设置几个动作，让其更有趣，更详细的使用可以参考 [SpriteSheet Class](https://createjs.com/docs/easeljs/classes/SpriteSheet.html)

### Ticker 刷新

#### 主要作用

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

```js
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`结合起来做性能优化，比如说我们在游戏暂停时候，可以这样防止不停刷新，类似这样：

```js
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样式属性，是一个很强大的补间动画功能库。

### 案例

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

{::nomarkdown}
<p data-height="265" data-theme-id="dark" data-slug-hash="yqYXaL" data-default-tab="js,result" data-user="tw93" data-pen-title="TweenJS" class="codepen">See the Pen <a href="https://codepen.io/tw93/pen/yqYXaL/">TweenJS</a> by Tw93 (<a href="https://codepen.io/tw93">@tw93</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>
{:/nomarkdown}

### 使用

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

一般场景用上面完全够用，还有如下高级特性，可以通过[【文档】](https://www.createjs.com/docs/tweenjs/modules/TweenJS.html)来查询：

- 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 类来管理加载内容的，在游戏中很常见的场景是预加载图片资源和游戏音乐。

### 案例

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

 {::nomarkdown}
<p data-height="265" data-theme-id="dark" data-slug-hash="LBpzKb" data-default-tab="js,result" data-user="tw93" data-pen-title="PreloadJS" data-preview="true" class="codepen">See the Pen <a href="https://codepen.io/tw93/pen/LBpzKb/">PreloadJS</a> by Tw93 (<a href="https://codepen.io/tw93">@tw93</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>
 {:/nomarkdown}

### 使用

#### 加载方式

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 类，创建好后，我们可以通过它来直接控制音频的播放或者其他操作，同时可以监听其播放状态：

```js
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](https://tinypng.com) 压缩后的图片
- 对于精灵图或者很多小图场景，可用 [TexturePacker](https://www.codeandweb.com/texturepacker)工具将他们合成一张雪碧图
- 对于帧动画的场景，可用[gka](https://github.com/gkajs/gka) 工具对图片进行压缩和直接生成动画文件

### 善用回收机制

- 对于屏幕外或者不在舞台里面的需及时销毁
- 对于复杂的对象使用cache来增加性能，简单的尽量就不要用了，会有反作用
- [SpriteSheetBuilder](https://createjs.com/docs/easeljs/classes/SpriteSheetBuilder.html) 的方式可以代替 cache，而且效果更好

### 多用位图少用矢量

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

### 滤镜和动画场景优化

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

### 减少嵌套和叠加

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

### 帧率优化

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

### 试试 StageGL 神器

- [StageGL](https://createjs.com/docs/easeljs/classes/StageGL.html) 在目前压缩版本没有引入，需要额外引入
- 使用起来和 Stage 差不多，相当于将原有的 Stage 改成 StageGL，但是相当于使用WebGl能力，性能会比原有的好太多，更多详细可见[GETTING STARTED WITH STAGEGL](http://blog.createjs.com/getting-started-with-stagegl/)

 ```js
 // 常规的2D场景
  const stage = new createjs.Stage("canvasId");
   
  // 使用 WebGl
  const stage = new createjs.StageGL("canvasId");
 ```

### 移动端点击事件优化

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

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

## 结束

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