废话不多说直接上代码,素材来源均为网络
<!DOCTYPE html>
<html lang="cn">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- <script src="./plane.js"></script> -->
<title>飞机大战</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
canvas {
position: absolute;
top: 0;
left: 0;
}
body{
width: 100vw;
height: 100vh;
}
</style>
</style>
</head>
<body>
<audio src="https://webfs.hw.kugou.com/202312131420/1e8f640849ef748768e19499779b74d9/v2/178f890105e2305438245ad23fd96ffe/G232/M0A/0A/04/yJQEAF9RI-uAb0OhAB8GCj15-JQ512.mp3"></audio>
<canvas id="canvas"></canvas>
<script>
// 获取画布比例9:16
// 先创建素材
const bgImage = new Image();bgImage.src = "https://upload-images.jianshu.io/upload_images/14016888-07ab84dec27c6612.png?imageMogr2/auto-orient/strip|imageView2/2/w/480/format/webp";
const userplane = new Image();userplane.src = "https://upload-images.jianshu.io/upload_images/14016888-e3d2f21c6e2cc265.png?imageMogr2/auto-orient/strip|imageView2/2/w/46/format/webp";
const enemyplane = new Image();enemyplane.src = "https://upload-images.jianshu.io/upload_images/14016888-6b9624c1deb561ae.png?imageMogr2/auto-orient/strip|imageView2/2/w/69/format/webp";
const bullet1 = new Image();bullet1.src = "https://upload-images.jianshu.io/upload_images/14016888-ff389915bee91bc6.png?imageMogr2/auto-orient/strip|imageView2/2/w/5/format/webp";
const bullet2 = new Image();bullet2.src = "https://upload-images.jianshu.io/upload_images/14016888-8fe683430d5cd233.png?imageMogr2/auto-orient/strip|imageView2/2/w/5/format/webp";
const enemydown1 = new Image();enemydown1.src = "https://upload-images.jianshu.io/upload_images/14016888-5de26da9ac6b826d.png?imageMogr2/auto-orient/strip|imageView2/2/w/69/format/webp";
const enemydown2 = new Image();enemydown2.src = "https://upload-images.jianshu.io/upload_images/14016888-36c5ade8705d1b12.png?imageMogr2/auto-orient/strip|imageView2/2/w/69/format/webp";
const enemydown3 = new Image();enemydown3.src = "https://upload-images.jianshu.io/upload_images/14016888-70f509fa1fcb2c06.png?imageMogr2/auto-orient/strip|imageView2/2/w/69/format/webp";
const enemydown4 = new Image();enemydown4.src = "https://upload-images.jianshu.io/upload_images/14016888-ca62513fe0687c25.png?imageMogr2/auto-orient/strip|imageView2/2/w/69/format/webp";
const zd = new Image();zd.src = "https://upload-images.jianshu.io/upload_images/14016888-a75c7fdeac527b6b.png?imageMogr2/auto-orient/strip|imageView2/2/w/60/format/webp";
const zdnum = new Image();zdnum.src = "https://upload-images.jianshu.io/upload_images/14016888-78f4b7ce15c74a8c.png?imageMogr2/auto-orient/strip|imageView2/2/w/63/format/webp";
/**
* 判断是否碰撞
* 因为只记录的飞机的x,y坐标所有要加上size,才是飞机的面积
*/
class Game {
constructor() {
this.Images = {
0:enemyplane,
1:enemydown1,
2:enemydown2,
3:enemydown3,
4:enemydown4,
zd:zd,
zdnum:zdnum
}
this.size = 50; // 格子大小
this.bulletSize = 10; // 子弹大小
this.requestAFId = null;
this.isGameOver = false; // 是否开始游戏
this.isGameOver = false;
this.cvs = document.getElementById("canvas"),
this.ctx = document.getElementById("canvas").getContext("2d"),
this.config = {
width: window.innerWidth,
height: window.innerHeight,
offsetLeft:this.cvs.offsetLeft,
offsetTop:this.cvs.offsetTop,
};
this.planeSpeed = 2; // 飞机移动速度
this.userBulletSpeed = 10; // 玩家子弹移动速度
this.enemyBulletSpeed = 3; // 敌人子弹移动速度
this.userBulletStep = 200; // 玩家子弹间隔时间
this.enemyBulletStep = 1500; // 敌人子弹间隔时间
this.bullets = {user:[],enemy:[]}; // 子弹数组 enemy弃用
this.lifeNum = 3;
this.life = this.lifeNum;
this.userPointer = {x: this.size *4,y: document.body.clientHeight / 1.5,width:this.size,height:this.size}; // 玩家飞机坐标
this.enemyPointer = [{
x: this.size * 4,
y: 0,is:false,
width:this.size,
height:this.size,
destroyStep:0,
image:this.Images[0],
bullet:[]}
] // 敌人飞机坐标
this.enemyPlaneNum = 50; // 场上敌人飞机数量
this.enemyPlaneStep = 500; // 生成敌人飞机间隔
this.touchEnter = false; // 是否点击到玩家飞机
this.collision = false; // 是否碰撞
this.score = 0; // 分数
this.enemyDestroyTime = 200; // 敌人飞机销毁时间
this.enemyBulletSetI = null; // 敌人子弹定时器
this.userBulletSetI = null; // 玩家子弹定时器
this.enemyCreateSetI = null; // 生成敌机定时器
// 炸弹
this.zdSpeed = 5;
this.zdStep = 1;
this.zdnum = 0;
this.zd = [];
// min/max
this.min = 0;
this.max = document.body.clientWidth / this.size;
console.dir(document.body);
this.startBg();
this.addEvent();
}
gameStart(){
if(!this.isActive){
this.isActive = true;
this.init();
}
}
startBg(){
const cvs = this.cvs;
const ctx = this.ctx;
this.BG(cvs,ctx)
ctx.font = '50px 宋体';
ctx.fillText("点击开始",cvs.width / 2 - 100 , cvs.height / 2);
}
//重新开始
restart(){
this.clearEnemyCreateSetI();
this.isActive = false;
this.isGameOver = false;
this.life = this.lifeNum;
this.zdStep = 1;
this.zdnum = 0;
this.zd = [];
clearInterval(this.enemyBulletSetI);
clearInterval(this.userBulletSetI);
clearInterval(this.enemyCreateSetI);
this.score = 0;
this.enemyPointer = [];
this.userPointer = {x: this.size *4,y: document.body.clientHeight / 1.5,width:this.size,height:this.size}; // 玩家飞机坐标
}
restartBg(){
this.restart();
const cvs = this.cvs;
const ctx = this.ctx;
cvs.removeEventListener("mousedown", this.mousedownFn);
this.mouseupFn();
ctx.clearRect(0, 0, cvs.width, cvs.height);
this.BG(cvs,ctx);
ctx.font = '50px 宋体';
console.log(this.userPointer)
ctx.fillText("点击重新开始",cvs.width / 2 - 100 - 50 , cvs.height / 2);
this.addEvent();
}
// 清除敌人飞机定时器
clearEnemyCreateSetI(){
for(let i = 0; i < this.enemyPointer.length; i++){
const item = this.enemyPointer[i];
clearInterval(item.setInterval);
}
}
// 奖励
reward(){
}
init() {
const { width, height } = this.config;
const cvs = this.cvs;
cvs.width = width;
cvs.height = height;
this.render();
this.enemyCreate();
this.enemyBullet();
this.userBullet();
this.enemyBulletSetI = setInterval(() => {
this.enemyBullet();
}, this.enemyBulletStep);
this.userBulletSetI = setInterval(() => {
this.userBullet();
}, this.userBulletStep);
}
// 生成炸弹道具
zdCreate() {
const step = this.score / this.zdStep;
if(step == 100){
++this.zdStep;
const obj = {
x:this.getRandom(this.min,this.max) * this.size,
y:0,
width:this.size,
height:this.size
}
this.zd.push(obj);
}
}
// 判断是否触碰到炸弹道具
touchzd(){
this.zdMove();
for(let i = 0; i < this.zd.length; i++){
const item = this.zd[i];
if(this.isCollisionFn(item,this.userPointer)){
this.zd.splice(i,1);
this.zdnum++;
}
}
}
zdMove(){
for(let i = 0; i < this.zd.length; i++){
const item = this.zd[i];
if(item.y < this.cvs.height){
item.y += this.zdSpeed;
}else{
this.zd.splice(i,1);
}
}
}
// 绘制炸弹
drawzd() {
this.touchzd();
for(let i = 0; i < this.zd.length; i++){
const item = this.zd[i];
this.ctx.drawImage(this.Images.zd, item.x, item.y, this.size, this.size);
}
}
// 绘制炸弹数量
drawzdnum() {
this.ctx.drawImage(this.Images.zdnum, this.cvs.width -this.size*2, 20, this.size, this.size);
this.ctx.font = "30px 宋体";
this.ctx.fillStyle = "yellow";
this.ctx.fillText(this.zdnum, this.cvs.width -this.size + 10, this.size);
}
// 使用炸弹
usezd() {
if(this.zdnum <= 0) return;
this.zdnum--;
this.usezdAll()
}
// 使用炸弹请出屏幕所有敌人
usezdAll() {
this.isBulletCollision(true);
}
// 获取随机数
getRandom(min, max) {
return Math.random() * (max - min + 1) + min;
}
drawScore(){
this.ctx.font = "20px 宋体";
this.ctx.fillStyle = "yellow";
this.ctx.fillText(this.score + '分', 10, 20);
}
// 背景
BG(cvs,ctx) {
const { width, height } = this.config;
ctx.clearRect(0, 0, width, height);
cvs.width = width;
cvs.height = height;
ctx.drawImage(bgImage, 0, 0, width, height);
}
// 生成玩家发射子弹
userBullet() {
const { x, y } = this.userPointer;
this.bullets.user.push({ x:x + this.size / 2 - 3.5, y:y - 25,width:this.bulletSize,height:this.bulletSize*2});
}
// 生成敌人子弹
enemyBullet() {
if(this.enemyPointer.length <= 0) return;
for(let i = 0; i < this.enemyPointer.length; i++){
if(this.enemyPointer[i].is) continue;
const { x, y } = this.enemyPointer[i];
const item = this.enemyPointer[i];
item.bullet.push({bulletX:x + this.size / 2 - 3.5,bulletY:y + 25,bulletWidth:this.bulletSize,bulletHeight:this.bulletSize*2})
}
}
// 绘制所有子弹
bulletDraw(ctx) {
for (let i = 0; i < this.bullets.user.length; i++) {
const { x, y,width,height } = this.bullets.user[i];
ctx.drawImage(bullet1, x, y, width, height);
}
for (let i = 0; i < this.enemyPointer.length; i++) {
const item = this.enemyPointer[i];
for(let j = 0; j < item.bullet.length; j++){
const { bulletX:x,bulletY:y,bulletWidth:width,bulletHeight:height } = item.bullet[j];
ctx.drawImage(bullet2, x, y, width, height);
}
}
}
// 子弹移动
bulletMove() {
for (let i = 0; i < this.bullets.user.length; i++) {
const { y } = this.bullets.user[i];
this.bullets.user[i].y -= this.userBulletSpeed;
if (y <= 0) {
this.bullets.user.splice(i, 1);
i--;
}
}
for (let i = 0; i < this.enemyPointer.length; i++) {
const item = this.enemyPointer[i];
if(item.is == true && item.bullet.length == 0 && !item.setInterval){
this.enemyPointer.splice(i, 1);
continue;
}
for(let j = 0; j < item.bullet.length; j++){
const itemJ = item.bullet[j];
itemJ.bulletY += this.enemyBulletSpeed;
if (itemJ.bulletY >= this.config.height) {
item.bullet.splice(i, 1);
}
}
}
this.bulletHit();
}
// 子弹抵消
bulletHit() {
this.enemyPointer.map(itemp => {
itemp.bullet.map((itemb,indexb) => {
this.bullets.user.map((itemu,indexu) => {
if(this.isCollisionFn(itemu,{x:itemb.bulletX,y:itemb.bulletY,width:itemb.bulletWidth,height:itemb.bulletHeight})){
itemp.bullet.splice(indexb, 1);
this.bullets.user.splice(indexu, 1);
}
})
})
})
}
// 生成敌人飞机
enemyCreate(){
this.enemyCreateSetI = setInterval(() => {
this.enemyCreateFn();
}, this.enemyPlaneStep);
}
enemyCreateFn(){
if(this.enemyPointer.length >= this.enemyPlaneNum) return;
const x = this.getRandom(this.min,this.max);
this.enemyPointer.push({x:x * this.size,y:0,is:false,width:this.size,height:this.size,destroyStep:0,image:this.Images[0],bullet:[]});
}
// 绘制敌对飞机
enemy(ctx) {
for(let i = 0; i < this.enemyPointer.length; i++){
const item = this.enemyPointer[i];
if(item.is && item.destroyStep >= 4) {
continue;
};
const { x, y,image } = this.enemyPointer[i];
ctx.drawImage(image, x, y, this.size, this.size);
}
}
// 绘制被击毁的动画
enemyDestroy() {
for(let i = 0; i < this.enemyPointer.length; i++){
const item = this.enemyPointer[i];
if(!item.is)continue;
if(item.destroyStep >= 4){
clearInterval(item.setInterval);
item.setInterval = null;
continue;
};
item.destroyStep++;
item.image = this.Images[item.destroyStep];
}
}
// 判断是否碰撞
isCollision(){
for(let i = 0; i < this.enemyPointer.length; i++){
const item = this.enemyPointer[i];
if(item.is) continue;
if(this.isCollisionFn(this.userPointer,item)){
--this.life;
this.enemyPointer.splice(i, 1);
if(this.life == 0){
this.isGameOver = true;
}
}
}
}
// 判断矩形是否相交
isCollisionFn(rectangle1, rectangle2) {
const x1 = rectangle1.x;
const y1 = rectangle1.y;
const width1 = rectangle1.width;
const height1 = rectangle1.height;
const x2 = rectangle2.x;
const y2 = rectangle2.y;
const width2 = rectangle2.width;
const height2 = rectangle2.height;
const left1 = x1;
const right1 = x1 + width1;
const top1 = y1;
const bottom1 = y1 + height1;
const left2 = x2;
const right2 = x2 + width2;
const top2 = y2;
const bottom2 = y2 + height2;
return !(right1 < left2 || left1 > right2 || bottom1 < top2 || top1 > bottom2);
}
// 判断子弹是否击中敌人或被击中
// is:清空敌人
isBulletCollision(is = false){
for(let i = 0; i < this.enemyPointer.length; i++){
const item = this.enemyPointer[i];
if(is){
this.score += 10;
item.is = true;
item.destroyStep = 1;
item.image = this.Images[item.destroyStep];
item.bullet = [];
if(item.setInterval)return;
item.setInterval = setInterval(() => {
this.enemyDestroy();
}, this.enemyDestroyTime);
continue;
}
if(item.is) continue;
if(this.isBulletCollisionFn(item)){
this.score += 10;
item.is = true;
item.destroyStep = 1;
item.image = this.Images[item.destroyStep];
if(item.setInterval)return;
item.setInterval = setInterval(() => {
this.enemyDestroy();
}, this.enemyDestroyTime);
}
}
for(let i = 0; i < this.enemyPointer.length; i++){
const item = this.enemyPointer[i];
for(let j = 0; j < item.bullet.length; j++){
const itemJ = item.bullet[j];
const obj = {
x:itemJ.bulletX,
y:itemJ.bulletY,
width:itemJ.bulletWidth,
height:itemJ.bulletHeight
}
if(this.isCollisionFn(this.userPointer,obj)){
--this.life;
item.bullet.splice(j,1)
if(this.life == 0){
this.isGameOver = true;
}
}
}
}
}
// 是否击中敌人
isBulletCollisionFn(plane) {
for (let i = 0; i < this.bullets.user.length; i++) {
if(this.isCollisionFn(this.bullets.user[i],{x:plane.x,y:plane.y,width:plane.width,height:plane.height})){
return true;
}
}
}
// 生成玩家
player(ctx) {
const { x, y } = this.userPointer;
ctx.beginPath();
ctx.drawImage(userplane, x, y, this.size, this.size);
ctx.rect(x,y,this.size, this.size);
ctx.stroke()
ctx.closePath();
this.drawLife();
}
// 绘制生命条
drawLife() {
const { x, y } = this.userPointer;
const ctx = this.ctx;
const step = this.lifeNum / this.life;
ctx.beginPath();
ctx.rect(x,y - 10,this.size,10);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.rect(x + 1,y - 9,this.size / step,8)
ctx.fillStyle = "red";
ctx.fill();
ctx.closePath();
}
// 敌对飞机移动
enemyMove() {
for (let i = 0; i < this.enemyPointer.length; i++) {
const item = this.enemyPointer[i];
if(item.is)continue;
item.y += this.planeSpeed;
if(item.y >= this.size * 16){
this.enemyPointer.splice(i,1);
}
}
this.isBulletCollision();
this.isCollision();
}
// 渲染
render() {
const cvs = this.cvs;
const ctx = this.ctx;
this.BG(cvs,ctx);
this.player(ctx);
this.enemy(ctx);
this.bulletMove();
this.bulletDraw(ctx);
this.enemyMove();
this.zdCreate();
this.drawzd();
this.drawScore();
this.drawzdnum();
if(this.isGameOver){
cancelAnimationFrame(this.requestAFId);
alert("游戏结束");
this.restartBg();
}else{
this.requestAFId = requestAnimationFrame(() => {
this.render();
})
}
}
// 判断是否点击到飞机
isTouchPlane = (init) => {
const { x: ux, y: uy } = this.userPointer;
const { x, y } = init;
if (x >= ux && x <= ux + this.size && y >= uy && y <= uy + this.size) {
this.touchEnter = true;
} else {
this.touchEnter = false;
}
};
// 添加事件
mousedownFn = (event) => {
// openFullscreen(document.documentElement);
if(!this.isActive){
this.gameStart();
return;
}
const {x,y} = this.eventValue(event);
const {offsetLeft:left,offsetTop:top} = this.cvs;
const e = { x: x - left, y: y - top };
this.isTouchPlane(e);
if (this.touchEnter) {
document.addEventListener(emove, this.mousemoveFn,false);
document.addEventListener(eup, this.mouseupFn);
}
};
mousemoveFn = (event) => {
if (!this.touchEnter) return;
const { x:offsetX, y:offsetY } = this.eventValue(event);
const { offsetLeft: left, offsetTop: top } = this.cvs;
const x = offsetX - left - this.size / 2;
const y = offsetY - top - this.size / 2;
this.userPointer.x = x;
this.userPointer.y = y
};
mouseupFn = () => {
document.removeEventListener(emove, this.mousemoveFn);
document.removeEventListener(eup, this.mouseupFn);
};
// 判断pc还是移动端使用不同的值
eventValue(e) {
let x,y;
if(e.touches){
x = e.touches[0].clientX;
y = e.touches[0].clientY;
}else{
x = e.clientX;
y = e.clientY;
}
return {x,y}
}
// 空格按键
keydownFn = (e) => {
if (e.keyCode == 32) {
this.usezd();
}
};
addEvent = () => {
const canvas = this.cvs;
document.addEventListener('keydown',this.keydownFn)
canvas.addEventListener(edown, this.mousedownFn);
};
}
let game;
function isMobileDevice() {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
}
function openFullscreen(elem) {
if (elem.requestFullscreen) {
elem.requestFullscreen();
} else if (elem.mozRequestFullScreen) { /* Firefox */
elem.mozRequestFullScreen();
} else if (elem.webkitRequestFullscreen) { /* Chrome, Safari 和 Opera */
elem.webkitRequestFullscreen();
} else if (elem.msRequestFullscreen) { /* IE/Edge */
elem.msRequestFullscreen();
}
}
let edown,emove,eup;
if(isMobileDevice()){
edown = "touchstart";
emove = "touchmove";
eup = "touchend";
}else{
edown = "mousedown";
emove = "mousemove";
eup = "mouseup";
}
function start() {
if (game) {
game.re();
}
game = new Game();
}
window.onload = () => {
game = new Game();
};
</script>
</body>
</html>