使用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
英文地址,請注明出處:http://en.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;這里的鍵與訓練數據中出…