【项目】全民飞机大战-H5版-实训课程

一、实训意义

近年来,Web 应用在整个软件与互联网行业承载的责任越来越重,软件复杂度和维护成本越来越高,Web 技术,尤其是 Web 客户端技术,迎来了爆发式的发展。Web前端技术已经成为Web应用程序开发的重要领域。随着互联网的不断发展和移动设备的普及,Web前端技术也在不断地演变和创新。因此掌握Web前端技术是顺应时代发展,迅速融入互联网行业一个非常有效的途径。

高级网页设计实训是教学过程中重要的实践性教学环节。它是根据专业教学计划的要求,在教师指导下对学生进行的Web前端技术专业技能的综合训练,培养学生综合运用理论知识分析和解决实际问题的能力,实现由理论知识向操作技能的转化,是对理论与实践教学效果的检验,也是对学生综合分析能力与独立工作能力的培养过程。因此,加强实践教学环节,搞好实训教学,对实现本专业的培养目标,提高学生的综合素质有着重要的作用。

二、实训目的

  1. 通过综合实训进一步巩固、深化和扩展学生的理论知识与专业技能。
  2. 训练和培养学生获取信息和处理信息的能力,充分培养和提高学生的动手能力。
  3. 培养学生运用所学的理论知识和技能解决网页设计中所遇到的实际问题的能力及其基本工作素质。
  1. 培养学生的编程思想,了解实际开发中所需要具备的技能知识以及环境知识。向企业的实际用人需求靠拢。
  2. 培养学生理论联系实际的工作作风、严肃认真的科学态度以及独立工作的能力,树立自信心。

三、实训项目-飞机大战

3.1 实训项目效果展示

3.2 实训涉及知识点

  1. 盒子模型的运用
  2. JS 操作DOM节点的运用
  3. JS 面向对象的运用
  4. CSS常规样式的运用
  5. CSS定位,选择器等知识的运用

四、实训内容

4.2 制作游戏开始界面

》》搭建HTML界面框架,index.html

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<title>全民飞机大战</title>
		<link href="css/main.css" type="text/css" rel="stylesheet"/>
	</head>
	<body>
		
		
		<div id="content_div">
			<!-- 准备开始游戏的界面 -->
			<div id="start_div">
				<!-- 游戏的LOGO -->
				<img src="img/LOGO.png" width="380px" id="logo"> 
				<!-- 开始界面上的玩家 -->
				<img src="img/player01.png" id="start_player" >
				<!-- 开始游戏按钮 -->
				<div id="start" onclick="startGame()">开始游戏</div>
				<!-- 关闭音效按钮 -->
				<div id="sort" onclick="sortGame()">关闭音效</div>
				<!-- 查看帮助按钮 -->
				<div id="help" onclick="helpGame()">查看帮助</div>
			</div>
			
			<!-- 游戏界面 -->
			<div id="game_div">
				<!-- hp血量图片 -->
				<img id="my_hp" src="img/hp_0.png">
				<!-- 血槽 -->
				<div id="rect_hp">
					<!-- 血条 -->
					<div id="fill_rect_hp"> </div>
				</div>
			</div>
			<!-- 结束游戏界面 -->
			<div id="game_over">
					Game Over<br/>		
			</div>
    </div>	
	</body>
</html>

》》 设置开始界面的样式,main.css

/* 网页整体背景设置成黑色*/
body{
	background-color: black;
}
/*主div设置相对定位*/
#content_div{
	position: relative;	
}

/* 设置准备游戏界面的样式*/
#start_div{
	position: absolute;
	width: 512px;
	height:768px;
	/* 在屏幕中居中显示 */
	margin: auto;
	left:0;
	right:0;
	background: url('../img/img_bg_logo.jpg');
	/* 将鼠标指针切换成手指 */
	cursor:pointer;
}

/* 准备界面中图片样式设置 */
#start_div>img{
	position: absolute;
	display: inline-block;
	margin: auto;
	left:0;
	right:0;
	
}

/* 准备游戏界面中按钮样式的设置 */
#start_div>div{
	
	width: 270px;
	height: 40px;
	border-radius: 5px;
	text-align: center;
	line-height: 40px;
	background-color: #4876FF;
	font: '楷体' 20px ;
	color:white;
	font-weight: bold;
	position: absolute;
	margin: auto;
	left:0;
	right:0;
	
}


/* 开始游戏按钮*/
#start{
	
	top:600px;
}

/* 关闭音效按钮 */
#sort{
	
	top:650px;
}
/* 查看帮助按钮 */
#help{
	
	top:700px;
}

/* logo显示的位置 */
#logo{
	top:80px;
}

/* 开始页面玩家图片显示的位置 */
#start_player{
	top:420px;
}


/* 开始页面按钮的伪类样式 */
#start_div>div:hover{
	background-color: cyan;
	
}


/* 游戏页面样式**/
#game_div{
	position: absolute;
	width: 512px;
	height: 768px;
	background:url('../img/bg1.jpg') repeat;
	background-position: 0 100px;
	margin: auto;
	left: 0;
	right: 0;
	
	display:none;
	overflow: hidden;
	
}

/* 玩家血量样式 */
#my_hp{
	margin-top: 10px;
	margin-left: 10px;
}

/* 玩家血槽样式 */
#rect_hp{
	border: 1px solid red;
	width: 210px;
	height: 30px;
	display: inline-block;
	margin-top: 10px;
	margin-left: 10px;
	text-align: left;
}

/* 玩家血量样式 */
#fill_rect_hp{
	width: 210px;
	height: 30px;
	background: green;
}


/* 游戏结束样式 */
#game_over{
	font-size: 80px;
	color:red;
	font-family: '微软雅黑';
	font-weight: bold;
	position: absolute;
	width: 512px;
	height:768px;
	margin: auto;
	left:0;
	right:0;
	text-align: center;
	line-height: 500px;
	display: none;
	
}

本游戏由三个页面组成:准备游戏页面,游戏进行时页面,以及游戏结束时的页面,游戏初始的时候,只显示准备游戏页面,其他的页面display都设置为none,先隐藏起来。

》》 开始界面,玩家动画展示,main.js

