使用MatterJs物理2D引擎实现重力和鼠标交互等功能,有点击事件(盒子堆叠效果)

使用MatterJs物理2D引擎实现重力和鼠标交互等功能,有点击事件(盒子堆叠效果)

效果图:

在这里插入图片描述

直接上代码,我是用的是html,使用了MatterJs的cdn,直接复制到html文件中然后在浏览器打开即可

<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Matter.js Mixed Effects Demo</title><style>body {margin: 0;padding: 20px;font-family: Arial, sans-serif;background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);min-height: 100vh;box-sizing: border-box;}.container {max-width: 1200px;margin: 0 auto;}h1 {text-align: center;color: white;margin-bottom: 20px;text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);font-size: 2.2rem;}.controls {text-align: center;margin-bottom: 20px;display: flex;flex-wrap: wrap;justify-content: center;gap: 10px;}button {background: #4caf50;color: white;border: none;padding: 10px 18px;margin: 5px 0;border-radius: 5px;cursor: pointer;font-size: 1rem;transition: background 0.3s;min-width: 90px;}button:hover {background: #45a049;}button:active {transform: scale(0.95);}.canvas-container {text-align: center;margin-top: 20px;}#canvas {border: 3px solid #333;border-radius: 10px;box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);background: #f0f0f0;width: 100%;max-width: 800px;height: auto;aspect-ratio: 4/3;display: block;margin: 0 auto;}.info {background: rgba(255, 255, 255, 0.9);padding: 15px;border-radius: 10px;margin-top: 20px;box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);font-size: 1rem;}.info h3 {margin-top: 0;color: #333;}.info p {margin: 5px 0;color: #666;}@media (max-width: 900px) {.container {padding: 0 10px;}h1 {font-size: 1.5rem;}.info {font-size: 0.95rem;}}@media (max-width: 600px) {body {padding: 8px;}.container {padding: 0 2px;}.controls {gap: 6px;}button {font-size: 0.95rem;padding: 8px 10px;min-width: 70px;}#canvas {max-width: 100vw;min-width: 0;border-width: 2px;}.info {font-size: 0.9rem;padding: 10px;}}</style></head><body><div class="container"><h1>Matter.js Mixed Effects Demo</h1><div class="controls"><button onclick="addBox()">添加方块</button><button onclick="addCircle()">添加圆形</button><button onclick="addPolygon()">添加多边形</button><button onclick="addText()">添加文字</button><button onclick="addConstraint()">添加约束</button><button onclick="addExplosion()">爆炸效果</button><button onclick="addWind()">风力效果</button><button onclick="clearAll()">清除所有</button><button onclick="toggleGravity()">切换重力</button></div><div class="canvas-container"><canvas id="canvas" width="800" height="600"></canvas></div><div class="info"><h3>功能说明:</h3><p><strong>添加方块/圆形/多边形</strong>:创建不同形状的物体</p><p><strong>添加约束</strong>:在物体之间创建连接</p><p><strong>爆炸效果</strong>:在鼠标位置创建爆炸力</p><p><strong>风力效果</strong>:模拟风力对物体的影响</p><p><strong>切换重力</strong>:开启/关闭重力效果</p><p><strong>鼠标交互</strong>:点击并拖拽物体</p></div></div><script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js"></script><script>// 初始化Matter.js模块const {Engine,Render,World,Bodies,Body,Composite,Constraint,Mouse,MouseConstraint,Events,} = Matter;// 获取canvas实际宽高function getCanvasSize() {const container = document.querySelector(".canvas-container");let width = container.offsetWidth;let height = width * 0.75; // 4:3比例if (width > 800) {width = 800;height = 600;}return { width, height };}// 初始化canvas尺寸const canvas = document.getElementById("canvas");const { width: initWidth, height: initHeight } = getCanvasSize();canvas.width = initWidth;canvas.height = initHeight;// 创建引擎和渲染器,宽高为自适应canvas宽高const engine = Engine.create();const render = Render.create({canvas: canvas,engine: engine,options: {width: initWidth,height: initHeight,wireframes: false,background: "#f0f0f0",},});// 创建地面和墙体的函数function createBounds(width, height) {const ground = Bodies.rectangle(width / 2, height - 10, width, 20, {isStatic: true,render: { fillStyle: "#2c3e50" },});const leftWall = Bodies.rectangle(10, height / 2, 20, height, {isStatic: true,render: { fillStyle: "#2c3e50" },});const rightWall = Bodies.rectangle(width - 10, height / 2, 20, height, {isStatic: true,render: { fillStyle: "#2c3e50" },});const ceiling = Bodies.rectangle(width / 2, 10, width, 20, {isStatic: true,render: { fillStyle: "#2c3e50" },});return [ground, leftWall, rightWall, ceiling];}// 初始边界let bounds = createBounds(initWidth, initHeight);World.add(engine.world, bounds);// 创建鼠标约束const mouse = Mouse.create(render.canvas);const mouseConstraint = MouseConstraint.create(engine, {mouse: mouse,constraint: {stiffness: 0.2,render: {visible: false,},},});World.add(engine.world, mouseConstraint);// 启动引擎和渲染器Engine.run(engine);Render.run(render);// 全局变量let gravityEnabled = true;let windForce = 0;let constraints = [];// 工具函数:生成随机数字function getRandomNumber() {return Math.floor(Math.random() * 100) + 1;}// 添加方块函数function addBox() {const margin = 50;const width = render.options.width;const height = render.options.height;const number = getRandomNumber();const box = Bodies.rectangle(Math.random() * (width - 2 * margin) + margin,Math.random() * (height / 3 - margin) + margin,40,40,{render: {fillStyle: `hsl(${Math.random() * 360}, 70%, 60%)`,number: number,},restitution: 0.8,friction: 0.1,});box.customNumber = number;World.add(engine.world, box);}// 添加圆形函数function addCircle() {const margin = 50;const width = render.options.width;const height = render.options.height;const number = getRandomNumber();const circle = Bodies.circle(Math.random() * (width - 2 * margin) + margin,Math.random() * (height / 3 - margin) + margin,20,{render: {fillStyle: `hsl(${Math.random() * 360}, 70%, 60%)`,number: number,},restitution: 0.9,friction: 0.05,});circle.customNumber = number;World.add(engine.world, circle);}// 添加多边形函数function addPolygon() {const margin = 50;const width = render.options.width;const height = render.options.height;const number = getRandomNumber();const sides = Math.floor(Math.random() * 4) + 3; // 3-6边形const vertices = [];for (let i = 0; i < sides; i++) {const angle = (i / sides) * Math.PI * 2;const radius = 15 + Math.random() * 10;vertices.push({x: Math.cos(angle) * radius,y: Math.sin(angle) * radius,});}const polygon = Bodies.fromVertices(Math.random() * (width - 2 * margin) + margin,Math.random() * (height / 3 - margin) + margin,[vertices],{render: {fillStyle: `hsl(${Math.random() * 360}, 70%, 60%)`,number: number,},restitution: 0.7,friction: 0.2,});polygon.customNumber = number;World.add(engine.world, polygon);}// 添加文字函数function addText() {const margin = 50;const width = render.options.width;const height = render.options.height;const number = getRandomNumber();const text = Bodies.rectangle(Math.random() * (width - 2 * margin) + margin,Math.random() * (height / 3 - margin) + margin,80,30,{render: {fillStyle: `#ffffff00`,number: number,isText: true,},restitution: 0.8,friction: 0.1,});text.customNumber = number;text.isText = true;World.add(engine.world, text);}// 添加约束函数function addConstraint() {const bodies = Composite.allBodies(engine.world).filter((body) => !body.isStatic);if (bodies.length >= 2) {const bodyA = bodies[Math.floor(Math.random() * bodies.length)];const bodyB = bodies[Math.floor(Math.random() * bodies.length)];if (bodyA !== bodyB) {const constraint = Constraint.create({bodyA: bodyA,bodyB: bodyB,pointA: { x: 0, y: 0 },pointB: { x: 0, y: 0 },stiffness: 0.1,render: {strokeStyle: "#e74c3c",lineWidth: 2,},});constraints.push(constraint);World.add(engine.world, constraint);}}}// 爆炸效果函数function addExplosion() {const bodies = Composite.allBodies(engine.world).filter((body) => !body.isStatic);const explosionPoint = { x: 400, y: 300 };const explosionForce = 0.05;bodies.forEach((body) => {const distance = Math.sqrt(Math.pow(body.position.x - explosionPoint.x, 2) +Math.pow(body.position.y - explosionPoint.y, 2));if (distance < 200) {const force = explosionForce * (1 - distance / 200);const angle = Math.atan2(body.position.y - explosionPoint.y,body.position.x - explosionPoint.x);Body.applyForce(body, body.position, {x: Math.cos(angle) * force,y: Math.sin(angle) * force,});}});}// 风力效果函数function addWind() {windForce = windForce === 0 ? 0.001 : 0;}// 清除所有物体函数function clearAll() {const bodies = Composite.allBodies(engine.world).filter((body) => !body.isStatic);bodies.forEach((body) => {World.remove(engine.world, body);});constraints.forEach((constraint) => {World.remove(engine.world, constraint);});constraints = [];}// 切换重力函数function toggleGravity() {gravityEnabled = !gravityEnabled;engine.world.gravity.y = gravityEnabled ? 1 : 0;}// 应用风力效果Events.on(engine, "beforeUpdate", function () {if (windForce !== 0) {const bodies = Composite.allBodies(engine.world).filter((body) => !body.isStatic);bodies.forEach((body) => {Body.applyForce(body, body.position, {x: windForce,y: 0,});});}});// 自定义渲染数字(function patchRender() {const originalBodies = Render.bodies;Render.bodies = function (render, bodies, context) {// 先调用原始的 Render.bodiesoriginalBodies.call(this, render, bodies, context);const ctx = context || render.context;for (let i = 0; i < bodies.length; i++) {const body = bodies[i];if (body.customNumber) {ctx.save();if (body.isText) {// 文字图形:只显示文字,不显示背景ctx.font = "20px Arial";ctx.fillStyle = "#222";ctx.textAlign = "center";ctx.textBaseline = "middle";ctx.globalAlpha = 0.9;ctx.fillText(body.customNumber,body.position.x,body.position.y);} else {// 普通图形:显示数字ctx.font = `${Math.max(16,Math.floor(body.circleRadius? body.circleRadius: body.bounds.max.x - body.bounds.min.x) * 0.8)}px Arial`;ctx.fillStyle = "#222";ctx.textAlign = "center";ctx.textBaseline = "middle";ctx.globalAlpha = 0.9;ctx.fillText(body.customNumber,body.position.x,body.position.y);}ctx.restore();}}};})();// 点击事件,打印数字let touchStartPos = null;let touchStartTime = null;function handleClick(e) {const rect = render.canvas.getBoundingClientRect();let mouseX, mouseY;if (e.type === "touchstart" || e.type === "touchmove") {const touch = e.touches[0] || e.changedTouches[0];mouseX =(touch.clientX - rect.left) *(render.options.width / render.canvas.width);mouseY =(touch.clientY - rect.top) *(render.options.height / render.canvas.height);} else {mouseX =(e.clientX - rect.left) *(render.options.width / render.canvas.width);mouseY =(e.clientY - rect.top) *(render.options.height / render.canvas.height);}const bodies = Composite.allBodies(engine.world).filter((body) => !body.isStatic);for (let body of bodies) {if (Matter.Bounds.contains(body.bounds, { x: mouseX, y: mouseY })) {// 更精确判断(圆形/多边形)if (Matter.Vertices.contains(body.vertices, { x: mouseX, y: mouseY })) {if (body.customNumber) {console.log("点击数字:", body.customNumber);}break;}}}}function handleTouchStart(e) {const touch = e.touches[0];const rect = render.canvas.getBoundingClientRect();touchStartPos = {x: touch.clientX - rect.left,y: touch.clientY - rect.top,};touchStartTime = Date.now();}function handleTouchEnd(e) {if (!touchStartPos || !touchStartTime) return;const touch = e.changedTouches[0];const rect = render.canvas.getBoundingClientRect();const touchEndPos = {x: touch.clientX - rect.left,y: touch.clientY - rect.top,};// 计算移动距离和时间const distance = Math.sqrt(Math.pow(touchEndPos.x - touchStartPos.x, 2) +Math.pow(touchEndPos.y - touchStartPos.y, 2));const duration = Date.now() - touchStartTime;// 只有移动距离小于10px且时间小于300ms才认为是点击if (distance < 10 && duration < 300) {const mouseX =touchEndPos.x * (render.options.width / render.canvas.width);const mouseY =touchEndPos.y * (render.options.height / render.canvas.height);const bodies = Composite.allBodies(engine.world).filter((body) => !body.isStatic);for (let body of bodies) {if (Matter.Bounds.contains(body.bounds, { x: mouseX, y: mouseY })) {if (Matter.Vertices.contains(body.vertices, {x: mouseX,y: mouseY,})) {if (body.customNumber) {console.log("点击数字:", body.customNumber);}break;}}}}// 重置触摸状态touchStartPos = null;touchStartTime = null;}render.canvas.addEventListener("click", handleClick);render.canvas.addEventListener("touchstart", handleTouchStart);render.canvas.addEventListener("touchend", handleTouchEnd);// 鼠标点击事件Events.on(mouseConstraint, "mousedown", function (event) {const bodies = event.source.body;if (bodies) {Body.setAngularVelocity(bodies, 0);}});// 键盘控制document.addEventListener("keydown", function (event) {switch (event.key) {case "b":case "B":addBox();break;case "c":case "C":addCircle();break;case "p":case "P":addPolygon();break;case "e":case "E":addExplosion();break;case "w":case "W":addWind();break;case "g":case "G":toggleGravity();break;case " ":clearAll();break;}});// 让canvas自适应屏幕宽度和高宽比function resizeCanvas() {const prevWidth = render.options.width;const prevHeight = render.options.height;const { width, height } = getCanvasSize();canvas.width = width;canvas.height = height;render.options.width = width;render.options.height = height;render.canvas.width = width;render.canvas.height = height;// 缩放所有非静态物体的位置和大小const scaleX = width / prevWidth;const scaleY = height / prevHeight;const bodies = Composite.allBodies(engine.world).filter((body) => !body.isStatic);bodies.forEach((body) => {// 缩放位置Body.setPosition(body, {x: body.position.x * scaleX,y: body.position.y * scaleY,});// 缩放大小(仅对矩形和圆形,复杂多边形可选)if (body.circleRadius) {Body.scale(body, scaleX, scaleY);} else if (body.vertices.length === 4) {Body.scale(body, scaleX, scaleY);}});// 移除旧边界if (bounds) {bounds.forEach((b) => World.remove(engine.world, b));}// 添加新边界bounds = createBounds(width, height);World.add(engine.world, bounds);}window.addEventListener("resize", resizeCanvas);resizeCanvas();// 初始添加一些物体setTimeout(() => {for (let i = 0; i < 5; i++) {addBox();addCircle();}}, 1000);</script></body>
</html>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/917891.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/917891.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何玩转 Kubernetes K8S

在容器化时代&#xff0c;虽然Docker已经很强大了&#xff0c;但是在实际使用上还是有诸多不便&#xff0c;比如集群管理、资源调度、文件管理等等。 不过目前也涌现了很多解决方案&#xff0c;比如 Mesos、Swarm、Kubernetes 等等&#xff0c;其中谷歌开源的 Kubernetes就是其…

论文阅读笔记:Dataset Condensation with Gradient Matching

论文阅读笔记&#xff1a;Dataset Condensation with Gradient Matching1. 解决了什么问题&#xff1f;(Motivation)2. 关键方法与创新点 (Key Method & Innovation)2.1 核心思路的演进&#xff1a;从参数匹配到梯度匹配2.2 算法实现细节 (Implementation Details)3. 实验结…

网安学习no.22

一、基础系统信息命令&#xff08;简单入门&#xff09;uname作用&#xff1a;查看系统内核信息示例&#xff1a;uname -a&#xff08;显示完整内核版本、主机名、硬件架构等&#xff09;hostname作用&#xff1a;查看或设置主机名示例&#xff1a;hostname&#xff08;显示当前…

AJAX的引入

是的&#xff0c;AJAX 的一个主要特点就是通过 局部刷新 来实现与服务器的交互&#xff0c;而不需要重新加载整个页面。通过 AJAX&#xff0c;JavaScript 可以发送异步的 HTTP 请求&#xff0c;接收到响应数据后&#xff0c;更新页面的某个部分&#xff08;DOM&#xff09;。这…

SpringBoot 整合 Langchain4j AIService 深度使用详解

目录 一、前言 二、AIService 介绍 2.1 AiService 是什么 2.2 AiService 主要功能 2.3 AiService 使用步骤 三、AIService 操作实践 3.1 前置准备 3.1.1 获取apikey 3.1.2 导入核心依赖 3.1.3 添加配置文件 3.1.4 前置导入案例 3.2 AIService 案例操作详解 3.2.1 入…

基于FFmpeg和HLS的大文件分片传输方案

1&#xff1a;功能介绍 在视频这类大文件的传输过程中&#xff0c;经常会因为文件太大而受到网络带宽的限制。比如在实现视频预览功能时&#xff0c;常常会出现长时间加载、缓存卡顿的问题。我在项目中也遇到了类似的情况&#xff0c;于是采用了这个解决方案。 我们可以利用 FF…

体育场预定-下单-扣减库存一致性

流程1:通过库存服务缓存(缓存里面不仅有位图存储该时间点id的位置信息还有库存信息)的Redis获取令牌2:拿着令牌向订单服务同步下单如果有令牌就执行下面的Redis&#xff0c;如果没有就直接返回扣减Redis库存缓存扣减成功:继续扣减失败:返回前端重试整套流程3:1锁2查3更新生成订…

【计算机网络】王道考研笔记整理(3)数据链路层

目录 第三章 数据链路层 3.1 数据链路层的功能 3.2 组帧 3.2.1 字符计数法 3.2.2 字节填充法 3.2.3 零比特填充法 3.2.4 违规编码法 3.3 差错控制 3.3.1 奇偶校验码 3.3.2 CRC 校验码 3.3.3 海明校验码 3.4 可靠传输与流量控制 3.4.1 滑动窗口机制 3.4.2 停止 - 等待…

【后端】java 抽象类和接口的介绍和区别

文章目录一、抽象类&#xff08;Abstract Class&#xff09;二、接口&#xff08;Interface&#xff09;三、核心区别总结四、使用场景对比五、从设计思想理解最佳实践在Java中&#xff0c;抽象类&#xff08;Abstract Class&#xff09;和接口&#xff08;Interface&#xff0…

Apache OFBiz Scrum 组件命令注入漏洞

【严重】Apache OFBiz Scrum 组件命令注入漏洞 漏洞描述 Apache OFBiz 是一款知名的开源企业资源规划(ERP)解决方案&#xff0c;它提供了一整套开箱即用的企业级应用。Scrum 是 OFBiz 的一个插件&#xff0c;旨在为敏捷开发团队提供项目管理功能&#xff0c;其中包括与 SVN 版…

FastAPI入门:多个文件、后台任务、元数据和文档 URL

更大的应用 - 多个文件 假设文件结构如下&#xff1a;. ├── app # 「app」是一个 Python 包 │ ├── __init__.py # 这个文件使「app」成为一个 Python 包 │ ├── main.py # 「main」模块&#xff0c;例如 import app.main │ ├…

一个示例mcp agent功能的交互式框架

https://github.com/whym3/Deepseek_MCPDeepseek_MCP https://github.com/whym3/Deepseek_MCP Deepseek_MCP是一个演示mcp agent的框架&#xff0c;基于Flask开发&#xff0c;支持在浏览器采用交互方式与deepseek及agent对话。需要注册外部Deepseek api&#xff0c;不支持本地…

nodejs 基础知识-2

模块的暴露和导入 编写date.js module.exports.echo 导出的名称 module.exports.echo function echo(){ return Date.now(); } 编写 index.js const echoDate require(‘./date.js’) 在index引入 console.log(echoDate.echo()); //调用 开发一个自定义模块 exports.forma…

递归推理树(RR-Tree)系统:构建认知推理的骨架结构

探索基于三维评估的动态推理系统如何实现智能决策与知识演化引言 在复杂问题求解领域&#xff08;如战略决策或科学探索&#xff09;&#xff0c;人类思维的递归本质为AI系统设计提供了重要启发。我设计并实现的递归推理树&#xff08;Recursive Reasoning Tree, RR-Tree&#…

《动手学深度学习》读书笔记—9.5机器翻译与数据集

本文记录了自己在阅读《动手学深度学习》时的一些思考&#xff0c;仅用来作为作者本人的学习笔记&#xff0c;不存在商业用途。 语言模型是自然语言处理的关键&#xff0c; 而机器翻译是语言模型最成功的基准测试。 因为机器翻译正是将输入序列转换成输出序列的 序列转换模型&a…

Mysql进行操作时锁的具体行为

场景一&#xff1a;单个事务更新一条存在的数据 假设有表 user (id PK, name, age)&#xff0c;数据&#xff1a;[id1, nameAlice, age25] 你的 SQL&#xff1a; UPDATE user SET age 26 WHERE id 1; 底层动作&#xff1a; 事务 A (主动方) 发起更新请求。Lock Manager 介入&…

人工智能领域、图欧科技、IMYAI智能助手2025年7月更新月报

IMYAI 平台 2025 年 7 月重要功能更新与优化汇总 2025年07月31日更新 细节优化&#xff1a; 修复了移动端提交后自动弹出侧边栏的BUG。优化对话高级配置界面&#xff0c;增加滚动条并固定高度&#xff0c;避免内容超出屏幕。音乐生成界面的人声选择新增“合唱”选项&#xff…

HTTP 与 HTTPS 的区别深度解析:从原理到实践

HTTP 和 HTTPS 是现代 Web 开发中不可或缺的协议&#xff0c;它们决定了浏览器与服务器之间数据传输的方式。HTTPS 作为 HTTP 的安全版本&#xff0c;在安全性、性能和用户体验上都有显著提升。本文将通过万字篇幅&#xff0c;结合图表和代码示例&#xff0c;详细剖析 HTTP 与 …

STM32F407VET6学习笔记11:smallmodbus_(多从机)创建新的slave从机

今日记录一些smallmodbus 创建新的slave 从机 的过程&#xff0c;以及使用的关键点. 目录 创建新的从机对应操作函数与buffer 创建新的从机线程与操作代码&#xff1a; slave使用的要点&#xff1a; 完整的slave代码&#xff1a; 能正常通信&#xff1a; 创建新的从机对应操作函…

【论文阅读】Transformer Feed-Forward Layers Are Key-Value Memories

Transformer Feed-Forward Layers Are Key-Value Memories 原文摘要 研究背景与问题&#xff1a; 前馈层占Transformer模型参数总量的2/3&#xff0c;但其功能机制尚未得到充分研究 核心发现&#xff1a;提出前馈层实质上是键值存储系统 键&#xff1a;这里的键与训练数据中出…