SpringBoot项目——vue 实现游戏页面
回顾:
文章目录
vue 实现前端页面——Web
1、导航栏功能+PK地图的实现
- 导航栏组件及所路由跳转各组件代码如下:
// NavBar.vue
<template>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<router-link class="navbar-brand" :to="{name: 'home'}">King of Bots</router-link>
<div class="collapse navbar-collapse" id="navbarText">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<!-- 加冒号使得属性可以赋值为一个表达式,实现点击active -->
<router-link :class="route_name == 'pk_index' ? 'nav-link active' : 'nav-link'" :to="{name: 'pk_index'}">对战</router-link>
</li>
<li class="nav-item">
<router-link :class="route_name == 'record_index' ? 'nav-link active' : 'nav-link'" :to="{name: 'record_index'}">对局列表</router-link>
</li>
<li class="nav-item">
<router-link :class="route_name == 'ranklist_index' ? 'nav-link active' : 'nav-link'" :to="{name: 'ranklist_index'}">排行榜</router-link>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle active" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Lijiao
</a>
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
<li><router-link class="dropdown-item" :to="{name: 'user_bot_index'}">我的Bot</router-link></li>
<li><a class="dropdown-item" href="#">退出</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#">Something else here</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
</template>
<script>
// 为了判断当前url在哪个页面
import { useRoute } from 'vue-router'
import { computed } from 'vue'
export default {
name: 'NavBar',
setup() {
const route = useRoute();
let route_name = computed(() => route.name);
return{
route_name,
}
},
}
</script>
<style scoped>
</style>
路由跳转到的组件PKIndexView
、RecordIndexView
、RanklistIndexView
、UserBotIndexView
、NotFound
。
// PKIndexView.vue
<template>
<PKPlayGround/>
</template>
<script>
import PKPlayGround from '@/components/PKPlayGround.vue'
export default {
name: 'PKIndexView',
components: {
PKPlayGround,
},
setup() {
},
}
</script>
<style scoped>
</style>
路由操作:
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import PKIndexView from '../views/pk/PKIndexView.vue'
import RecordIndexView from '@/views/record/RecordIndexView'
import RanklistIndexView from '@/views/ranklist/RanklistIndexView'
import UserBotIndexView from '@/views/user/bot/UserBotIndexView'
import NotFound from '@/views/error/NotFound'
const routes = [
{
path: "/",
name: "home",
redirect: "/pk/",
},
{
path: "/pk/",
name: "pk_index",
component: PKIndexView,
},
{
path: "/record/",
name: "record_index",
component: RecordIndexView,
},
{
path: "/ranklist/",
name: "ranklist_index",
component: RanklistIndexView,
},
{
path: "/user/bot/",
name: "user_bot_index",
component: UserBotIndexView,
},
{
path: "/404/",
name: "404",
component: NotFound,
},
{
path: "/:catchAll(.*)",
name: "404",
component: NotFound,
},
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
2、js 控制 html、css 实现 PK 地图
AcGameObject.js
作为总父类 js 控制 每秒60次 执行GameMap.js
、Wall.js
的 updata;注意:只要new了某个js类,就会执行其所在的整个js文件
js 控制
GameMap.vue
组件中地图canvas
随PKPlayGround动态改变大小;js 控制
GameMap.vue
组件中墙canvas
的构建。
GameMap 组件代码
// GameMap.vue
<template>
<div ref="parent" class="gamemap">
<canvas ref="canvas">
</canvas>
</div>
</template>
<script>
// 引入自己写的 js
import {GameMap} from "@/assets/scripts/GameMap.js"
import {ref} from 'vue'
// 组件挂载完以后需要执行的操作
import {onMounted} from 'vue'
export default {
setup:() => {
let parent = ref(null);
let canvas = ref(null);
// 组件挂载完以后需要执行的操作:这里是js控制canvas
onMounted(() => {
// 通过 js 控制 html 显示
// 传入画布和父组件的值
// getContext('2d') 获取这个元素的 context,由 CanvasRenderingContext2D 接口完成实际的绘制。
// 用 js 求出动态长方形 PKPlayGround(w * L)的 最大网格(row*col)正方形,可求内部小正方形的边长
new GameMap(canvas.value.getContext('2d'),parent.value);
});
return {
parent,
canvas
}
}
}
</script>
<style scoped>
div.gamemap{
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
</style>
GameMap.js
// GameMap.js
import { AcGameObject } from "./AcGameObject";
import { Wall } from "./Wall";
export class GameMap extends AcGameObject {
// 构造函数传入画布和画布的父元素
constructor(ctx, parent) {
super();
this.ctx = ctx;
this.parent = parent;
// 存画布格子的绝对距离
this.L = 0;
this.rows = 13;
this.cols = 13;
// 存内部障碍物数量
this.inner_walls_count = 15;
// 画墙
this.walls = [];
}
// 检查左下和右上是否联通
check_connectivity(g, sx, sy, ex, ey){
if (sx == ex && sy == ey) 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];
if (!g[x][y] && this.check_connectivity(g, x, y, ex, ey)) return true;
}
return false;
}
create_walls() {
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;
}
// 创建随机障碍物
for (let i = 0; i < this.inner_walls_count; i++){
for (let j = 0; j<1000 ; j++){
// js 如何产生随机数
// Math.random: 产生[0,1)之间随机浮点数
// 即产生0-行数之间的随机浮点值后取整。
let r = parseInt(Math.random() * this.rows);
let c = parseInt(Math.random() * this.cols);
if (g[r][c] || g[c][r] ) continue;
if (c == 1 && r == this.rows-2 || c == this.cols-2 && r == 1) continue;
g[r][c] = g[c][r] = true;
break;
}
}
// js 实现深度复制一个对象
// 先转化成JSON, 再把JSON解析出来
const copy_g =JSON.parse(JSON.stringify(g));
if (!this.check_connectivity(copy_g, this.rows - 2, 1, 1, this.cols - 2)) return false;
// 真正加
for (let r = 0; r < this.rows; r++) {
for (let c = 0; c < this.cols; c++) {
if (g[r][c] == true) {
new Wall(r, c, this);
}
}
}
// 如果联通,返回true
return true;
}
start() {
for (let i = 0; i < 1000; i++){
if (this.create_walls()){
break;
}
}
}
updata_size() {
// 用 js 求出动态长方形 PKPlayGround(w * L)的 最大网格(row*col)正方形,可求内部小正方形的边长
// clientWidth/clientHeight: API,求div的长宽
// why parseInt()? 这里算得为浮点型,而canvas渲染为整型,渲染会有空隙
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;
}
updata() {
// 每帧都更新下边长
this.updata_size();
this.render();
}
render() {
// 画基数偶数格颜色分离
// #424242
// fillStyle 属性赋值颜色。fillRect(坐标,宽,高)
const color_even = "#D8D8D8", color_odd = "#E7E7E7";
for (let r = 0; r < this.rows; r ++) {
for ( let c = 0; c < this.cols; c ++) {
if ((r+c) % 2 == 0) {
this.ctx.fillStyle = color_odd;
} else {
this.ctx.fillStyle = color_even;
}
// canvas 往右是x正方向,往下是y正方向
// 因此第 r 行 c 列坐标是(c,r)
this.ctx.fillRect(c * this.L, r * this.L, this.L, this.L);
}
}
}
}
Wall.js
import { AcGameObject } from "./AcGameObject";
export class Wall extends AcGameObject {
// r,c 为画墙的起始坐标,gamemap 为canvas画布
constructor(r, c, gamemap) {
super();
this.r = r;
this.c = c;
this.gamemap = gamemap;
this.color = "#424242"
}
updata() {
this.render();
}
render() {
const L = this.gamemap.L;
const ctx = this.gamemap.ctx;
ctx.fillStyle = this.color;
//
ctx.fillRect(this.c * L,this.r * L, L, L);
}
}