赞
踩
按键机的年代,想必大家都玩过类似雷电,雷霆战机之类的飞行射击类游戏吧,今天我就试着用canvas来还原一下游戏场景。
还原场景的第一步是准备素材,首先我们需要一架雷霆战机,经过九牛二虎之力,我在网上找到了心仪的战机,就是下面这架:
战机的子弹也要准备一下,我给我的战机准备了黄色和蓝色两种子弹,如下:
素材准备完成,我们就开始把它们渲染到页面上吧,首先先用3个img标签引入3个素材,并且设置img的display属性为none,使的它们不显示在页面上。同时,我们定义一个canvas元素,用来把我们的飞机渲染在上面。
<img src="../imgs/plane.png" alt="飞机" style="display: none" id="plane">
<img src="../imgs/blue-bullet.png" alt="蓝子弹" style="display: none" id="blue-bullet">
<img src="../imgs/yellow-bullet.png" alt="黄子弹" style="display: none" id="yellow-bullet">
<canvas id="space"></canvas>
4个元素我都设置了id属性,这是为了一会更方便的获取DOM元素。
样式部分,我们只需要使用通配符选择器,将页面自带的margin和padding设为0,防止页面有白边,影响体验感。
* {margin: 0;padding: 0;
}
首先,我们要先获取到canvas元素,然后设置它的宽高为浏览器页面的宽高,然后我们还要获取到3个img元素,因此img加载图片是异步的,所以我们需要通过轮询3个img元素的complete属性是否都为true来判断3张图片是否都加载好了,只有图片都加载好了,才可以执行我们的动画效果,进行战机的渲染。
// 获取canvas - 设置宽高
const space = document.getElementById('space') // 太空
space.width = window.innerWidth
space.height = window.innerHeight
// 获取上下文
const ctx = space.getContext('2d')
// 获取两个img元素
const plane = document.getElementById('plane') // 飞机
const blueBullet = document.getElementById('blue-bullet') // 蓝子弹
const yellowBullet = document.getElementById('yellow-bullet') // 黄子弹
// 判断三张图片是否都已加载完成
const timer = setInterval(() => {if (plane.complete && blueBullet.complete && yellowBullet.complete) {animate() // 执行动画clearInterval(timer)}
}, 50)
因为是在浏览器上,我们是通过移动鼠标来操作我们的战机,所以我们还需要对鼠标的位置进行监控,可以通过监听 mousemove
事件来获取到鼠标的位置,并且定义一个全局对象 mouse
,用其中的xy属性来存放当前鼠标的位置,代码如下:
const mouse = {x: 0, y: 0}// 鼠标位置参数
// 监控鼠标位置改变
window.addEventListener('mousemove', (e) => {mouse.x = e.clientXmouse.y = e.clientY
})
这一次我们需要两个构造函数,一个是战机子弹的构造函数 Bullet
另一个则是战机的构造函数 Plane
,两个构造函数的结构大体相同,都会拥有xy属性,用于表示自身当前所在位置,有draw方法,基于自身的位置进行绘制,有update方法,对自身的位置进行更新并重绘。 。 不一样的是,子弹构造函数还拥有一个dy属性,表示子弹在y轴的速度,因为子弹是沿着一个方向运动的。
Plane
function Plane(x, y) {this.x = xthis.y = y// 绘制飞机this.draw = () => { }// 更新飞机位置this.update = () => {}
}
在我们使用 drawImage
进行战机绘制的时候,是以图像的左上角为起点的,也就是鼠标的光标会在战机的左上角,那感觉特别违和,如下图:
战机的素材大小为 140 * 96
,我们想要让鼠标在战机中心的话,就要把鼠标的x减去战机宽度的一半,鼠标的y减去战机高度的一半。这一步我们直接放在构造函数的update方法中进行即可。
this.update = () => { // 飞机图像宽高 140 * 96 减去一半 鼠标正好在飞机中间 this.x = mouse.x - 70 this.y = mouse.y - 48 this.draw()
}
// 绘制飞机
this.draw = () => { ctx.drawImage(plane, this.x , this.y)
}
这下舒服多了
Bullet
function Bullet(x, y) {this.x = xthis.y = ythis.dy = 120 // 子弹速度写死// 绘制子弹this.draw = () => {}// 更新子弹位置this.update = () => { }
}
子弹也有和战机一样的问题但又不尽相同,已知子弹素材的宽高为 38 * 90
,因为子弹是要飞出去的,所以我们不必理会y,只需对子弹的x坐标进行计算,让子弹在绘制的时候,x坐标减去19,使的子弹看起来刚好是从机头那里发射出去的。
然后因为我希望我的战机可以发射两种颜色的子弹,间隔发射,所以我定义了一个子弹类型变量 bulletType
,每创建一颗子弹就会给它的值加1,如果是奇数就蓝色子弹,如果是偶数就黄色子弹。
有子弹,自然也要又弹夹,因此我还定义了一个全局弹夹 BULLET_POOL
,每个创建的子弹都会push进去这里,然后通过遍历这个数组,调用里面每个子弹的update方法进行子弹位置的更新和绘制。
最后要考虑的就是,因为每颗子弹发射的频率相同,间隔也相等,就会导致这些子弹彷佛是静止的,就像下面这样:
所以我们在绘制的时候,要考虑加点随机因素进去,打破它的这种均衡,弹道的效果显得更加真实。
通过上面的分析,接下来将子弹的构造函数补充完整。
// 更新子弹位置
this.update = () => {// 随机发射this.y -= this.dyif (Math.random() > 0.5) {this.draw()}
}
// 绘制子弹
this.draw = () => {// 子弹图像的大小是38* 90if (bulletType % 2 === 0) {ctx.drawImage(blueBullet, this.x - 19, this.y - 80)} else {ctx.drawImage(yellowBullet, this.x - 19, this.y - 80)}bulletType++ // 更新子弹颜色
}
一开始我曾说过不需要理会子弹的y坐标,其实不然,之所以减80,是因为我们需要让子弹看起来是从飞机头发射出去的,而不是从鼠标(飞机中心)发射出去。
素材准备好了,构造函数也准备好了,接下来开始进行场景的还原。
第一步当然是先创建一个雷霆战机啦
// 创建一个雷霆战机
const fighter = new Plane(0, 0) // 飞机起始位置在左上角
第二步就是执行 requestAnimationFrame
来绘制飞机和子弹啦
function animate() {requestAnimationFrame(animate)// 清空画布ctx.clearRect(0, 0, space.width, space.height)// 子弹发射间隔if (interval % 1000 === 0) {const newBullet = new Bullet(mouse.x, mouse.y)// 创建一个新的子弹BULLET_POOL.push(newBullet) // 加入子弹池} else {interval++}// 绘制子弹for (let bullet of BULLET_POOL) {bullet.update()}// 绘制飞机fighter.update()
}
这里要注意的是,要先绘制子弹,然后再绘制战机,因此有层级的存在,这个顺序搞乱了,会导致子弹挡住战机。然后子弹发射间隔那里,本来是想减缓子弹发射的速度的,好像没啥子用,我设置到了10w都没见子弹变慢,但是懒得删掉了。
下面就是最后实现的效果啦
什么,你说雷霆战机的背景是外太空黑色的?我也想呀,但是我抠不出来图,只能用白色糊弄过去啦。
最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。
有需要的小伙伴,可以点击下方卡片领取,无偿分享
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。