1000字范文,内容丰富有趣,学习的好帮手!
1000字范文 > 从零开始用js手写飞机大战全过程(附源码)

从零开始用js手写飞机大战全过程(附源码)

时间:2022-12-10 12:11:32

相关推荐

从零开始用js手写飞机大战全过程(附源码)

plane game

前言

此文章帮助初学者学习制作一个简单小游戏

目标

利用css,html,js制作出飞机大战的简略版

前置准备

1. 绝对定位与相对定位

这里除了背景都设置为绝对定位,使飞机和敌机相对于背景定位。具体区别请看之前的帖。

2. setTimeout 与 clearTimer

建立子弹与敌机的定时器并且在他们消失时清除定时器

下面为敌机设置的例子

// 启动定时器,每一秒执行一次moveEnemyfunction startEnemyInterval() {enemyIntervalId = setInterval(moveEnemy, 100);}// 清除定时器function stopEnemyInterval() {clearInterval(enemyIntervalId);}// 碰撞时消失if (xcrashFlag === true && ycrashFlag === true) {// 横纵轴碰撞// 飞机和敌人消失let domEnemy = document.querySelector("#enemy");if (domEnemy !== null) {domEnemy.remove();stopEnemyInterval();// 调用清除定时器}gameOver();}

3. 碰撞计算原理

这里其实是个难点,拿子弹与敌机相撞举例:先判断敌机与子弹谁在左侧,再以他们的绝对距离比较较左者的宽度,若绝对距离大则横轴不相碰,反之,横轴相碰。而再以子弹与顶部的距离减去敌机与顶部的距离则是他们上下的绝对距离,当绝对距离小于0时纵轴相碰。当横纵轴都相碰时,子弹与敌机碰撞。

4. querySelector

这里需要利用dom来操纵元素,使js与html连接

5. bullet 对象数组

设置bullet为一个空数组,创造一个对象以获得每个子弹的index,top,left,来为判断子弹与敌机相碰提供数据。

6. ps 绘图

这里使用了ps绘图引入飞机,敌机与子弹的图片,以透明色为底。如下图为飞机

7. console.log 查问题

编写的过程中难免遇上各种预料外的问题,如果发现不了问题的原因,则可以利用console.log打印一些地方,以检测代码是否运行,null情况是否return等的情况

思路

编写背景板

在css中设置背景板样式,html中引入

.screen {position: relative;display: flex;height: 700px;width: 700px;background-image: url("background.webp");background-size: cover;background-repeat: no-repeat;border: 10px solid blue;border-top: url("top.png");}

<div class="screen" id="screen">

在背景板中写一个 div 作为 plane,写一个 div 作为 enemy

在背景板下写plane与enemy的div

<div class="screen" id="screen"><div id="plane" class="move-plane"></div></div>

利用 top left 来上下左右移动 plane

top:到顶部的距离;left:到最左侧的距离。设置初始飞机top和left,再设置可移动的范围。

let positionTop = 660; // 初始飞机toplet positionLeft = 330; // 初始飞机left

// 向上移动自身一次function moveUp() {if (positionTop <= 0) {gameOver();return;}let dom = document.querySelector("#plane");positionTop -= step;dom.style.top = `${positionTop}px`;}// 向下移动自身一次function moveDown() {if (positionTop >= 660) {gameOver();return;}let dom = document.querySelector("#plane");positionTop += step;dom.style.top = `${positionTop}px`;}// 向左移动自身一次function moveLeft() {if (positionLeft <= 0) {gameOver();return;}let dom = document.querySelector("#plane");positionLeft -= planeStep;dom.style.left = `${positionLeft}px`;}// 向右移动自身一次function moveRight() {if (positionLeft >= 660) {gameOver();return;}let dom = document.querySelector("#plane");positionLeft += planeStep;dom.style.left = `${positionLeft}px`;}

利用 setTimeout 来持续向下移动 enemy

通过定时器每一秒执行一次moveEnemy

// 向上移动子弹一次function moveBullet(bullet) {if (bullet.top <= 0) {bullet.dom?.remove();bullet.stop();}if (bullet.dom === null) {return;}const bulletTop = parseInt(bullet.top);bullet.top = bulletTop - step;bullet.dom.style.top = bulletTop + "px";enemyAndBulletCrash(bullet);}// 启动定时器,每一秒执行一次moveBulletfunction startBulletInterval(bullet) {bulletIntervalId = setInterval(() => {moveBullet(bullet);}, 100);return bulletIntervalId;}

编写碰撞规则

通过设置横纵两轴碰撞条件,当两轴都碰撞时,则碰撞生效

