javascript - 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);
如果你能帮助我谢谢!
解决方案
有很多方法可以做到这一点。标准方法是使用第二个变换并将其与当前上下文变换相乘。
二维矩阵乘法
2d API 有两个函数可以输入一个矩阵来改变当前的变换。
ctx.setTransform
用新的变换替换当前变换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);