//############## 开始页面 天使动画 ##################
// 索引,用来取图片
var player_index = 0;
// 开始页面玩家动画的方法
function start_player_animation(){
	// 每次执行方法时,索引增加,让索引指向下一张图片
	player_index ++;
	// 如果索引大于了最后一张图片的索引
	if(player_index>10){
		// 让索引回归到1,重新从第一张图片选取
		player_index = 1;
	}
	// 如果索引小于10
	if(player_index<10){
		// 拼接图片路径时,加0
		img_path = "img/player0"+player_index+".png";
	}else{
		img_path = "img/player"+player_index+".png";
	}
	// 根据拼接的图片路径,修改src属性值
	document.getElementById("start_player").src = img_path;
	
}
// 每隔300毫秒执行玩家动画的方法
setInterval(start_player_animation,300);

》》 在index.html中引入main.js

<script type="text/javascript" src="js/main.js"></script>

》》 展示效果

4.3 开始游戏界面切换

当点击“开始游戏”时,就要从当前开始界面切换到游戏界面,实现方式很简单。实际上就是点击按钮时,将开始界面的 display设置为none,再将游戏界面的display设置为 block 即可。在main.js中添加如下代码:

// ########## 开始界面切换到游戏界面#######
// 获取开始界面
var startDiv = document.getElementById("start_div");
// 获取游戏界面
var gameDiv = document.getElementById("game_div");

// 开始游戏的方法
function startGame(){
	// 隐藏开始界面
	startDiv.style.display = 'none';
	// 显示游戏界面
	gameDiv.style.display = 'block';
}

4.4 创建玩家机

》》 在js目录中中新增玩家类player.js,代码如下所示:

显示玩家实际上是在游戏界面gamediv中添加一个图片节点,然后设置好图片节点的图片,位置以及大小。因此我们在设计玩家的时候,需要创建一个图片节点,并设置好相应的属性。同时玩家需要上下左右移动的方法,而实际上上下左右移动是通过修改图片节点的left,top样式来实现的,原理图如下:

具体代码如下:


/*
	玩家飞机
	
	属性:
		图片节点、图片、x坐标、y坐标、速度
	行为:
		移动
		发射子弹
		初始化: 将图片加到gamediv游戏界面中
	
	
*/
function playerPlane(imgSrc){
	// 创建一个img图片节点
	this.imageNode = document.createElement("img");
	// 初始化图片路径
	this.imgSrc = imgSrc;
	// 初始化玩家的速度
	this.speed = 10;

	
	// 初始化图片,将图片节点加入到游戏界面中
	this.init = function(){
		// 初始化图片节点中显示的图片
		this.imageNode.src = this.imgSrc;
		this.imageNode.setAttribute("id","player");
		// 初始化玩家的大小
		this.width = this.imageNode.width;
		this.height = this.imageNode.height;
		// 初始化图片节点的定位方式,以及位置
		this.imageNode.style.position = 'absolute';
		// 让玩家的位置显示在游戏界面水平居中的位置
		this.imageNode.style.left = (512-this.imageNode.width)/2+'px';
		this.imageNode.style.top = 550+'px';
		// 将初始化好的图片节点,作为子节点加入到游戏界面中
		gameDiv.appendChild(this.imageNode);
		
		
	}
	
	// 调用初始化函数
	this.init();

	
	// 向左运动的方法
	this.moveLeft=function(){
		// 获取玩家的横坐标
		this.x = parseInt(this.imageNode.style.left);
		// 如果横坐标小于0了
		if(this.x<0){
			// 让玩家停留在坐标为0的位置
			this.imageNode.style.left = 0;
		}else{
			// 让玩家横坐标减小
			this.imageNode.style.left = this.x-this.speed+"px";
		}
		
		
	}
	// 向右运动的方法
	this.moveRight=function(){
		// 获取玩家的横坐标
		this.x = parseInt(this.imageNode.style.left);
		// 如果玩家的横坐标超出了游戏界面的宽度
		if(this.x>512-this.width){
			// 让玩家不能继续向右移动,给一个固定的坐标
			this.imageNode.style.left = 512-this.width+"px" ;
		}else{
			// 让玩家向右移动
			this.imageNode.style.left = this.x+this.speed+"px";
		}
		
	}
	// 向上运动的方法
	this.moveUp=function(){
		console.log("shang");
		this.y = parseInt(this.imageNode.style.top);
		if(this.y<0){
			this.imageNode.style.top = 0;
		}else{
			this.imageNode.style.top = this.y-this.speed+"px";
		}
		
	}
	// 向下运动的方法
	this.moveDown=function(){
		this.y =  parseInt(this.imageNode.style.top);
		if(this.y>768-this.height){
			this.imageNode.style.top = 768-this.height+"px";
		}else{
			this.imageNode.style.top = parseInt(this.imageNode.style.top)+this.speed+"px";
		}
		
	}
	
	
}

》》 在index.html中在引入main.js之前引入player.js

<script type="text/javascript" src="js/player.js"></script>
<script type="text/javascript" src="js/main.js"></script>

》》 在 main.js中添加创建玩家的代码,指定显示的图片路径

// 创建玩家
var player =  new playerPlane("img/player01.png");

》》 显示效果

4.5 背景移动

设置背景移动实际上是要改变游戏界面的背景位置,背景要向下移动,这样玩家就感觉是向上移动的。

》》 首先gameDiv的背景需要设置成平铺,这样当背景移动向下移动后,上面确实的部分会自动平铺补全。

/* 游戏页面样式**/
#game_div{
	position: absolute;
	width: 512px;
	height: 768px;
	background:url('../img/bg1.jpg') repeat;
	background-position: 0 100px;
	margin: auto;
	left: 0;
	right: 0;	
	display:none;
  /*超出界面的部分隐藏*/
	overflow: hidden;
	
}

》》 在main.js中的startGame方法中添加计时器,每隔一定时间调用一下让背景移动的方法,具体代码如下:

// 开始游戏的方法
function startGame(){
  // 隐藏开始界面
  startDiv.style.display = 'none';
  // 显示游戏界面
  gameDiv.style.display = 'block';
  // 开始游戏
  game_start = true;


  times = setInterval(function(){
    if(game_start){
      // 背景移动方法
      bg_move();
    }

  },20);
}

》》 在main.js中添加bg_move方法,让背景移动,具体代码如下:

