避开小行星游戏
- 避开小行星游戏
-
- 1. 游戏概述
- 2. 核心功能
-
- 2.1 构建 HTML 代码
- 2.2 美化界面
- 2.3 编写 JavaScript 代码
- 3. 创建游戏对象
-
- 3.1 创建小行星
- 3.2 创建玩家使用的火箭
- 4. 检测键盘输入
-
- 4.1 键值
- 4.2 键盘事件
- 5. 让物体移动
- 6. 假设横向卷轴效果
-
- 6.1 回收小行星
- 6.2 添加边界
- 6.3 保持玩家连续移动
- 7. 添加声音
- 8. 结束游戏
-
- 8.1 计分系统
- 8.2 杀死玩家
- 9. 增加游戏难度
- 10. 完整源码
避开小行星游戏
本文将使用 canvas 创建一个避开小行星的游戏。另外两个关键方面是:如何使用 JavaScript 检测键盘输入,以及如何在游戏中使用和处理 HTML5 音频。希望你能喜欢!
1. 游戏概述
顾名思义,避免小行星游戏的目标非常明显:当小行星冲向你时,让火箭飞行和生存尽可能长(如图91所示)。如果你遇到一颗小行星,游戏就会结束,游戏的分数是通过火箭的生存时间来计算的。
避开小行星游戏是一款横向卷轴游戏,或者至少类似于这样的游戏,它将专注于动态场景。
2. 核心功能
在创建游戏之前,首先需要构建一些基本框架。这些框架是创建避免小行星游戏的基本框架HTML、CSS以及JavaScript代码(作为未来要添加的高级代码的基础)。
2.1 构建 HTML 代码
在浏览器中创建游戏的优点是可以使用一些常用的技术来构建网站。换句话说,它可以使用 HTML 创建游戏用户界面的语言(UI)。现在的界面看起来不太漂亮,因为我们还没过 CSS 设计用户界面的样式,但内容的原始结构是最重要的。
在你的计算机上为游戏创建一个新目录和一个新目录index.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>Asteroid avoidance</title> <link rel="stylesheet" href="style.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="./main.js"></script>
</head>
<body>
<div id="game">
<div id="game-ui">
<div id="game-intro">
<h1>Asteroid avoidance</h1>
<p>Click play and then press any key to start.</p>
<p>
<a id="game-play" class="button" href="#">Play</a>
</p>
</div>
<div id="game-stats">
<p>Time: <span class="game-score"></span> </p>
<p> <a class="game-reset" href="#">Reset</a> </p>
</div>
<div id="game-complete">
<h1>Game over!</h1>
<p>You survived for <span class="game-score"></span> seconds. </p>
<p><a class="game-reset buyyon" href="#">Play</a></p>
</div>
</div>
<canvas id="game-canvas" width="800" height="600">
<!-- 在此处插入后备代码 -->
</canvas>
</div>
</body>
</html>
我不打算过多解释这些 HTML 代码,因为它们比较简单,但你只要知道这就是游戏所需的所有标记即可。
2.2 美化界面
创建一个名为 style.css
的新文件,并把它和 HTML 文件放在相同的目录下。在该 CSS 文件中插入以下代码:
* {
margin: 0;
padding: 0;
}
html, body {
height: 100%;
width: 100%;
}
canvas {
display: block;
}
body {
background-color: #000;
color: #fff;
font-family: Verdana, Arial, sans-serif;
font-size: 18px;
height: 100%;
}
h1 {
font-size: 30px;
}
p {
margin: 0 20px;
}
a {
color: #fff;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
a.button {
background-color: #185da8;
border-radius: 5px;
display: block;
font-size: 30px;
margin: 40px 0 0 270px;
padding: 10px;
width: 200px;
}
a.button:hover {
background-color: #2488f5;
color: #fff;
text-decoration: none;
}
#game {
height: 600px;
left: 50%;
margin: -300px 0 0 -400px;
position: relative;
top: 50%;
width: 800px;
}
#game-canvas {
background-color: #001022;
}
#game-ui {
height: 600px;
position: absolute;
width: 800px;
}
#game-intro, #game-complete {
background-color: rgba(0, 0, 0, .5);
margin-top: 100px;
padding: 40px 0;
text-align: center;
}
#game-stats {
font-size: 14px;
margin: 20px 0;
}
#game-stats, .game-reset {
margin: 20px 20px 0 0;
position: absolute;
right: 0;
top: 0;
}
2.3 编写 JavaScript 代码
在添加一些有趣的游戏逻辑之前,首先需要用JavaScript实现核心功能。创建一个名为 main.js
的新文件,并把它和 HTML 文件放在相同的目录下。在该 js 文件中插入以下代码:
$(document).ready(function () {
const canvas = $('#game-canvas');
const context = canvas.get(0).getContext("2d");
// 画布尺寸
const canvasWidth = canvas.width();
const canvasHeight = canvas.height();
// 游戏设置
let playGame;
// 游戏UI
const ui = $("#game-ui");
const uiIntro = $("#game-intro");
const uiStats = $("#game-stats");
const uiComplete = $("#game-complete");
const uiPlay = $("#game-play");
const uiReset = $(".game-reset");
const uiScore = $(".game-score");
// 重至和启动游戏
function startGame() {
// 重置游戏状态
uiScore.html("0");
uiStats.show();
// 初始游戏设置
playGame = false;
// 开始动画糖环
animate();
}
//初始化游戏环境
function init() {
uiStats.hide();
uiComplete.hide();
uiPlay.click(function (e) {
e.preventDefault();
uiIntro.hide();
startGame();
});
uiReset.click(function (e) {
e.preventDefault();
uiComplete.hide();
startGame();
});
}
// 动画循环,游戏的嫌味性就在这里
function animate() {
// 清除
context.clearRect(0, 0, canvasWidth, canvasHeight);
if (playGame) {
setTimeout(animate, 33);
}
}
init();
});
在你最喜欢的浏览器中运行该游戏,应该会看到一个更加美观的 UI。另外,你还可以单击 Play 按钮来显示游戏的主窗口,尽管它看上去也许还有些单调。
3. 创建游戏对象
躲避小行星游戏使用两个主要对象:小行星和玩家使用的火箭。我们将使用 JavaScript 类来创建这些对象。你也许会觉得奇怪,既然玩家只有一个,为什么还要通过一个类来定义它呢?简而言之,如果你以后需要在游戏中添加多个玩家,通过类创建玩家就会更容易一些。
3.1 创建小行星
通过类创建游戏对象意味着你可以在其他游戏中非常方便地重用和改变它们的用途。
第一步是声明主要变量,我们将使用这些变量来存储所有的小行星。同时,还需要声明另外一个变量,用于计算游戏中应该存在的小行星数目。在 JavaScript 代码顶部的 playGame
变量下面添加以下代码:
let asteroids;
let numAsteroids;
稍后你将会为这些变量赋值,但现在我们只建立小行星类。在startGame
函数上面添加以下代码:
function Asteroid(x, y, radius, vX) {
this.x = x;
this.y = y;
this.radius = radius;
this.vX = vX;
}
这里存在一个速度属性,这是因为小行星只需要从右向左运动,即只需要 x
速度。这里不需要 y
速度,所以就省略了。
在开始创建所有小行星之前,需要建立数组来存储这些小行星,并声明实际需要使用的小行星数目。在startGame
函数中的PlayGame
变量下面添加以下代码:
asteroids = new Array();
numAsteroids = 10;
你也许认为 10 个小行星是一个很小的数目,但是当这些小行星在屏幕上消失时,你将重复使用它们,所以在游戏中你实际看到的小行星数目可以有无穷多个。你可以把这里的小行星数目看做屏幕上某一时刻可能出现的小行星总数。
创建小行星的过程其实就是一个创建循环的过程,循环的次数就是你刚才声明的小行星数目。在你刚才赋值的numAsteroids
变量下面添加以下代码:
for (let i = 0; i < numAsteroids; i++) {
const radius = 5 + (Math.random() * 10);
const x = canvasWidth + radius + Math.floor(Math.random() * canvasWidth);
const y = Math.floor(Math.random() * canvasHeight);
const vx = -5 - (Math.random() * 5);
asteroids.push(new Asteroid(x, y, radius, vX));
}
为了让每颗小行星的外观都与众不同,并且使游戏看上去更有趣一些,可以把小行星的半径设为一个介于 5 到 15 像素之间的随机数( 5 加上一个介于 0 到 10 之间的随机数)。虽然 x
速度的值介于 -5 到 -10 之间,但你也可以采用同样的方法来设置它( -5 减去一个 0 到 5 之间的数)。因为你需要让小行星按从右向左的方向运动,所以使用的是一个负的速度值,这说明 x
的位置将随着时间的推移而减小。
计算每颗小行星的 x
位置看上去有些复杂,但其实非常简单。在开始启动游戏的时候,如果让所有的小行星全部显示在屏幕上,就让人觉得有些太奇怪了。因此在游戏开始之前,最好把小行星放在屏幕的右侧,当游戏开始时才让它们按从右向左的顺序穿过屏幕。
为此,首先需要把 x
位置设为 canvas
元素的宽度,然后加上小行星的半径。这意味着如果你现在画出小行星,那么它应该位于屏幕的右侧。如果仅仅这样做,那么所有的小行星将会排成一行,所以下一步我们需要把 x
位置加上一个介于 0 到画布宽度之间的随机值。与 x
位置相比,y
位置简单一些,它只是一个介于 0 到画布高度之间的随机值。
这样可以产生一个与画布尺寸相同的方框,方框中随机分布着一些小行星。当游戏开始时,这些小行星将穿过可见的画布。
最后一步是把一个新的小行星推送到数组中,做好移动和绘制小行星的准备。
3.2 创建玩家使用的火箭
首先声明用于建立玩家的初始化变量。在 JavaScript 顶部的 numAsteroids
变量下面添加以下代码:
let player;
该变量将用于存储玩家对象的引用,但现在我们还没有定义玩家对象。在Asteroid
类下面添加以下代码:
function Player(x, y) {
this.x = x;
this.y = y;
this.width = 24;
this.height = 24;
this.halfWidth = this.width / 2;
this.halfHeight = this.height / 2;
this.vX = 0;
this.vY = 0;
}
你应该熟悉以上代码的某些部分,例如位置和速度属性。其余属性用于描述玩家使用的火箭的尺寸,包括整个尺寸和一半的尺寸。绘制火箭和执行碰撞检测时,你需要使用这些尺寸。
最后一步是创建一个新的玩家对象。为此,在 startGame
函数中的numAsteroids
变量下面添加以下对象:
player = new Player(150, canvasHeight / 2);
通过以上代码,玩家的位置将垂直居中,并且距离画布左边界 150 像素。
现在还不能看到任何效果,稍后当你开始着手移动所有的游戏对象时,将会从视觉上看到这种效果。
4. 检测键盘输入
本游戏将使用键盘来控制游戏。更确切地说,你将使用方向键来四处移动玩家使用的火箭。如何才能实现这种控制呢?这比控制鼠标输入更难吗?不,其实非常简单。下面我来教你怎么做。
4.1 键值
在处理键盘输人时,首先需要知道哪一个按键被按下了。在 JavaScript 中,普通键盘上的每一个按键都分配了一个特定的键值(key code)。通过这些键值,可以唯一确定按下或释放了哪个键。稍后你将学习如何使用键值,现在我们首先需要了解每个按键所对应的数值。
例如,键 a
到 z
(无论在什么情况下)对应的键值分别是从 65 到 90 。箭头键对应的键值是从 37 到 40,其中左箭头的键值是 37、上箭头的键值是 38、右箭头的键值是 39、下箭头的键值是 40。空格键的键值是 32。
在躲避小行星游戏中,你需要重点关注的是箭头键,因此在 JavaScript 代码顶部的 player
变量下面添加以下代码:
const arrowUp = 38;
const arrowRight = 39;
const arrowDown = 40;
以上代码为每个箭头对应的键值分别分配了一个变量。这种方法称作枚举(enumeration),它是对值进行命名的过程。这主要是为后面的工作提供方便,因为通过这些名称你能很容易确定变量引用的是哪个箭头键。
请注意,为什么没有为左箭头声明一个变量呢?因为你不会手动地让玩家向后移动。相反,当玩家没有按任何按键时,就会表现为向后移动的状态。稍后你就会明白其中的道理。
4.2 键盘事件
在向游戏中添加键盘交互效果之前,首先需要确定玩家在何时按下或释放某个按键。为此,需要使用 keydown
和 keyup
事件监听器。
在 startGame
函数中的 animate
函数调用上面(在创建所有小行星的循环下面)添加以下代码:
$(window).keydown(e => {
});
$(window).keyup(e => {
});
按下某个按键时将触发第一个监听器,释放某个按键时将触发第二个监听器。非常简单。稍后我们将在这些事件监听器中添加一些有用的代码,但首先需要在重新设置游戏时删除这些监听器,这能防止玩家由于无意按下某个按键而启动游戏。在 uiReset.click
事件监听器中的 startGame
调用上面添加以下代码:
$(window).unbind('keyup');
$(window).unbind('keydown');
接下来,还需要添加一些在激活玩家后用到的属性。在 Player
类的末尾添加以下代码:
this.moveRight = false;
this.moveUp = false;
this.moveDown = false;
通过这些属性,你可以知道玩家的移动方向,这些属性值的设置取决于玩家按下了哪个按键。现在你是不是已经理解了其中的所有道理呢?
最后,需要向键盘事件监听器中添加一些逻辑。首先,在 keydown
事件监听器中添加以下代码:
const keyCode = e.keyCode;
if (!playGame) {
playGame = true;
animate();
}
if (keyCode == arrowRight) {
player.moveRight = true;
} else if (keyCode == arrowUp) {
player.moveUp = true;
} else if (keyCode == arrowDown) {
player.moveDown = true;
}
并在 keyup
事件监听器中添加以下代码:
const keyCode = e.keyCode;
if (keyCode == arrowRight) {
player.moveRight = false;
} else if (keyCode == arrowUp) {
player.moveUp = false;
} else if (keyCode == arrowDown) {
player.moveDown = false;
}
以上代码的作用非常明显,但我还需要作一些说明。在两个监听器中,第一行的作用都是把按键的键值赋给一个变量。然后在一组检查语句中使用该键值来判断是否按下了某个箭头键,如果按下了箭头键,判断是哪个箭头键。这样,我们就可以启动(如果按下了该键)或禁用(如果释放了该键)玩家对象的对应属性。
例如,如果按下了向右的箭头键,那么玩家对象的 moveRight
属性将被设为 true
。如果释放了该方向键,则 moveRight
属性将被设为false
。
**注意:**如果玩家一直按住某个按键,那么将触发多个 keydown
事件。因此,代码要具备处理多个被触发的 keydown
事件的能力,这一,点非常重要。在每个 keydown
事件之后不一定总是一个 keyup
事件,另外还要注意的是,在 keydown
事件监听器中是如何通过一个条件语句来查看游戏当前是否正在进行的。如果玩家没有做好游戏准备,该语句将阻止游戏运行。只有玩家按下键盘上的某个键时,才会启动游戏。方法很简单,但却非常有效。
游戏中的键盘输入非常多,我们不可能一一列举。在下一节中,我们将通过这些输入来控制玩家沿着正确的方向运动。
5. 让对象运动起来
现在你已经做好了实现游戏对象动画的所有准备。当你实际看到游戏效果时,这一切会变得更有趣。
第一步是更新所有游戏对象的位置。我们从更新小行星对象的位置开始,在 animate
函数中 画布的 clearRect
方法下面添加以下代码:
const asteroidsLength = asteroids.length; for (let i = 0; i < asteroidsLength; i++) { const tmpAsteroid = asteroids[i]; tmpAsteroid.x += tmpAsteroid.vX; context.fillStyle = "rgb(255, 255, 255)"; context.beginPath(); context.arc(tmpAsteroid.x, tmpAsteroid.y, tmpAsteroid.radius, 0, Math.PI * 2, true); context