下面我们来看代码!!!

 

项目结构


整个项目由五部分组成,分为背景音乐、基础样式、动图、核心JS及首页静态展示。不涉及后端,纯前端实现。

index.html



<!DOCTYPE html>

<html lang="zh" class="no-js demo-1">

<head>

<meta charset="UTF-8" />

<meta ="X-UA-Compatible" content="IE=edge,chrome=1">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<script src="js/jquery.min.js"></script>

<script src="js/Helper.js"></script>

<script src="js/keyboard.js"></script>

<script src="js/const.js"></script>

<script src="js/level.js"></script>

<script src="js/crackAnimation.js"></script>

<script src="js/prop.js"></script>

<script src="js/bullet.js"></script>

<script src="js/tank.js"></script>

<script src="js/num.js"></script>

<script src="js/menu.js"></script>

<script src="js/map.js"></script>

<script src="js/Collision.js"></script>

<script src="js/stage.js"></script>

<script src="js/main.js"></script>

<link rel="stylesheet" type="text/css" href="css/default.css" />

<style type="text/css">

#canvasDiv canvas{

position:absolute;

}

</style>

</head>

<body>

<div class="container">

<head><h3>操作说明:玩家1:WASD上左下右,space射击;玩家2:方向键,enter射击。n下一关,p上一关。</h3></head>

<div class="main clearfix">

<div id="canvasDiv" >

<canvas id="wallCanvas" ></canvas>

<canvas id="tankCanvas" ></canvas>

<canvas id="grassCanvas" ></canvas>

<canvas id="overCanvas" ></canvas>

<canvas id="stageCanvas" ></canvas>

</div>

</div>



</div><!-- /container -->

<div style="text-align:center;">

<p>来源:<a href="https://sunmenglei.blog.csdn.net/" target="_blank">孙叫兽的博客</a></p>

</div>



</body>

</html>
 

css



*, *:after, *:before { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; }

body, html { font-size: 100%; padding: 0; margin: 0; height: 100%;}



/* Clearfix hack by Nicolas Gallagher: http://nicolasgallagher.com/micro-clearfix-hack/ */

.clearfix:before, .clearfix:after { content: " "; display: table; }

.clearfix:after { clear: both; }



body {

font-family: "Helvetica Neue",Helvetica,Arial,'Microsoft YaHei',sans-serif,'Lato', Calibri;

color: #777;

background: #f6f6f6;

}



a {

color: #555;

text-decoration: none;

outline: none;

}



a:hover,

a:active {

color: #777;

}



a img {

border: none;

}



.main,

.container > header {

margin: 0 auto;

/*padding: 2em;*/

}



.container {

height: 100%;

}



.container > header {

padding-top: 20px;

padding-bottom: 20px;

text-align: center;

background: rgba(0,0,0,0.01);

}



.container > header h1 {

font-size: 2.625em;

line-height: 1.3;

margin: 0;

font-weight: 300;

}



.container > header span {

display: block;

font-size: 60%;

opacity: 0.3;

padding: 0 0 0.6em 0.1em;

}



/* Main Content */

.main {

/*max-width: 69em;*/

width: 100%;

height: 100%;

overflow: hidden;

}

.demo-scroll{

overflow-y: scroll;

width: 100%;

height: 100%;

}

.column {

float: left;

width: 50%;

padding: 0 2em;

min-height: 300px;

position: relative;

}



.column:nth-child(2) {

box-shadow: -1px 0 0 rgba(0,0,0,0.1);

}



.column p {

font-weight: 300;

font-size: 2em;

padding: 0;

margin: 0;

text-align: right;

line-height: 1.5;

}



/* To Navigation Style */

.htmleaf-top {

background: #fff;

background: rgba(255, 255, 255, 0.6);

text-transform: uppercase;

width: 100%;

font-size: 0.69em;

line-height: 2.2;

}



.htmleaf-top a {

padding: 0 1em;

letter-spacing: 0.1em;

color: #888;

display: inline-block;

}



.htmleaf-top a:hover {

background: rgba(255,255,255,0.95);

color: #333;

}



.htmleaf-top span.right {

float: right;

}



.htmleaf-top span.right a {

float: left;

display: block;

}