/** 背景移动的方法**/
var flight = 0;
function bg_move(){
	//改变 gameDiv背景的位置
	gameDiv.style.backgroundPosition = "0px "+ flight+"px";
	flight ++;
}

4.6 玩家机移动

本游戏中我们通过键盘的上下左右键控制玩家移动。使用键盘控制就需要监听键盘的事件,然后根据不同的事件让玩家做不同的操作。

》》 在main.js 中添加监听键盘操作事件

// 按键开关
var upBtn = false;
var downBtn = false;
var leftBtn = false;
var rightBtn = false;
// 监听键盘按下去操作
document.body.onkeydown = function(){
	var e = window.event||arguments[0];
	// 如果按的是左键
	if(e.keyCode==37){
		// 打开向左移动的开关
		leftBtn = true; 
	}
	
	// 如果按的是上键
	if(e.keyCode==38){
		// 打开向上移动的开关
		upBtn  = true; 
	}
	// 如果按的是右键
	if(e.keyCode==39){
		// 打开向右移动的开关
		rightBtn = true;
	}
	// 如果按的是下键
	if(e.keyCode==40){
		// 打开向右移动的开关
		downBtn = true;
	}
}

// 检测键盘松开事件
document.body.onkeyup = function(){
	var e = window.event||arguments[0];
	// 如果松开的是左键
	if(e.keyCode==37){
		// 关闭向左移动的开关
		leftBtn = false;
	}
	if(e.keyCode==38){
    // 关闭向上移动的开关
		upBtn  = false;
	}
	if(e.keyCode==39){
		rightBtn = false;
	}
	if(e.keyCode==40){
		downBtn = false;
	}
}

上面的代码中,我们分别为上下左右的按键设置了开关,如果按下了上键,就把向上移动的开关打开,让玩家向上移动,如果松开了向上移动的开关,就把向上移动的开关关闭,以此类推。

具体做法就是,不停监听上、下、左、右的开关是否被打开,如果被打开,就调用玩家对应的方法移动,具体代码如下:

// 控制玩家移动的方法
function contr_player(){
  // 如果向上的开关被打开
	if(upBtn){
		player.moveUp();
	}

  // 如果向左的开关被打开
	if(leftBtn){
		player.moveLeft();
	}

	// 如果向右的开关被打开、
	if(rightBtn){
		player.moveRight();
	}
  
  // 如果向下的开关被打开
	if(downBtn){
		player.moveDown();
	}
}

紧接着在计时器中,不停的去执行这个contr_player方法,去监听各个方向的开关是否被打开,具体代码如下:

// 开始游戏的方法
function startGame(){
	// 隐藏开始界面
	startDiv.style.display = 'none';
	// 显示游戏界面
	gameDiv.style.display = 'block';
	// 开始游戏
	game_start = true;
	
	
	times = setInterval(function(){
		if(game_start){
			// 背景移动方法
			bg_move();
			// 控制玩家移动
			contr_player();
		}
		
	},20);
}

4.7 创建敌机

》》 创建ep.js,制作敌机模板


/**
 * 敌机模板
 */
function epPlane(){
	// 创建敌机图片节点
	this.imageNode = document.createElement("img");
	// 随机敌机种类(每次随机出现敌机)
	this.type = randomRange(1,10);
	// 敌机的移动速度,种类越大,速度越小
	this.speed = 12-this.type;
	// 敌机的血量,敌机越大血量越多
	this.hp = this.type;

	/*初始化敌机*/
	this.init = function(){
		// 根据敌机的种类设置敌机的图片
		this.imageNode.src = "img/enemy0"+this.type+".png";
		// 设置敌机的定位
		this.imageNode.style.position = "absolute";
		// 设置敌机的宽度
		this.width = parseInt(this.imageNode.width)/2;
		// 设置敌机的高度
		this.height = parseInt(this.imageNode.height)/2;
		this.imageNode.width = this.width;
		// 设置敌机的横坐标: 在游戏界面宽度范围内随机生成
		this.imageNode.style.left = randomRange(0,512-this.width)+"px";
		// 设置敌机的纵坐标
		this.imageNode.style.top = -this.height +"px";
		// 在游戏界面中添加敌机图片节点
		gameDiv.appendChild(this.imageNode)
	}
	// 调用初始化函数
	this.init();
		
}

/** 在min-max之间生成随机数的函数**/
function randomRange(min, max) { // min最小值,max最大值

    return Math.floor(Math.random() * (max - min)) + min;

}

》》 在index.html中引入ep.js

<script type="text/javascript" src="js/player.js"></script>
<script type="text/javascript" src="js/ep.js"></script>
<script type="text/javascript" src="js/main.js"></script>

》》 在main.js中创建敌机

// 创建敌机
var ep_ary=[]; // 敌机数组
var ep_index = 0; // 
function create_ep(){
	ep_index++;
	// 每执行30次create_ep方法,创建一个敌机,降低敌机出现的频率
	if(ep_index>30){  
		// 创建敌机
		var ep = new epPlane();
		// 将创建好的敌机加入到敌机数组中
		ep_ary.push(ep);
		
		// 计数器归0 重新计数
		ep_index = 0;
	}
	
}

》》 在main.js的startGame方法中调用创建敌机的方法,游戏开始后就创建敌机,具体代码如下:

// 开始游戏的方法
function startGame(){
	// 隐藏开始界面
	startDiv.style.display = 'none';
	// 显示游戏界面
	gameDiv.style.display = 'block';
	// 开始游戏
	game_start = true;
	
	
	times = setInterval(function(){
		if(game_start){
			// 背景移动方法
			bg_move();
			// 控制玩家移动
			contr_player();
			// 创建敌机
			create_ep();
		}
		
	},20);
}

4.8 敌机移动

》》 在ep.js中添加move方法,在move方法中实际上是通过改变图片的top样式去让敌机移动,具体代码如下:

// 敌机移动的方法
	this.move = function(){
		this.imageNode.style.top = parseInt(this.imageNode.style.top)+this.speed+"px";
	}

》》 为了节省内存开销,移出屏幕的敌机,我们需要从敌机数组和游戏界面中删除,在ep.js中添加判断敌机是否移出屏幕之外的方法,具体如下:

