diff --git a/web/src/App.vue b/web/src/App.vue index f73ff78..6aabd64 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -17,7 +17,8 @@ export default { diff --git a/web/src/assets/background.png b/web/src/assets/images/background.png similarity index 100% rename from web/src/assets/background.png rename to web/src/assets/images/background.png diff --git a/web/src/assets/scripts/AcGameObject.js b/web/src/assets/scripts/AcGameObject.js new file mode 100644 index 0000000..764e489 --- /dev/null +++ b/web/src/assets/scripts/AcGameObject.js @@ -0,0 +1,83 @@ +// 该类作为基类使用,用于刷新绘制 +// 定义绘制对象数组,存放每一帧绘制的对象 +const AC_GAME_OBJECTS = []; + +// 导出类 +export class AcGameObject { + // 构造函数 + constructor(){ + // push(this) 是将当前对象存下来的意思 + // 每创建一个,就 push 一个,先创建先 push,后创建后 push + // 先创建的先执行 update ,后创建的会把先创建的给覆盖掉 + AC_GAME_OBJECTS.push(this); + // 帧与帧执行的时间间隔 + this.timedelta=0; + // 是否执行过 start 函数 + this.has_called_start = false; + } + + // start 函数只执行一次 + start(){ + + } + + // 除第一帧之外,每一帧执行一遍 + update(){ + + } + + // 删除之前执行 + on_destroy(){ + + } + + // 删除 + destroy(){ + // 删除之前调用 on_destroy 函数 + this.on_destroy(); + + // 在 js 里,使用 of 遍历的是数组里的值;使用 in 遍历的是数组的下标。 + for(let i in AC_GAME_OBJECTS){ + const obj = AC_GAME_OBJECTS[i]; + // 如果 obj 等于当前对象,则删除该对象 + if(obj === this){ + // 使用 splice 删除数组里的对象 + AC_GAME_OBJECTS.splice(i); + break; + } + } + + } +} + + +// 上一帧执行的时刻 +let last_timestamp; +// step 函数需要传入当前帧执行的时刻 timestamp +const step = (timestamp) => { + // 遍历所有的物品 + // 在 js 里,使用 of 遍历的是数组里的值;使用 in 遍历的是数组的下标。 + for(let obj of AC_GAME_OBJECTS){ + // 如果当前物品没有执行 start 函数,则该物品执行一次 start 函数 + if(!obj.has_called_start){ + // 将该物品的 has_called_start 赋值为 true,表示其已经执行过了 + obj.has_called_start = true; + obj.start(); + } + // 如果执行过 start ,则接下来应该执行 update 函数 + else{ + // 当前帧与上一帧的时间间隔:当前帧执行时刻减去上一帧执行时刻 + obj.timedelta = timestamp - last_timestamp; + obj.update(); + } + } + + // 更新 last_timestamp ,作为下一次更新的“上一帧执行的时刻” + last_timestamp = timestamp; + // 递归调用 + requestAnimationFrame(step) + +} + +// 定义需要的刷新次数,传入的函数step会在下一帧浏览器渲染之前执行一遍。 +requestAnimationFrame(step) \ No newline at end of file diff --git a/web/src/assets/scripts/GameMap.js b/web/src/assets/scripts/GameMap.js new file mode 100644 index 0000000..9487cf0 --- /dev/null +++ b/web/src/assets/scripts/GameMap.js @@ -0,0 +1,170 @@ +// 在 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); + } + } + } +} \ No newline at end of file diff --git a/web/src/assets/scripts/Wall.js b/web/src/assets/scripts/Wall.js new file mode 100644 index 0000000..b62cfab --- /dev/null +++ b/web/src/assets/scripts/Wall.js @@ -0,0 +1,36 @@ +// 定义墙组件 +import { AcGameObject } from "./AcGameObject"; + +export class Wall extends AcGameObject { + // 构造函数定义,参数为墙的坐标 r 行, c 列,gamemap 用于绘制 + constructor(r,c,gamemap){ + // 先执行基类的构造函数 + super(); + + this.r = r; + this.c = c; + this.gamemap = gamemap; + this.color = "#b47226"; + } + + // 墙的更新 + update(){ + // 执行渲染 + this.render(); + } + + // 墙的渲染 + render(){ + // 从 gamemap 对象中拿到小格(墙)的边长 + const L = this.gamemap.L; + + // 拿到ctx画布 + this.ctx = this.gamemap.ctx; + + // 设置 ctx 画布填充色 + this.ctx.fillStyle = this.color; + // 绘制矩形 + this.ctx.fillRect(this.c*L, this.r*L, L, L); + + } +} \ No newline at end of file diff --git a/web/src/components/ContentBase.vue b/web/src/components/ContentBase.vue index 26422a5..b3f73e1 100644 --- a/web/src/components/ContentBase.vue +++ b/web/src/components/ContentBase.vue @@ -14,7 +14,7 @@ - diff --git a/web/src/components/NavBar.vue b/web/src/components/NavBar.vue index f7da1fd..b656648 100644 --- a/web/src/components/NavBar.vue +++ b/web/src/components/NavBar.vue @@ -73,4 +73,4 @@ export default { }; - + diff --git a/web/src/components/PlayGround.vue b/web/src/components/PlayGround.vue new file mode 100644 index 0000000..aa11a2e --- /dev/null +++ b/web/src/components/PlayGround.vue @@ -0,0 +1,28 @@ +// 定义游戏区域 + + + + + diff --git a/web/src/views/pk/PkIndexView.vue b/web/src/views/pk/PkIndexView.vue index bb20eee..9dcb296 100644 --- a/web/src/views/pk/PkIndexView.vue +++ b/web/src/views/pk/PkIndexView.vue @@ -1,13 +1,13 @@