0
点赞
收藏
分享

微信扫一扫

微信小游戏开发入门 2/5

第一篇介绍了游戏demo的工程创建,以及main.js主驱动模块的初始化和主Loop循环。我们还是按照调用顺序先后介绍。

今天学习的是main.js中剩下的几个功能,以及Background和Sprite两个类。

Main.js

update函数

我们进入update函数,这里对着代码进行解读,以下不涉及画面显示,仅仅是计算。​update函数是在Loop中被循环调用的,每一帧调用一次。

// 游戏逻辑更新主函数
update() {

if (databus.gameOver) return
// gugw:背景更新,实际上就是计算这一次的背景显示的坐标位置, 因为背景实际上是两张图片上下方式连接而成,不断往下移动,从而游戏上就感觉是飞机往前飞的视觉效果。
this.bg.update()
// gugw:所有子弹以及敌人飞机更新计算,子弹是根据速度计算每一帧应该飞行距离,从而确定这一次显示的坐标,若超出地图范围,则从databus里面移除该对象。
// gugw:敌人飞机也是根据速度计算每一帧应该飞行的距离,确定这一帧应该显示的位置,若超出地图范围,则从databus里面移除该对象。
databus.bullets
.concat(databus.enemys)
.forEach((item) => {

item.update()
})
// gugw:根据帧数设定,此处我设置成了10,每10帧,就创建一个新的敌人飞机对象。
this.enemyGenerate()
// gugw:物理碰撞检测,实际上就是检查所有敌人飞机是否与子弹碰撞了。若碰撞,则播放爆炸动画,并且子弹消失,飞机消失,得分加1
this.collisionDetection()
// gugw:以下为子弹射出的逻辑,每20帧发生一粒新子弹。并播放射击音乐。
if (databus.frame % 20 === 0) {
this.player.shoot()
this.music.playShoot()
}
}

​在Loop函数中,光完成了计算,如果不需要显示画面,其实游戏已经可以测试运行了。但是既然是游戏,给玩家玩的,肯定要有图像动画反馈才会爽。接下里就看画面部分,计算机里面绘画图形也叫做渲染,英文一般叫render,所以这里可以看到main.js中有个render函数。

render函数

每一帧重新绘制所有的需要展示的元素。该飞机游戏中,每次都是先把场景全部清空,然后依次画背景、子弹、敌人飞机、玩家、动画效果、游戏得分、游戏结束画面。

/**
* canvas重绘函数
* 每一帧重新绘制所有的需要展示的元素
*/
render() {
// gugw:清空画板
ctx.clearRect(0, 0, canvas.width, canvas.height)
// gugw:画上最新的背景,实际上就是把背景图片画到画板上
this.bg.render(ctx)
// gugw:画上所有的子弹和敌人,实际上就是把子弹和敌人的图片画到画板上
databus.bullets
.concat(databus.enemys)
.forEach((item) => {
item.drawToCanvas(ctx)
})
// gugw:画上玩家
this.player.drawToCanvas(ctx)
// gugw:播放动画效果,比如敌人飞机被子弹击中,敌人飞机会消失,爆炸动画会出现在此处。
databus.animations.forEach((ani) => {
if (ani.isPlaying) {
ani.aniRender(ctx)
}
})
// gugw:画上得分
this.gameinfo.renderGameScore(ctx, databus.score)

// 游戏结束停止帧循环
if (databus.gameOver) {
// gugw:游戏结束,画结束弹出界面
this.gameinfo.renderGameOver(ctx, databus.score)
// gugw:弹出结束界面之后,触摸屏幕事件就不能用于移动飞机了,而是用于点击“重新开始”,所以事件处理逻辑切换为touchEventHandler函数。
// gugw:玩家飞机的触摸事件绑定是在玩家的初始化时发生,明天分析玩家、敌人和子弹。
if (!this.hasEventBind) {

this.hasEventBind = true

this.touchHandler = this.touchEventHandler.bind(this)

canvas.addEventListener('touchstart', this.touchHandler)

}
}
}

