Revan1i

vuePress-theme-reco revan1i    2019 - 2020
Revan1i Revan1i

Choose mode

  • dark
  • auto
  • light
Home
TimeLine
Category
  • javascript
  • css
  • react
  • js
  • how
  • math
  • regexp
  • algorithm
  • feeling
Tag
Contact
  • GitHub
author-avatar

revan1i

25

文章

20

标签

Home
TimeLine
Category
  • javascript
  • css
  • react
  • js
  • how
  • math
  • regexp
  • algorithm
  • feeling
Tag
Contact
  • GitHub

以特步小游戏为例入门CreateJS

vuePress-theme-reco revan1i    2019 - 2020

以特步小游戏为例入门CreateJS


revan1i 2019-10-15 12:14:41 js createjs

# 以特步小游戏为例入门CreateJS

# 目的

复盘特步小游戏,分享这个小游戏的实现思路以及介绍CreateJS的基本用法。

# CreateJS 介绍

# 特步小游戏设计稿以及最终效果

设计稿

# CreateJS简介

CreateJS 是基于 HTML5 开发的用于实现 HTML5游戏、动画和交互应用的模块化库和工具。包含以下四个部分:

  • EaselJS: 用于 Sprites、动画、向量和位图的绘制。简化h5 canvas的操作(核心部分)。
  • TweenJS: 动画效果引擎。
  • SoundJS: 音频播放引擎。
  • PreloadJS: 预加载资源:图像、视频、声音、JS、或其他数据(SVG、JSON、JSONP、XML)。

这四个部分都可以单独引用,可选

# PreloadJS

PreloadJS实现预加载图片,并显示进度

let assetsPath = '//' + document.domain + '/activity/xtepgame/dist/assets/img/texture/'
// preferXHR | basePath | crossOrigin(Anonymous或者'*'表示允许跨域, 不需要xhr请求图片)
queue = new createjs.LoadQueue(false, assetsPath, 'Anonymous')
    
var manifest = [
  { src: 'new-bg.png', id: 'background' },
  // ...
]
queue.loadManifest(manifest)
queue.on('progress', () => {
  this.percent = Math.ceil(100 * queue.progress)
})
queue.on('complete', this.handleComplete, this)
1
2
3
4
5
6
7
8
9
10
11
12
13

Image跨域小坑: canvas 渲染不同源的图片会存在跨域问题,虽然有时候可以将跨域的图片成功渲染出来,但是这样会“污染” canvas,污染后就不能从画布提取数据,也就是不能 再调用 toDataURL() 和 getImageData() 方法,会抛出安全错误(security error)的提示。

解决图片跨域的解决方法:

  • 图片所在的服务器设置 Access-Control-Allow-Origin "*" 响应头。
  • nginx代理。
  • 手动将 IMG 的 url 转换成当前域名的 url。 Blob, 详细处理方法 , createjs imageLoader.js

# EaselJS(核心)

EaseJS, 增强了对 canvas 的操作。

# Stage

选取 HTML 中的一个 canvas 标签来创建一个 Stage,createJS中用到的所有元素都是添加到这个 Stage 当中,添加后通过 stage.update() 的方法即可将添加的元素显示在页面上。

<body>
  <canvas id="game" width="750" height="1624"></canvas>
</body>

<script>
  var stage = new createjs.Stage('game');
  // 添加元素...
  stage.update();
</script>
1
2
3
4
5
6
7
8
9

# 添加文字

// 显示的字符,字体及大小,颜色
var txt = new createjs.Text('HELLO', '20px Times', '#000');
// 创建完后可以随意改变坐标、对齐等其他属性
txt.x = 100
txt.y = 100
txt.text = 'hello world'
// 添加到stage才能正常显示
stage.addChild(txt)
1
2
3
4
5
6
7
8

# 添加图形

// 创建一个Graphics, 可以理解为有绘制能力的画笔
var g = new createjs.Graphics()
g.setStrokeStyle(1);
g.beginStroke('#000000');
g.beginFill('red');
// x, y, radius
g.drawCircle(0, 0, 30);
var shape = new createjs.Shape(g)
stage.addChild(shape)

// 第二种方式
var shape = new createjs.Shape()
shape.graphics.beginStroke("#000").beginFill("#ff0000").drawRect(0, 0, 100, 100);
stage.addChild(shape)
1
2
3
4
5
6
7
8
9
10
11
12
13
14

类似于改变坐标,增加阴影 Shadow, 透明度 Alpha, 缩小放大 ScaleX / ScaleY 都可以用 Shape 做到。

# 图片Bitmap

var bg = new createjs.Bitmap('./background.png');
stage.addChild(bg)
stage.update()
1
2
3

