kos/web/src/assets/scripts/GameMap.js
2023-02-11 18:51:00 +08:00

170 lines
6.6 KiB
JavaScript

// 在 AcGameObject.js 里使用的是 export class ,因此这里需要使用 {} 括起来引用;如果是 export default 则不需要用括号括起来
import { AcGameObject } from "./AcGameObject";
// 导入墙组件
import { Wall } from "./Wall";
// 导出定义的 GameMap 游戏地图类
export class GameMap extends AcGameObject{
// 构造函数参数: ctx 画布; parent 画布的父元素,用来动态修改画布的长宽
constructor(ctx,parent){
// super() 用于先执行基类的构造函数
super();
// 存下 ctx 和 parent
this.ctx = ctx;
this.parent = parent;
// 存下每个格子的绝对距离
this.L = 0;
// 定义棋盘格的行数和列数
this.rows = 13;
this.cols = 13;
// 绘制棋盘内部区域的障碍物(墙)的数量
this.inner_walls_count = 50;
// 存储所有的墙
// 上面的 super() 会先将 AcGameObject 先绘制, walls 的绘制在后面执行,因此墙最后会覆盖原棋盘格进行绘制
this.walls = [];
}
// 判断函数:判断角色路径是否联通。传入参数:g数组,起点和终点的横纵坐标
check_connectivity(g, sx, sy, tx, ty){
// 当起点坐标和中点坐标一致时,判断联通,直接返回
if(sx == tx && sy == ty) return true;
g[sx][sy] = true;
// 定义四方向偏移量
let dx = [-1, 0, 1, 0], dy = [0, 1, 0 ,-1];
// 枚举上下左右四个方向,求当前点下一个相邻点的坐标
for(let i = 0; i < 4; i++){
let x = sx + dx[i], y = sy + dy[i];
// 判断是否撞墙,如果没有撞墙,且可以搜到终点的话,返回 true ,否则返回 false
if(!g[x][y] == true && this.check_connectivity(g, x, y, tx, ty))
return true;
}
// 搜不到终点,返回 false
return false;
}
// 创建墙函数
create_wall(){
// 创建一个墙格进行测试
// new Wall(0,0,this);
// 开一个布尔数组,有墙为 true
// 一开始先将所有墙初始化为 false
const g = [];
for(let r = 0; r < this.rows; r ++){
g[r] = [];
for(let c = 0; c < this.cols; c ++){
g[r][c] = false;
}
}
// 给左右加上墙
for(let r = 0; r < this.rows; r ++){
g[r][0] = g[r][this.cols-1] = true;
}
// 给上下加上墙
for(let c = 0; c < this.cols; c ++){
g[0][c] = g[this.rows-1][c] = true;
}
// 创建内部随机障碍物
// 因为每次计算都会生成两个障碍物,因此这里的循环次数 this.inner_walls_count 需要处以 2
for(let i = 0; i < this.inner_walls_count / 2; i ++){
// 避免位置重复:重复 1000 次,只要找到了就禁止随机
for(let j = 0; j < 1000; j ++){
let r = parseInt(Math.random()*this.rows);
let c = parseInt(Math.random()*this.cols);
// 主对角线对称 g[r][c] 和 g[c][r] 完成两种联合判断
// 当此位置已经有障碍物了,则重新计算下一个位置
if(g[r][c] || g[c][r]) continue;
// 将计算求得的随机障碍物的位置置为 true ,以对该位置进行绘制
// g[r][c] 和 g[c][r] 的坐标在对角线位置会重合,会被绘制为一个障碍物
g[r][c] = g[c][r] = true;
// 1000 次中,规定数量的内部障碍物已经够了之后就 break 掉
break;
}
}
// 避免内部障碍物覆盖掉左下角和右上角的角色出发点
g[this.rows-2][1] = g[1][this.cols-2] = false;
// 保证两个对角角色的运动区域是联通的
// 检测联通需要把 g[][] 传过去给 check_connectivity() 函数进行判断,传过去之前需要把当前 g[][] 状态复制一份,避免当前数据被修改掉
// 深度复制方法:先转换数据为 JSON ,再把 JSON 解析出来
const copy_g = JSON.parse(JSON.stringify(g));
// 检测到不连通,则直接在生成对象之前 return false 退出函数
if(!this.check_connectivity(copy_g, this.rows-2, 1, 1, this.cols-2)) return false;
// 枚举数组,将 g[r][c] == true 的部分绘制出来
// 如果上一步连通性检测失败,则退出 this.create_wall() 函数,本步骤不再执行生成新对象的操作
for(let r = 0; r < this.rows; r ++){
for(let c = 0; c < this.cols; c ++){
if(g[r][c]){
// 将每个新生成的 Wall 对象 push 存入 walls 数组中
this.walls.push(new Wall(r,c,this));
}
}
}
// 绘制成功则 return turn
return true;
}
start(){
// 开始时调用一次创建墙的函数
// 循环 1000 次,如果成功创建则 break ,否则继续循环创建
for(let i = 0; i < 1000; i ++)
if(this.create_wall())
break;
}
// 每一帧都更新一下小正方格的边长
update_size(){
// 计算当前帧每个格子的宽度, parseInt 取整是为了避免渲染出的格子之间出现小空隙
this.L = parseInt(Math.min(this.parent.clientWidth / this.cols, this.parent.clientHeight / this.rows));
// 计算当前画布的宽度
this.ctx.canvas.width = this.L * this.cols;
// 计算当前画布的高度
this.ctx.canvas.height = this.L * this.rows;
}
update(){
this.update_size();
// 每次更新都重新执行渲染
this.render();
}
// 渲染函数,把当前的游戏对象绘制到地图上
render(){
// b47226 棕色 aad751 浅绿 a2d048 深绿
// this.ctx.fillStyle = 'green';
// this.ctx.fillRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
// 定义偶数格even、奇数格odd的颜色
const color_even = "#aad751", color_odd = "#a2d048";
for(let r = 0; r < this.rows; r++){
for(let c = 0; c < this.cols; c++){
// 当列标加行标: r + c 是偶数时,选取偶数颜色,否则选取奇数颜色。
if((r + c) % 2 == 0){
this.ctx.fillStyle = color_even;
}else{
this.ctx.fillStyle = color_odd;
}
// 绘制小方格:起始坐标x,起始坐标y,水平边长,竖直边长
this.ctx.fillRect(c*this.L, r*this.L, this.L, this.L);
}
}
}
}