// 判断敌机是否向下移动越界
	this.overline= function(){
		// 如果敌机的top样式已经大于游戏界面的高度
		if(parseInt(this.imageNode.style.top)>=768){
			return true;
		}
	}

》》 在main.js中添加让所有敌机移动的方法,具体代码如下:

// 敌机移动的方法
function ep_move(){
	// 遍历所有的敌机
	for(var i=0;i<ep_ary.length;i++){
		// 获取敌机
		var ep = ep_ary[i];
		// 调用敌机移动的方法
		ep.move();
		// 如果当前敌机越界
		if(ep.overline()){
			// 删除节点
			gameDiv.removeChild(ep.imageNode);
			// 删除数组中元素
			ep_ary.splice(i,1);
		}
	}
}

》》 在main.js的startGame方法中找到计时器,在计时器中调用ep_move方法,让敌机移动,具体代码如下:

// 开始游戏的方法
function startGame(){
  // 隐藏开始界面
  startDiv.style.display = 'none';
  // 显示游戏界面
  gameDiv.style.display = 'block';
  // 开始游戏
  game_start = true;


  times = setInterval(function(){
    if(game_start){
      // 背景移动方法
      bg_move();
      // 控制玩家移动
      contr_player();
      // 创建敌机
      create_ep();
      // 敌机移动
      ep_move();
    }

  },20);
}

4.9 发射子弹

》》 新建子弹模板,bulletProto.js



/**
 * 子弹模板
 */

function bulletProto(x,y){
	// 子弹的图片节点
	this.imageNode = document.createElement("img");
	// 子弹的位置要根据玩家的横纵坐标来确定
	this.x = x;
	this.y = y;
	
	// 确定子弹的图片的路径
	this.imgsrc = "img/deathbullet.png";
	// 设置子弹的速度
	this.speed = 10;
	
	// 初始化子弹
	this.init = function(){
		// 设置子弹的图片
		this.imageNode.src = this.imgsrc;
		// 设置子弹的定位方式
		this.imageNode.style.position = 'absolute';
		// 子弹的大小缩小一下(原图太大了)
		this.width = this.imageNode.width/2;
		this.height = this.imageNode.height/2;
		this.imageNode.width = this.width;
		// 设置子弹的初始位置
		this.imageNode.style.left = this.x +"px";
		this.imageNode.style.top = this.y+"px";
		// 在游戏界面中加入子弹图片子节点
		gameDiv.appendChild(this.imageNode);
	}
	// 调用初始化函数
	this.init();
	
	// 子弹移动的方法
	this.move = function(){
		this.imageNode.style.top = parseInt(this.imageNode.style.top)-this.speed+"px";
	}
	
	// 子弹越界的判断方法
	this.overline = function(){
		if(parseInt(this.imageNode.style.top)<-this.imageNode.height){
			return true;
		}
	}
	
}

》》 在index.html中引入子弹模板

<script type="text/javascript" src="js/player.js"></script>
<script type="text/javascript" src="js/bulletProto.js"></script>
<script type="text/javascript" src="js/ep.js"></script>
<script type="text/javascript" src="js/main.js"></script>

》》 在main.js中 添加发射子弹的代码,这里我们发射三排子弹,具体代码如下:

// 发射子弹
var bs_ary = []; // 子弹数组
var b_index = 0;
function shoot_bullet(){
	b_index ++;
	if (b_index>10){  // 每执行10次shoot_bullet方法,发射一次子弹
		b_index = 0;
		//获取到玩家的横纵坐标
		var bx = parseInt(player.imageNode.style.left);
		var by = parseInt(player.imageNode.style.top);
		// 创建第一排子弹
		bullet = new bulletProto(bx+30,by-20);
		bs_ary.push(bullet);
		// 创建第二排子弹
		bullet = new bulletProto(bx+50,by-40);
		bs_ary.push(bullet);
		// 创建第三排子弹
		bullet = new bulletProto(bx+70,by-20);
		bs_ary.push(bullet);
	}
}

》》 在startGame方法的计时器中调用shoot_bullet方法,具体代码如下:

function startGame(){
	// 隐藏开始界面
	startDiv.style.display = 'none';
	// 显示游戏界面
	gameDiv.style.display = 'block';
	// 开始游戏
	game_start = true;
	
	
	times = setInterval(function(){
		if(game_start){
			// 背景移动方法
			bg_move();
			// 控制玩家移动
			contr_player();
			// 创建敌机
			create_ep();
			// 敌机移动
			ep_move();
			// 发射子弹
			shoot_bullet();
		}
		
	},20);
}

》》 运行效果如下图所示,每当玩家挪动位置时,就会在当前的位置发射三颗子弹:

》》 在main.js中添加让子弹移动的方法,具体代码如下:

// 子弹移动的方法
function bullet_move(){
	// 遍历所有的子弹
	for(var i=0;i<bs_ary.length;i++){
		// 获取子弹
		var b = bs_ary[i];
		// 让子弹移动
		b.move();
		// 如果子弹越界了
		if(b.overline()){
			// 从游戏界面中删除子弹节点
			gameDiv.removeChild(b.imageNode);
			// 从子弹数组中删除子弹
			bs_ary.splice(i,1);
		}
	}
}

》》 在startGame方法的计时器中调用子弹移动的方法,具体代码如下:

// 开始游戏的方法
function startGame(){
	// 隐藏开始界面
	startDiv.style.display = 'none';
	// 显示游戏界面
	gameDiv.style.display = 'block';
	// 开始游戏
	game_start = true;
	
	
	times = setInterval(function(){
		if(game_start){
			// 背景移动方法
			bg_move();
			// 控制玩家移动
			contr_player();
			// 创建敌机
			create_ep();
			// 敌机移动
			ep_move();
			// 发射子弹
			shoot_bullet();
			// 子弹移动
			bullet_move();
		}
		
	},20);
}

》》 运行效果,如下图所示:

4.10 子弹击中敌机检测

》》 在main.js中添加检测子弹是否击中敌机的代码

因为每一颗子弹都有可能击中敌机,每一个敌机都有可能被子弹击中,因此我们需要遍历所有的子弹和所有的敌机,去做碰撞检测,如果碰撞到了,就删除子弹,敌机血量减少,如果敌机血量小于0了,就删除敌机,具体代码如下:

