一款多人,pvp, pve在线平台

xBcp6S.jpg


“Springboot实战项目”

1.创建公共组件AcGameObject.js

用作前端所有对象的基类,包括start(), update(), 每秒更新60帧
具体代码如下:

const AC_GAME_OBJECT = [];

export class AcGameobject {
constructor() {
AC_GAME_OBJECT.push(this);

this.timedelta = 0;
this.has_called_satrt = false;
}

start() { //只执行一次

}

update() { //每一帧执行一次,除了第一帧之外

}

on_destroy() {//删除之前执行

}



destroy() {//删除对象
this.on_destroy();

for (let i in AC_GAME_OBJECT) {
const obj = AC_GAME_OBJECT[i];
if (obj == this) {
AC_GAME_OBJECT.splice(i);
break;
}

}
}


}


let last_timestamp; //上一次执行的时刻
const step = timestamp => {
for (let obj of AC_GAME_OBJECT) {
if (!obj.has_called_satrt) {
obj.has_called_satrt = true;
obj.start();
} else {
obj.timedelta = timestamp - last_timestamp;
obj.update();
}
}
last_timestamp = timestamp;
requestAnimationFrame(step)
}

requestAnimationFrame(step)

2.创建PlayGround.vue用于显示地图,在PkindexView.vue中引用(入口):

PkindexView.vue代码如下:

<template>
<PlayGround />
</template>

<script>
import PlayGround from '../../components/PlayGround.vue'
export default {
components: {
PlayGround
}
}

</script>

<style scoped>
</style>

PlayGround.vue代码如下:

<template>
<div class="playground">
<GameMap/>
</div>
</template>

<script>
import GameMap from "./GameMap.vue";

export default {
components: {
GameMap,
}
}
</script>

<style scope>
div.playground {
width: 60vw;
height: 70vh;
margin: 40px auto;


}
</style>

2.1在PlayGround中还需要显示地图的区域,PlayGround为map的基层:

Map.vue代码如下:

<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);
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>


2.2由上代码可知需要一个对象逻辑GameMap.js来产生地图,以及根据页面大小时时根据canvas的大小来调整正方形地图的大小代码如下:

import { AcGameobject } from "./AcGameObject";
import { Wall } from "./Wall";

export class GameMap extends AcGameobject {
//ctx画布, parent画布的父元素用来动态修改画布长宽

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 r = 0; r < this.rows; r++) {
for (let c = 0; c < this.cols; c++) {
g[r][0] = g[r][this.cols - 1] = true;
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 (c == 1 && r == this.rows - 2 || r == 1 && c == this.cols - 2) continue;

if (g[r][c] || g[c][r]) 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_even = "rgb(162, 209, 73)", color_odd = "rgb(170, 214, 81)";
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);
}
}
}

}

2.3以上代码中产生了地图,墙,墙后渲染所以覆盖了地图,除此以外还加入了中间的障碍物,为了保证游戏公平性障碍物以对角线对称,并且每秒刷新60词,地图保证连通性通过check_connectivity()中的八皇后算法,中间障碍物需要一个Wall.js逻辑来渲染产生,生成墙函数create_wall()在satart()中调用,通过连同性函数检查一千次,如果超过一千次则无法产生地图。

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 = "rgb(185, 119, 43)";
}


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);
}
}