我们再来看看像背景、飞机等一个个具体元素,我们都可以统称他们叫精灵(Sprite)。

Background.js

文件代码如下,BackGround继承了Sprite类,Sprite也叫做精灵,这是游戏开发中的主要概念之一,下一段会分析Sprite。

BackGround要实现显示背景图片以及切换的动画,那至少得有保存图片的变量,以及显示图片的长宽,以及渲染显示。因为背景需要变换,所以需要每帧计算是否变换,以及如何变换,然后渲染显示。

import Sprite from '../base/sprite'



const screenWidth = window.innerWidth

const screenHeight = window.innerHeight



const BG_IMG_SRC = 'images/bg.jpg'

const BG_WIDTH = 512

const BG_HEIGHT = 512



/**
* 游戏背景类
* 提供update和render函数实现无限滚动的背景功能
*/
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

}


/**
* 背景图重绘函数
* 绘制两张图片,两张图片大小和屏幕一致
* 第一张漏出高度为top部分,其余的隐藏在屏幕上面
* 第二张补全除了top高度之外的部分,其余的隐藏在屏幕下面
*/
render(ctx) {
// 将图片img显示到画板的位置( 0,-screenHeight + this.top ),裁剪图片的长度和宽度为(screenWidth, screenHeight)
// 显示img的时候是剪裁图片,从 (0,0)位置开始剪裁,剪裁长和宽为(this.width,this.height),这个长宽如果比裁剪的长度和宽度不一样,就会进行缩减或者伸长像素。
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
)
}
}

此处ctx.drawImage就是将图片画到画板上,所有显示,包括后续子弹、玩家,都是通过这种方式渲染。

this.bg = new BackGround(ctx)
this.player = new Player(ctx)
this.gameinfo = new GameInfo()
this.music = new Music()

Sprite.js

构造函数中定义了Sprite的坐标位置,以及长宽,当然精灵要显示,当然有个img,精灵可以显示也可以不显示,所以需要visible来控制是否渲染显示。

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

}

定义了精灵的元素,接下来精灵要实现的几个功能,主要就是渲染显示,碰撞检测,也就是飞机和子弹的碰撞检测。渲染如下,这个和上面背景的渲染不大一样,这里无需剪裁图片,只需要按照

/**
* 将精灵图绘制在canvas上
*/
drawToCanvas(ctx) {

if (!this.visible) return
ctx.drawImage(
this.img,
this.x,
this.y,
this.width,
this.height
)
}

碰撞检测代码如下:根据坐标来判断,因为没有精灵都是x、y、width、height,这就是一个矩形区域,只要判断一个精灵的中心是否在另一个精灵的矩形区域。

当然,实际上可以做更好的碰撞检测,就是两个矩形有相交就算碰撞了,不过demo中的代码并没有这样做。此处15行代码,因为子弹和敌人飞机都不是碰撞后立即回收对象,而是采用了设置为visible为false,不然其显示来让玩家看不到它们,所以一旦为false,就相当于这些精灵已经消失了,无需进行物理检测。

/**
* 简单的碰撞检测定义:
* 另一个精灵的中心点处于本精灵所在的矩形内即可
* @param{Sprite} sp: Sptite的实例

*/
isCollideWith(sp) {

const spX = sp.x + sp.width / 2

const spY = sp.y + sp.height / 2



if (!this.visible || !sp.visible) return false



return !!(spX >= this.x

&& spX <= this.x + this.width

&& spY >= this.y

&& spY <= this.y + this.height)

}
}

小结

通过Sprite的学习,知道了游戏中计算和画面移动都是如何实现的了吧。编程是一门工具,每行每业中的编程虽说都是万变不离其宗,但是各有各的千秋。


举报

相关推荐

0 条评论