bootstrap地址
Bootstrap v5 中文文档 · Bootstrap 是全球最受欢迎的 HTML、CSS 和 JS 前端工具库。 | Bootstrap 中文网 (bootcss.com)
创建导航栏组件
web--src--components--NavBar.vue
<!-- html --> <template><nav class="navbar navbar-expand-lg navbar-dark bg-dark"><div class="container"><!-- 刷新 --><!-- <a class="navbar-brand" href="/">King Of Bots</a> --><!-- 点击页面不刷新用router-link --><router-link class="navbar-brand" :to="{ name: 'pk_index' }">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="nav-link active " :to="{ name: 'pk_index' }">对战</router-link> --><!-- 选中的高亮 --><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" href="#" role="button" data-bs-toggle="dropdown"aria-expanded="false">USERNAME</a><ul class="dropdown-menu"><li><router-link class="dropdown-item" :to="{ name: 'user_bot_index' }">my bot</router-link></li><li><a class="dropdown-item" href="#">exit</a></li></ul></li></ul></div></div></nav> </template><!-- js --> <script > // 实现选中的页面高亮 import { useRoute } from 'vue-router'; import { computed } from 'vue'; export default {setup() {const route = useRoute();let route_name = computed(() => route.name)return {route_name}} } </script><!-- css --> <!-- scoped 作用:写的css会加上一个随机字符串,使得样式不会影响组件以外的部分 --> <style scoped></style>
App.vue
<template><NavBar /><router-view></router-view> </template><script> import NavBar from './components/NavBar.vue' import "bootstrap/dist/css/bootstrap.min.css" import "bootstrap/dist/js/bootstrap" export default {components: {NavBar} } </script><style> body {background-image: url("@/assets/background.png");background-size: 100% 900%; } </style>
创建各页面组件
<template><div>404</div> </template><script> </script><style scoped></style>
<template><ContentField>对战</ContentField> </template><script> import ContentField from "../../components/ContentField.vue" export default {components: {ContentField} } </script><style scoped></style>
<template><ContentField>RankList排行榜</ContentField> </template><script> import ContentField from "../../components/ContentField.vue" export default {components: {ContentField} } </script><style scoped></style>
<template><ContentField>对局列表</ContentField> </template><script> import ContentField from "../../components/ContentField.vue" export default {components: {ContentField} } </script><style scoped></style>
<template><ContentField>我的Bot</ContentField> </template><script> import ContentField from "../../../components/ContentField.vue" export default {components: {ContentField} } </script><style scoped></style>
router---组件
import { createRouter, createWebHistory } from 'vue-router' import NotFound from "../views/error/NotFound" import PkIndexView from "../views/pk/PkIndexView" import RanklistIndexView from "../views/ranklist/RanklistIndexView" import RecordIndexView from "../views/record/RecordIndexView" import UserBotIndexView from "../views/user/bot/UserBotIndexView" const routes = [{path: "/pk/",name: "pk_index",component: PkIndexView},{path: "/error/",name: "404",component: NotFound},{path: "/record/",name: "record_index",component: RecordIndexView},{path: "/ranklist/",name: "ranklist_index",component: RanklistIndexView},{path: "/user/bot/",name: "user_bot_index",component: UserBotIndexView},// 重定向到404{path: "/:catchAll(.*)",redirect: "/error/"} ]const router = createRouter({history: createWebHistory(),routes })export default router
白板--组件
各页面白板构成一个组件components->ContentField.vue
<template><div class="container content-field"><div class="card"><div class="card-body"><!-- 渲染的东西放到 slot 中 --><slot></slot></div></div></div> </template><script> </script><style scoped> /* 20px的上边距 */ div.content-field {margin-top: 20px; } </style>
游戏对象类
scrips---->ACGameObject.js
const AC_GAME_OBJECTS = []; //存储所有运动对象 export class AcGameObject {constructor() {AC_GAME_OBJECTS.push(this);// 走的距离=速度*时间间隔//当前帧执行的时刻距离上一帧执行时刻的间隔,浏览器每一帧时间间隔可能有误差不一样因此需要记录this.timedelta = 0;// 是否执行过start函数this.has_called_start = false;}start() { //创建时,只执行一次}update() { // 除了第一帧外每一帧执行一次}// 删除之前用到的回调函数on_destroyed() {}destroyed() {this.on_destroyed();for (let i in AC_GAME_OBJECTS) {const obj = AC_GAME_OBJECTS[i];if (obj == this) {AC_GAME_OBJECTS.splice(i); //从数组删除break;}}} } let last_timestamp; //上一次执行的时刻 // requestAnimationFrame(函数) 函数在浏览器下一次渲染之前执行一遍 // 迭代执行step // in是下标 of是值 const step = timestamp => { // 每次调用会传入当前时刻for (let obj of AC_GAME_OBJECTS) {// 如果没执行start()if (!obj.has_called_start) {obj.has_called_start = true;obj.start();} else {obj.timedelta = obj.timestamp - last_timestamp;obj.update();}}last_timestamp = timestamp;// 下一帧执行steprequestAnimationFrame(step) } requestAnimationFrame(step)
地图
动起来:60帧/s
地图对象类
// export import {} export default import // 游戏地图的对象 import { AcGameObject } from "./AcGameObject"; import { Wall } from "./Wall"; export class GameMap extends AcGameObject {// 构造函数的两个参数, 画布,画布的父元素用于动态修改画布的长宽constructor(ctx, parent) { // ctx表示画布,parent表示画布的父元素// 先执行AcGameObject的构造函数super();this.ctx = ctx;this.parent = parent;this.L = 0; // 一个单位的绝对长度this.rows = 13; // 地图的行数this.cols = 13; // 地图的列数this.inner_walls_count = 6;this.wall = [];}check_connectivity(sx, sy, tx, ty, g) {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];// 没有撞墙,可以走到终点if (!g[x][y] && this.check_connectivity(x, y, tx, ty, g)) {return true;}}return false;}create_walls() {//new Wall(0, 0, this);// 墙true,无墙falseconst g = [];for (let r = 0; r < this.cols; 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;}// 创建随机障碍物,数量为inner_walls_countfor (let i = 0; i < this.inner_walls_count; i++) {for (let j = 0; j < 1000; j++) {let r = parseInt(Math.random() * this.rows);let c = parseInt(Math.random() * this.cols);if (g[r][c] || g[c][r]) continue;if ((r == this.rows - 2 && c == 1) || (r == 1 && c == this.cols - 2)) continue;g[r][c] = g[c][r] = true;break;}}// 转换成JSON再转换回来const copy_g = JSON.parse(JSON.stringify(g));if (!this.check_connectivity(this.rows - 2, 1, 1, this.cols - 2, copy_g)) {return false;}for (let r = 0; r < this.rows; r++) {for (let c = 0; c < this.cols; c++) {if (g[r][c]) {this.wall.push(new Wall(r, c, this));}}}return true;}start() {for (let i = 0; i < 1000; i++) {if (this.create_walls())break;}}update_size() { //每一帧都更新边长// 动态求最小正方形边长// 取整数this.L = 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() { //渲染 // 画地图const color_even = "#AAD751", color_odd = "#A2D149";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_even;} else {this.ctx.fillStyle = color_odd;}// 左上角左边,明确canvas坐标系this.ctx.fillRect(c * this.L, r * this.L, this.L, this.L);}}} }
游戏区域
components--->PlayGround.vue
<!-- 游戏区域 --> <template><div class="playground"><GameMap /></div> </template><script> import GameMap from "./GameMap.vue";export default {components: {GameMap,} } </script><style scoped> div.playground {width: 60vw;height: 70vh;margin: 40px auto;background-color: rgb(247, 248, 248); } </style>
地图组件
GameMap.vue
<template><!-- ref映射关联 --><div ref="parent" class="gamemap"><!-- 画布 --><canvas ref="canvas"></canvas></div> </template><script> import { ref, onMounted } from "vue"; import { GameMap } from "@/assets/scripts/GameMap";export default {setup() {let parent = ref(null);let canvas = ref(null);//组件挂载完之后,创建GameMap对象onMounted(() => {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>
实体墙
scrips----->wall.js
// 实体墙 import { AcGameObject } from "./AcGameObject"; export class Wall extends AcGameObject {constructor(r, c, gamemap) {super();this.r = r;this.c = c;this.gamemap = gamemap;this.color = "#B37226";}update() {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);} }
参考
项目实战——创建菜单与游戏页面(上)_游戏页面是如何实现的-CSDN博客