// 检查是否射中敌机
function check_shoot(){
	//遍历所有的子弹
	for (var i=0;i<bs_ary.length;i++){
		// 获取当前子弹
		var b = bs_ary[i];
		// 遍历所有敌机
		for(var j=0;j<ep_ary.length;j++){
			// 获取当前敌机
			var ep = ep_ary[j];
			// 如果当前敌机被当前子弹击中
			if(ep.shootBy(b)){
				// 删除子弹节点
				gameDiv.removeChild(b.imageNode);
				// 从子弹数组中删除子弹
				bs_ary.splice(i,1);
				// 敌机血量减小
				ep.hp --;
				// 如果敌机的血量小于0
				if(ep.hp<0){
					// 删除当前敌机节点
					gameDiv.removeChild(ep.imageNode);
					// 从敌机数组中删除当前敌机
					ep_ary.splice(j,1);
				}
			
			}
		}
	}
}

》》 在ep.js中添加碰撞检测的方法shootBy:

// 子弹是否击中敌机的碰撞检测方法
	this.shootBy = function(b){
		var ex = parseInt(this.imageNode.style.left);
		var ey = parseInt(this.imageNode.style.top);
		var ew = parseInt(this.imageNode.width);
		var eh = parseInt(this.imageNode.height);
		var bx = parseInt(b.imageNode.style.left);
		var by = parseInt(b.imageNode.style.top);
		var bw = parseInt(b.imageNode.width);
		var bh = parseInt(b.imageNode.height);
		var hit = ex+ew>bx&& bx+bw>ex&&ey+eh>by&&by+bh>ey;
		
		return hit;
		
	}

》》 在main.js的startGame方法中找到计时器,在里面调用check_shoot方法,做碰撞检测,具体代码如下:

// 开始游戏的方法
function startGame(){
	// 隐藏开始界面
	startDiv.style.display = 'none';
	// 显示游戏界面
	gameDiv.style.display = 'block';
	// 开始游戏
	game_start = true;
	
	
	times = setInterval(function(){
		if(game_start){
			// 背景移动方法
			bg_move();
			// 控制玩家移动
			contr_player();
			// 创建敌机
			create_ep();
			// 敌机移动
			ep_move();
			// 发射子弹
			shoot_bullet();
			// 子弹移动
			bullet_move();
			// 子弹击中敌机的碰撞检测
			check_shoot();
		}
		
	},20);
}

4.11 敌机碰撞玩家检测

》》 在main.js中添加敌机碰撞玩家检测代码,具体如下:

// 检测敌机是否碰撞玩家的方法
function check_hit(){
	// 遍历所有的敌机
	for(var i=0;i<ep_ary.length;i++){
		// 获取当前敌机
		var ep = ep_ary[i];
		// 如果玩家被当前敌机击中
		if(player.hitBy(ep)){
			// 删除当前敌机节点
			gameDiv.removeChild(ep.imageNode);
			// 从敌机数组中删除当前敌机
			ep_ary.splice(i,1);
			// 修改玩家血量
			changePlayerHp();
		}
	}
}

// 找到显示血量的div
var hp_div = document.getElementById("fill_rect_hp");
// 找到显示游戏结束的div
var gameover_div = document.getElementById("game_over");
// 游戏是否结束的开关
var gameover = false;

// 修改玩家血量
function changePlayerHp(){
	// 玩家血量减少
	player_hp --;
	// 修改血条的宽度
	hp_div.style.width = player_hp*70 +"px";
	// 如果玩家血量小于0
	if(player_hp<=0){
		// 游戏结束
		gameover = true;
		// 显示游戏结束界面
		game_over.style.display = 'block';
	}
	
}

》》 在player.js中添加check_hit方法,实现检测碰撞的具体业务逻辑,代码如下:

// 检测是否被敌机撞上
	this.hitBy = function(e){
		var x = parseInt(this.imageNode.style.left);
		var y = parseInt(this.imageNode.style.top);
		var w = parseInt(this.imageNode.width);
		var h = parseInt(this.imageNode.height);
		var ex = parseInt(e.imageNode.style.left);
		var ey = parseInt(e.imageNode.style.top);
		var ew = parseInt(e.imageNode.width);
		var eh = parseInt(e.imageNode.height);
		var hit = x+w-20>ex&& ex+ew-20>x&&y+h-20>ey&&ey+eh-20>y;
		
		return hit;
		
	}

》》 在startGame方法中,找到计时器,在计时器中调用check_hit方法,检测玩家是否撞到敌机,并将计时器中调用的方法和gameover开关绑定起来,如果只有当游戏开始了且游戏没有结束时背景移动、玩家移动、发射子弹、创建敌机等等,具体代码如下:

// 开始游戏的方法
function startGame(){
	// 隐藏开始界面
	startDiv.style.display = 'none';
	// 显示游戏界面
	gameDiv.style.display = 'block';
	// 开始游戏
	game_start = true;
	
	
	times = setInterval(function(){
		if(game_start&&!gameover){
			// 背景移动方法
			bg_move();
			// 控制玩家移动
			contr_player();
			// 创建敌机
			create_ep();
			// 敌机移动
			ep_move();
			// 发射子弹
			shoot_bullet();
			// 子弹移动
			bullet_move();
			// 子弹击中敌机的碰撞检测
			check_shoot();
			// 检测敌机是否撞到玩家
			check_hit();
		}
		
	},20);
}

五、完整代码

5.1 游戏主页代码

》》 index.html中代码如下所示:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<title>全民飞机大战</title>
		<link href="css/main.css" type="text/css" rel="stylesheet"/>
	</head>
	<body>
		
		
		<div id="content_div">
			<!-- 准备开始游戏的界面 -->
			<div id="start_div">
				<!-- 游戏的LOGO -->
				<img src="img/LOGO.png" width="380px" id="logo"> 
				<!-- 开始界面上的玩家 -->
				<img src="img/player01.png" id="start_player" >
				<!-- 开始游戏按钮 -->
				<div id="start" onclick="startGame()">开始游戏</div>
				<!-- 关闭音效按钮 -->
				<div id="sort" onclick="sortGame()">关闭音效</div>
				<!-- 查看帮助按钮 -->
				<div id="help" onclick="helpGame()">查看帮助</div>
			</div>
			
			<!-- 游戏界面 -->
			<div id="game_div">
				<!-- hp血量图片 -->
				<img id="my_hp" src="img/hp_0.png">
				<!-- 血槽 -->
				<div id="rect_hp">
					<!-- 血条 -->
					<div id="fill_rect_hp"> </div>
				</div>
			</div>
			<!-- 结束游戏界面 -->
			<div id="game_over">
					Game Over<br/>		
				
			</div>
			
		</div>
		
	</body>
	<script type="text/javascript" src="js/player.js"></script>
	<script type="text/javascript" src="js/bulletProto.js"></script>
	<script type="text/javascript" src="js/ep.js"></script>
	<script type="text/javascript" src="js/main.js"></script>

