Javascript画布将图像附加到其他图像

如何解决Javascript画布将图像附加到其他图像

我正在使用Node JS和Socket io制作2D多人剑术。 我有后端工作,但在前端我无法将剑固定在播放器上。 我希望它像 Sword is attached to player

但是问题是,当我移动鼠标时,剑会发生变化并且移到播放器外面。 Sword is not attached to player。因此,如果您想知道我的游戏的工作原理,基本上笑脸(玩家)总是指向鼠标(是的,它可能会颠倒过来),并且您可以使用箭头键移动玩家并使用剑杀死其他玩家。

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

function drawImageLookat(img,x,y,lookx,looky){
   ctx.setTransform(1,1,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); // 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.looky)
}

  };

  socket.on('state',(gameState) => {
      ctx.clearRect(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',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',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,() => {
  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,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",1000 / 60);

如果您能帮助我,谢谢!

解决方法

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

2D矩阵乘法

2d API具有两个用于输入矩阵以更改当前变换的函数。

  1. ctx.setTransform用新的替换当前的转换
  2. ctx.transform将当前变换乘以新设置,将当前变换乘以结果矩阵。

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

ctx.setTransform用于设置根变换,而您使用ctx.transform附加一个子对象,该子对象也继承了先前的变换,并且已经获得了辅助变换。

假设您有一个可以像处理一样变换的图像

   ctx.setTransform(1,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,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,-img.height / 2);
   ctx.save();

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

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

   // draw 3rd image at top right
   ctx.transform(1,-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,y];
   const child1Matrix = new DOMMatrix([1,img.height / 2]);
   const child2Matrix = new DOMMatrix([1,-img.height / 2]);


   // draw first image with root transform
   ctx.setTransform(...parentMatrix);  
   ctx.drawImage(img,-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.height / 2);

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-
参考1 参考2 解决方案 # 点击安装源 协议选择 http:// 路径填写 mirrors.aliyun.com/centos/8.3.2011/BaseOS/x86_64/os URL类型 软件库URL 其他路径 # 版本 7 mirrors.aliyun.com/centos/7/os/x86
报错1 [root@slave1 data_mocker]# kafka-console-consumer.sh --bootstrap-server slave1:9092 --topic topic_db [2023-12-19 18:31:12,770] WARN [Consumer clie
错误1 # 重写数据 hive (edu)&gt; insert overwrite table dwd_trade_cart_add_inc &gt; select data.id, &gt; data.user_id, &gt; data.course_id, &gt; date_format(
错误1 hive (edu)&gt; insert into huanhuan values(1,&#39;haoge&#39;); Query ID = root_20240110071417_fe1517ad-3607-41f4-bdcf-d00b98ac443e Total jobs = 1
报错1:执行到如下就不执行了,没有显示Successfully registered new MBean. [root@slave1 bin]# /usr/local/software/flume-1.9.0/bin/flume-ng agent -n a1 -c /usr/local/softwa
虚拟及没有启动任何服务器查看jps会显示jps,如果没有显示任何东西 [root@slave2 ~]# jps 9647 Jps 解决方案 # 进入/tmp查看 [root@slave1 dfs]# cd /tmp [root@slave1 tmp]# ll 总用量 48 drwxr-xr-x. 2
报错1 hive&gt; show databases; OK Failed with exception java.io.IOException:java.lang.RuntimeException: Error in configuring object Time taken: 0.474 se
报错1 [root@localhost ~]# vim -bash: vim: 未找到命令 安装vim yum -y install vim* # 查看是否安装成功 [root@hadoop01 hadoop]# rpm -qa |grep vim vim-X11-7.4.629-8.el7_9.x
修改hadoop配置 vi /usr/local/software/hadoop-2.9.2/etc/hadoop/yarn-site.xml # 添加如下 &lt;configuration&gt; &lt;property&gt; &lt;name&gt;yarn.nodemanager.res