下面我们来看代码!!!
项目结构
整个项目由五部分组成,分为背景音乐、基础样式、动图、核心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;
}
感兴趣的小伙伴也可以下载体验一下,勾起了慢慢的童年回忆。
源码地址:
好啦,本期内容就分享到这里,我们下期见!