网盘链接:https://pan.baidu.com/s/1gaSsG8fCfLlMhLDcK7pPYQ?pwd=6666
提取码:6666
链接里面有项目代码和图片资源,需要者自取,如果满意,还请关住、点赞、收藏,一键三连。
我们先上效果图
整体效果:点击画布开始游戏,鼠标移出画布,画面将暂停,鼠标移动控制飞机,乙方飞机与敌方飞机碰撞,life-1
游戏阶段分析过程
第一阶段分析过程
1.实现HTML页面布局<canvas>
元素
- 问题 - 元素不能实现页面居中?
2.实现游戏背景 - 背景使用图片 -
drawImage()
方法 - 如何实现背景图片动态效果?
3.实现显示游戏LOGO - 使用图片内容 - drawImage()方法
分析第二阶段:
- 加载游戏的背景,让背景循环播放
- 动画效果
- 加载用于显示动画效果的所有图片 - 4张
- 定义一个数组,用于存储动画效果的4张图片
- 初始化动画效果的图片数据
- 定义动画效果的构造器
- 绘制图片 -
drawImage(img)
- 绘制的图片从第一张切换到第四章 - 当动画效果执行完毕之后,游戏进入到运行阶段
分析第三阶段
我方飞机
- 绘制我方飞机
- 加载我方飞机所使用的图片 - 2张
- 使用数组格式存储我方飞机图片
- 初始化我方飞机的数据
- 创建我方飞机的构造器
- 绘制我方飞机 - drawImage(imgs[0],x,y)
- 让我方飞机跟随鼠标移动而移动
- 绑定
onmousemove
事件 - 元素 - 根据当前鼠标的坐标值,修改我方飞机的(x,y)
我方飞机的子弹
问题 - 子弹到底是独立对象,还是作为我方飞机对象的一个属性存在?
- 面向对象 - 黑板与黑板擦的关系
- 选择独立对象 - 画布中可能同时存在多个子弹(对象),选择使用数组格式
- 加载子弹图片 - 1张
- 初始化子弹的数据
- 创建子弹的构造器
- 绘制子弹的方法 paint()
- 控制子弹运动方法 step()
- 创建子弹对象 - 为我方飞机增加射击的功能
- 使用数组格式
- 使用数组格式
操作敌方飞机(小、中、大三种飞机)
- 加载敌方飞机的图片
- 小飞机 - 1张
- 中飞机 - 1张
- 大飞机 - 2张
- 初始化敌方飞机的数据
- 创建敌方飞机的构造器 - 针对每种敌方飞机创建一个构造器,还是统一创建一个构造器?
- 绘制方法
- 运动方法
- 创建敌方飞机对象 - 画布中同时存在多个敌方飞机
- 使用数组存储多个敌方飞机
- 专门定义函数
1)创建敌方飞机;
2)绘制所有的敌方飞机;
3)控制所有的敌方飞机运动
4)当敌方飞机飞出画布时,删除操作
撞击逻辑
- 画布中具有我方飞机、我方子弹以及敌方飞机
- 我方子弹撞击敌方飞机
- 我方飞机撞击敌方飞机
分析我方飞机与敌方飞机撞击后的逻辑
- 敌方飞机
- 爆破动画效果
- 当爆破动画效果执行完毕后,将该敌方飞机删除
- 我方飞机
- 爆破动画效果
- 当爆破动画效果执行完毕后,进入第五阶段
分析敌方飞机被撞击后的逻辑
0.正常 - 标示
1.被撞击后,开始执行爆破动画 - 标示(down)
2.当爆破动画执行完毕,删除 - 标示(canDelete)
函数enemiesHit() - 用于检查敌方飞机是否被撞击
分析第四阶段 - 游戏暂停
- 当鼠标离开游戏的画布时,暂停游戏
- 画布的元素 - 元素
- 鼠标离开画布元素 - onmouseout事件
- 判断当前处在运行阶段时 - state
- 背景依旧具有动画效果
- 我方飞机和子弹 - 只绘制,不移动 paint()
- 判断当前处在运行阶段时 - state
- 当鼠标重新进入到游戏的画布中时,继续游戏
- 画布的元素 - 元素
- 鼠标重新进入到画布元素 - onmouseover事件
- 判断当前处在暂停阶段时 - state
分析第五阶段 - gameover
-
我方飞机的生命值为 0
-
进入到gameover阶段时,需要完成的事情
代码部分:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
canvas{
display: block;
border: 1px solid;
margin: 0 auto;
}
</style>
</head>
<body>
<canvas id="myCanvas" width="480" height="650"></canvas>
<script>
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d")
// 0.游戏初始化
// 0.1 定义将游戏划分为5个阶段的常量
const START = 0;//游戏开始前
const STARTING = 1;//游戏加载阶段
const RUNNING = 2;//游戏运行阶段
const PAUSE = 3;//游戏暂停阶段
const GAMEOVER = 4;//游戏结束阶段
// 0.2 定义一个标识,表示当前游戏处于第几个阶段
var state = START;
// 0.3 定义画布的宽度和高度
const WIDTH = 480;
const HEIGHT = 650;
// 0.4 定义游戏得分
var score = 0;
// 0.5 定义我方飞机
var life = 3;
// 1.游戏欢迎阶段
// 1.1 加载背景图片
var bg = new Image();
bg.src = "./images/background.png";
// 1.2 初始化背景的数据
var BG = {
imgs : bg,
width : 480,
height : 852
}
// 1.3 定义背景对象的构造函数
function Bg(config){
this.imgs = config.imgs;
this.width = config.width;
this.height = config.height;
// 定义绘制背景的坐标值
this.x1 = 0;
this.y1 = 0;
this.x2 = 0;
this.y2 = -this.height;
// 定义绘制背景的方法
this.paint = function(){
ctx.drawImage(this.imgs,this.x1,this.y1)
ctx.drawImage(this.imgs,this.x2,this.y2)
}
// 定义背景运动的方法
this.step = function(){
// 控制两张图片向下运动
this.y1++;
this.y2++;
if(this.y1 == this.height){
this.y1 = -this.height;
}
if(this.y2 == this.height){
this.y2 = -this.height;
}
}
}
// 1.4 利用构造函数创建背景对象
var sky = new Bg(BG)
console.log(sky)
// 1.5 加载的LOGO图片
var logo = new Image()
logo.src = "./images/start.png";
// 2. 游戏过渡阶段
// 2.1 加载过度动画的图片
var loadings = []
loadings[0] = new Image()
loadings[0].src = "./images/game_loading1.png"
loadings[1] = new Image()
loadings[1].src = "./images/game_loading2.png"
loadings[2] = new Image()
loadings[2].src = "./images/game_loading3.png"
loadings[3] = new Image()
loadings[3].src = "./images/game_loading4.png"
// 2.2 初始化图片的数据
var LOADINGS = {
imgs : loadings,
length : loadings.length,
width : 186,
height : 38
}
// 2.3 定义加载动画的构造函数
function Loading(config){
this.imgs = config.imgs;
this.length = config.length
this.width = config.width;
this.height = config.height;
// 定义一个表示图片的索引值
this.startIndex = 0;
// 定义绘制图片
this.paint = function(){
ctx.drawImage(this.imgs[this.startIndex],147,550)
}
// 定义表示速度
this.time = 0;
// 定义动画方法
this.step= function(){
this.time++;
if(this.time % 3 == 0){
this.startIndex ++;
}
if(this.startIndex == this.length){
// 加载动画执行完毕,游戏进入下一阶段
state = RUNNING
}
}
}
// 2.4 创建动画效果的对象
var loading = new Loading(LOADINGS)
// 2.5 为canvas元素绑定click事件
canvas.onclick = function(){
state = STARTING;
}
// 3.游戏运行阶段
// 3.1 绘制我方飞机
// 3.1.1 加载我方飞机图片
var heros = []
heros[0] = new Image()
heros[0].src = "./images/hero1.png";
heros[1] = new Image()
heros[1].src = "./images/hero2.png";
heros[2] = new Image()
heros[2].src = "./images/hero_blowup_n1.png";
heros[3] = new Image()
heros[3].src = "./images/hero_blowup_n2.png";
heros[4] = new Image()
heros[4].src = "./images/hero_blowup_n3.png";
heros[5] = new Image()
heros[5].src = "./images/hero_blowup_n4.png";
// 3.1.2 初始化我方飞机的数据
var HEROS = {
imgs : heros,
length : heros.length,
width : 99,
height : 124,
frame : 2
}
// 3.1.3 创建我方飞机的构造函数
function Hero(config){
this.imgs = config.imgs;
this.length = config.length
this.width = config.width;
this.height = config.height;
this.frame = config.frame;
// 定义我方飞机的坐标值
this.x = WIDTH / 2 - this.width / 2;
this.y = HEIGHT - 150;
// 定义一个属性表示图片的索引值
this.startIndex = 0
// 新增一个标识-表示是否被撞击
this.down = false;
// 新增一个标识-表示是否执行完毕
this.candel = false;
// 定义绘制方法
this.paint = function(){
ctx.drawImage(this.imgs[this.startIndex],this.x,this.y)
}
// 定义动画方法
this.step = function(){
if(!this.down){// 没被撞
// if(this.startIndex == 0){
// this.startIndex = 1
// }else{
// this.startIndex = 0
// }
this.startIndex++;
this.startIndex = this.startIndex % 2;
}else{//被撞
this.startIndex++;
// 判断是否执行完毕
if(this.startIndex == this.length){
life--
if(life == 0){
state = GAMEOVER;
this.startIndex = this.length - 1;
}else{
hero = new Hero(HEROS)
}
}
}
}
// 增加我方飞机射击的方法
this.time = 0;
this.shoot = function(){
this.time++;
if(this.time % 3 == 0){
bullets.push(new Bullet(BULLET))
}
}
// 新增一个方法,用于处理撞击后的逻辑
this.bang = function(){
this.down = true;
}
}
// 3.1.4 创建我方飞机对象
var hero = new Hero(HEROS)
// 3.1.5 为canvas绑定mousemove事件
canvas.onmousemove = function(event){
// console.log(event)
var x = event.offsetX;
var y = event.offsetY;
// 修改我方飞机坐标值
hero.x = x - hero.width / 2;
hero.y = y - hero.height / 2;
}
// 3.2 绘制子弹
// 3.2.1 加载子弹图片
var bullet = new Image()
bullet.src = "./images/bullet1.png"
// 3.2.2 初始化子弹的数据
var BULLET = {
imgs : bullet,
width : 9,
height : 21
}
// 3.2.3 创建子弹的构造器
function Bullet(config){
this.imgs = config.imgs;
this.width = config.width;
this.height = config.height;
// 定义子弹绘制的坐标值
this.x = hero.x + hero.width / 2 - this.width / 2;
this.y = hero.y - this.height - 10
// 绘制
this.paint = function(){
ctx.drawImage(this.imgs,this.x,this.y)
}
// 运动方法
this.step = function(){
this.y -= 10;
}
// 新增方法 - 用于处理撞击后的逻辑
this.candel = false;//没有撞击
this.bang = function(){
this.candel = true;
}
}
// 3.2.4 创建存储子弹的数组
var bullets = [];
// 3.2.5 创建函数,用于绘制所有子弹
function bulletsPaint(){
for(var i = 0 ; i < bullets.length;i++){
bullets[i].paint()
}
}
// 3.2.6 创建函数,用于控制所有子弹运动
function bulletsStep(){
for(var i = 0 ; i < bullets.length;i++){
bullets[i].step()
}
}
// 3.2.7 当子弹移出画布,删除子弹
function bulletsDel(){
for(var i = 0 ; i < bullets.length ; i++){
if(bullets[i].y < -bullets[i].height || bullets[i].candel){
bullets.splice(i,1)
}
}
// console.log(bullets.length)
}
// 3.3 创建敌方飞机
// 3.3.1 加载敌方飞机的图片
var enemy1 = []//小飞机
enemy1[0] = new Image()
enemy1[0].src = "./images/enemy1.png"
enemy1[1] = new Image()
enemy1[1].src = "./images/enemy1_down1.png"
enemy1[2] = new Image()
enemy1[2].src = "./images/enemy1_down2.png"
enemy1[3] = new Image()
enemy1[3].src = "./images/enemy1_down3.png"
enemy1[4] = new Image()
enemy1[4].src = "./images/enemy1_down4.png"
var enemy2 = []//中飞机
enemy2[0] = new Image()
enemy2[0].src = "./images/enemy2.png"
enemy2[1] = new Image()
enemy2[1].src = "./images/enemy2_down1.png"
enemy2[2] = new Image()
enemy2[2].src = "./images/enemy2_down2.png"
enemy2[3] = new Image()
enemy2[3].src = "./images/enemy2_down3.png"
enemy2[4] = new Image()
enemy2[4].src = "./images/enemy2_down4.png"
var enemy3 = []//大飞机
enemy3[0] = new Image()
enemy3[0].src = "./images/enemy3_n1.png"
enemy3[1] = new Image()
enemy3[1].src = "./images/enemy3_n2.png"
enemy3[2] = new Image()
enemy3[2].src = "./images/enemy3_down1.png"
enemy3[3] = new Image()
enemy3[3].src = "./images/enemy3_down2.png"
enemy3[4] = new Image()
enemy3[4].src = "./images/enemy3_down3.png"
enemy3[5] = new Image()
enemy3[5].src = "./images/enemy3_down4.png"
enemy3[6] = new Image()
enemy3[6].src = "./images/enemy3_down5.png"
enemy3[7] = new Image()
enemy3[7].src = "./images/enemy3_down6.png"
// 3.3.2 初始化敌方飞机的数据
var ENEMY1 = {
imgs : enemy1,
length : enemy1.length,
width : 57,
height : 51,
type : 1,//飞机类型
frame : 1,
life : 3,
score : 1
}
var ENEMY2 = {
imgs : enemy2,
length : enemy2.length,
width : 69,
height : 95,
type : 2,//飞机类型
frame : 1,//正常状态下的形态
life : 5,
score : 3
}
var ENEMY3 = {
imgs : enemy3,
length : enemy3.length,
width : 165,
height : 361,
type : 3,//飞机类型
frame : 2,
life : 20,
score : 10
}
// 3.3.3 创建敌方飞机的构造器
function Enemy(config){
this.imgs = config.imgs
this.length = config.length
this.width = config.width
this.height = config.height
this.type = config.type
this.frame = config.frame
this.life = config.life
this.score = config.score
// 定义敌方飞机的坐标值
this.y = -this.height;
this.x = Math.random() * ( WIDTH - this.width )//0 - (画布宽-飞机宽)
// 定义数组的索引
this.startIndex = 0
// 新增属性 - 表示当前绘制的图片
this.imgTarget = this.imgs[this.startIndex]
// 新增状态 - 表示敌机是否被撞击
this.down = false;
// 新增状态 - 表示敌机是否被删除
this.candel = false;
// 绘制方法
this.paint = function(){
ctx.drawImage(this.imgs[this.startIndex],this.x,this.y)
}
// 运动方法
this.step = function(){
if(!this.down){//正常状态
// 小中飞机 下标为0
// 大飞机 下标在01切换
this.startIndex ++;
this.startIndex = this.startIndex % this.frame;
this.y += 2;
}else{//爆破
// 小中飞机 下标为1
// 大飞机 下标在2
this.startIndex ++;
// 问题 - 进入爆破,只能执行一次
if(this.startIndex == this.length){
this.candel = true;
this.startIndex = this.length - 1
}
}
}
// 增加用于检查敌机是否被撞击
this.checkHit = function(wo){
// 参数wo 1)我方飞机 2)子弹
return wo.x + wo.width > this.x
&& wo.x < this.x + this.width
&& wo.y + wo.height > this.y
&& wo.y < this.y + this.height
}
// 增加用于被撞击后的逻辑
this.bang = function(){
this.life--;
if(this.life == 0){
this.down = true
score += this.score
}
}
}
// 3.3.4 创建数组 - 用于存储所有的地方飞机
var enemies = []
// 3.3.5 创建函数 - 用于创建敌方飞机
function enterEnemies(){
/*
小飞机 - 数量最多
中飞机 - 数量居中
大飞机 - 数量最少
*/
var rand = Math.floor(Math.random() * 100)
if(rand <= 7){
enemies.push(new Enemy(ENEMY1))
}else if(rand == 8 || rand == 9){
enemies.push(new Enemy(ENEMY2))
}else if(rand == 10){
// 只允许数组中的第一个元素为大飞机
if(enemies[0].type !=3 && enemies.length >0){
enemies.splice(0,0,new Enemy(ENEMY3))
}
}
}
// 3.3.6 用于绘制敌方飞机
function paintEnemies(){
for(var i = 0 ; i < enemies.length;i++){
enemies[i].paint()
}
}
// 3.3.6 用于敌方飞机运动
function stepEnemies(){
for(var i = 0 ; i < enemies.length;i++){
enemies[i].step()
}
}
// 3.3.7 用于删除敌方飞机
function delEnemies(){
for(var i = 0 ; i < enemies.length;i++){
if(enemies[i].y > HEIGHT || enemies[i].candel){
enemies.splice(i,1)
}
}
// console.log(enemies.length)
}
// 3.3.8 用于检测敌机被撞击
function hitEnemies(){
for(var i = 0 ; i < enemies.length;i++){
if(enemies[i].checkHit(hero)){//敌机和我方飞机
// 处理敌机撞击后的逻辑
enemies[i].bang()
// 处理我方飞机撞击后的逻辑
hero.bang()
}
for(var j = 0 ; j < bullets.length;j++){
// console.log(enemies[i].checkHit(bullets[j]))
if(enemies[i].checkHit(bullets[j])){//子弹和我方飞机
// 处理敌机撞击后的逻辑
enemies[i].bang()
// 处理子弹撞击后的逻辑
bullets[j].bang()
}
}
}
}
// 3.4 绘制游戏得分和我方飞机的生命值
function paintText(){
ctx.font = "bold 24px 微软雅黑"
ctx.fillText("SCORE:"+score,10,30)
ctx.fillText("LIFE:" + life,390,30)
}
// 4.游戏暂停
canvas.onmouseover = function(){
if(state == PAUSE){
state = RUNNING
}
}
canvas.onmouseout = function(){
if(state == RUNNING){
state = PAUSE
}
}
var pause = new Image()
pause.src = "./images/game_pause_nor.png"
// 5.绘制gameover文本
function paintOver(){
ctx.font = "bold 48px 微软雅黑"
ctx.fillText("GAME OVER",100,320)
}
// 游戏核心控制器
setInterval(function(){
sky.paint()
sky.step()
switch (state) {
case START:
// 绘制logo
ctx.drawImage(logo,40,0)
break;
case STARTING:
// 绘制加载动画效果
loading.paint()
// 加载动画
loading.step()
break;
case RUNNING:
// 绘制我方飞机
hero.paint()
// 我方飞机动画
hero.step()
// 我方飞机射击
hero.shoot()
// 绘制子弹们
bulletsPaint()
// 子弹们运动
bulletsStep()
// 子弹删除
bulletsDel()
// 地方飞机创建
enterEnemies()
// 敌方飞机绘制
paintEnemies()
// 敌方飞机运动
stepEnemies()
// 敌机被删除
delEnemies()
// 检测是否被撞击
hitEnemies()
paintText()
break;
case PAUSE :
hero.paint()
bulletsPaint()
paintEnemies()
paintText()
ctx.drawImage(pause,WIDTH / 2 - 30,HEIGHT / 2 - 20)
break;
case GAMEOVER :
hero.paint()
bulletsPaint()
paintEnemies()
paintText()
paintOver()
break;
default:
break;
}
},100)
</script>
</body>
</html>