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.type
    const data = msg.data
    switch (type) {
      case "gameSync":
        players.value = data.players
        break;
    }
  }
})

</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 * 180
  playerShoot(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.mouseX
  const cy = keys.mouseY
  const len = 8
  const gap = 3

  ctx.strokeStyle = '#fff'
  ctx.lineWidth = 2

  ctx.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.value
  ctx = 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.width
    let y = Math.random() * canvas.height
    //抱着试试的心态写,没想到真能用
    const me = new Player(new StandardPlayer({ name: name, id: Date.now(), color: color }))
    me.name = name
    me.x = x
    me.y = y
    playerJoin(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.type
    const data = msg.data
    switch (type) {
      case "gameSync":
        players = data.players
        bullets = 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,
    //射击间隔ms
    shootGap: 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.at
                    if (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)

posted @ 2026-05-12 19:51  Leeszz  阅读(14)  评论(0)    收藏  举报