1.创建导航栏
因为每个界面的导航栏都是固定的,所以可以把导航栏写成一个组件。
如何单独写出一个组件并使用出来?
step1:在components文件夹下创建NavBar.vue文件
step2:在bootstrap上选择合适的样式复制下来
step3:在App.vue文件中导入NavBar.vue文件
<template>
<NavBar />
<router-view></router-view>
</template>
<script>
import NavBar from './components/NavBar.vue'
import "bootstrap/dist/css/bootstrap.min.css" //要导入bootstrap的样式
import "bootstrap/dist/js/bootstrap"
export default {
components: {
NavBar
}
}
</script>
<style>
body {
background-image: url("@/assets/background.png"); /*图片地址*/
background-size: cover; /*百分百覆盖界面*/
}
</style>
2.调整导航栏的样式
3.创建其他的界面
pk界面、ranklist界面、record界面、404界面
如何实现根据url跳转到相应的界面呢? 使用vue的路由机制 在router文件夹下的index.js里面进行修改
import { createRouter, createWebHistory } from 'vue-router'
import PkIndexView from '../views/pk/PkIndexView'
import RankListIndexView from '../views/ranklist/RankListView'
import RecordIndexView from '../views/record/RecordIndex'
import UserBotIndexView from '../views/user/bot/UserBotIndex'
import NotFound from '../views/error/NotFound'
const routes = [
//默认路由是pk界面
{
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
},
{
//匹配其他所有的非法的url
path:"/:catchAll(.*)",
redirect:"/404/"
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
实现点击界面上的按钮进行页面的跳转
方法1:修改href(每次都会刷新界面)
<a class="navbar-brand" href="/">King Of Bots</a>
方法2:(每次不会刷新界面)
将a标签改为router-link标签 name对应router下面的index.js里面的名称
<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">
<router-link class="nav-link" :to="{name:'pk_index'}">对战</router-link>
</li>
<li class="nav-item">
<router-link class="nav-link" :to="{name:'ranklist_index'}">对局列表</router-link>
</li>
<li class="nav-item">
<router-link class="nav-link" :to="{name:'record_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">
chenjiabin
</a>
<ul class="dropdown-menu">
<li>
<router-link class="dropdown-item" :to="{name:'user_bot_index'}">我的Bots</router-link>
</li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#">退出</a></li>
</ul>
</li>
</ul>
</div>
4.创建公共组件,放内容的(卡片) ContentField.vue
<template>
<div class="container content-field">
<div class="card">
<div class="card-body">
<slot>
</slot>
</div>
</div>
</div>
</template>
<script>
//上面的slot标签等待被填充
</script>
<style scoped>
div.content-field {
margin-top: 20px;
}
</style>
别的界面在导入
<template>
<div>
<ContentField>pk</ContentField>
</div>
</template>
<script>
import ContentField from '../../components/ContentField.vue'
export default {
components:{
ContentField
}
}
</script>
<style>
</style>
5.选中部分的高亮处理
使用vue的路由判断当前所在的界面,然后再从css上进行判断是否进行高亮处理
如何使用路由获取当前的界面?
<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上进行判断?
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<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 == 'ranklist_index' ? 'nav-link active' : 'nav-link'" :to="{name:'ranklist_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>
</ul>
其中 : 是v-bind 的缩写,用于绑定某个属性,到vue中去寻找这个值。.
6.画地图
每秒钟刷新60次,动态的计算,渲染帧(1s中60张图片)。
游戏当中需要动的每秒钟都要刷新60次,所以我们可以写一个基类AcGameObject.js来完成基本功能,别的功能来继承这个基类。
const AC_GAME_OBJECTS = [];
export class AcGameObject {
constructor() {
AC_GAME_OBJECTS.push(this);
this.timedelta = 0; //时间间隔
this.has_called_start = false;
}
start() { //只执行一次,创建的时候执行
}
update() { //每一帧执行一次,除了第一次之外
}
on_destory() { //删除之前执行
}
destory() {
this.on_destory();
for(let i in AC_GAME_OBJECTS) { //in是遍历的下标
const obj = AC_GAME_OBJECTS[i];
if(obj === this) {
AC_GAME_OBJECTS.splice(i);
break;
}
}
}
}
let last_timestamp; //上一次执行的时刻
const step = timestamp => {
for(let obj of AC_GAME_OBJECTS) { //of遍历的是对象
if(!obj.has_called_start) {
obj.has_called_start = true;
obj.start();
}
else {
obj.timedelta = timestamp - last_timestamp;
obj.update();
}
}
requestAnimationFrame(step); //递归下去 每一帧都执行step
}
requestAnimationFrame(step); //第一帧执行step
地图:
写一个GameMap组件,放地图
<template>
<div ref="parent" class="gamemap">
<canvas ref="canvas"></canvas>
</div>
</template>
<script>
//地图组件
import { GameMap } from '../assets/scripts/GameMap';
import { ref,onMounted } from 'vue';
export default {
setup() {
let parent = ref(null); //父元素 ref绑定div
let canvas = ref(null); //子元素 ref绑定canvas
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>
写一个地图类继承AcGameObject类
const AC_GAME_OBJECTS = [];
export class AcGameObject {
constructor() {
AC_GAME_OBJECTS.push(this);
this.timedelta = 0; //时间间隔
this.has_called_start = false;
}
start() { //只执行一次,创建的时候执行
}
update() { //每一帧执行一次,除了第一次之外
}
on_destory() { //删除之前执行
}
destory() {
this.on_destory();
for(let i in AC_GAME_OBJECTS) { //in是遍历的下标
const obj = AC_GAME_OBJECTS[i];
if(obj === this) {
AC_GAME_OBJECTS.splice(i);
break;
}
}
}
}
let last_timestamp; //上一次执行的时刻
const step = timestamp => {
for(let obj of AC_GAME_OBJECTS) { //of遍历的是对象
if(!obj.has_called_start) {
obj.has_called_start = true;
obj.start();
}
else {
obj.timedelta = timestamp - last_timestamp;
obj.update();
}
}
requestAnimationFrame(step); //递归下去 每一帧都执行step
}
requestAnimationFrame(step); //第一帧执行step
写一个墙类继承AcGameObject类
import { AcGameObject } from "./AcGameObject";
export class Wall extends AcGameObject {
constructor(r,c,gamemap) {
super();
this.r = r;
this.c = c;
this.game = gamemap;
this.color = "#B37226";
}
update() {
this.render();
}
render() {
const L = this.game.L;
const ctx = this.game.ctx;
ctx.fillStyle = this.color;
ctx.fillRect(this.c * L,this.r * L,L,L);
}
}
写一个地图类继承AcGameObject类
import { AcGameObject } from "./AcGameObject";
import { Wall } from './Wall.js';
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 = 20;
this.walls = [];
}
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];
if(!g[x][y] && this.check_connectivity(g,x,y,tx,ty))
return true;
}
return false;
}
create_walls() {
//用一个bool类型的数组判断是否加障碍物 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;
}
//创建随机障碍物
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);
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;
}
}
//复制一遍g数组
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]) {
this.walls.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 = 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() {
const color_even = "#AAD751",color_odd = "#AAD254";
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;
}
this.ctx.fillRect(c * this.L,r * this.L,this.L,this.L);
}
}
}
}