.htmleaf-icon:before {

font-family: 'codropsicons';

margin: 0 4px;

speak: none;

font-style: normal;

font-weight: normal;

font-variant: normal;

text-transform: none;

line-height: 1;

-webkit-font-smoothing: antialiased;

}







/* Demo Buttons Style */

.htmleaf-demos {

padding-top: 1em;

font-size: 0.9em;

}



.htmleaf-demos a {

display: inline-block;

margin: 0.2em;

padding: 0.45em 1em;

background: #999;

color: #fff;

font-weight: 700;

border-radius: 2px;

}



.htmleaf-demos a:hover,

.htmleaf-demos a.current-demo,

.htmleaf-demos a.current-demo:hover {

opacity: 0.6;

}



.htmleaf-nav {

text-align: center;

}



.htmleaf-nav a {

display: inline-block;

margin: 20px auto;

padding: 0.3em;

}

.bb-custom-wrapper {

width: 420px;

position: relative;

margin: 0 auto 40px;

text-align: center;

}

/* Demo Styles */



.demo-1 body {

color: #87968e;

background: #fff2e3;

}



.demo-1 a {

color: #72b890;

}



.demo-1 .htmleaf-demos a {

background: #72b890;

color: #fff;

}



.demo-2 body {

color: #fff;

background: #c05d8e;

}



.demo-2 a {

color: #d38daf;

}



.demo-2 a:hover,

.demo-2 a:active {

color: #fff;

}



.demo-2 .htmleaf-demos a {

background: #883b61;

color: #fff;

}



.demo-2 .htmleaf-top a:hover {

background: rgba(255,255,255,0.3);

color: #333;

}



.demo-3 body {

color: #87968e;

background: #fff2e3;

}



.demo-3 a {

color: #ea5381;

}



.demo-3 .htmleaf-demos a {

background: #ea5381;

color: #fff;

}



.demo-4 body {

color: #999;

background: #fff2e3;

overflow: hidden;

}



.demo-4 a {

color: #1baede;

}



.demo-4 a:hover,

.demo-4 a:active {

opacity: 0.6;

}



.demo-4 .htmleaf-demos a {

background: #1baede;

color: #fff;

}



.demo-5 body {

background: #fffbd6;

}

/****/

.related {

/*margin-top: 5em;*/

color: #fff;

background: #333;

text-align: center;

font-size: 1.25em;

padding: 3em 0;

overflow: hidden;

}



.related a {

display: inline-block;

text-align: left;

margin: 20px auto;

padding: 10px 20px;

opacity: 0.8;

-webkit-transition: opacity 0.3s;

transition: opacity 0.3s;

-webkit-backface-visibility: hidden;

}



.related a:hover,

.related a:active {

opacity: 1;

}



.related a img {

max-width: 100%;

}



.related a h3 {

font-weight: 300;

margin-top: 0.15em;

color: #fff;

}



@media screen and (max-width: 40em) {



.htmleaf-icon span {

display: none;

}



.htmleaf-icon:before {

font-size: 160%;

line-height: 2;

}



}



@media screen and (max-width: 46.0625em) {

.column {

width: 100%;

min-width: auto;

min-height: auto;

padding: 1em;

}



.column p {

text-align: left;

font-size: 1.5em;

}



.column:nth-child(2) {

box-shadow: 0 -1px 0 rgba(0,0,0,0.1);

}

}



@media screen and (max-width: 25em) {



.htmleaf-icon span {

display: none;

}



}
 

核心js



/**

* 检测2个物体是否碰撞

* @param object1 物体1

* @param object2 物体2

* @param overlap 允许重叠的大小

* @returns {Boolean} 如果碰撞了,返回true

*/

function CheckIntersect(object1, object2, overlap)

