前端迷宫绘制完成
This commit is contained in:
parent
4eb9cdfae7
commit
faa10babe6
|
@ -17,7 +17,8 @@ export default {
|
|||
</script>
|
||||
<style>
|
||||
body {
|
||||
background-image: url("@/assets/background.png");
|
||||
/* 页面背景图片 */
|
||||
background-image: url("@/assets/images/background.png");
|
||||
background-size: cover;
|
||||
}
|
||||
</style>
|
||||
|
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 102 KiB |
|
@ -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)
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
<script></script>
|
||||
|
||||
<style>
|
||||
<style scoped>
|
||||
.container {
|
||||
/* 到顶部距离 20px */
|
||||
margin-top: 20px;
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
// 定义计分板
|
||||
<template>
|
||||
<!-- ref="parent" 用于将 return 返回的 parent 指向 div -->
|
||||
<div ref="parent" class="gamemap">
|
||||
<!-- canvas 画布 -->
|
||||
<canvas ref="canvas"></canvas>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 导入 GameMap.js
|
||||
import { GameMap } from "@/assets/scripts/GameMap";
|
||||
// 引入 canvas, onMounted 用于挂载操作定义
|
||||
import { ref, onMounted } from "vue";
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
// 定义两个变量 parent 和 canvas, 一开始这两个变量都没有指向任何元素,因此 ref(null)
|
||||
let parent = ref(null);
|
||||
let canvas = ref(null);
|
||||
|
||||
// 定义挂载函数,挂载完成后执行
|
||||
onMounted(() => {
|
||||
new GameMap(canvas.value.getContext("2d"), parent.value);
|
||||
});
|
||||
|
||||
return {
|
||||
parent,
|
||||
canvas,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.gamemap {
|
||||
/* 宽度、高度设为100%,目的是与其父元素等长等宽 */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* flex可用于同时实现水平和竖直居中 */
|
||||
display: flex;
|
||||
/* 水平居中 */
|
||||
justify-content: center;
|
||||
/* 竖直居中 */
|
||||
align-content: center;
|
||||
}
|
||||
</style>
|
|
@ -73,4 +73,4 @@ export default {
|
|||
};
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
<style scoped></style>
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
// 定义游戏区域
|
||||
<template>
|
||||
<div class="playground">
|
||||
<GameMap />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GameMap from "./GameMap.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GameMap,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.playground {
|
||||
/* 60% 浏览器宽度, 70% 浏览器高度 */
|
||||
width: 60vw;
|
||||
height: 70vh;
|
||||
/* 背景色 */
|
||||
/* background: lightblue; */
|
||||
/* 距上边距40px,左右居中 */
|
||||
margin: 40px auto;
|
||||
}
|
||||
</style>
|
|
@ -1,13 +1,13 @@
|
|||
<template>
|
||||
<ContentBase>对战</ContentBase>
|
||||
<PlayGround>对战</PlayGround>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ContentBase from "../../components/ContentBase.vue";
|
||||
import PlayGround from "../../components/PlayGround.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ContentBase,
|
||||
PlayGround,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
Loading…
Reference in New Issue