前言
几个月前我曾发表了一篇JavaScript 子弹跟踪算法的文章,其起因是因一时兴起,想实现一个 具有 “元素反应” 的塔防游戏,但要想实现一个具有 “元素反应” 的塔防游戏,那么首先我就需要先把一个最基本的塔防游戏实现出来。也因为这样,所以才有了接下来的这一系列的文章。不过话不多说,我们就先来开始着手实现吧(当然了,现在只实现最基本的,待到这个基础系列完成后,我将会以此思路实现一个具有 “元素反应” 玩法的塔防游戏)。
PS: 该游戏交互效果参考于 Github 里某个 名为 html5-tower-defense-master 的塔防游戏。
目录
- JavaScript 从零开始实现一个塔防游戏 - 01.游戏预览与准备工作
- JavaScript 从零开始实现一个塔防游戏 - 02. 游戏画布类与游戏状态类的基础结构
- JavaScript 从零开始实现一个塔防游戏 - 03. 渲染静态游戏面板,道具类的基础结构与坐标/链表类
一. 实例截图
二. 准备工作
1. HTML
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Tower Defense</title>
<link rel="stylesheet" href="./css/style.css">
</head>
<body>
<div id="app">
<div class="content" style="display: block;">
<h2 style="margin-bottom:20px;">HTML5 塔防游戏</h2>
<div class="footer">
<small>HTML5 塔防游戏 © 2021 柴不是柴</small>
</div>
<canvas width="700" height="600"></canvas> <!-- 游戏画布 -->
</div>
</div>
<script src="./js/Class/Pos.js"></script>
<script src="./js/Class/Canvas.js"></script>
<script src="./js/Class/State.js"></script>
<script src="./js/Class/Tool.js"></script>
<script src="./js/Class/Monster.js"></script>
<script src="./js/Class/Bullet.js"></script>
<script src="./js/Class/Event.js"></script>
<script src="./js/app.js"></script>
<script>
runGame(); // 调用游戏运行程序
</script>
</body>
</html>
2. CSS
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
#app, #app > div {
position: relative;
width: 100vw;
height: 100vh;
}
.footer {
position: absolute;
bottom: 0;
height: 300px;
left: 0;
width: 100vw;
background: #181818;
color: #fff;
padding: 20px;
}
#app .content {
background: rgba(4, 201, 194, 0.99);
}
canvas {
background: #fff;
}
.content {
padding: 20px;
}
3. 图片素材
网盘:pan.baidu.com/s/1qcqVO2At…
提取码:mro1
该文件内的图片均为本人使用 AI 实现。
4. 目录结构
- css
- style.css (样式文件)
- images
- xxx.svg (图片文件)
- js
- Class
- Bullet.js (子弹类)
- Canvas.js (画布类)
- Monster.js (怪物类)
- Pos.js (坐标类/链表类)
- State.js (游戏状态类)
- Tool.js (道具类)
- Event.js (事件绑定类)
- Data
- Config.json (游戏配置文件)
- app.js (游戏辅助方法以及游戏运行所在文件)
- Class
- index.html (项目入口)
三. 实现辅助方法
由于本篇文章只负责做准备工作,故不论游戏逻辑。
但又考虑到后续有些方法可能需要共用/或者来启动游戏,故此我们先来实现这几个将来会用到的几个方法。
PS: 本篇文章内的所有方法均写于 app.js 里
1. 碰撞检测算法
考虑到目前的怪物皆为圆形怪物,攻击范围也是呈圆形的,故此我们只先需实现 圆形与圆形 之间碰撞的算法。
function collection(c1, c2) {
return (
Math.sqrt(Math.pow(c1.x - c2.x, 2) + Math.pow(c1.y - c2.y, 2)) < c1.r + c2.r
);
}
2. 加载数据方法
由于我们的游戏配置数据与图片均加载于外部文件,故此我们需要实现两个加载方法。
async function getData(src) {
return fetch(src).then(res => res.json());
}
async function loadImage(src) {
return new Promise(resovle => {
const image = new Image;
image.onload = () => resovle(image);
image.src = src;
});
}
3. 数据克隆方法
由于之后我们可能时常需要用到某个数据,并且在不能污染它的情况下,我们可以先实现一个深度克隆数据的方法。
function deepClone(arr) {
return JSON.parse(JSON.stringify(arr));
}
4. 游戏启动方法
既然是一个游戏,那么它必然少不了运行方法。
但在游戏运行起来前,我们需要加载一下道具图片与地图数据。
function runGame() {
// 这里我们使用 Promise.all 来同步加载多个文件。
Promise.all([
loadImage('./images/tool.svg'),
getData('./js/Data/Config.json')
]).then(([tool, config_data]) => {
let canvas = new Canvas(), // 这里我们创建一个画布类
state = new State(config_data, 'playing', tool); // 这里我们创建一个 游戏状态类
/*
这里我们调用一个每帧都刷新的方法,并且传回一个回调函数,
其目的就是当游戏状态变为 结束时,我们终止游戏的运行
*/
return requestAnimationFrameFunc(() => {
// 如果游戏状态为正在运行时
if (state.status === 'playing') {
state.update(); // 此为游戏状态更新方法
canvas.update(state); // 此为画布渲染方法
// 如果游戏状态为结束时
} else if (state.status === 'over') {
console.log('Game Over'); // 控制台输出 Game Over
return true; // 返回 true 终止游戏循环
}
// 如果游戏状态为暂停时,即既不是运行与结束两种状态时,则什么都不执行
});
});
}
function requestAnimationFrameFunc(callback) {
function frame() {
if (callback()) return; // 当回调函数返回结果为 true 时, 游戏终止循环
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
}
那么至此,我们的准备工作基本都完成了。
下篇,我们就来实现游戏画布类与游戏状态类的基础结构。
(最后有个小小的请求,请问朋友是否可以赏我一个小小的赞呢 😜,您的点赞/收藏将会是我最大的动力~)