20260509

歪日,早知道一帧就只发一个大包了,现在小包满天飞...
目前依旧是半成品,前端就写了个大概,后端也只写个大概,目前还不能控制移动...
不过也学到了挺多,比如渲染模块单独写到渲染器里,能让代码耦合度更低
依旧展示进度

后端

app.js
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,
    shootGap: 1000,
}

wss.on('connection', ws => {
    console.log('有玩家连接服务器');

    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':
                players.push(new Player(data))
                //这里有点屎了,为了解决新玩家不能同步老玩家的问题:一有玩家加入就给所有玩家广播现已有全部玩家
                wss.clients.forEach((client) => {
                    players.forEach(player => {
                        client.send(JSON.stringify({type:"pJoin",data:player}))
                    })
                })
                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: bullets.length,
                        from: players[index].name,
                        color: players[index].color,
                        at: players[index].at,
                        x: players[index].x,
                        y: players[index].y,
                        w: 10,
                        h: 10,
                        a: 0,
                        v: 5,
                        angle: players[index].angle
                    }));
                }
                break;
            //玩家死亡
            case 'pDelete':
                index = players.findIndex(player => player.id === data.id)
                if (index > -1) {
                    players.splice(index, 1)
                    console.log(data.name + " 离开游戏")
                }
                break;
        }
    })
})
//广播定时器
setInterval(() => {
    players.forEach(player => {
        //更新玩家位置
        player.playerMove();
        //广播玩家位置
        wss.clients.forEach(client => {
            client.send(JSON.stringify({type: "pPosition", data: player}));
        })
    })
    bullets.forEach(bullet => {
        //更新子弹位置
        bullet.bulletMove();
        //广播子弹位置
        wss.clients.forEach(client => {
            client.send(JSON.stringify({type: "bPosition", data: bullet}));
        })
    })
    //碰撞检测


}, config.FPS)

entity/Player.js
//玩家类
class Player {
  constructor({
    name,
    id,
    color,
    maxHp,
    hp,
    at,
    latestShoot,
    latestHurt,
    x,
    y,
    w,
    h,
    a,
    v,
    up,
    down,
    left,
    right,
    angle
  }) {
    this.name = name;
    this.id = id;
    this.color = color;
    this.maxHp = maxHp;
    this.hp = hp;
    this.at = at;
    this.latestShoot = latestShoot;
    this.latestHurt = latestHurt;
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    this.a = a;
    this.v = v;
    this.up = up;
    this.down = down;
    this.left = left;
    this.right = right;
    this.angle = angle;
  }
}

export default Player;
entity/Bullet.js
//子弹类
class Bullet {
  constructor({ id, from, color, at, x, y, w, h, a, v, angle }) {
    this.id = id;
    this.from = from;
    this.color = color;
    this.at = at;
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    this.a = a;
    this.v = v;
    this.angle = angle;
  }
}

export default Bullet;

前端

RectangleMove.vue
<template>
  <div class="CanvasContainer">
    <canvas width="1000" height="800" ref="backgroundCanvas"></canvas>
  </div>
</template>

<script setup>
import { onMounted, ref } from "vue"
import Player from "../entity/Player.js"
import Bullet from "../entity/Bullet.js"
import { drawPlayer, drawBullet } from "../renderer/renderer.js"

const backgroundCanvas = ref(null)
let ctx = null
let ws = null
//我的名字
let myName = Date.now()
//玩家集合
const players = []
//子弹集合
const bullets = []
//按键字典
const keys = {
  w: false,
  a: false,
  s: false,
  d: false,
  mouseX: 0,
  mouseY: 0,
  mouseDown: false,
  mouseLatestDown: 0
}
//按下按键
function keyUp() {

}
//松开按键
function keyDown() {

}
//鼠标按下
function mouseDown(e) {

}
//鼠标移动
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(() => {
  myName = prompt("输入你的昵称")

  const canvas = backgroundCanvas.value
  ctx = canvas.getContext("2d")

  ws = new WebSocket("ws://10.99.121.4:5000")

  ws.onopen = () => {
    let x = Math.random() * canvas.width
    let y = Math.random() * canvas.height
    const player1 = new Player({ name: myName, id: Date.now(), color: "red", maxHp: 100, hp: 100, at: 10, latestShoot: 0, latestHurt: 0, x: x, y: y, w: 60, h: 60, a: 0, v: 2, up: false, down: false, left: false, right: false, angle: 0 })
    players.push(player1)
    ws.send(JSON.stringify({ type: "pJoin", data: player1 }))
    console.log(player1)
    //绑定按键事件
    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
    if (type === "pJoin") {
      if (data.name === myName)
        return
      players.push(data)
      console.log(data.name + "加入了游戏")
    } else if (type === "pDelete") {
      const index = players.findIndex(p => p.id === data.id)
      if (index !== -1) {
        players.splice(index, 1)
        console.log(data.name + "离开了游戏")
      }
    } else if (type === "pPosition") {
      const index = players.findIndex(p => p.id === data.id)
      if (index !== -1) {
        players[index] = data
      }
    } else if (type === "pShoot") {
      bullets.push(data)
      console.log(data.from + "发射了一颗子弹")
    }
  }
})


</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>
Player.js和Bullet.js内只有和后端一样属性,没函数,就不写了
renderer.js
//渲染器
export const drawPlayer = (ctx, player) => {
  //画玩家
  ctx.fillStyle = player.color;
  ctx.fillRect(player.x - player.w / 2, player.y - player.h / 2, player.w, player.h);

  //画血条
  const barWidth = player.w;
  const barHeight = 4;
  const barY = player.y - player.h / 2 - 10;
  ctx.fillStyle = "#ff3333";
  ctx.fillRect(player.x - barWidth / 2, barY, barWidth, barHeight);
  const currentWidth = (player.hp / player.maxHp) * barWidth;
  ctx.fillStyle = "#33ff33";
  ctx.fillRect(player.x - barWidth / 2, barY, currentWidth, barHeight);

  //画名字
  ctx.fillStyle = "#fff";
  ctx.font = "14px Arial";
  ctx.textAlign = "center";
  ctx.fillText(player.name, player.x, player.y + player.h / 2 + 5);
};

export const drawBullet = (ctx, bullet) => {
  ctx.fillStyle = bullet.color;
  ctx.fillRect(bullet.x - bullet.w / 2, bullet.y - bullet.h / 2, bullet.w, bullet.h);
};
posted @ 2026-05-09 22:21  Leeszz  阅读(13)  评论(0)    收藏  举报