我正在学习如何使用websocket和socket.io库.我正在创建一个测试2D在线游戏.在游戏中,用户控制一个球.通信方案如下:

Client (game.js):

  • 向服务器发送消息"new player",如果连接了新的播放器,则发送socket.id.
  • 向服务器发送消息"movement",如果玩家按下/解压控制键,则发送表单{ up: boolean, down: boolean, right: boolean, left: boolean, ID: socket.id }的所按键的对象.
  • 当接收到消息"state"时,应该呈现连接到游戏but for some reason it does not happen的所有玩家(我在游戏中只看到自己).

Server (server.js):

  • 当接收到消息"new player"时,在players对象中存储所连接的玩家的坐标[socket.id from the client]: {x: number, y: number}.
  • 当接收到消息"movement"时,根据按下的键改变玩家在players对象中的坐标.
  • 每隔16,67毫秒向客户端发送一条"state"消息.

game.js代码:

var socket = io();

socket.on("connect", function () {
  socket.emit("new player", socket.id);

  const canvas = document.getElementById("gameCanvas");
  const ctx = canvas.getContext("2d");

  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;

  const gridSize = 50;
  const playerSize = 20;
  const speed = 25;

  let playerX;
  let playerY;

  let movement = {
    up: false,
    down: false,
    left: false,
    right: false,
    shift: false,
    ID: socket.id
  };

  function drawGrid() {
    let colorIndex = 0;
    for (let x = 0; x < canvas.width; x += gridSize) {
      for (let y = 0; y < canvas.height; y += gridSize) {
        ctx.fillStyle = "green";
        ctx.fillRect(x, y, gridSize, gridSize);
      }
    }
  }

  function drawPlayer() {
    if (!playerX) return;
    if (!playerY) return;
    
    ctx.beginPath();
    ctx.arc(playerX, playerY, playerSize, 0, Math.PI * 2);
    ctx.fillStyle = "red";
    ctx.fill();
  }

  function clearCanvas() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
  }

  function updateCamera() {
    if (!playerX) return;
    if (!playerY) return;
    const cameraX = canvas.width / 2 - playerX;
    const cameraY = canvas.height / 2 - playerY;
    ctx.translate(cameraX, cameraY);
  }

  function update() {
    clearCanvas();
    updateCamera();
    drawGrid();
    drawPlayer();
    ctx.setTransform(1, 0, 0, 1, 0, 0);
  }

  function handleKeyPress(event) {
    switch (event.key) {
      case "a":
        movement.left = true;
        break;
      case "d":
        movement.right = true;
        break;
      case "w":
        movement.up = true;
        break;
      case "s":
        movement.down = true;
        break;
      case "Shift":
        movement.shift = true;
        break;
    }
  }

  function handleKeyRelease(event) {
    switch (event.key) {
      case "a":
        movement.left = false;
        break;
      case "d":
        movement.right = false;
        break;
      case "w":
        movement.up = false;
        break;
      case "s":
        movement.down = false;
        break;
      case "Shift":
        movement.shift = false;
        break;
    }
  }

  window.addEventListener("keydown", handleKeyPress);
  window.addEventListener("keyup", handleKeyRelease);

  function gameLoop() {
    update();
    requestAnimationFrame(gameLoop);
    socket.emit("movement", movement);
  }

  window.addEventListener("resize", () => {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    update();
  });

  gameLoop();

  socket.on("state", function (players) {
    clearCanvas();

    playerX = players[socket.id]?.x;
    playerY = players[socket.id]?.y;
    ctx.fillStyle = "yellow";

    for (var id in players) {
      if (id === socket.id) continue;
      var player = players[id];
      ctx.beginPath();
      ctx.arc(player.x, player.y, playerSize, 0, 2 * Math.PI);
      ctx.fill();
    }
  });
});

server.js代码:

let express = require('express');
let http = require('http');
let path = require('path');
let socketIO = require('socket.io');
let app = express();
let server = http.Server(app);
let io = socketIO(server);
let port = 5000;

app.set('port', port);
app.use('/static', express.static(__dirname + '/static'));

app.get('/', function (request, response) {
    response.sendFile(path.join(__dirname, 'index.html'));
});

