在本系列的上一篇文章中,我们介绍了在小游戏中使用 requestAnimationFrame 函数来控制界面更新频率的方法。本文将要使用该技术让游戏背景动起来。
构造可视基类 - Sprite
在多数的GUI系统中都会构造一个可视基类,用来封装可视对象的基本行为和属性。在案例中,构造了一个 Sprite 作为可视基类。
在 js 目录下新建名为 base 的目录,在 base 目录中新建一个名为: sprite.js 的文件。 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| export default class Sprite {
constructor(imgSrc = '', width = 0, height = 0, x = 0, y = 0) { this.img = new Image() this.img.src = imgSrc
this.width = width this.height = height
this.x = x this.y = y
this.visible = true }
}
|
可以看到,我们使用了 ES6 的 class 关键字来构建了一个名为: Sprite 的类,因为在游戏中,每个可视化的对象都有一张对应的图片,都具有一定的大小(width, height)和位置(x, y), 因此我们将这些公共的属性都定义在 Sprite 类中。
构造背景图类
在 js/runtime 目录中新建一个名为: background.js 的文件,用来保存背景图像类。
定义上背景图类: BackGround,我们让背景图类继承于 Sprite, 对应的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const BG_IMG_SRC = 'images/bg.jpg' const BG_WIDTH = 512 const BG_HEIGHT = 512
export default class BackGround extends Sprite {
constructor(ctx) { super(BG_IMG_SRC, BG_WIDTH, BG_HEIGHT)
this.top = 0
.... } }
|
在 BackGround 中增加了一个 top 属性,用来记录显示时的起始纵坐标, 并将用于画图的 Context 对象作为参数传给 BackGround。
按照 上一篇文章 中介绍的框架,我们将属性的更新和对象的显示分别封装在 update() 和 render 方法中,因此在 BackGround 中实现这两个方法如下:
1 2 3 4 5 6 7
| update() { this.top += 2
if (this.top >= screenHeight) { this.top = 0 } }
|
update 方法用来在刷新周期中计算新的 y 坐标,以是的背景有不断向前移动的效果。
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
| render(ctx) {
ctx.drawImage( this.img, 0, 0, this.width, this.height, 0, -screenHeight + this.top, screenWidth, screenHeight )
ctx.drawImage( this.img, 0, 0, this.width, this.height, 0, this.top, screenWidth, screenHeight ) }
|
在 render 方法中,连续画了两次背景图,但 y 坐标不同,主要时实现背景无限滚动向前的效果。
完整的 background.js 代码如下:
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 39 40 41 42 43 44 45 46 47 48 49
| import Sprite from '../base/sprite.js'
const screenWidth = window.innerWidth const screenHeight = window.innerHeight
const BG_IMG_SRC = 'images/bg.jpg' const BG_WIDTH = 512 const BG_HEIGHT = 512
export default class BackGround extends Sprite {
constructor(ctx) { super(BG_IMG_SRC, BG_WIDTH, BG_HEIGHT)
this.top = 0
this.render(ctx) }
update() { this.top += 2
if (this.top >= screenHeight) { this.top = 0 } }
render(ctx) {
ctx.drawImage( this.img, 0, 0, this.width, this.height, 0, -screenHeight + this.top, screenWidth, screenHeight )
ctx.drawImage( this.img, 0, 0, this.width, this.height,
|
让背景动起来
为了让背景动起来,我们将写好的 BackGround 类加入到主程序 main.js 中。
首先,在 Main 中增加一个属性:
1
| this.bg = new BackGround(ctx)
|
然后在 update 方法中调用背景对象的 update 方法类更新背景属性:
再在 render 方法中画背景:
现在,保存程序就可以看到一个不断滚动的背景了。
完整的 main.js 程序如下:
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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| import BackGround from './runtime/background'
let ctx = canvas.getContext('2d')
export default class Main { constructor() { this.aniId = 0
this.bg = new BackGround(ctx)
this.bindLoop = this.loop.bind(this)
this.restart() }
restart() { window.cancelAnimationFrame(this.aniId);
this.aniId = window.requestAnimationFrame( this.bindLoop, canvas ) }
render() { ctx.clearRect(0, 0, canvas.width, canvas.height)
this.bg.render(ctx) }
update() {
this.bg.update() }
loop() {
this.update() this.render()
this.aniId = window.requestAnimationFrame( this.bindLoop, canvas )
} }
|
思考题
现在看起来背景滚动的速度很快,如果要想让背景滚动的速度降到现在的 1/3 或 1/2, 要改程序中的什么地方呢? :)