CreateJS 提供了几种处理图片的方式

  • 给图片增加遮罩层
    使用 mask 属性,可以只显示图片和 shape 相交的区域

    stage = new createjs.Stage('./background.png');
    bg = new createjs.Bitmap('./example.png');
    //遮罩图形
    shape = new createjs.Shape();
    shape.graphics.beginFill("#000").drawCircle(0, 0, 100);
    bg.mask = shape;     //给图片bg添加遮罩
    stage.addChild(shape);
    stage.addChild(bg);
    stage.update();
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    适用场景:裁剪图片,比如显示圆形的图片。

  • 给图片增加滤镜效果

    var blur = new createjs.BlurFilter(5,5,1);
    bg.filters = [blur];
    // 添加 filter 后刷新 Stage, filter 只能保持一帧的效果,第二帧 filter 则失效,
    // 因此需要使用图片 cache() 方法后,使得 stage 刷新后仍然能保持住 filter 效果
    bg.cache(0,0,bg.image.width,bg.image.height);
    
    1
    2
    3
    4
    5

    适用场景:图片处理。

  • 使用 Rectangle 剪裁图片
    使用 EaseJS 内置的 Rectangle 对象来创建一个选取框, 显示图片的某个部分。

    bg = new createjs.Bitmap('./example.png');
    var rect = new createjs.Rectangle(0, 0, 121, 171);
    bg.sourceRect = rect;
    
    1
    2
    3

    适用场景:拼图小游戏,裁剪图片。

# Sprite (精灵)以及 TweenJS 制作动画

先跳过,后面结合实际开发来介绍

# 容器Container

Container 容器可以包含Text 、Bitmap、Shape、Sprite等 EaselJS 元素在同一个组中,方便统一管理。
比如一个跑动的人由人物,气泡提示组成,可以把这几个部分放到同一个 Container 中,统一移动。

# 交互

想要与 stage 中的所有元素交互只需要添加事件监听即可,例如 addEventListener('click')、 addEventListener('mouseover')等。对于 stage 来说,有一个特别的事件 tick,类似于 setInterval 定时的运行一个函数,可以定时刷新 Stage 。

var stage;
function init() {
  stage = new createjs.Stage('game');
  stage.enableMouseOver();
  // 

  // 理想
  createjs.Ticker.setFPS(60);
  createjs.Ticker.addEventListener('tick', update);
}