</html>

5.2 游戏样式代码

》》 main.css 代码如下所示:

/* 网页整体背景设置成黑色*/
body{
  background-color: black;
}
/*主div设置相对定位*/
#content_div{
  position: relative;	
}

/* 设置准备游戏界面的样式*/
#start_div{
  position: absolute;
  width: 512px;
  height:768px;
  /* 在屏幕中居中显示 */
  margin: auto;
  left:0;
  right:0;
  background: url('../img/img_bg_logo.jpg');
  /* 将鼠标指针切换成手指 */
  cursor:pointer;
}

/* 准备界面中图片样式设置 */
#start_div>img{
  position: absolute;
  display: inline-block;
  margin: auto;
  left:0;
  right:0;

}

/* 准备游戏界面中按钮样式的设置 */
#start_div>div{

  width: 270px;
  height: 40px;
  border-radius: 5px;
  text-align: center;
  line-height: 40px;
  background-color: #4876FF;
  font: '楷体' 20px ;
  color:white;
  font-weight: bold;
  position: absolute;
  margin: auto;
  left:0;
  right:0;

}


/* 开始游戏按钮*/
#start{

  top:600px;
}

/* 关闭音效按钮 */
#sort{

  top:650px;
}
/* 查看帮助按钮 */
#help{

  top:700px;
}

/* logo显示的位置 */
#logo{
  top:80px;
}

/* 开始页面玩家图片显示的位置 */
#start_player{
  top:420px;
}


/* 开始页面按钮的伪类样式 */
#start_div>div:hover{
  background-color: cyan;

}


/* 游戏页面样式**/
#game_div{
  position: absolute;
  width: 512px;
  height: 768px;
  background:url('../img/bg1.jpg') repeat;
  background-position: 0 100px;
  margin: auto;
  left: 0;
  right: 0;

  display:none;
  overflow: hidden;

}

/* 玩家血量样式 */
#my_hp{
  margin-top: 10px;
  margin-left: 10px;
}

/* 玩家血槽样式 */
#rect_hp{
  border: 1px solid red;
  width: 210px;
  height: 30px;
  display: inline-block;
  margin-top: 10px;
  margin-left: 10px;
  text-align: left;
}

/* 玩家血量样式 */
#fill_rect_hp{
  width: 210px;
  height: 30px;
  background: green;
}


/* 游戏结束样式 */
#game_over{

  font-size: 80px;
  color:red;
  font-family: '微软雅黑';
  font-weight: bold;
  position: absolute;
  width: 512px;
  height:768px;
  margin: auto;
  left:0;
  right:0;
  text-align: center;
  line-height: 500px;
  display: none;

}

5.3 玩家模板代码

》》 player.js 代码如下:

/*
	玩家飞机
	
	属性:
		图片节点、图片、x坐标、y坐标、速度
	行为:
		移动
		发射子弹
		初始化: 将图片加到gamediv游戏界面中
	
	
*/
function playerPlane(imgSrc){
  // 创建一个img图片节点
  this.imageNode = document.createElement("img");
  // 初始化图片路径
  this.imgSrc = imgSrc;
  // 初始化玩家的速度
  this.speed = 10;


  // 初始化图片,将图片节点加入到游戏界面中
  this.init = function(){
    // 初始化图片节点中显示的图片
    this.imageNode.src = this.imgSrc;
    this.imageNode.setAttribute("id","player");
    // 初始化玩家的大小
    this.width = this.imageNode.width;
    this.height = this.imageNode.height;
    // 初始化图片节点的定位方式,以及位置
    this.imageNode.style.position = 'absolute';
    // 让玩家的位置显示在游戏界面水平居中的位置
    this.imageNode.style.left = (512-this.imageNode.width)/2+'px';
    this.imageNode.style.top = 550+'px';
    // 将初始化好的图片节点,作为子节点加入到游戏界面中
    gameDiv.appendChild(this.imageNode);


  }

  // 调用初始化函数
  this.init();


  // 向左运动的方法
  this.moveLeft=function(){
    // 获取玩家的横坐标
    this.x = parseInt(this.imageNode.style.left);
    // 如果横坐标小于0了
    if(this.x<0){
      // 让玩家停留在坐标为0的位置
      this.imageNode.style.left = 0;
    }else{
      // 让玩家横坐标减小
      this.imageNode.style.left = this.x-this.speed+"px";
    }


  }
  // 向右运动的方法
  this.moveRight=function(){
    // 获取玩家的横坐标
    this.x = parseInt(this.imageNode.style.left);
    // 如果玩家的横坐标超出了游戏界面的宽度
    if(this.x>512-this.width){
      // 让玩家不能继续向右移动,给一个固定的坐标
      this.imageNode.style.left = 512-this.width+"px" ;
    }else{
      // 让玩家向右移动
      this.imageNode.style.left = this.x+this.speed+"px";
    }

  }
  // 向上运动的方法
  this.moveUp=function(){
    console.log("shang");
    this.y = parseInt(this.imageNode.style.top);
    if(this.y<0){
      this.imageNode.style.top = 0;
    }else{
      this.imageNode.style.top = this.y-this.speed+"px";
    }

  }
  // 向下运动的方法
  this.moveDown=function(){
    this.y =  parseInt(this.imageNode.style.top);
    if(this.y>768-this.height){
      this.imageNode.style.top = 768-this.height+"px";
    }else{
      this.imageNode.style.top = parseInt(this.imageNode.style.top)+this.speed+"px";
    }

  }
  // 检测是否被敌机撞上
  this.hitBy = function(e){
    var x = parseInt(this.imageNode.style.left);
    var y = parseInt(this.imageNode.style.top);
    var w = parseInt(this.imageNode.width);
    var h = parseInt(this.imageNode.height);
    var ex = parseInt(e.imageNode.style.left);
    var ey = parseInt(e.imageNode.style.top);
    var ew = parseInt(e.imageNode.width);
    var eh = parseInt(e.imageNode.height);
    var hit = x+w-20>ex&& ex+ew-20>x&&y+h-20>ey&&ey+eh-20>y;

    return hit;

  }



}