{

// x-轴 x-轴

// A1------>B1 C1 A2------>B2 C2

// +--------+ ^ +--------+ ^

// | object1| | y-轴 | object2| | y-轴

// | | | | | |

// +--------+ D1 +--------+ D2

//

//overlap是重叠的区域值

A1 = object1.x + overlap;

B1 = object1.x + object1.size - overlap;

C1 = object1.y + overlap;

D1 = object1.y + object1.size - overlap;



A2 = object2.x + overlap;

B2 = object2.x + object2.size - overlap;

C2 = object2.y + overlap;

D2 = object2.y + object2.size - overlap;



//假如他们在x-轴重叠

if(A1 >= A2 && A1 <= B2

|| B1 >= A2 && B1 <= B2)

{

//判断y-轴重叠

if(C1 >= C2 && C1 <= D2 || D1 >= C2 && D1 <= D2)

{

return true;

}

}

return false;

}



/**

* 坦克与地图块碰撞

* @param tank 坦克对象

* @param mapobj 地图对象

* @returns {Boolean} 如果碰撞,返回true

*/

function tankMapCollision(tank,mapobj){

//移动检测,记录最后一次的移动方向,根据方向判断+-overlap,

var tileNum = 0;//需要检测的tile数

var rowIndex = 0;//map中的行索引

var colIndex = 0;//map中的列索引

var overlap = 3;//允许重叠的大小



//根据tank的x、y计算出map中的row和col

if(tank.dir == UP){

rowIndex = parseInt((tank.tempY + overlap - mapobj.offsetY)/mapobj.tileSize);

colIndex = parseInt((tank.tempX + overlap- mapobj.offsetX)/mapobj.tileSize);

}else if(tank.dir == DOWN){

//向下,即dir==1的时候,行索引的计算需要+tank.Height

rowIndex = parseInt((tank.tempY - overlap - mapobj.offsetY + tank.size)/mapobj.tileSize);

colIndex = parseInt((tank.tempX + overlap- mapobj.offsetX)/mapobj.tileSize);

}else if(tank.dir == LEFT){

rowIndex = parseInt((tank.tempY + overlap- mapobj.offsetY)/mapobj.tileSize);

colIndex = parseInt((tank.tempX + overlap - mapobj.offsetX)/mapobj.tileSize);

}else if(tank.dir == RIGHT){

rowIndex = parseInt((tank.tempY + overlap- mapobj.offsetY)/mapobj.tileSize);

//向右,即dir==3的时候,列索引的计算需要+tank.Height

colIndex = parseInt((tank.tempX - overlap - mapobj.offsetX + tank.size)/mapobj.tileSize);

}

if(rowIndex >= mapobj.HTileCount || rowIndex < 0 || colIndex >= mapobj.wTileCount || colIndex < 0){

return true;

}

if(tank.dir == UP || tank.dir == DOWN){

var tempWidth = parseInt(tank.tempX - map.offsetX - (colIndex)*mapobj.tileSize + tank.size - overlap);//去除重叠部分

if(tempWidth % mapobj.tileSize == 0 ){

tileNum = parseInt(tempWidth/mapobj.tileSize);

}else{

tileNum = parseInt(tempWidth/mapobj.tileSize) + 1;

}

for(var i=0;i<tileNum && colIndex+i < mapobj.wTileCount ;i++){

var mapContent = mapobj.mapLevel[rowIndex][colIndex+i];

if(mapContent == WALL || mapContent == GRID || mapContent == WATER || mapContent == HOME || mapContent == ANOTHREHOME){

if(tank.dir == UP){

tank.y = mapobj.offsetY + rowIndex * mapobj.tileSize + mapobj.tileSize - overlap;

}else if(tank.dir == DOWN){

tank.y = mapobj.offsetY + rowIndex * mapobj.tileSize - tank.size + overlap;

}

return true;

}

}

}else{

var tempHeight = parseInt(tank.tempY - map.offsetY - (rowIndex)*mapobj.tileSize + tank.size - overlap);//去除重叠部分

if(tempHeight % mapobj.tileSize == 0 ){

tileNum = parseInt(tempHeight/mapobj.tileSize);

}else{

tileNum = parseInt(tempHeight/mapobj.tileSize) + 1;

}

for(var i=0;i<tileNum && rowIndex+i < mapobj.HTileCount;i++){

var mapContent = mapobj.mapLevel[rowIndex+i][colIndex];

if(mapContent == WALL || mapContent == GRID || mapContent == WATER || mapContent == HOME || mapContent == ANOTHREHOME){

if(tank.dir == LEFT){

tank.x = mapobj.offsetX + colIndex * mapobj.tileSize + mapobj.tileSize - overlap;

}else if(tank.dir == RIGHT){

tank.x = mapobj.offsetX + colIndex * mapobj.tileSize - tank.size + overlap;

}

return true;

}

}

}

return false;

}



