这节课只涉及前端知识
bootsrap 是让程序员实现美工的效果
建议先不跟着敲代码,可以先写笔记
工欲善其事,必先利其器;
启动脚手架 ,powershell 中使用 vue ui 上节课实现前端最终的效果,如下 ![img](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)
分析页面: 网站导航栏都一样,先实现导航栏组件 NavBar.vue;接在在 bootstrap 找到一个合适的导航栏模板前端代码;粘贴到标签中
导航栏都是一样的,不怎么改变,因此可以把导航栏提取出来,作为一个组件,复用。
创建一个组件 NavBar
在 App.vue 中 import NavBar from ‘./components/NavBar.vue’
利用 bootsrap 来快速实现
https://v5.bootcss.com/docs/components/navbar/
导航栏分析:
对应要写的各个页面,vue 文件,推荐在 views 文件夹创建对应的文件夹。每个页面包含三个标签
![img](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)
router
知识点 1:写完每个页面,需要和地址 url 关联起来 ;会使用到 router 路由组件。
<router-view>
会自动根据网址来变。定义的方式在 router/index.js 文件定义;
router-link
知识点 2:实现点击每个导航栏,填写<a href>
标签就跳转相应页面;
但是页面会重新刷新,前后端分离为了不让页面刷新;在 NavBar.vue 页面使用router-link
标签,代替<a href>
<router-link class=”navbar-brand” :to=”{ name: ‘home’ }”>King of Bots</router-link>
bootsrap 卡片
知识点 3:内容页面使用 card,在 bootstrap 框起来-
bootsrap 网址:https://v5.bootcss.com/docs/components/card/
每个页面的内容都要使用 card 框起来。会发现这个框是公共的部分。因此单做一个组件ContentField.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <template> <!-- 这可以当作公共内容,实现一个白框 --> <div class="container"> <div class="card"> <div class="card-body"> <!-- 框内填充的内容写在slot里面 --> <slot></slot>
</div> </div> </div> </template>
<script> </script>
<style> div.content-field {
margin-top: 20px; } </style>
|
之后把每个页面使用这个组件就可以。 例如,NotFound 页面先引入组件;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <template> <ContentField> 404 Not Found </ContentField> </template>
<script>
import ContentField from '../../components/ContentField.vue'
export default {
components: {
ContentField } } </script>
<style> </style>
|
另外一个功能就是点击每个导航栏,显示聚焦效果,高亮;
在标签里面加 active;
点击那个页面,使得哪个栏高亮;
方法:
用到userRoute
API; 在 NavBar 中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import { useRoute } from "vue-router"
import { computed } from "vue"
export default {
setup() {
const route = useRoute(); let route_name = computed(() => route.name) return {
route_name } } }
|
获取之后怎样使用? 需要在里面加:表达式,写法如下; 对战</router-link>
13*13 的地图,周围一圈都是墙,里面有些随机是墙。保证从左下角能够走到右上角。 生成合法地图,每次刷新都可以生成新的地图。
游戏里面怎移动的?
游戏动画的原理;
假设每秒刷新 60 次,每秒画面渲染 60 次,60 帧;每一帧的位置在哪里,放 60 张图片就会显示物体在移动;
需要写一个基类(工具),实现公共的功能;移动的物体。
每秒刷新 60 次;每个物体都需要 60 次;
代码脚本放到 assest 文件、里面
每一帧都要渲染一次
移动的基类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| const AC_GAME_OBJECTS = [];
export class AcGameObject {
constructor() {
AC_GAME_OBJECTS.push(this); this.timedelta = 0; this.has_called_start = false; } start() {
} upadate() {
} on_destroy() {
} destroy() {
this.on_destroy();
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;
const step = timestamp => {
for (let obj of AC_GAME_OBJECTS) {
if (!obj.has_called_start) {
obj.has_called_start = true; obj.start(); } else {
obj.timedelta = timestamp - last_timestamp; obj.update(); } } last_timestamp = timestamp;
requestAnimationFrame(step) }
requestAnimationFrame(step)
|
墙或者障碍物每一帧都需要渲染一遍; 游戏里面每一个组件都是一个类; 创建地图类:![img](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)
游戏的地图随着浏览器界面的变化,会自动发生界面变化;所以距离在写的时候不要使用绝对距离,而要使用相对距离。
整个地图单位是 13x13
在 PK 界面,内容框没有用,直接删区,需要把地图界面显示到这个页面;
可以写一个组件,让游戏地图显示在这个区域;
PlayGroud.vue
playground
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| <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; } </style>
|
需要在 PkIndexView.vue 页面把 playground 引入 ![img](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)
之后对局页面会显示方框,方框会随着浏览器大小自动变化
使其变为正方形;
创建一个板子 GameMap.vue 首先需要在 PlayGround.vue 引入 GameMap.vue plyground
GameMap.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| <template> <div ref="parent" class="gamemap"> <!-- 画布 --> <canvas ref="cannas"></canvas> </div> </template>
<script> import { GameMap } from "@/assets/scripts/GameMap"; import { ref, onMounted } from 'vue'
export default {
setup() {
let parent = ref(null); let canvas = ref(null);
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>
|
MDN 前端资料网站
接下来全程写 GameMap.js 文件~
需要求被这个方框包裹的最大面积的方块; ![img](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)
数组坐标系 ,往下是行,往右是列 canva 坐标系颠倒了 横着的是行 x 竖着的是列 y
课后一定要抄代,而不是复制代码,一个字母一个字母。
这节课实现结果如下:
代码保存一下 打开 kob 这个项目文件夹,打开 git bash
$ git status
$ git add .
$ git commit -m “实现了导航栏+对战页面的地图和障碍物”
$ git push
学会浏览器调错; GameMap.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
| 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 = 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() {
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++) {
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; } } 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_event = "#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_event; } else {
this.ctx.fillStyle = color_odd; } this.ctx.fillRect(c * this.L, r * this.L, this.L, this.L) } }
}
}
|
这节课效果如下,地图和蛇已经完成 ![img](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)