先看看效果图
github地址:https://github.com/dong432/plane
1、首先准备好需要的素材,己方飞机、敌方飞机、爆炸效果(可以自行寻找素材,一下是该项目所用素材)
2、创建好父类-plane.js,(好像没啥用,就保存dom和一些属性)
class plane {
constructor(options) {
this.dom = options.dom;
for (let key in options) {
this[key] = options[key];
}
}
}
3、创建己方飞机类-mine.js
继承父类plane,给定己方飞机高度宽度和位置,然后给己方飞机绑定拖拽事件
class mine extends plane {
constructor (options) {
super({
name: 'mine',
left: 0,
top: 0,
...options,
dom: document.createElement('div')
})
this.dom.className = 'mine'
// 设置飞机在屏幕下方中间
this.left = bodyWidth / 2 - this.width / 2;
this.top = bodyHeight - 100;
// 把飞机放到body里面
document.body.appendChild(this.dom)
// 绑定拖拽事件
this.draggable()
}
// 让元素可拖拽
draggable() {
this.dom.onmousedown = (e) => {
let left = e.clientX - this.dom.offsetLeft;
let top = e.clientY - this.dom.offsetTop;
document.onmousemove = ({ clientX, clientY }) => {
this.left =
clientX - left < 0 - this.width / 2
? -this.width / 2
: clientX - left > bodyWidth - this.width / 2
? this.left
: clientX - left;
this.top =
clientY - top < 0
? 0
: clientY - top > bodyHeight - this.height
? this.top
: clientY - top;
render(this)
};
document.onmouseup = (e) => {
document.onmousemove = null;
this.dom.onmouseup = null;
};
};
}
}
4、创建子弹类-bullet.js
子弹为敌人和英雄共用类,所以设置了子弹方向(direction),速度(speed),颜色(color),
给子弹实例一个定时器,让它不停向下或向上移动,当子弹超出屏幕时,删除子弹实例,移除dom元素
class bullet extends plane {
constructor(options) {
super({
...options,
name: 'enemy',
dom: document.createElement('div')
})
this.dom.style.backgroundColor = options.color || "#000";
this.dom.className = 'bullet';
// 子弹显示停留做个透明度过渡衔接
this.dom.style.opacity = 0;
document.body.appendChild(this.dom);
this.upgo()
}
upgo() {
let clock = setInterval(() => {
if (this.direction == 'up') this.top -= this.speed;
else if (this.direction == 'down') this.top += this.speed;
else {
clearInterval(clock)
console.warn('请设置子弹方向')
}
this.dom.style.opacity = 1;
if (this.top <= -this.height && this.direction == 'up') {
bulletObj[this.index] = null;
delete bulletObj[this.index]
clearInterval(clock)
return this.dom.remove()
} else if (this.top >= bodyHeight && this.direction == 'down') {
enemyBulletObj[this.index] = null;
delete enemyBulletObj[this.index]
clearInterval(clock)
return this.dom.remove()
}
}, 100);
this.moveClock = clock
}
}
5、创建敌机类-enemy.js
创建一个敌机,left 为屏幕宽度随机位置,top 为负的敌机高度,然后给敌机创建子弹,速度应快于敌机下降速度,当敌机超出屏幕,删除敌机实例,移除dom元素
class enemy extends plane {
constructor(options) {
super({
...options,
top: -options.height,
left: Math.floor(Math.random() * bodyWidth),
name: 'enemy',
dom: document.createElement('div')
})
this.dom.className = "enemy"
document.body.appendChild(this.dom)
this.down()
this.createBullet()
}
// 向下坠
down() {
let clock = setInterval(() => {
this.top += this.speed
if (this.top >= bodyHeight) {
clearInterval(clock)
clearInterval(this.enemyBulletClock)
enemyObj[this.index] = null
delete enemyObj[this.index]
return this.dom.remove()
}
}, 100);
this.moveClock = clock
}
// 创建子弹
createBullet() {
let enemyBulletClock = setInterval(() => {
enemyBulletKey++;
const width = 10, height = 20
let enemyBullet = new bullet({
speed: this.speed + 30,
width,
height,
direction: 'down',
left: this.left + this.width / 2 - width / 2,
top: this.top + this.height,
index: enemyBulletKey
})
enemyBulletObj[enemyBulletKey] = enemyBullet
}, 500);
this.enemyBulletClock = enemyBulletClock
}
}
6、创建游戏结束类-gameover.js
游戏结束,创建一个弹窗在屏幕正中间,显示分数和评语,以及重新开始按钮
class gameover extends plane{
constructor(options) {
super({
...options,
dom: document.createElement('div')
})
this.draggable()
this.dom.className = 'gameover'
let title = document.createElement('div')
title.className = 'title'
title.innerText = '游戏结束'
this.dom.appendChild(title)
let scoreDom = document.createElement('div')
scoreDom.className = 'score'
scoreDom.innerText = '分数:' + score
this.dom.appendChild(scoreDom)
let time = document.createElement('div')
time.className = 'time'
time.innerText = score > 200 ? '超神' : score > 120 ? '666' : score > 50 ? '太棒啦' : '继续努力'
this.dom.appendChild(time)
let btn = document.createElement('button')
btn.className = 'resetGame'
btn.innerText = '重新开始'
btn.onclick = () => {
location.reload()
}
this.dom.appendChild(btn)
document.body.appendChild(this.dom)
}
}
7、创建html文件-index.html
创建维护己方子弹对象
创建维护敌机对象
创建维护敌方子弹对象
用于监听他们之间的碰撞情况,用一个自增key作为他们的属性
changeCoordinate-更新dom,并监听是否发生碰撞
render-执行各个元素位置更新
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>飞机大战</title>
<link rel="stylesheet" href="./index.css" />
</head>
<body>
<div class="scores">得分:0</div>
<button class="begin">开始游戏</button>
<script>
// 创建维护敌机的对象
let enemyObj = {};
// 创建维护子弹的对象
let bulletObj = {};
// 创建维护敌人子弹的对象
let enemyBulletObj = {};
// 创建敌人子弹自增key
let enemyBulletKey = 0;
// 创建子弹自增key
let bulletKey = 0;
// 创建敌机自增key
let enemyKey = 0;
// 游戏是否结束
let over = false;
// 分数
let score = 0;
// 屏幕宽高
let bodyWidth =
document.compatMode == "BackCompat"
? document.body.clientWidth
: document.documentElement.clientWidth;
let bodyHeight =
document.compatMode == "BackCompat"
? document.body.clientHeight
: document.documentElement.clientHeight;
// 更新dom,监听是否碰撞
function changeCoordinate() {
if (over) return;
if (mineExample) {
this.render(mineExample);
}
for (let key in bulletObj) {
this.render(bulletObj[key]);
}
for (let key in enemyObj) {
this.render(enemyObj[key]);
}
for (let key in enemyBulletObj) {
this.render(enemyBulletObj[key]);
}
let l3 = mineExample.dom.offsetLeft;
let t3 = mineExample.dom.offsetTop;
let r3 = l3 + mineExample.dom.offsetWidth;
let b3 = t3 + mineExample.dom.offsetHeight;
for (let bulletKey in bulletObj) {
const bulletDom = bulletObj[bulletKey].dom;
for (let enemyKey in enemyObj) {
const enemyDom = enemyObj[enemyKey].dom;
let l1 = bulletDom.offsetLeft;
let t1 = bulletDom.offsetTop;
let r1 = l1 + bulletDom.offsetWidth;
let b1 = t1 + bulletDom.offsetHeight;
let l2 = enemyDom.offsetLeft;
let t2 = enemyDom.offsetTop;
let r2 = l2 + enemyDom.offsetWidth;
let b2 = t2 + enemyDom.offsetHeight;
if (r1 > l2 && l1 < r2 && b1 > t2 && t1 < b2 && !enemyDom.bao) {
enemyDom.bao = true;
enemyDom.style.backgroundImage = `url(./bao.gif?time=${Math.random()})`;
bulletObj[bulletKey] = null;
delete bulletObj[bulletKey];
// 敌机毁灭,停止发射子弹
clearInterval(enemyObj[enemyKey].enemyBulletClock);
clearInterval(enemyObj[enemyKey].moveClock);
bulletDom.remove();
score++;
scoreDom.innerHTML = "得分:" + score;
setTimeout(() => {
enemyObj[enemyKey] = null;
delete enemyObj[enemyKey];
enemyDom.remove();
}, 800);
}
if (r2 > l3 && l2 < r3 && b2 > t3 && t2 < b3 && !enemyDom.bao) {
return this.gameOver();
}
}
}
for (let key in enemyBulletObj) {
const enemyBulletDom = enemyBulletObj[key].dom;
let l2 = enemyBulletDom.offsetLeft;
let t2 = enemyBulletDom.offsetTop;
let r2 = l2 + enemyBulletDom.offsetWidth;
let b2 = t2 + enemyBulletDom.offsetHeight;
if (r2 > l3 && l2 < r3 && b2 > t3 && t2 < b3) {
return this.gameOver();
}
}
}
function render(temp) {
if (over) return;
temp.dom.style.width = temp.width + "px";
temp.dom.style.height = temp.height + "px";
temp.dom.style.left = temp.left + "px";
temp.dom.style.top = temp.top + "px";
}
// 游戏结束
function gameOver() {
over = true;
const width = 400,
height = 400;
new gameover({
width,
height,
left: bodyWidth / 2 - width / 2,
top: bodyHeight / 2 - height / 2,
});
for (let key in enemyObj) {
const item = enemyObj[key];
clearInterval(item.enemyBulletClock);
clearInterval(item.clock);
clearInterval(item.moveClock);
}
for (let key in bulletObj) {
const item = bulletObj[key];
clearInterval(item.clock);
clearInterval(item.moveClock);
}
for (let key in enemyBulletObj) {
const item = enemyBulletObj[key];
clearInterval(item.moveClock);
}
mineExample.dom.onmousedown = null;
}
const begin = document.querySelector(".begin");
let scoreDom = null;
let mineExample = null;
// 子弹发射频率 - 100毫秒
const launch = 100;
begin.onclick = () => {
begin.style.display = "none";
// 获取分数dom对象
scoreDom = document.querySelector(".scores");
// 创建自己
/*
width: number - 自己宽度
height: number - 自己高度
*/
mineExample = new mine({
width: 52,
height: 26,
});
// 循环创建自己的子弹
let bulletClock = setInterval(() => {
bulletKey++;
/*
speed: number - 子弹速度
direction: up | down - 子弹方向
left: number - 坐标x
top: number - 坐标y
width: number - 子弹宽度
height: number - 子弹高度
color: string - 子弹颜色
*/
// 生成随机颜色
const getRandomColor = () => {
let color = "#";
const str = "abcdef1234567890";
const strL = str.length;
for (let i = 0; i < 6; i++) {
color += str[Math.floor(Math.random() * strL)];
}
return color;
};
const width = 4,
height = 10;
let biu = new bullet({
speed: 60,
direction: "up",
left: mineExample.left + mineExample.width / 2 - width / 2,
top: mineExample.top,
width,
height,
color: getRandomColor(),
index: bulletKey,
clock: bulletClock,
});
bulletObj[bulletKey] = biu;
}, launch);
const createEnemyTime = 100;
// 循环生成敌人
let enemyClock = setInterval(() => {
enemyKey++;
/*
speed: number - 敌机速度
width: number - 敌机宽度
height: number - 敌机高度
*/
let di = new enemy({
width: 50,
height: 50,
speed: 20,
index: enemyKey,
clock: enemyClock,
});
enemyObj[enemyKey] = di;
}, createEnemyTime);
setInterval(() => {
changeCoordinate();
}, 20);
};
</script>
<script src="./plane.js"></script>
<script src="./gameover.js"></script>
<script src="./mine.js"></script>
<script src="./bullet.js"></script>
<script src="./enemy.js"></script>
</body>
</html>
8、最后加上样式-index.css
* { margin: 0; padding: 0; user-select: none; }
body {
width: 100vw;
height: 100vh;
overflow: hidden;
}
.mine {
cursor: pointer;
position: absolute;
background-color: transparent;
background-image: url(./mine.png);
background-repeat: no-repeat;
background-size: cover;
background-position: center center;
border-radius: 3px;
}
.bullet {
position: absolute;
border-radius: 3px;
transition: all .1s linear 0s;
}
.enemy {
cursor: pointer;
position: absolute;
background-color: transparent;
background-image: url(./enemy.png);
background-repeat: no-repeat;
background-size: cover;
background-position: center center;
border-radius: 3px;
transition: all .1s linear 0s;
}
.gameover {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1;
width: 400px;
height: 400px;
background-color: #eee;
border-radius: 20px;
overflow: hidden;
box-shadow: 0 0 5px 5px #999;
text-align: center;
}
.gameover .title {
background-color: brown;
line-height: 50px;
color: #fff;
font-size: 20px;
font-weight: bold;
}
.gameover .score {
font-size: 60px;
color: #0000ae;
font-family: monospace;
margin-top: 30px;
}
.gameover .time {
margin-top: 40px;
font-size: 30px;
}
.gameover .resetGame {
width: 50%;
height: 40px;
margin-top: 80px;
cursor: pointer;
background-color: #ffa500;
border: none;
border-radius: 10px;
font-size: 18px;
color: #fff;
box-shadow: 0 0 5px #787878;
}
.gameover .resetGame:active {
background-color: #ffb938;
}
.scores {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
font-size: 30px;
font-weight: bold;
color: #0000ae;
}
.begin {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #ffa500;
color: #fff;
border-radius: 10px;
border: none;
width: 200px;
height: 40px;
font-size: 20px;
box-shadow: 0 0 5px #787878;
}
.begin:active {
background-color: #ffb938;
}