当前位置: 首页 > news >正文

使用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.dtcms.com/a/317089.html

相关文章:

  • GaussDB 数据库架构师修炼(六)-3 集群工具管理-主备倒换
  • CentOS7中Docker的安装与卸载
  • 8.6 CSS3rem布局
  • 聊一聊RPC接口测试工具及方法
  • 基于串口实现可扩展的硬件函数 RPC 框架(附完整 Verilog 源码)
  • 【第5话:相机模型1】针孔相机、鱼眼相机模型的介绍及其在自动驾驶中的作用及使用方法
  • 【计算机网络】王道考研笔记整理(3)数据链路层
  • 自己本地搭建的服务器怎么接公网?公网IP直连服务器方法,和只有内网IP直接映射到互联网
  • STM32 外设驱动模块二:蜂鸣器模块
  • 工控机 vs 服务器:核心区别与应用场景深度解析
  • 支持多网络协议的测试工具(postman被无视版)
  • Cortex-M MCU分散加载文件与链接文件关系
  • WebSocket 通信与 WebSocketpp 库使用指南
  • 哈尔滨云前沿-关于物理服务器
  • 计算机网络:一个 IP 地址可以同时属于 A 类、B 类或 C 类吗?
  • Anthropic MCP架构深度解析:下一代AI工具集成协议的设计哲学
  • 乱码原因、解决
  • SSL/TLS协议深度解析
  • Agent安全机制:权限控制与风险防范
  • React 表单处理:移动端输入场景下的卡顿问题与防抖优化方案
  • OpenAI最新开源:GPT-OSS原理与实践
  • OpenAI 开源GPT OSS系列模型
  • 【第6话:相机模型2】相机标定在自动驾驶中的作用、相机标定方法详解及代码说明
  • Ansys Discovery 2025R2的主要增强功能:CFD仿真的亮点
  • ubuntu 22.04 中安装python3.11 和 3.11 的 pip
  • PowerShell 入门4:动手实验篇
  • DHCP 服务器练习
  • 密集表盘漏检率↓79%!陌讯多模态融合算法在电表箱状态识别的边缘优化
  • QT+opencv+yolov8推理
  • 微软系统直链下载工具