首页 > 解决方案 > Javascript Canvas 将图像附加到其他图像

问题描述

我正在使用 Node JS 和 Socket io 制作 2d 多人剑游戏。我有后端工作,但在前端我无法将剑连接到播放器上。我希望它像 剑附在玩家身上

但问题是当我移动鼠标时,剑会改变并离开玩家。 剑不附在玩家身上。所以如果你想知道我的游戏是如何工作的,基本上笑脸(玩家)总是指向鼠标(是的,它可能是颠倒的),你可以用箭头键移动玩家并用剑杀死其他玩家。

这是我绘制玩家和剑的代码。

function drawImageLookat(img, x, y, lookx, looky){
   ctx.setTransform(1, 0, 0, 1, x, y);  // set scale and origin
   ctx.rotate(Math.atan2(looky - y, lookx - x)); // set angle
   ctx.drawImage(img,-img.width / 2, -img.height / 2); // draw image
   ctx.setTransform(1, 0, 0, 1, 0, 0); // restore default not needed if you use setTransform for other rendering operations
}

const drawPlayer = (player) => {
    const img = new Image()
    const img2 = new Image()

img.src = "./assets/images/player.png"
img2.src = "./assets/images/sword.png"
img.onload = () => {
drawImageLookat(img, player.x, player.y, player.lookx, player.looky)
drawImageLookat(img2, player.x+50, player.y, player.lookx, player.looky)
}

  };

  socket.on('state', (gameState) => {
      ctx.clearRect(0,0, 1200, 700)
    for (let player in gameState.players) {
      drawPlayer(gameState.players[player])
    }
  })

基本上,这个“状态”在游戏状态中每秒发出 60 次。在游戏状态中,我们有玩家对象,其中包含 LookX(鼠标 X 位置)、LookY(鼠标 Y 位置)以及玩家的 X 和 Y。下面的代码在服务器端。

const gameState = {
  players: {}
}

