前言
在上篇文章里,我们实现了 游戏画布类与游戏状态类的基本结构。那么本篇文章里,我们就准备实现道具类的基础结构、坐标/链表类与渲染游戏面板。
目录
- JavaScript 从零开始实现一个塔防游戏 - 01.游戏预览与准备工作
- JavaScript 从零开始实现一个塔防游戏 - 02. 游戏画布类与游戏状态类的基础结构
- JavaScript 从零开始实现一个塔防游戏 - 03. 渲染静态游戏面板,道具类的基础结构与坐标/链表类
一. 坐标/链表类
首先我们进入 pos.js 文件里,创建一个 Pos 类。
class Pos {
constructor(x, y, next = null) {
this.x = x; // x 轴值
this.y = y; // y 轴值
this.next = next; // 当此对象作用于链表类的时候,通过该属性来连接下一个节点。
}
}
其次我们来为其实现几个后面可能会用到的方法
/**
* 更改x y的值
* @param x {Number} 更改的x值
* @param y {Number} 更改的y值
* @return void
*/
set(x, y) {
this.x = x;
this.y = y;
}
/**
* 在该对象的属性x与y的值的基础上,乘以一个值,并返回一个新的坐标类
* @param num {Number} 值
* @param bool {Boolean} 判定是否生成一个链表结构
* @return {Pos} 坐标类
*/
factor(num, bool = false) {
return new Pos(this.x * num, this.y * num, bool ? this.next : null);
}
/**
* 在该对象的属性x与y的值的基础上,加上一个值,并返回一个新的坐标类
* @param num {Number} 值
* @param bool {Boolean} 判定是否生成一个链表结构
* @return {Pos} 坐标类
*/
add(num, bool = false) {
return new Pos(this.x + num, this.y + num, bool ? this.next : null);
}
/**
* 创建一个新的 Pos 类
* @param obj {Object}
* @return {Pos} 坐标类
*/
static add(obj) {
return new Pos(obj.x, obj.y);
}
/**
* 判定两个坐标是否为同一个坐标
* @param obj {Object|Pos}
* @return {Boolean} true/false
*/
same(obj) {
return ( obj.y === this.y && obj.x === this.x );
}
待到这些完成后,我们就来实现以下道具类的基础结构。
二. 创建 Tool 类
但在我们实现一个 Tool 类的基础结构前,我们先要思考思考。
作为一个道具,它应该拥有哪些数据呢?
...
- 坐标:这个数据用于记录该道具位于地图的哪一位置。
- 大小:这个数据用于记录道具占用了地图格子的大小(1代表一整个单元格)。
- 半径:这个数据用于判定道具的生效范围/攻击范围。
- 偏移量:由于道具的大小不一定占满整个格子(或者溢出),所以我们需要这个对象来帮我们确定道具所绘制的中心点。
- 价格:这个数据在用户拜访该道具时,会去判定金币是否够不够,若够则扣除对应金币,不够则禁止摆放。
- 伤害:这个数据用于判定当道具对怪物造成伤害时,所减去怪物血量的值(怪物可以拥有抗性,以此来减免一定的伤害,但本系列不考虑这些抗性,只考虑实现最基础的功能)。
- 冷却时间:这个数据用于决定道具效果生效/发射攻击的时间间隔。
- 冷却记时:用于判定是道具是否结束冷却。
- 图片:用于绘制道具的图片。
- 首要攻击目标:判断道具优先造成伤害的目标(假设有两个怪物同处一个坐标位置,且现在道具的攻击性质为单体攻击,那么这个首要目标就会收到伤害,另一个不会,反之两者皆受伤害)
- 是否展示攻击范围:这个数据通常用于判定此道具是否在地图单元格里受到点击,若受到点击,则会展示其攻击范围。
- 角度:这个数值用于判定当前道具的枪口朝向。
至此,我们可以先来实现一下 Tool 类的基础结构
class Tool {
constructor(pos, size, img, radius, money = 0, cd = 10, attack = 10) {
this.pos = pos; // 坐标
this.size = size; // 大小
this.radius = radius; // 半径
this.offset = {
x: (scale - size.x) / 2, // x 偏移量
y: (scale - size.y) / 2 // y 偏移量
}
this.money = money; // 价格
this.attack = attack; // 伤害
this.cd = cd; // 冷却时间 以 1 帧为单位
this.cdTime = 0; // 冷却计时
this.img = img; // 图片
this.first = undefined; // 首要攻击目标
this.showScope = false; // 展示攻击范围
this.angle = Math.atan2(0 - this.pos.y, 0 - this.pos.x) * (180 / Math.PI) + 90; // 角度
}
}
在实现了基础的 道具类结构后,我们还需要为其添加几个属性以及一个更新方法。
get type() {
return 'tool';
}
get x() {
return this.pos.x * scale + this.offset.x;
}
get y() {
return this.pos.y * scale + this.offset.y;
}
/**
* 该方法后续用于获取进入了该道具攻击范围内,更新道具枪口朝向与发射子弹。
* @param state {State}
* @return void
*/
update(state) {
// 后续会写入...
}
那么至此,我们的道具类基础结构就实现了,接下来让我们绘制一个静态的游戏面板。
三. 为 Canvas 类的 添加/修改内容(方法)。
在为 Canvas 类添加/修改内容(方法)前,我们先来看一下下面这张图里,我们需要绘制哪些东西(我用红框来区分一下)。
首先,我们先往 Canvas 类里的 更新方法添加一些内容。
update(state) {
this.clearCanvas();
// 我们把 游戏面板 数据传给 drawTextPanel 方法
this.drawTextPanel(state.panel);
this.drawToolBox(state.toolsBox);
this.drawBtn(state);
this.drawGameCells(state.map);
}
其中,drawTextPanel 方法是为绘制 游戏地图右侧 得分数据。
/**
* 绘制游戏得分数据
* @param data {Object} 道具箱对象
* @return void
*/
drawTextPanel(data) {
let y = 30;
this.siderbarX = (18 * scale + 35);
for (let i in data) {
this.renderFont(`${data[i].title}: ${data[i].value}`, this.siderbarX, y);
y += 20;
}
}
drawToolBox 方法是为绘制 游戏地图右侧 道具箱(与内部道具)的方法。
/**
* 绘制道具箱方法
* @param tools {Array} 道具箱对象
* @return void
*/
drawToolBox(tools) {
// 绘制道具箱
this.renderCell(this.siderbarX, 125, 90, 90, '#C8C8C8');
// 绘制目前拥有的道具
for (let i in tools) {
this.cx.drawImage(tools[i].image, this.siderbarX + tools[i].x + tools[i].offsetX, 125 + tools[i].y, 26, scale);
}
}
drawGameCells 方法为绘制 游戏地图单元格方法。
/**
* 绘制地图单元格方法
* @param map {Array} 地图单元格数据
* @return void
*/
drawGameCells(map) {
for (let y in map) {
for (let i in map[y]) {
let color = '#fff';
(y == 0 && i == 0 || y == map.length - 1 && i == map[y].length - 1) && (color = '#ededed');
this.renderCell(i * scale + 20, y * scale + 20, scale, scale, '#ddd', color);
}
}
}
drawBtn 方法为绘制按钮方法。
/**
* 绘制按钮方法
* @param state {State} 游戏状态类
* @return void
*/
drawBtns(state) {
this.renderCell(this.siderbarX, 230, 90, 32, '#4F4F4F', '#EFEFEF');
this.renderFont('暂停游戏', this.siderbarX + this.padding, 250, 12);
}
至此,我们游戏面板就已经渲染完毕。
下篇,我们来实现道具的置放功能。
(最后有个小小的请求,请问朋友是否可以赏我一个小小的赞呢 😜,您的点赞/收藏将会是我最大的动力~)