5.4 敌机模板代码

》》 ep.js代码如下:


/**
 * 敌机模板
 */
function epPlane(){
	// 创建敌机图片节点
	this.imageNode = document.createElement("img");
	// 随机敌机种类(每次随机出现敌机)
	this.type = randomRange(1,10);
	// 敌机的移动速度,种类越大,速度越小
	this.speed = 12-this.type;
	// 敌机的血量,敌机越大血量越多
	this.hp = this.type;

	/*初始化敌机*/
	this.init = function(){
		// 根据敌机的种类设置敌机的图片
		this.imageNode.src = "img/enemy0"+this.type+".png";
		// 设置敌机的定位
		this.imageNode.style.position = "absolute";
		// 设置敌机的宽度
		this.width = parseInt(this.imageNode.width)/2;
		// 设置敌机的高度
		this.height = parseInt(this.imageNode.height)/2;
		this.imageNode.width = this.width;
		// 设置敌机的横坐标: 在游戏界面宽度范围内随机生成
		this.imageNode.style.left = randomRange(0,512-this.width)+"px";
		// 设置敌机的纵坐标
		this.imageNode.style.top = -this.height +"px";
		// 在游戏界面中添加敌机图片节点
		gameDiv.appendChild(this.imageNode)
	}
	// 调用初始化函数
	this.init();
	// 敌机移动的方法
	this.move = function(){
		this.imageNode.style.top = parseInt(this.imageNode.style.top)+this.speed+"px";
	}
	
	// 判断敌机是否向下移动越界
	this.overline= function(){
		// 如果敌机的top样式已经大于游戏界面的高度
		if(parseInt(this.imageNode.style.top)>=768){
			return true;
		}
	}
	
	// 子弹是否击中敌机的碰撞检测方法
	this.shootBy = function(b){
		var ex = parseInt(this.imageNode.style.left);
		var ey = parseInt(this.imageNode.style.top);
		var ew = parseInt(this.imageNode.width);
		var eh = parseInt(this.imageNode.height);
		var bx = parseInt(b.imageNode.style.left);
		var by = parseInt(b.imageNode.style.top);
		var bw = parseInt(b.imageNode.width);
		var bh = parseInt(b.imageNode.height);
		var hit = ex+ew>bx&& bx+bw>ex&&ey+eh>by&&by+bh>ey;
		
		return hit;
		
	}
		
}

/** 在min-max之间生成随机数的函数**/
function randomRange(min, max) { // min最小值,max最大值

    return Math.floor(Math.random() * (max - min)) + min;

}

5.5 子弹模板代码

》》 bulletProto.js代码如下:



/**
 * 子弹模板
 */

function bulletProto(x,y){
	// 子弹的图片节点
	this.imageNode = document.createElement("img");
	// 子弹的位置要根据玩家的横纵坐标来确定
	this.x = x;
	this.y = y;
	
	// 确定子弹的图片的路径
	this.imgsrc = "img/deathbullet.png";
	// 设置子弹的速度
	this.speed = 10;
	
	// 初始化子弹
	this.init = function(){
		// 设置子弹的图片
		this.imageNode.src = this.imgsrc;
		// 设置子弹的定位方式
		this.imageNode.style.position = 'absolute';
		// 子弹的大小缩小一下(原图太大了)
		this.width = this.imageNode.width/2;
		this.height = this.imageNode.height/2;
		this.imageNode.width = this.width;
		// 设置子弹的初始位置
		this.imageNode.style.left = this.x +"px";
		this.imageNode.style.top = this.y+"px";
		// 在游戏界面中加入子弹图片子节点
		gameDiv.appendChild(this.imageNode);
	}
	// 调用初始化函数
	this.init();
	
	// 子弹移动的方法
	this.move = function(){
		this.imageNode.style.top = parseInt(this.imageNode.style.top)-this.speed+"px";
	}
	
	// 子弹越界的判断方法
	this.overline = function(){
		if(parseInt(this.imageNode.style.top)<-this.imageNode.height){
			return true;
		}
	}
	
}

5.6 主js程序代码

》》 main.js代码如下:

//############## 开始页面 天使动画 ##################
// 索引,用来取图片
var player_index = 0;
// 开始页面玩家动画的方法
function start_player_animation(){
	// 每次执行方法时,索引增加,让索引指向下一张图片
	player_index ++;
	// 如果索引大于了最后一张图片的索引
	if(player_index>10){
		// 让索引回归到1,重新从第一张图片选取
		player_index = 1;
	}
	// 如果索引小于10
	if(player_index<10){
		// 拼接图片路径时,加0
		img_path = "img/player0"+player_index+".png";
	}else{
		img_path = "img/player"+player_index+".png";
	}
	// 根据拼接的图片路径,修改src属性值
	document.getElementById("start_player").src = img_path;
	
}
// 每隔300毫秒执行玩家动画的方法
setInterval(start_player_animation,300);


// ########## 开始界面切换到游戏界面#######
// 获取开始界面
var startDiv = document.getElementById("start_div");
// 获取游戏界面
var gameDiv = document.getElementById("game_div");

// 开始游戏的方法
function startGame(){
	// 隐藏开始界面
	startDiv.style.display = 'none';
	// 显示游戏界面
	gameDiv.style.display = 'block';
	// 开始游戏
	game_start = true;
	
	
	times = setInterval(function(){
		if(game_start&&!gameover){
			// 背景移动方法
			bg_move();
			// 控制玩家移动
			contr_player();
			// 创建敌机
			create_ep();
			// 敌机移动
			ep_move();
			// 发射子弹
			shoot_bullet();
			// 子弹移动
			bullet_move();
			// 子弹击中敌机的碰撞检测
			check_shoot();
			// 检测敌机是否撞到玩家
			check_hit();
		}
		
	},20);
}

