当前位置:   article > 正文

【Canvas】童年玩过的雷霆战机你还记得吗?_雷霆战机素材

雷霆战机素材

按键机的年代,想必大家都玩过类似雷电,雷霆战机之类的飞行射击类游戏吧,今天我就试着用canvas来还原一下游戏场景。

素材准备

还原场景的第一步是准备素材,首先我们需要一架雷霆战机,经过九牛二虎之力,我在网上找到了心仪的战机,就是下面这架:

战机的子弹也要准备一下,我给我的战机准备了黄色和蓝色两种子弹,如下:

HTML部分

素材准备完成,我们就开始把它们渲染到页面上吧,首先先用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> 
  • 1
  • 2
  • 3
  • 4

4个元素我都设置了id属性,这是为了一会更方便的获取DOM元素。

CSS部分

样式部分,我们只需要使用通配符选择器,将页面自带的margin和padding设为0,防止页面有白边,影响体验感。

* {margin: 0;padding: 0;
} 
  • 1
  • 2

JS部分

基础准备

首先,我们要先获取到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) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

因为是在浏览器上,我们是通过移动鼠标来操作我们的战机,所以我们还需要对鼠标的位置进行监控,可以通过监听 mousemove 事件来获取到鼠标的位置,并且定义一个全局对象 mouse,用其中的xy属性来存放当前鼠标的位置,代码如下:

const mouse = {x: 0, y: 0}// 鼠标位置参数
// 监控鼠标位置改变
window.addEventListener('mousemove', (e) => {mouse.x = e.clientXmouse.y = e.clientY
}) 
  • 1
  • 2
  • 3
  • 4

构造函数

这一次我们需要两个构造函数,一个是战机子弹的构造函数 Bullet 另一个则是战机的构造函数 Plane,两个构造函数的结构大体相同,都会拥有xy属性,用于表示自身当前所在位置,有draw方法,基于自身的位置进行绘制,有update方法,对自身的位置进行更新并重绘。 。 不一样的是,子弹构造函数还拥有一个dy属性,表示子弹在y轴的速度,因为子弹是沿着一个方向运动的。

战机构造函数 Plane

function Plane(x, y) {this.x = xthis.y = y// 绘制飞机this.draw = () => { }// 更新飞机位置this.update = () => {}
} 
  • 1
  • 2

在我们使用 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)
} 
  • 1
  • 2
  • 3
  • 4
  • 5

这下舒服多了

战机子弹构造函数 Bullet

function Bullet(x, y) {this.x = xthis.y = ythis.dy = 120 // 子弹速度写死// 绘制子弹this.draw = () => {}// 更新子弹位置this.update = () => { }
} 
  • 1
  • 2

子弹也有和战机一样的问题但又不尽相同,已知子弹素材的宽高为 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++ // 更新子弹颜色
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

一开始我曾说过不需要理会子弹的y坐标,其实不然,之所以减80,是因为我们需要让子弹看起来是从飞机头发射出去的,而不是从鼠标(飞机中心)发射出去。

场景还原

素材准备好了,构造函数也准备好了,接下来开始进行场景的还原。

第一步当然是先创建一个雷霆战机啦

// 创建一个雷霆战机
const fighter = new Plane(0, 0) // 飞机起始位置在左上角 
  • 1
  • 2

第二步就是执行 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()
} 
  • 1
  • 2

这里要注意的是,要先绘制子弹,然后再绘制战机,因此有层级的存在,这个顺序搞乱了,会导致子弹挡住战机。然后子弹发射间隔那里,本来是想减缓子弹发射的速度的,好像没啥子用,我设置到了10w都没见子弹变慢,但是懒得删掉了。

最终效果

下面就是最后实现的效果啦

什么,你说雷霆战机的背景是外太空黑色的?我也想呀,但是我抠不出来图,只能用白色糊弄过去啦。

最后

最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/87621?site
推荐阅读
相关标签
  

闽ICP备14008679号