io.on('connection', (socket) => {
  socket.on('newPlayer', () => {
    gameState.players[socket.id] = {
      x: 250,
      y: 250,
      lookx: 0,
      looky: 0,
      width: 25,
      height: 25
    }
  })
//More stuff below here which is skipped
setInterval(() => {
  io.sockets.emit('state', gameState);
}, 1000 / 60);

同样在客户端,这是我获得鼠标位置的方式

  //mouse stuf
  var pos = 0;
  document.addEventListener("mousemove", (e) => {
      pos = getMousePos(e)
  })
  
  function getMousePos(evt) {
      var rect = canvas.getBoundingClientRect();
      return {
        x: evt.clientX - rect.left,
        y: evt.clientY - rect.top
      };
    }

//send 2 server
setInterval(() => {
    socket.emit('mouseAngle', pos);
    socket.emit('playerMovement', playerMovement);
  }, 1000 / 60);

所以基本上我想要的是,当我旋转鼠标时,玩家应该始终指向鼠标,并且剑附在玩家的侧面并随之旋转......如果你需要,这是我的完整代码...

//SERVER.JS
var app = require('express')();
var express = require('express');
var path = require('path');
var http = require('http').createServer(app);
var io = require('socket.io')(http)

var htmlPath = path.join(__dirname, 'client');

app.use(express.static(htmlPath));

const gameState = {
  players: {}
}

io.on('connection', (socket) => {
  socket.on('newPlayer', () => {
    gameState.players[socket.id] = {
      x: 250,
      y: 250,
      lookx: 0,
      looky: 0,
      width: 25,
      height: 25
    }
  })

  socket.on('playerMovement', (playerMovement) => {
    const player = gameState.players[socket.id]
    const canvasWidth = 1200
    const canvasHeight = 700
    
    if (playerMovement.left && player.x > 0) {
      player.x -= 4
    }
    if (playerMovement.right && player.x < canvasWidth - player.width) {
    player.x += 4
  }
    
    if (playerMovement.up && player.y > 0) {
      player.y -= 4
    }
    if (playerMovement.down && player.y < canvasHeight - player.height) {
      player.y += 4
    }
  })

  socket.on("mouseAngle", (pos) => {
    const player = gameState.players[socket.id]
    player.lookx = pos.x;
    player.looky = pos.y;
  })

  socket.on("disconnect", () => {
    delete gameState.players[socket.id]
  })
})

setInterval(() => {
  io.sockets.emit('state', gameState);
}, 1000 / 60);




http.listen(3000, () => {
  console.log('listening on *:3000');
});
//Client/Index.html
var app = require('express')();
var express = require('express');
var path = require('path');
var http = require('http').createServer(app);
var io = require('socket.io')(http)

var htmlPath = path.join(__dirname, 'client');

app.use(express.static(htmlPath));

const gameState = {
  players: {}
}

io.on('connection', (socket) => {
  socket.on('newPlayer', () => {
    gameState.players[socket.id] = {
      x: 250,
      y: 250,
      lookx: 0,
      looky: 0,
      width: 25,
      height: 25
    }
  })

  socket.on('playerMovement', (playerMovement) => {
    const player = gameState.players[socket.id]
    const canvasWidth = 1200
    const canvasHeight = 700
    
    if (playerMovement.left && player.x > 0) {
      player.x -= 4
    }
    if (playerMovement.right && player.x < canvasWidth - player.width) {
    player.x += 4
  }
    
    if (playerMovement.up && player.y > 0) {
      player.y -= 4
    }
    if (playerMovement.down && player.y < canvasHeight - player.height) {
      player.y += 4
    }
  })

  socket.on("mouseAngle", (pos) => {
    const player = gameState.players[socket.id]
    player.lookx = pos.x;
    player.looky = pos.y;
  })

  socket.on("disconnect", () => {
    delete gameState.players[socket.id]
  })
})

setInterval(() => {
  io.sockets.emit('state', gameState);
}, 1000 / 60);




http.listen(3000, () => {
  console.log('listening on *:3000');
});
//Client/Assets/Js/script.js
var socket = io();

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

document.getElementById("game").width =1200;
document.getElementById("game").height = 700;

socket.emit('newPlayer');
//mouse stuff
function drawImageLookat(img, x, y, lookx, looky){
   ctx.setTransform(1, 0, 0, 1, x, y);  // set scale and origin
   ctx.rotate(Math.atan2(looky - y, lookx - x)); // set angle
   ctx.drawImage(img,-img.width / 2, -img.height / 2); // draw image
   ctx.setTransform(1, 0, 0, 1, 0, 0); // restore default not needed if you use setTransform for other rendering operations
}

const drawPlayer = (player) => {
    const img = new Image()
    const img2 = new Image()

img.src = "./assets/images/player.png"
img2.src = "./assets/images/sword.png"
img.onload = () => {
drawImageLookat(img, player.x, player.y, player.lookx, player.looky)
drawImageLookat(img2, player.x+50, player.y, player.lookx, player.looky)
}

  };

  socket.on('state', (gameState) => {
      ctx.clearRect(0,0, 1200, 700)
    for (let player in gameState.players) {
      drawPlayer(gameState.players[player])
    }
  })
//Client/assets/js/Controller.js
//v1
var canvas = document.getElementById("game")


const playerMovement = {
    up: false,
    down: false,
    left: false,
    right: false
  };
  const keyDownHandler = (e) => {
    if (e.keyCode == 39 || e.keyCode == 68) {
        playerMovement.right = true;
      } else if (e.keyCode == 37 || e.keyCode == 65) {
        playerMovement.left = true;
      } else if (e.keyCode == 38 || e.keyCode == 87) {
        playerMovement.up = true;
      } else if (e.keyCode == 40 || e.keyCode == 83) {

        playerMovement.down = true;
      }
  };
  const keyUpHandler = (e) => {
    if (e.keyCode == 39 || e.keyCode == 68) {
      playerMovement.right = false;
    } else if (e.keyCode == 37 || e.keyCode == 65) {
      playerMovement.left = false;
    } else if (e.keyCode == 38 || e.keyCode == 87) {
      playerMovement.up = false;
    } else if (e.keyCode == 40 || e.keyCode == 83) {
      playerMovement.down = false;
    }
  };
  document.addEventListener('keydown', keyDownHandler, false);
  document.addEventListener('keyup', keyUpHandler, false);

  //mouse stuf
  var pos = 0;
  document.addEventListener("mousemove", (e) => {
      pos = getMousePos(e)
  })
  
  function getMousePos(evt) {
      var rect = canvas.getBoundingClientRect();
      return {
        x: evt.clientX - rect.left,
        y: evt.clientY - rect.top
      };
    }

//send 2 server
setInterval(() => {
    socket.emit('mouseAngle', pos);
    socket.emit('playerMovement', playerMovement);
  }, 1000 / 60);

如果你能帮助我谢谢!

标签: javascripthtmlnode.jssocket.iohtml5-canvas

解决方案


有很多方法可以做到这一点。标准方法是使用第二个变换并将其与当前上下文变换相乘。

二维矩阵乘法

2d API 有两个函数可以输入一个矩阵来改变当前的变换。

  1. ctx.setTransform用新的变换替换当前变换
  2. ctx.transform将当前变换与当前变换的新设置相乘到结果矩阵。

这使您可以在分层转换结构中附加图像元素。(树状结构)

ctx.setTransform用于设置根变换,您用于ctx.transform附加继承前一个变换以及获得二级变换的子级。

假设您有一个图像,您可以像使用它一样进行转换

   ctx.setTransform(1, 0, 0, 1, x, y);  
   ctx.rotate(Math.atan2(looky - y, lookx - x));
   ctx.drawImage(img,-img.width / 2, -img.height / 2);

原点在图像的中心

要附加另一个图像,例如第二个图像以现有图像的右下角为中心,并将与现有图像一起旋转(缩放、平移、倾斜、镜像等)。

只需创建相对于当前变换(上一个图像)的第二个变换并用于ctx.transform进行矩阵乘法,然后渲染第二个图像

   ctx.transform(1, 0, 0, 1, img.width / 2, img.height / 2);  // bottom right of prev image
   ctx.drawImage(img2, -img2.width / 2, -img2.height / 2);

保存并恢复转换。

如果您需要获取父变换,则需要保存和恢复 2D API 状态

   ctx.setTransform(1, 0, 0, 1, x, y);  
   ctx.rotate(Math.atan2(looky - y, lookx - x));
   ctx.drawImage(img,-img.width / 2, -img.height / 2);
   ctx.save();

   // draw second image
   ctx.transform(1, 0, 0, 1, img.width / 2, img.height / 2); 
   ctx.drawImage(img2, -img2.width / 2, -img2.height / 2);

   ctx.restore();  // get the parent transform

   // draw 3rd image at top right
   ctx.transform(1, 0, 0, 1, img.width / 2, -img.height / 2); 
   ctx.drawImage(img3, -img3.width / 2, -img3.height / 2);

这对于简单的渲染是可以的,但对于更复杂的渲染可能会很慢或不方便。

DOMMatrix

而不是使用内置的转换,您可以使用DOMMatrix它,因为它提供了执行所有标准矩阵数学的函数。

例如前面的 3 张图像可以做为

   const ang = Math.atan2(looky - y, lookx - x);
   const ax = Math.cos(ang);
   const ay = Math.sin(ang);
   const parentMatrix = [ax, ay, -ay, ax, x, y];
   const child1Matrix = new DOMMatrix([1, 0, 0, 1, img.width / 2, img.height / 2]);
   const child2Matrix = new DOMMatrix([1, 0, 0, 1, img.width / 2, -img.height / 2]);


   // draw first image with root transform
   ctx.setTransform(...parentMatrix);  
   ctx.drawImage(img,-img.width / 2, -img.height / 2);

   // draw second image at bottom right
   const mat1 = new DOMMatrix(...parentMatrix);
   matrix.multiplySelf(child1Matrix);
   ctx.setTransform(mat1.a, mat1.b, mat1.c, mat1.d, mat1.e, mat1.f);  
   ctx.drawImage(img1,-img1.width / 2, -img1.height / 2);

   // draw third image at top right
   const mat2 = new DOMMatrix(...parentMatrix);
   matrix.multiplySelf(child2Matrix);
   ctx.setTransform(mat2.a, mat2.b, mat2.c, mat2.d, mat2.e, mat2.f);  
   ctx.drawImage(img2,-img2.width / 2, -img2.height / 2);

推荐阅读