/** 背景移动的方法**/
var flight = 0;
function bg_move(){
	//改变 gameDiv背景的位置
	gameDiv.style.backgroundPosition = "0px "+ flight+"px";
	flight ++;
}

// 创建玩家
var player =  new playerPlane("img/player01.png");

// 按键开关
var upBtn = false;
var downBtn = false;
var leftBtn = false;
var rightBtn = false;
// 监听键盘按下去操作
document.body.onkeydown = function(){
	var e = window.event||arguments[0];
	// 如果按的是左键
	if(e.keyCode==37){
		// 打开向左移动的开关
		leftBtn = true; 
	}
	
	// 如果按的是上键
	if(e.keyCode==38){
		// 打开向上移动的开关
		upBtn  = true; 
	}
	// 如果按的是右键
	if(e.keyCode==39){
		// 打开向右移动的开关
		rightBtn = true;
	}
	// 如果按的是下键
	if(e.keyCode==40){
		// 打开向右移动的开关
		downBtn = true;
	}
}

// 检测键盘松开事件
document.body.onkeyup = function(){
	var e = window.event||arguments[0];
	console.log(e.keyCode);
	// 如果松开的是左键
	if(e.keyCode==37){
		// 关闭向左移动的开关
		leftBtn = false;
	}
	if(e.keyCode==38){
		upBtn  = false;
	}
	if(e.keyCode==39){
		rightBtn = false;
	}
	if(e.keyCode==40){
		downBtn = false;
	}
}

// 控制玩家移动的方法
function contr_player(){
	if(upBtn){
		player.moveUp();
	}
	if(leftBtn){
		player.moveLeft();
	}
	if(rightBtn){
		player.moveRight();
	}
	if(downBtn){
		player.moveDown();
	}
}


// 创建敌机
var ep_ary=[]; // 敌机数组
var ep_index = 0; // 
function create_ep(){
	ep_index++;
	// 每执行30次create_ep方法,创建一个敌机,降低敌机出现的频率
	if(ep_index>30){  
		// 创建敌机
		var ep = new epPlane();
		// 将创建好的敌机加入到敌机数组中
		ep_ary.push(ep);
		
		// 计数器归0 重新计数
		ep_index = 0;
	}
	
}


// 敌机移动的方法
function ep_move(){
	// 遍历所有的敌机
	for(var i=0;i<ep_ary.length;i++){
		// 获取敌机
		var ep = ep_ary[i];
		// 调用敌机移动的方法
		ep.move();
		// 如果当前敌机越界
		if(ep.overline()){
			// 删除节点
			gameDiv.removeChild(ep.imageNode);
			// 删除数组中元素
			ep_ary.splice(i,1);
		}
	}
}



// 发射子弹
var bs_ary = []; // 子弹数组
var b_index = 0;
function shoot_bullet(){
	b_index ++;
	if (b_index>10){  // 每执行10次shoot_bullet方法,发射一次子弹
		b_index = 0;
		//获取到玩家的横纵坐标
		var bx = parseInt(player.imageNode.style.left);
		var by = parseInt(player.imageNode.style.top);
		// 创建第一排子弹
		bullet = new bulletProto(bx+30,by-20);
		bs_ary.push(bullet);
		// 创建第二排子弹
		bullet = new bulletProto(bx+50,by-40);
		bs_ary.push(bullet);
		// 创建第三排子弹
		bullet = new bulletProto(bx+70,by-20);
		bs_ary.push(bullet);
	}
}

// 子弹移动的方法
function bullet_move(){
	// 遍历所有的子弹
	for(var i=0;i<bs_ary.length;i++){
		// 获取子弹
		var b = bs_ary[i];
		// 让子弹移动
		b.move();
		// 如果子弹越界了
		if(b.overline()){
			// 从游戏界面中删除子弹节点
			gameDiv.removeChild(b.imageNode);
			// 从子弹数组中删除子弹
			bs_ary.splice(i,1);
		}
	}
}

// 检查是否射中敌机
function check_shoot(){
	//遍历所有的子弹
	for (var i=0;i<bs_ary.length;i++){
		// 获取当前子弹
		var b = bs_ary[i];
		// 遍历所有敌机
		for(var j=0;j<ep_ary.length;j++){
			// 获取当前敌机
			var ep = ep_ary[j];
			// 如果当前敌机被当前子弹击中
			if(ep.shootBy(b)){
				// 删除子弹节点
				gameDiv.removeChild(b.imageNode);
				// 从子弹数组中删除子弹
				bs_ary.splice(i,1);
				// 敌机血量减小
				ep.hp --;
				// 如果敌机的血量小于0
				if(ep.hp<0){
					// 删除当前敌机节点
					gameDiv.removeChild(ep.imageNode);
					// 从敌机数组中删除当前敌机
					ep_ary.splice(j,1);
				}
			
			}
		}
	}
}


// 英雄机的血量
var player_hp = 3;

// 检测敌机是否碰撞玩家的方法
function check_hit(){
	// 遍历所有的敌机
	for(var i=0;i<ep_ary.length;i++){
		// 获取当前敌机
		var ep = ep_ary[i];
		// 如果玩家被当前敌机击中
		if(player.hitBy(ep)){
			// 删除当前敌机节点
			gameDiv.removeChild(ep.imageNode);
			// 从敌机数组中删除当前敌机
			ep_ary.splice(i,1);
			// 修改玩家血量
			changePlayerHp();
		}
	}
}

// 找到显示血量的div
var hp_div = document.getElementById("fill_rect_hp");
// 找到显示游戏结束的div
var gameover_div = document.getElementById("game_over");
// 游戏是否结束的开关
var gameover = false;

// 修改玩家血量
function changePlayerHp(){
	// 玩家血量减少
	player_hp --;
	// 修改血条的宽度
	hp_div.style.width = player_hp*70 +"px";
	// 如果玩家血量小于0
	if(player_hp<=0){
		// 游戏结束
		gameover = true;
		// 显示游戏结束界面
		game_over.style.display = 'block';
	}
	
}

好辣结束辣-给个不要钱三连吧:关注+点赞+收藏 O(∩_∩)O 

  • 18
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

听潮阁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值