摘要:当“羊了个羊”的消除玩法遇上物理引擎的重力掉落,会碰撞出怎样的火花?本文将带你手把手使用 Matter.js 2D 物理引擎配合 HTML5 Canvas,仅用 300 行代码实现一个自带物理碰撞、重力掉落、堆叠效果的消除小游戏。告别枯燥的网格布局,让卡牌“动”起来!
一、 创意缘起:为什么是物理版?
传统的“羊了个羊”类三消游戏,卡牌通常是静态层叠的,点击上层卡牌后,下层卡牌露出。虽然经典,但缺乏动态交互的爽快感。
如果我们给每一张卡牌加上实体(Body)和重力,让它们像积木一样堆叠在一起,会有什么效果?
- 动态布局:卡牌不再死板地排列,而是自然散落或堆成金字塔。
- 交互反馈:抽取底层卡牌时,上层卡牌会因为重力失去支撑而发生物理崩塌,视觉冲击力极强。
- 技术挑战:如何高性能地渲染几十上百个物理实体?如何处理点击拾取?
今天我们就用Matter.js来挑战这个创意。
二、 技术选型与架构
2.1 核心库选择
- 物理引擎:Matter.js
- 理由:最流行的 Web 2D 物理引擎,稳定、文档齐全,自带轻量级渲染器(Render),非常适合快速原型开发和轻量级游戏。
- 渲染层:HTML5 Canvas
- 理由:Matter.js 自带的 Render 基于 Canvas,足以应对数百个刚体的渲染,且修改绘制逻辑(如贴图、文字)非常方便。
2.2 核心逻辑架构
我们使用典型的游戏循环架构,物理模拟与渲染同步进行。
三、 核心代码实现
3.1 初始化物理世界
首先,我们需要创建一个物理世界,并设定重力。为了让掉落感更沉重、更爽快,我们将 Y 轴生力调大一点。
constEngine=Matter.Engine,Render=Matter.Render,Runner=Matter.Runner,Bodies=Matter.Bodies,Composite=Matter.Composite;constengine=Engine.create();// 调整重力,默认是 1,调到 1.2 让掉落更迅速engine.gravity.y=1.2;constrender=Render.create({element:document.body,engine:engine,options:{width:window.innerWidth,height:window.innerHeight,wireframes:false,// 关闭线框模式,使用颜色填充background:'#cce8cf'}});Render.run(render);construnner=Runner.create();Runner.run(runner,engine);3.2 生成卡牌实体
卡牌本质上就是矩形刚体(Rectangle Body)。这里有一个技巧:为了让它看起来像圆角卡牌,我们使用chamfer属性。
同时,我们给每个 Body 绑定一个自定义属性gameType,用于后续判断是否消除。
functioncreateTile(x,y,typeIndex){consttile=Bodies.rectangle(x,y,50,50,{chamfer:{radius:8},// 圆角效果render:{fillStyle:'#f5f5f5',// 卡牌底色strokeStyle:'#8d6e63',// 描边lineWidth:2}});tile.gameType=typeIndex;// 绑定类型数据returntile;}// 金字塔式生成for(letr=0;r<5;r++){for(letc=0;c<=r;c++){// 计算坐标,错位堆叠constx=window.innerWidth/2+(c-r/2)*55;consty=150+r*55;consttile=createTile(x,y,Math.floor(Math.random()*6));Composite.add(engine.world,tile);}}3.3 自定义渲染(绘制 Emoji)
Matter.js 默认只能渲染颜色或图片贴图。如果想直接显示 Emoji(🥕, 🐑),我们需要 Hook 它的渲染循环,在afterRender事件中直接操作 Canvas Context。
Matter.Events.on(render,'afterRender',function(){constctx=render.context;ctx.font='30px Arial';ctx.textAlign='center';ctx.textBaseline='middle';constbodies=Composite.allBodies(engine.world);bodies.forEach(body=>{// 排除墙壁和地面,只渲染有 gameType 的卡牌if(body.gameType!==undefined){const{x,y}=body.position;// 获取刚体中心实时坐标// 随刚体移动绘制 Emojictx.fillStyle='#000';ctx.fillText(TYPES[body.gameType],x,y+2);}});});🔥 重点解析:这是 Canvas 游戏开发常用的技巧。物理引擎只负责计算 Position 和 Angle,渲染层完全可以由我们接管。这样既享受了物理计算,又拥有了 Canvas 的绘图灵活性。
3.4 交互与射线检测
Matter.js 提供了Matter.Query.point类似于 Unity 的 Raycast,用于检测鼠标位置下的刚体。
// 监听鼠标按下Matter.Events.on(mouse,"mousedown",(event)=>{// 获取所有卡牌constallTiles=Composite.allBodies(engine.world).filter(b=>b.gameType!==undefined);// 射线检测constclickedBodies=Matter.Query.point(allTiles,event.mouse.position);if(clickedBodies.length>0){// 获取最上层的一个consttarget=clickedBodies[0];pickTile(target);}});3.5 消除逻辑
当卡牌被点击后,我们需要做两件事:
- 从物理世界移除:
Composite.remove(world, body)。只要移除了,上面的卡牌就会因为失去支撑而自然掉落——这就是物理版的精髓! - 加入 UI 槽位:将数据转移到逻辑数组
slots中,并检查三消。
functionpickTile(body){// 1. 物理移除Composite.remove(engine.world,body);// 2. 逻辑加入槽位addToSlot(body.gameType);}functioncheckEliminate(){// 简单的数组匹配逻辑letcount=1;for(leti=1;i<slots.length;i++){if(slots[i]===slots[i-1]){count++;if(count===3){// 触发消除,移除 i, i-1, i-2slots.splice(i-2,3);renderUI();// 更新 UIreturn;}}else{count=1;}}}四、 性能与体验优化
4.1 刚体休眠(Sleeping)
如果场景中有几百个方块,一直计算碰撞极其消耗 CPU。Matter.js 默认开启enableSleeping。当物体静止一段时间后,引擎会停止该物体的物理计算,直到它被撞击。
在初始化时确保开启(默认通常开启):
// engine.enableSleeping = true;但在消除游戏中,我们需要频繁唤醒,所以通常不需要手动干预,Matter.js 处理得很好。
4.2 手机端适配
Matter.js 的MouseConstraint在移动端可能需要处理touchstart事件兼容。本例中我们直接使用了 Matter.Mouse 模块,它内部已经处理了 DOM 事件的归一化,但在真机调试时,注意 Canvas 的touch-action: none防止页面滚动。
五、 总结
不到 300 行代码,我们就实现了一个具备以下特性的游戏原型:
- ✅真实物理反馈:卡牌掉落、碰撞、堆叠。
- ✅自定义渲染:Canvas 绘制 Emoji。
- ✅完整游戏闭环:点击 -> 拾取 -> 消除 -> 胜负判定。
Matter.js是一个非常强大的 2D 物理引擎,它不仅能做游戏,还能做物理特效网页(如掉落的彩带、重力感应的 Logo)。希望这篇教程能给你带来灵感,去创造更有趣的交互体验!