// 判断飞机和敌人碰撞function planeAndEnemyCrash() {const enemyDom = document.querySelector(`#enemy`);if (enemyDom === null) {return;}const enemyLeft = parseInt(enemyDom.style.left);const enemyTop = parseInt(enemyDom.style.top);let peLeftDistance = enemyLeft - positionLeft;let peAbsoluteLeftDistance = Math.abs(peLeftDistance);let peAbsoluteTopDistance = positionTop - enemyTop - 40;let xcrashFlag = false;let ycrashFlag = false;if (peAbsoluteLeftDistance <= 40) {// 横轴碰撞xcrashFlag = true;}if (peAbsoluteTopDistance <= 0) {// 纵轴碰撞ycrashFlag = true;}if (xcrashFlag === true && ycrashFlag === true) {// 横纵轴碰撞// 飞机和敌人消失let domEnemy = document.querySelector("#enemy");if (domEnemy !== null) {domEnemy.remove();stopEnemyInterval();}gameOver();}}

// 判断子弹与敌机碰撞function enemyAndBulletCrash(bullet) {const enemyDom = document.querySelector(`#enemy`);if (enemyDom === null) {return;}const enemyLeft = parseInt(enemyDom.style.left);const enemyTop = parseInt(enemyDom.style.top);let ebLeftDistance = enemyLeft - bullet.left;let ebAbsoluteLeftDistance = Math.abs(ebLeftDistance);let ebAbsoluteTopDistance = bullet.top - enemyTop - 40;let xcrashFlag = false;let ycrashFlag = false;if (enemyLeft < bullet.left) {if (ebAbsoluteLeftDistance <= 40) {// 横轴碰撞xcrashFlag = true;}} else {if (ebAbsoluteLeftDistance <= 10) {// 横轴碰撞xcrashFlag = true;}}if (ebAbsoluteTopDistance <= 0) {// 纵轴碰撞ycrashFlag = true;}if (xcrashFlag === true && ycrashFlag === true) {// 横纵轴碰撞// 子弹和敌人消失}

当按下空格后,生成 bullet,利用同样原理持续向上移动 bullet

先实现用document.onkeydown监听键盘输入以及创造bullet的dom,在监听到按下空格时调用生成bullet的函数以及bullet向前移动的函数。

if (key === " ") {shotBullet();

// 发射子弹function shotBullet() {let domPlane = document.querySelector("#plane");let left = getComputedStyle(domPlane, null)["left"];let top = getComputedStyle(domPlane, null)["top"];// 创建子弹 domconst index = bullet.length;let screenDom = document.querySelector("#screen");screenDom.insertAdjacentHTML("beforeend",`<div class="move-bullet" id="bullet${index}"></div>`);const currentBulletDom = document.querySelector(`#bullet${index}`);currentBulletDom.style.top = top;currentBulletDom.style.left = left;// 定义子弹对象const newBullet = {index: index,dom: currentBulletDom,left: parseInt(left),top: parseInt(top),};bullet.push(newBullet);const stopId = startBulletInterval(bullet.at(-1));// 清除定时器newBullet.stop = function () {clearInterval(stopId);};}

替换贴图

在css中设置子弹,飞机以及敌机的background-image为自己ps创作的图片

.move-plane {background-image: url("plane4.png");background-size: cover;background-repeat: no-repeat;}.move-enemy {background-image: url("plane5.png");background-size: cover;background-repeat: no-repeat;}.move-bullet {background-image: url("bullet.png");background-size: cover;background-repeat: no-repeat;}

当 bullet 和 enemy 碰撞后,重置 enemy

当碰撞后,子弹与敌人都消失,此时重新调用创造敌人的函数,使敌人重置

domEnemy.remove();stopEnemyInterval();createEnemy();}