server.listen(port, function () {
    console.log('Starting server on port: ' + port);
});

var players = {};
io.on('connection', function (socket) {
    socket.on('new player', function (ID) {
        players[ID] = {
            x: 959,
            y: 496.5
        };
    });
    socket.on('movement', function (data) {
        var player = players[data.ID] || {};
        if (data.left) {
            if (data.shift) {
                player.x -= 7.5
            } else {
                player.x -= 15;
            }
        }
        if (data.up) {
            player.y -= 15;
        }
        if (data.right) {
            if (data.shift) {
                player.x += 7.5
            } else {
                player.x += 15;
            }
        }
        if (data.down) {
            player.y += 15;
        }
    });
});
setInterval(function () {
    io.sockets.emit('state', players);
}, 1000 / 60);

index.html代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Canvas Game</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
        }
        canvas {
            display: block;
            background-color: #f0f0f0;
        }
    </style>
</head>
<body>
    <canvas id="gameCanvas"></canvas>
    <script src="/socket.io/socket.io.js"></script>
    <script src="static/game.js"></script>
</body>
</html>

我不明白到底是什么问题.我想我已经正确配置了WebSocket通信.但尽管如此,当一些人进入游戏时,出于某种原因,他们在游戏中看不到对方.

游戏预览(红球为球员):

Image

推荐答案

大多数动画的经验法则是:将所有位置更新和渲染保持在更新循环中,以便您可以控制顺序.循环外的任何呈现代码都会与主循环争夺优先级.回调应该只更新渲染循环可以读取的状态,并考虑更新位置和渲染下一帧.

你的主requestAnimationFrame圈擦go 了画布,只吸引了一个玩家:

function update() {
  clearCanvas();
  updateCamera();
  drawGrid();
  drawPlayer();
  ctx.setTransform(1, 0, 0, 1, 0, 0);
}

这会以每秒60次的速度运行,立即 destruct 您在socket.on("state", function (players) {中所做的绘图.

以下是最直接的解决办法:

let players = {};
function update() {
  clearCanvas();
  updateCamera();
  drawGrid();

  ctx.fillStyle = "yellow";
  for (const id in players) {
    if (id === socket.id) continue;
    const player = players[id];
    ctx.beginPath();
    ctx.arc(player.x, player.y, playerSize, 0, 2 * Math.PI);
    ctx.closePath();
    ctx.fill();
  }

  drawPlayer();
  ctx.setTransform(1, 0, 0, 1, 0, 0);
}

// ...

socket.on("state", function (_players) {
  playerX = _players[socket.id]?.x;
  playerY = _players[socket.id]?.y;
  players = _players;
});

还有其他问题(比如当玩家断开连接时永远不会移除),以及总体设计的改进空间(例如,如果没有发生任何事情,就不需要发出移动,如果服务器没有更新,也不需要重新绘制),但为了解除对直接问题的阻止,我就不提了.

Javascript相关问答推荐

使用JavaScript单击上一个或下一个特定按钮创建卡滑动器以滑动单个卡

如何修复循环HTML元素附加函数中的问题?

如何访问Json返回的ASP.NET Core 6中的导航图像属性

模块与独立组件

如何修复(或忽略)HTML模板中的TypeScript错误?'

colored颜色 检测JS,平均图像 colored颜色 检测JS

如何为我的astro页面中的相同组件自动创建不同的内容?

Google maps API通过API返回ZERO_RESULTS,以获得方向请求,但适用于Google maps

并不是所有的iframe都被更换

如何从HTML对话框中检索单选项组的值?

React.Development.js和未捕获的ReferenceError:未定义useState

当输入字段无效时,我的应用程序不会返回错误

如何使用TypeScrip设置唯一属性?

当标题被点击时,如何使内容出现在另一个div上?

不同表的条件API端点Reaction-redux

Reaction useState和useLoaderData的组合使用引发无限循环错误

在JavaScript中将Base64转换为JSON

未捕获的不变违规:即使在使用DndProvider之后也应使用拖放上下文

重新渲染过多(&Q).REACT限制渲染次数以防止无限循环.使用REACT下拉菜单时

ReactJS Sweep Line:优化SciChartJS性能,重用wasmContext进行多图表渲染