/**

* 子弹与地图块的碰撞

* @param bullet 子弹对象

* @param mapobj 地图对象

*/

function bulletMapCollision(bullet,mapobj){

var tileNum = 0;//需要检测的tile数

var rowIndex = 0;//map中的行索引

var colIndex = 0;//map中的列索引

var mapChangeIndex = [];//map中需要更新的索引数组

var result = false;//是否碰撞

//根据bullet的x、y计算出map中的row和col

if(bullet.dir == UP){

rowIndex = parseInt((bullet.y - mapobj.offsetY)/mapobj.tileSize);

colIndex = parseInt((bullet.x - mapobj.offsetX)/mapobj.tileSize);

}else if(bullet.dir == DOWN){

//向下,即dir==1的时候,行索引的计算需要+bullet.Height

rowIndex = parseInt((bullet.y - mapobj.offsetY + bullet.size)/mapobj.tileSize);

colIndex = parseInt((bullet.x - mapobj.offsetX)/mapobj.tileSize);

}else if(bullet.dir == LEFT){

rowIndex = parseInt((bullet.y - mapobj.offsetY)/mapobj.tileSize);

colIndex = parseInt((bullet.x - mapobj.offsetX)/mapobj.tileSize);

}else if(bullet.dir == RIGHT){

rowIndex = parseInt((bullet.y - mapobj.offsetY)/mapobj.tileSize);

//向右,即dir==3的时候,列索引的计算需要+bullet.Height

colIndex = parseInt((bullet.x - mapobj.offsetX + bullet.size)/mapobj.tileSize);

}

if(rowIndex >= mapobj.HTileCount || rowIndex < 0 || colIndex >= mapobj.wTileCount || colIndex < 0){

return true;

}



if(bullet.dir == UP || bullet.dir == DOWN){

var tempWidth = parseInt(bullet.x - map.offsetX - (colIndex)*mapobj.tileSize + bullet.size);

if(tempWidth % mapobj.tileSize == 0 ){

tileNum = parseInt(tempWidth/mapobj.tileSize);

}else{

tileNum = parseInt(tempWidth/mapobj.tileSize) + 1;

}

for(var i=0;i<tileNum && colIndex+i < mapobj.wTileCount ;i++){

var mapContent = mapobj.mapLevel[rowIndex][colIndex+i];

if(mapContent == WALL || mapContent == GRID || mapContent == HOME || mapContent == ANOTHREHOME){

//bullet.distroy();

result = true;

if(mapContent == WALL){

//墙被打掉

mapChangeIndex.push([rowIndex,colIndex+i]);

}else if(mapContent == GRID){



}else{

isGameOver = true;

break;

}

}

}

}else{

var tempHeight = parseInt(bullet.y - map.offsetY - (rowIndex)*mapobj.tileSize + bullet.size);

if(tempHeight % mapobj.tileSize == 0 ){

tileNum = parseInt(tempHeight/mapobj.tileSize);

}else{

tileNum = parseInt(tempHeight/mapobj.tileSize) + 1;

}

for(var i=0;i<tileNum && rowIndex+i < mapobj.HTileCount;i++){

var mapContent = mapobj.mapLevel[rowIndex+i][colIndex];

if(mapContent == WALL || mapContent == GRID || mapContent == HOME || mapContent == ANOTHREHOME){

//bullet.distroy();

result = true;

if(mapContent == WALL){

//墙被打掉

mapChangeIndex.push([rowIndex+i,colIndex]);

}else if(mapContent == GRID){



}else{

isGameOver = true;

break;

}

}

}

}

//更新地图

map.updateMap(mapChangeIndex,0);

return result;

}
 

感兴趣的小伙伴也可以下载体验一下,勾起了慢慢的童年回忆。

源码地址:

点我下载

 

好啦,本期内容就分享到这里,我们下期见!

 

智一面web前端开发工程师(vue)