当前位置: 首页 > news >正文

20260512

差不多了,除了一些小bug
懒得复制粘贴了,就展示下主要的三个文件吧,完整代码等完善以后再发一下

Room.vue
<template><div class="background"><div class="dashboard"><div class="playerList"><div class="title">玩家列表</div><div class="container"><div class="player" v-for="player in players" :key="player.id">{{ player.name }}</div></div><div class="join"><button class="joinGame" @click="join">加入</button></div></div></div><div class="modal" v-show="showModal"><div class="characterName"><p>你叫啥</p><input type="text" v-model="name"></div><div class="characterType"><p>选个角色</p><select v-model="type"><option value="StandardPlayer">标准角色</option></select></div><div class="characterColor"><p>选个颜色</p><input type="color" v-model="color"></div><button class="confirm" @click="confirm">确定</button></div></div>
</template><script setup>
import { onMounted, ref } from "vue"
import router from "../router";
import { serverUrl } from "../api/api.js"let players = ref([])let ws = null
let showModal = ref(false)
let name = ref("")
let color = ref("")
let type = ref("")function join() {showModal.value = true
}function confirm() {if (!name.value || !color.value || !type.value) {alert("请输入完整信息")return}localStorage.setItem("name", name.value)localStorage.setItem("type", type.value)localStorage.setItem("color", color.value)router.push("/RectangleMove")
}onMounted(() => {ws = new WebSocket(serverUrl)ws.onmessage = (message) => {const msg = JSON.parse(message.data)const type = msg.typeconst data = msg.dataswitch (type) {case "gameSync":players.value = data.playersbreak;}}
})</script><style scoped>
.background {display: flex;width: 100vw;height: 100vh;background-color: #000;align-items: center;justify-content: center;
}.dashboard {width: 800px;height: 600px;background-color: #333;border-radius: 10px;border: #260c51 solid 3px;
}.playerList {display: flex;flex-direction: column;align-items: center;justify-content: center;width: 100%;height: 100%;
}.title {font-size: 24px;color: #ffffff;align-content: center;justify-content: center;flex: 1
}.container {display: flex;flex-direction: column;align-items: center;width: 80%;flex: 5;background-color: #222;border-radius: 10px;overflow-y: scroll;scrollbar-width: none;
}.player {font-size: 18px;color: #ffffff;background-color: #000;padding: 10px;width: 80%;margin: 10px 0;border-radius: 5px;
}.join {display: flex;align-items: center;justify-content: center;flex: 1;
}.joinGame {flex: 1;font-size: 18px;color: #ffffff;background-color: #38166e;padding: 10px 20px;border-radius: 5px;cursor: pointer;
}.modal {width: 500px;height: 400px;overflow-y: scroll;scrollbar-width: none;display: flex;flex-direction: column;gap: 10px;padding: 50px;border: #231051 3px solid;position: fixed;top: 50%;left: 50%;transform: translate(-50%, -50%);background: #31254a;z-index: 10000;border-radius: 20px;
}.characterName,
.characterType,
.characterColor {flex: 3;display: flex;align-items: center;justify-content: center;font-size: 18px;color: #ffffff;background-color: #3a0852;border-radius: 10px;gap: 10px;
}.confirm {flex: 1;font-size: 18px;color: #ffffff;background-color: #38166e;margin: 10px 20px;border-radius: 5px;cursor: pointer;
}input {width: 70%;height: 30%;padding: 5px;border-radius: 5px;background-color: #491063;color: #ffffff;font-size: 20px;
}select {width: 70%;height: 40%;padding: 5px;border-radius: 5px;background-color: #491063;color: #ffffff;
}option {background-color: #491063;color: #ffffff;
}
</style>
RectangleMove.vue
<template><div class="CanvasContainer"><canvas width="1000" height="800" ref="backgroundCanvas"></canvas><button @click="test" style="display: none">测试</button></div>
</template><script setup>
import { onMounted, ref } from "vue"
import Player from "../entity/Player.js"
import Bullet from "../entity/Bullet.js"
import { serverUrl, playerJoin, playerMove, playerShoot, playerDelete } from "../api/api.js"
import { drawPlayer, drawBullet } from "../renderer/renderer.js"import StandardPlayer from "../entity/character/StandardPlayer.js"function test() {ws.send(JSON.stringify({ type: "test" }))
}const backgroundCanvas = ref(null)
let ctx = null
let ws = null
//我的
let name = null
let type = null
let color = null
//玩家集合
let players = []
//子弹集合
let bullets = []
//按键字典
const keys = {w: false,a: false,s: false,d: false,mouseX: 0,mouseY: 0,mouseDown: false,mouseLatestDown: 0
}
//按下按键
function keyDown(e) {const me = players.find(player => player.name === name)switch (e.key) {case "w":me.up = true;break;case "s":me.down = true;break;case "a":me.left = true;break;case "d":me.right = true;break;}playerMove(ws, me)
}
//松开按键
function keyUp(e) {const me = players.find(player => player.name === name)switch (e.key) {case "w":me.up = false;break;case "s":me.down = false;break;case "a":me.left = false;break;case "d":me.right = false;break;}playerMove(ws, me)
}
//鼠标按下
function mouseDown(e) {const me = players.find(player => player.name === name)me.angle = Math.atan2(keys.mouseY - me.y, keys.mouseX - me.x) / Math.PI * 180playerShoot(ws, me)
}
//鼠标移动
function mouseMove(e) {keys.mouseX = e.clientX - backgroundCanvas.value.getBoundingClientRect().left;keys.mouseY = e.clientY - backgroundCanvas.value.getBoundingClientRect().top;
}
//画鼠标准心
function drawMouse() {const cx = keys.mouseXconst cy = keys.mouseYconst len = 8const gap = 3ctx.strokeStyle = '#fff'ctx.lineWidth = 2ctx.beginPath()ctx.moveTo(cx, cy - gap - len)ctx.lineTo(cx, cy - gap)ctx.moveTo(cx, cy + gap)ctx.lineTo(cx, cy + gap + len)ctx.moveTo(cx - gap - len, cy)ctx.lineTo(cx - gap, cy)ctx.moveTo(cx + gap, cy)ctx.lineTo(cx + gap + len, cy)ctx.stroke()
}
//画
function draw() {//清空画布ctx.clearRect(0, 0, backgroundCanvas.value.width, backgroundCanvas.value.height)players.forEach(player => {drawPlayer(ctx, player)})bullets.forEach(bullet => {drawBullet(ctx, bullet)})drawMouse()
}
//动画
function animate() {draw()requestAnimationFrame(animate)
}
//初始化
onMounted(() => {const canvas = backgroundCanvas.valuectx = canvas.getContext("2d")name = localStorage.getItem("name") || "defaultName"type = localStorage.getItem("type") || "StandardPlayer"color = localStorage.getItem("color") || "red"ws = new WebSocket(serverUrl)ws.onopen = () => {let x = Math.random() * canvas.widthlet y = Math.random() * canvas.height//抱着试试的心态写,没想到真能用const me = new Player(new StandardPlayer({ name: name, id: Date.now(), color: color }))me.name = nameme.x = xme.y = yplayerJoin(ws, me)//绑定按键事件window.addEventListener('keydown', keyDown)window.addEventListener('keyup', keyUp)window.addEventListener('mousemove', mouseMove)window.addEventListener('mousedown', mouseDown)//启动animate()}//同步服务器数据ws.onmessage = (message) => {const msg = JSON.parse(message.data)const type = msg.typeconst data = msg.dataswitch (type) {case "gameSync":players = data.playersbullets = data.bullets}}
})</script><style scoped>
.CanvasContainer {display: flex;width: 100vw;height: 100vh;background-color: #000;align-items: center;justify-content: center;
}canvas {background-color: #000333;border: red solid 2px;cursor: none;
}
</style>
#####app.js
```JavaScript
import Player from './entity/PLayer.js';
import Bullet from './entity/Bullet.js';
import express from 'express';
import {WebSocketServer} from 'ws';
import cors from 'cors';const app = express();app.use(express.json());
app.use(cors());const server = app.listen(5000, () => {console.log('服务器运行在 http://localhost:5000');
});const wss = new WebSocketServer({server});let players = []
let bullets = []let config = {//频率FPS: 1000 / 60,//画面宽高canvasWidth: 1000,canvasHeight: 800,//射击间隔msshootGap: 500,
}wss.on('connection', ws => {ws.on('message', message => {const msg = JSON.parse(message);const type = msg.type;const data = msg.data;const tsp = Date.now();let index;//接受玩家消息switch (type) {//玩家加入case 'pJoin':index = players.findIndex(player => player.id === data.id)if (index === -1) {players.push(new Player(data))console.log(data.name + " 加入游戏")} else {console.log(data.name + "重新加入游戏")}break;//玩家移动case 'pMove':index = players.findIndex(player => player.id === data.id)if (index > -1) {players[index].up = data.up;players[index].down = data.down;players[index].left = data.left;players[index].right = data.right;}break;//玩家射击case 'pShoot':index = players.findIndex(player => player.id === data.id)if ((index > -1) && (tsp - players[index].latestShoot > config.shootGap)) {//新建子弹bullets.push(new Bullet({id: tsp,from: players[index].id,color: players[index].color,at: players[index].at,x: players[index].x,y: players[index].y,w: 10,h: 10,a: 0,v: 10,angle: data.angle}));//更新玩家角度players[index].angle = data.angle;//更新玩家最新射击时间戳players[index].latestShoot = tsp;}break;//玩家死亡case 'pDelete':index = players.findIndex(player => player.id === data.id)if (index > -1) {players.splice(index, 1)console.log(data.name + " 离开游戏")}break;case 'test':console.log(players, bullets);}})
})//广播定时器
setInterval(() => {//更新玩家位置players.forEach(player => {player.playerMove();//防止玩家出界player.x = Math.min(Math.max(player.x, player.w / 2), config.canvasWidth - player.w / 2);player.y = Math.min(Math.max(player.y, player.h / 2), config.canvasHeight - player.h / 2);})//更新子弹位置bullets.forEach(bullet => {bullet.bulletMove();//移除边界外子弹if (bullet.x > config.canvasWidth || bullet.x < 0 || bullet.y > config.canvasHeight || bullet.y < 0) {bullets.splice(bullets.findIndex(b => b.id === bullet.id), 1);}})//所有客户端同步玩家和子弹wss.clients.forEach((client) => {client.send(JSON.stringify({type: "gameSync", data: {players: players, bullets: bullets}}))})//碰撞检测players.forEach(player => {bullets.forEach(bullet => {const crush = (Math.abs(bullet.x - player.x) < (bullet.w + player.w) / 2 &&Math.abs(bullet.y - player.y) < (bullet.h + player.h) / 2 &&bullet.from !== player.id)switch (crush) {case true://碰撞bullets.splice(bullets.findIndex(b => b.id === bullet.id), 1);player.hp -= bullet.atif (player.hp <= 0) {players.splice(players.findIndex(p => p.name === player.name), 1);console.log(player.name + "被" + bullet.from + "击败");}break;case false://未碰撞break;}})})}, config.FPS)
http://www.jsqmd.com/news/804211/

相关文章:

  • 扩散模型点亮夜间卫星视野:RefDiff实现全天候可见光云图生成
  • 高斯模糊原理与工业级应用实战指南
  • 零代码到全球上线:我用 Dify + EdgeOne Pages 为跨境电商打造了一个 7×24 小时 AI 智能客服
  • HumanEval基准测试深度复现,从环境配置到评分脚本校验,手把手带你跑通DeepSeek-R1完整评估链
  • Hack The Box注册遇阻?别慌,这份Console报错排查与解决指南请收好
  • 【STM32实战—TOF激光测距】第二篇、I2C协议驱动TOF10120实现精准距离采集与滤波
  • 2026年4月评价高的日本游学中介口碑分析,日本短期游学咨询/日本留学中介/日语考级培训,日本游学机构口碑分析 - 品牌推荐师
  • 初次使用Taotoken平台从注册到完成API调用的全程指引
  • Delphi 12 出现“[FireDAC][Phys][SQLite][sqlite3]-303.Capability is not supported”的错误。
  • 淘金币自动化助手终极指南:3分钟完成淘宝日常任务
  • 在OpenClawAgent工作流中集成Taotoken实现多模型调度能力
  • 按类型搜索文件
  • AI模型镜像仓库实战指南:从Stable Diffusion到GPT的本地部署与集成
  • CTF解题复盘:我是如何一步步解开BUUCTF安洵杯那道easy misc的(附盲水印与Base全家桶实战)
  • MobaXterm密钥生成解决方案:零成本解锁专业版功能实现远程开发效率革命
  • 海洋塑料垃圾AI量化系统:YOLOv8多光谱鲁棒检测实战
  • 手把手教你用非root用户搞定Hadoop集群:从‘ERROR: Attempting to operate on hdfs namenode as root’说开去
  • 深度学习在系外行星探测中的应用:ExoDNN框架解析与实践
  • 弯曲波触觉反馈技术:为触摸屏注入真实按键手感的工程实践
  • 计算机视觉入门:从OpenCV到PyTorch的实践指南
  • 奇瑞的“亲儿子”实锤了!纵横品牌与奇瑞集团28年技术积淀的深度绑定 - 行业深度观察
  • STC8H8K64U单片机IAP升级实战:从官方例程到自定义协议的完整移植指南
  • 计算机视觉在智慧农业中的实战:从算法到田间部署全解析
  • Taotoken Token Plan套餐如何帮助个人开发者有效控制成本
  • 在旧版iOS设备上部署ChatGPT客户端:逆向工程与兼容性实战
  • 告别手动改包!用Fiddler的Free HTTP插件实现自动化测试(附实战配置)
  • 为OpenClaw智能体工作流配置Taotoken作为统一的模型调用后端
  • 2026年排插有哪些品牌?值得关注的实力之选 - 品牌排行榜
  • 空间计算驱动VR变革:从环境理解到裸手交互的架构演进与开发实践
  • 环境可靠性测试(SPC)的需求