HTML5 Canvas Multiplayer Socket.io Sprite闪烁

时间:2018-02-06 01:51:26

标签: javascript canvas socket.io sprite multiplayer

我正在使用socket.io与Express结合来渲染画布多人游戏,我想从一开始就使用精灵和像素艺术,但是当我的画布更新时,我的精灵会闪烁一点。

这是socket.io服务器代码,它首先将连接的播放器添加到players字典,然后检查movement emit以更新播放器在画布上的位置:

    let players = {};

        io.on('connection', function (socket) {

          players[socket.id] = { x: 300, y: 300};

          socket.on('disconnect', function(){
           delete players[socket.id];

          });

         socket.on('movement', function(data) {
          let player = players[socket.id] || {};
          if (data.left) {
            player.x -= 5;
          }
          if (data.up) {
            player.y -= 5;
          }
          if (data.right) {
            player.x += 5;
          }
          if (data.down) {
            player.y += 5;
          }
        });

      });

    // Here the server emits\send the "state" of the canvas 
    // looping passing the dictionary of players

    setInterval(function() {
      io.emit('state', players);
    }, 1000 / 60);

这是客户端,有问题的代码是(imo):

var socket = io();

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

var movement = {
  up: false,
  down: false,
  left: false,
  right: false
}
document.addEventListener('keydown', function(event) {
  switch (event.keyCode) {
    case 65: // A
      movement.left = true;
      console.log("A");
      break;
    case 87: // W
      movement.up = true;
      console.log("W");
      break;
    case 68: // D
      movement.right = true;
      console.log("D");
      break;
    case 83: // S
      movement.down = true;
      console.log("S");
      break;
  }
});
document.addEventListener('keyup', function(event) {
  switch (event.keyCode) {
    case 65: // A
      movement.left = false;
      break;
    case 87: // W
      movement.up = false;
      break;
    case 68: // D
      movement.right = false;
      break;
    case 83: // S
      movement.down = false;
      break;
  }
});
/* end movements */
let canvas = document.getElementById('canvas');
canvas.width = width;
canvas.height = height;
let ctx = canvas.getContext('2d');

socket.on('state', function(players) {

    // Clear the canvas so that the previous positions are erased
    ctx.clearRect(0, 0, width, height);

    // Everytime you update (frame) the state of the canvas draw all players on it
    for (let id in players) {
    let player = players[id];
    // Drawing with fillRect = no flickering
    //ctx.fillRect(player.x, player.y, 100, 100);

    // Drawing my sprite image = flickering!

    let image = new Image();

    image.onload = function() {
     ctx.mozImageSmoothingEnabled = true;
     ctx.webkitImageSmoothingEnabled = true;
     ctx.msImageSmoothingEnabled = true;
     ctx.imageSmoothingEnabled = true;
     ctx.drawImage(image, player.x, player.y);
                            };
    image.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAAqCAYAAABYzsDTAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAUxJREFUWEftlk0KwjAQhb1CL9EjiD+4cyciint37goiiFAE1y506xkEV56gB/AC4jXcuIq8YmRME5MJqSJUeBT/vnnzOmlSq/3yNes3BDQfNHMthq1cy1H7TV4eK/hbbEmvLiDXWNJxR0BO2ZcKVxzAkU1OptUfvaD340qI07ogfO5Fpo6/Bu92NwJCJ7h6OZcxoAPqPAgcwPN2muceHH47pEIqOPy6T7RwOjXe01LBC6Max7GA5EKS73FF5uQ73phTkA5OC0dRxFtEX4F/ci1juewmPOd02aMA7YRGAjALLsGmmxkETh+3NufO7l0jkfMHcJZluawzyYUDCLA1f07eqktnuEve1ggKu/FzA3bY7e35WqrL54rpRMA2T/9QKhyF1AKmguwu1FOWrhgbSiG6Y5wXUJd3qfBgUZjaDXYTdQX+H15KB7p5t47jAzRfydFOtcn9AAAAAElFTkSuQmCC';
        }

});

setInterval(function() {
  socket.emit('movement', movement);
}, 1000 / 60);

问题是每当我在循环中使用ctx.clearRect(0, 0, width, height);清理画布时,我会有一些闪烁,但只有在使用图像时,如果我使用画布形状,我才会得到它。

为什么会这样?

如何避免闪烁?我真的需要使用精灵,简单的形状是禁忌。

有没有更好的方法在多人游戏中使用socket.io来“循环”我错过了? (我读到了关于animationframe但我真的不明白如何用socket.io来实现它。)

任何一点帮助都表示赞赏我一直在苦苦挣扎。

1 个答案:

答案 0 :(得分:0)

你的代码实际上比这个小闪烁有更大的问题 您每隔60秒通过网络发送图像数据 你无法通过这样的逻辑在每一端达到60FPS动画。

要做的第一件事是创建资产管理器,您将在其中加载游戏所需的spritesheets

在这种情况下,它似乎是一些皮肤。所以加载一次HTMLImage ,其精灵表将包含所有皮肤精灵,然后才能开始游戏,并使此HTMLImage可用于渲染循环,例如通过设置它作为游戏功能的全球化。

const assets = {
  skin: new Image();
};
assets.skin.onload = e => tellTheGameWeAreReady();
assets.skin.src = 'path/to/skin_spritesheet.png';

那么你将通过套接字发送的内容只是这个精灵表中的其他玩家必须绘制的坐标。

由于包含此精灵表的HTMLImage已经加载,因此您在socket.on('state'处理程序中所要做的就是在给定坐标处绘制精灵表的相应部分。

socket.on('state', function(players) {
  ctx.clearRect(0, 0, width, height);
  for( let id in players ) {
    let player = players[id];
    /*//socket should emit each 'player' as 
    {
      x: x_position of player in world,
      y: y_position of player in world,
      skin: {
        x: x_position of to be displayed sprite in the sprite-sheet
        y: y_position of to be displayed sprite in the sprite-sheet
        w: width of to be displayed sprite in the sprite-sheet
        h: height of to be displayed sprite in the sprite-sheet
      }
    }
    */
    ctx.drawImage( assets.skins, 
      player.skin.x,
      player.skin.y,
      player.skin.w,
      player.skin.h,
      player.x,
      player.y,
      player.skin.w,
      player.skin.h
    );
  }
});