function createEnemy() {// 创造敌人let screenDom = document.querySelector("#screen");screenDom.insertAdjacentHTML("beforeend",`<div class="move-enemy" id="enemy"></div>`);// 给敌人设初始位置const enemyDom = document.querySelector(`#enemy`);enemyDom.style.left = Math.random() * 640 + 10 + "px";// 让敌人向前移动startEnemyInterval();}

编写计分板

每当敌人与子弹碰撞一次,计一分,游戏结束时打印结果

// 计分function counter() {score += 1;}if (domEnemy !== null) {domEnemy.style.top = "0px";domEnemy.style.left = Math.random() * 650 + 10 + "px";enemyStep *= 1.5;domEnemy.remove();stopEnemyInterval();createEnemy();}counter();

编写 start pause

在点击开始游戏时调用start,start里调用点击开始时需调用的函数

在按esc时暂停游戏,停止计时器。

<button class="start-button" onclick="start()">开始游戏</button>

if (key === "Escape") {stopEnemyInterval(enemy);startFlag = false;}

全部代码

以下为css

html,body {padding: 0;margin: 0;}.screen {position: relative;display: flex;height: 700px;width: 700px;background-image: url("background.webp");background-size: cover;background-repeat: no-repeat;border: 10px solid blue;border-top: url("top.png");}.move-plane {position: absolute;height: 40px;width: 40px;background-image: url("plane4.png");background-size: cover;background-repeat: no-repeat;}.move-enemy {position: absolute;height: 40px;width: 40px;background-image: url("plane5.png");background-size: cover;background-repeat: no-repeat;}.move-bullet {position: absolute;height: 10px;width: 10px;background-image: url("bullet.png");background-size: cover;background-repeat: no-repeat;}.mask {background-color: rgba(1, 1, 1, 0.9);position: absolute;display: flex;height: 720px;width: 720px;top: 0;display: flex;align-items: center;justify-content: center;}.start-button {height: 30px;width: 200px;}

以下为html

<!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>Document</title><link rel="stylesheet" href="plane.css" /></head><body><div><div class="screen" id="screen"><div id="plane" class="move-plane"></div></div><div class="mask" id="mask"><button class="start-button" onclick="start()">开始游戏</button></div></div><script src="plane.js"></script></body></html>

let positionTop = 660; // 初始飞机toplet positionLeft = 330; // 初始飞机leftlet step = 20; // 每次运动的步长let enemyStep = 10; // 敌人运动步长let planeStep = 50; // 飞机步长let enemyIntervalId; // 敌人定时器idlet bulletIntervalId; // 子弹定时器idlet startFlag = false; // 开始标识const bullet = []; // 子弹数组let score = 0; // 分数初始值init();// 设置top和leftfunction init() {let domPlane = document.querySelector("#plane");domPlane.style.top = `${positionTop}px`;domPlane.style.left = `${positionLeft}px`;}// 向下移动敌人一次function moveEnemy() {const enemyDom = document.querySelector(`#enemy`);if (enemyDom === null) {return;}if (enemyDom.style.top === "") {enemyDom.style.top = "0px";}const enemyTop = parseInt(enemyDom.style.top);if (enemyTop >= 660) {stopEnemyInterval();enemyDom.remove();gameOver();}enemyDom.style.top = `${enemyTop + enemyStep}px`;planeAndEnemyCrash();}// 判断子弹碰撞function enemyAndBulletCrash(bullet) {// 先判断敌机与子弹谁在左侧,再以他们的绝对距离比较较左者的宽度,若绝对距离大则不相碰,反之,相碰const enemyDom = document.querySelector(`#enemy`);if (enemyDom === null) {return;}const enemyLeft = parseInt(enemyDom.style.left);const enemyTop = parseInt(enemyDom.style.top);let ebLeftDistance = enemyLeft - bullet.left;let ebAbsoluteLeftDistance = Math.abs(ebLeftDistance);let ebAbsoluteTopDistance = bullet.top - enemyTop - 40;let xcrashFlag = false;let ycrashFlag = false;if (enemyLeft < bullet.left) {if (ebAbsoluteLeftDistance <= 40) {// 横轴碰撞xcrashFlag = true;}} else {if (ebAbsoluteLeftDistance <= 10) {// 横轴碰撞xcrashFlag = true;}}if (ebAbsoluteTopDistance <= 0) {// 纵轴碰撞ycrashFlag = true;}if (xcrashFlag === true && ycrashFlag === true) {// 横纵轴碰撞// 子弹和敌人消失let domEnemy = document.querySelector("#enemy");// 计分function counter() {score += 1;}if (domEnemy !== null) {domEnemy.style.top = "0px";domEnemy.style.left = Math.random() * 650 + 10 + "px";enemyStep *= 1.5;domEnemy.remove();stopEnemyInterval();createEnemy();}counter();bullet.dom?.remove();bullet.stop();}}// 判断飞机和敌人碰撞function planeAndEnemyCrash() {const enemyDom = document.querySelector(`#enemy`);if (enemyDom === null) {return;}const enemyLeft = parseInt(enemyDom.style.left);const enemyTop = parseInt(enemyDom.style.top);let peLeftDistance = enemyLeft - positionLeft;let peAbsoluteLeftDistance = Math.abs(peLeftDistance);let peAbsoluteTopDistance = positionTop - enemyTop - 40;let xcrashFlag = false;let ycrashFlag = false;if (peAbsoluteLeftDistance <= 40) {// 横轴碰撞xcrashFlag = true;}if (peAbsoluteTopDistance <= 0) {// 纵轴碰撞ycrashFlag = true;}if (xcrashFlag === true && ycrashFlag === true) {// 横纵轴碰撞// 飞机和敌人消失let domEnemy = document.querySelector("#enemy");if (domEnemy !== null) {domEnemy.remove();stopEnemyInterval();}gameOver();}}// 启动定时器,每一秒执行一次moveEnemyfunction startEnemyInterval() {enemyIntervalId = setInterval(moveEnemy, 100);}// 清除定时器function stopEnemyInterval() {clearInterval(enemyIntervalId);}// 向上移动子弹一次function moveBullet(bullet) {if (bullet.top <= 0) {bullet.dom?.remove();bullet.stop();}if (bullet.dom === null) {return;}const bulletTop = parseInt(bullet.top);bullet.top = bulletTop - step;bullet.dom.style.top = bulletTop + "px";enemyAndBulletCrash(bullet);}// 启动定时器,每一秒执行一次moveBulletfunction startBulletInterval(bullet) {bulletIntervalId = setInterval(() => {moveBullet(bullet);}, 100);return bulletIntervalId;}// 清除定时器function stopBulletInterval() {clearInterval(bulletIntervalId);}// 点击开始游戏时执行,开启定时器function start() {if (startFlag === true) {return;}closeWindow();startFlag = true;createEnemy();const planeDom = document.querySelector("#plane");planeDom.style.top = "660px";planeDom.style.left = "330px";positionTop = 660; // 初始飞机toppositionLeft = 330; // 初始飞机leftenemyStep = 10;}// 向上移动自身一次function moveUp() {if (positionTop <= 0) {gameOver();return;}let dom = document.querySelector("#plane");positionTop -= step;dom.style.top = `${positionTop}px`;}// 向下移动自身一次function moveDown() {if (positionTop >= 660) {gameOver();return;}let dom = document.querySelector("#plane");positionTop += step;dom.style.top = `${positionTop}px`;}// 向左移动自身一次function moveLeft() {if (positionLeft <= 0) {gameOver();return;}let dom = document.querySelector("#plane");positionLeft -= planeStep;dom.style.left = `${positionLeft}px`;}// 向右移动自身一次function moveRight() {if (positionLeft >= 660) {gameOver();return;}let dom = document.querySelector("#plane");positionLeft += planeStep;dom.style.left = `${positionLeft}px`;}// 用document.onkeydown监听键盘输入,当输入上下左右键时执行对应方向操作document.onkeydown = (params) => {let dom = document.querySelector("#plane");if (dom === null) {return;}let key = params.key;if (key === "Control") {startFlag = true;startEnemyInterval(enemy);}if (startFlag === false) {return;}if (key === "ArrowUp") {moveUp();} else if (key === "ArrowDown") {moveDown();} else if (key === "ArrowLeft") {moveLeft();} else if (key === "ArrowRight") {moveRight();} else if (key === " ") {shotBullet();} else if (key === "Escape") {stopEnemyInterval(enemy);startFlag = false;}};// 发射子弹function shotBullet() {let domPlane = document.querySelector("#plane");let left = getComputedStyle(domPlane, null)["left"];let top = getComputedStyle(domPlane, null)["top"];// 创建一个 domconst index = bullet.length;let screenDom = document.querySelector("#screen");screenDom.insertAdjacentHTML("beforeend",`<div class="move-bullet" id="bullet${index}"></div>`);const currentBulletDom = document.querySelector(`#bullet${index}`);currentBulletDom.style.top = top;currentBulletDom.style.left = left;const newBullet = {index: index,dom: currentBulletDom,left: parseInt(left),top: parseInt(top),};bullet.push(newBullet);const stopId = startBulletInterval(bullet.at(-1));newBullet.stop = function () {clearInterval(stopId);};}// 结束function gameOver() {console.log(score, "game over, please refresh");startFlag = false;score = 0;const dom = document.querySelector("#mask");dom.style.display = "flex";stopEnemyInterval();}// 生成敌人function createEnemy() {// 创造敌人let screenDom = document.querySelector("#screen");screenDom.insertAdjacentHTML("beforeend",`<div class="move-enemy" id="enemy"></div>`);// 给敌人设初始位置const enemyDom = document.querySelector(`#enemy`);enemyDom.style.left = Math.random() * 640 + 10 + "px";// 让敌人向前移动startEnemyInterval();}function closeWindow() {const dom = document.querySelector("#mask");dom.style.display = "none";}

效果动图

总结

要有足够的关于html,css和js的基础知识储备,不懂时可以查找相关资料。写注释理好逻辑,耐心利用console.log和控制台的提示找到代码中的错误。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。