function update(event) {
  stage.update()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

CreateJs 提供了两种渲染模式,一种是用 setTimeout ,一种是用 requestAnimationFrame,默认是 setTimeout ,默认的帧数是 20 ,一般的话还没有什么区别,但是如果动画多的话,设置成 requestAnimationFrame 模式的话,就会感觉到动画比较丝滑。

createjs.Ticker.timingMode = createjs.Ticker.RAF;
1

使用这种模式,Ticker 的帧频 FPS 就会忽略。

# 跑起来

# 简单的画板

通过Text、Shape、Container、addEventListener等基本内容,就可以实现一些简单的工具和小游戏了。

# 加速状态的汽车

通过移动背景和实时刷细腻,实现一个无缝连接的背景图,模拟汽车加速状态。

this.backdrop = new createjs.Bitmap(bg);
this.backdrop.x = 0;
this.backdrop.y = 0;
this.stage.addChild(that.backdrop);
this.w = bg.width;
this.h = bg.height;

//创建一个背景副本,无缝连接
var copyy = -bg.height;
this.copy = new createjs.Bitmap(bg);
this.copy.x = 0;
this.copy.y = copyy;  //在画布上 y 轴的坐标为负的背景图长
//使用 CreateJS 的 Tick 函数,逐帧刷新舞台
createjs.Ticker.addEventListener("tick", tick);
function tick(e) {
  if (e.paused !== 1) {
    //舞台逐帧逻辑处理函数
    that.backdrop.y = that.speed + that.backdrop.y;
    that.copy.y = that.speed + that.copy.y;

    if (that.copy.y > -40) {
      that.backdrop.y = that.copy.y + copyy;
    }
    if (that.copy.y > -copyy - 100) {
      that.copy.y = copyy + that.backdrop.y;
    }
  }
  that.stage.update(e);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

加速状态的汽车

# 拼图小游戏

通过对图片进行裁剪,addEventListener, 添加滤镜等方式,就可以实现一款简单的拼图小游戏。

# 小游戏思路以及关键步骤分步实现

# 思路

思路

# 步骤

# 搭建跑道场景,做好机型适配

原理:整个游戏都在一个 canvas 上渲染,canvas 可以当做是一个内容会变化的图片,既然是图片,那么就可以借鉴 background-size: cover / contain 的思路进行适配。

  • cover模式: 保持 canvas 比例,缩放 canvas 覆盖 viewport, 不在 viewport 范围的 canvas 隐藏或者裁剪。
  • contain模式: 保持 canvas 比例,缩放 canvas 使其完全显示在 viewport 内,viewport 范围内可能会出现空白区域
// 按照设计图,设置绘图内容大小 750 * 1624
// 显示大小  375 * 812
// transform 不压缩比例变换,transform-origin 改变变换的原点
<canvas
  id="stage"
  :style="{width: `${375}px`, height: `${812}px`, transform: `scale(${ratio}, ${ratio})`, 'transform-origin': transformOrigin}"
  width="750" height="1624"
>

// 自适应
buildBackground() {
  let _background = queue.getResult('background')
  
  let backDrop = new createjs.Bitmap(_background)
  let backDropRatio = _background.width / _background.height
  let stageRatio = viewPort.w / viewPort.h

  // 计算原则:把最短边布满
  if (backDropRatio > stageRatio) {
    // fit height and center horizontally
    let fitHeightRatio = viewPort.h * 2 / _background.height
    let offsetLeft = viewPort.w * 2 / 2 - _background.width * fitHeightRatio / 2
    this.offsetLeft = offsetLeft
    this.ratio = fitHeightRatio
    // 默认transform-origin: 50% 50%;
    // 需求:不要遮挡上面的广告logo
    this.transformOrigin = '50% 0'
  } else {
    // fit horizontal and center vertically
    let fitWidthRatio = viewPort.w * 2 / _background.width
    let offsetHeight = (viewPort.h * 2 - _background.height * fitWidthRatio) / 2
    this.offsetHeight = offsetHeight
    this.ratio = fitWidthRatio
    this.transformOrigin = '0 50%'
  }
  stage.addChild(backDrop)
  stage.update()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# 使用连续的 Sprite 图片制作一个跑动的主角

动画的原理是利用人的视觉残留效应,连续播放一系列的静止图片,即可获得一段动画,每张图片都可以称作动画的一帧。在 EaseJS 中,调用 new createjs.Sprite(SpriteSheet) 来创建 Sprite, SpriteSheet 可以理解为一个动画索需要的数据合集,包括动画的原始图像, 每一帧的尺寸数据,以及动画的帧数。在实际的开发中,通常用 TexturePacker 来制作 SpriteSheet ,而且软件还可以导出 createjs 可用格式的 json, 非常方便。导出后的 SpriteSheet 大体是这样的:

SpriteSheet

// 创建雪碧动画
playerWithShoeSs = new createjs.SpriteSheet({
  images: [queue.getResult('players')],
  framerate: 10,
  frames: [
    // x, y, width, height, imageIndex, regX, regY
    [1, 1, 189, 454, 0, 0, 0],
    [192, 1, 193, 449, 0, 0, 0],
    [387, 1, 193, 449, 0, 0, 0],
    [582, 1, 193, 449, 0, 0, 0],
    [777, 1, 193, 449, 0, 0, 0],
    [972, 1, 193, 449, 0, 0, 0],
    [1167, 1, 216, 426, 0, 0, 0],
    [1385, 1, 243, 254, 0, 0, 0]
  ],
  animations: {
    "man-stand": { frames: [0] },
    "run-man": { frames: [1, 2, 3, 4, 5], speed: .1 },
    "man-jump": { frames: [6] },
    "man-fall": { frames: [7] }
  },
}, 'players')

player = new createjs.Sprite(playerWithShoeSs, 'man-stand')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 创建静态游戏界面

创建路边的参照物、路障、开始标志、结束标志、奖牌等静态元素。

# 加入动画

使用 TweenJS 来创建补间动画,简化操作。

// 跳跃
createjs.Tween
  .get(playerContainer, { ignoreGlobalPause: true})
  .to({y: playerContainer.y - jumpHeight}, 300, createjs.Ease.circOut)
  .to({y: playerContainer.y + jumpHeight}, 300, createjs.Ease.circIn)
  .call(() => {
    // 动画结束
    isJumping = false
    canPaused = true
    // 回到初始点
    playerContainer.setTransform(310, stage.canvas.height - playerContainer.getBounds().height - 500)
    player.gotoAndPlay("run-man")
})

// 参照物移动,逐渐放大, 近大远小
leftRoadRef.forEach((item, index) => {
  createjs.Tween
    .get(item)
    .wait(1500 * index)
    .to({x: -400, y: stage.canvas.height, scaleX: 1.4, scaleY: 1.4}, 3000, createjs.Ease.circIn)
})

// ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 更新游戏,检查是否通关或者失败

根据业务需求,游戏时长不超过 30 秒,总里程不超过 100 公里,根据时间换算成里程,进行里程更新

// 根据游戏总时间长度,更新里程
updateMile(e) {
  // 上一次tick到当前tick的ms
  let deltaS = e.delta
  currDuration += deltaS
  // 开头2.4s不计入计算
  if (currDuration >= 2400) {
    let percent = (currDuration - 2400) / gameDuration
    currentMile = (totalMile * percent / 1000).toFixed(2)
    let mileText = mileTextContainer.getChildByName('mile')
    mileText.text = currentMile
    this.finishedMile = currentMile
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 总结

  • 介绍了 CreateJS 核心概念及常用 API
  • 根据常用的 API 实现小 Demo
  • 介绍了特步小游戏的思路以及关键步骤实现