Scratch中构建简易2D物理引擎:从重力弹跳到碰撞检测 在游戏开发或互动动画项目中物理引擎是实现物体真实运动、碰撞和重力效果的核心。对于Scratch这样的图形化编程平台虽然内置了基础的移动和碰撞检测但想要实现更复杂、更真实的物理模拟就需要我们手动搭建一套简易的“引擎”。很多初学者觉得这很困难其实只要理解了几个核心物理概念并用Scratch的积木块来实现你会发现它并没有想象中那么复杂。本文将带你从零开始在Scratch中构建一个包含重力、弹跳、碰撞检测的简易2D物理引擎并提供完整的代码示例和项目思路无论是用于教学演示还是你自己的小游戏开发都能直接套用。1. 物理引擎核心概念与Scratch适配在开始动手之前我们首先要明确几个基本概念并思考如何在Scratch的积木逻辑中表达它们。1.1 什么是物理引擎物理引擎是一套模拟真实世界物理规律如牛顿力学的计算机程序。在游戏中它主要负责计算物体的位置、速度、旋转并处理它们之间的碰撞反应。一个完整的物理引擎通常包含刚体动力学处理物体的移动和旋转。碰撞检测判断两个物体是否接触。碰撞响应计算碰撞后物体的速度、方向变化。约束求解处理如关节、弹簧等特殊连接关系。对于Scratch我们的目标是实现一个简化版的2D刚体物理引擎重点关注速度、加速度和矩形碰撞。1.2 Scratch中的坐标与运动系统Scratch舞台是一个480x360像素的坐标系中心点(0,0)。角色的位置由x和y坐标决定。Scratch原生提供了移动10步、将x坐标增加10、面向90方向等积木但这些是“瞬移”式的不符合物理规律。我们要用物理的方式来模拟运动核心是位置 (Position)用x和y变量表示。速度 (Velocity)用vx水平速度和vy垂直速度变量表示单位是“像素/帧”。加速度 (Acceleration)用ax水平加速度和ay垂直加速度变量表示单位是“像素/帧²”。重力就是一种持续的垂直加速度。物理运动的基本公式是新速度 旧速度 加速度新位置 旧位置 新速度。我们将在每一帧循环中计算这些值。1.3 本教程的目标与范围我们将创建一个包含以下功能的简易物理引擎重力与自由落体物体受到向下的恒定加速度。弹跳与能量损失物体撞击地面后以一定比例反弹。基础的矩形碰撞检测判断两个矩形精灵是否重叠。碰撞后的简单响应发生碰撞后让物体停止或反弹。我们将创建两个角色一个受物理控制的“小球”和一个静止的“平台”来演示整个过程。2. 环境准备与项目初始化2.1 所需环境平台Scratch 在线编辑器 (https://scratch.mit.edu) 或 Scratch 3.0 桌面版均可。前置知识了解Scratch基本操作熟悉“事件”、“控制”、“运动”、“变量”类积木。2.2 创建新项目与角色打开Scratch创建一个新项目。删除默认的小猫角色。从角色库中添加一个“球”角色例如Ball这将作为我们的物理对象。再添加一个“矩形”角色例如Button将其压扁拉长作为地面或平台命名为“平台”。确保“球”角色在“平台”角色之上这样球才能掉落在平台前面。2.3 创建必要的变量为“球”角色创建以下变量选择“仅适用于当前角色”vx水平速度vy垂直速度重力重力加速度值这是一个常数可以适用于所有角色反弹系数碰撞后速度保留的比例例如0.8表示保留80%的速度。为整个项目创建一个变量选择“适用于所有角色”地面_y表示地面所在的y坐标。初始化这些变量是物理模拟正确开始的第一步。3. 核心物理逻辑实现我们将物理模拟的核心循环放在“球”角色的代码中。3.1 运动模拟速度与位置的更新这是物理引擎的心脏。我们使用一个“重复执行”循环来模拟每一帧的物理计算。当 ⚑ 被点击 隐藏变量 [vx] [vy] // 在舞台上隐藏便于观察 将 [vx] 设为 [0] // 初始水平速度为零 将 [vy] 设为 [0] // 初始垂直速度为零 将 [重力] 设为 [-0.5] // 重力加速度负值表示向下 将 [反弹系数] 设为 [0.8] // 碰撞后保留80%的速度 将 [地面_y] 设为 [-150] // 假设舞台底部y坐标为-150 重复执行 将 [vy] 增加 (重力) // 应用重力加速度 将 x 增加 (vx) // 根据水平速度更新x坐标 将 y 增加 (vy) // 根据垂直速度更新y坐标 如果 (y) (地面_y) 那么 // 检测是否碰到“地面” 将 y 设为 (地面_y) // 防止穿透地面 将 [vy] 设为 ((vy) * (-反弹系数))) // 垂直速度反向并衰减 end end代码解释将 [vy] 增加 (重力)每一帧垂直速度都受到重力影响而增加因为重力是负值所以是向下加速。将 x 增加 (vx)和将 y 增加 (vy)用速度来更新位置这是物理运动的关键区别于直接设定坐标。碰撞检测如果 (y) (地面_y)这里我们用一个简单的y坐标判断来模拟碰撞地面。更通用的碰撞检测在下一节。碰撞响应将 [vy] 设为 ((vy) * (-反弹系数))当碰撞发生时将垂直速度反向乘以-1并乘以一个小于1的反弹系数来模拟能量损失这样球每次弹起的高度都会降低。运行这段代码你会看到球做自由落体触地后反复弹跳且越跳越低最后几乎停在“地面”上。3.2 通用矩形碰撞检测上面的例子只检测了和“地面”的碰撞。为了检测球和“平台”角色的碰撞我们需要一个通用的矩形碰撞函数。在Scratch中我们可以利用角色的造型信息来获取其边界。为“球”角色创建以下自定义积木在“更多积木”中名称检测与 [其他角色] 的碰撞运行时不刷新屏幕打勾提高性能对应的脚本如下定义 检测与 (其他角色) 的碰撞 将 [球左边界] 设为 ((x) - ((造型 [造型1] 的 [宽度]) / (2))) 将 [球右边界] 设为 ((x) ((造型 [造型1] 的 [宽度]) / (2))) 将 [球上边界] 设为 ((y) ((造型 [造型1] 的 [高度]) / (2))) 将 [球下边界] 设为 ((y) - ((造型 [造型1] 的 [高度]) / (2))) 将 [其他左边界] 设为 (([其他角色] 的 [x坐标]) - (([其他角色] 的 [造型宽度]) / (2))) 将 [其他右边界] 设为 (([其他角色] 的 [x坐标]) (([其他角色] 的 [造型宽度]) / (2))) 将 [其他上边界] 设为 (([其他角色] 的 [y坐标]) (([其他角色] 的 [造型高度]) / (2))) 将 [其他下边界] 设为 (([其他角色] 的 [y坐标]) - (([其他角色] 的 [造型高度]) / (2))) 如果 (球右边界) (其他左边界) 与 (球左边界) (其他右边界) 与 (球下边界) (其他上边界) 与 (球上边界) (其他下边界) 那么 将 [检测结果] 设为 [1] // 碰撞发生 否则 将 [检测结果] 设为 [0] // 未碰撞 end代码解释首先计算球和“其他角色”的四个边界左、右、上、下。造型宽度和造型高度可以获取当前造型的尺寸除以2得到半径或半宽/半高。矩形碰撞的条件是一个矩形的右边界大于另一个矩形的左边界并且左边界小于右边界并且下边界小于上边界并且上边界大于下边界。这四个条件同时满足则矩形重叠。我们将结果存入变量检测结果。3.3 整合碰撞检测与响应现在修改主循环加入对“平台”角色的碰撞检测和更合理的响应。当 ⚑ 被点击 ... (初始化变量部分与之前相同) ... 重复执行 将 [vy] 增加 (重力) 将 x 增加 (vx) 将 y 增加 (vy) // 检测与地面的碰撞简化版 如果 (y) (地面_y) 那么 将 y 设为 (地面_y) 将 [vy] 设为 ((vy) * (-反弹系数)) end // 检测与平台的碰撞 检测与 [平台] 的碰撞 :: custom 如果 (检测结果) [1] 那么 // 简单响应判断是从上方碰撞还是侧方碰撞 如果 (vy) [0] 与 ((y) - ([平台] 的 [y坐标])) [0] 那么 // 球在平台上方且向下运动 将 y 设为 (([平台] 的 [y坐标]) (([平台] 的 [造型高度]) / (2)) ((造型 [造型1] 的 [高度]) / (2))) // 将球置于平台顶部 将 [vy] 设为 ((vy) * (-反弹系数)) // 垂直反弹 否则 // 假设为侧向碰撞 将 [vx] 设为 ((vx) * (-反弹系数)) // 水平反弹 end end end代码解释我们在主循环中调用了自定义的碰撞检测积木。碰撞响应部分做了简单区分如果球是向下运动vy 0且球的y坐标大于平台的y坐标即球在平台上面我们判定为“顶部碰撞”。此时将球的位置修正到平台顶部并触发垂直反弹。否则我们粗略地认为是侧面碰撞触发水平反弹。这是一个非常简化的处理真实的物理引擎会根据碰撞点的法线方向进行精确计算。4. 完整实战案例可交互的物理模拟场景让我们构建一个更完整的例子包含多个物理对象和用户交互。4.1 场景搭建保留“球”和“平台”角色。再添加2-3个球角色分别涂成不同颜色它们将共享同一套物理逻辑。添加一个“墙壁”角色一个细长的矩形竖立在舞台一侧。为“球”角色作为原型编写好完整的物理脚本包括变量定义、主循环和碰撞检测函数。4.2 为所有物理对象应用脚本由于Scratch没有“类”的概念我们需要为每个球复制一套变量和脚本或者使用“克隆”功能。这里使用克隆更高效。修改“球”角色原型的代码当 ⚑ 被点击 隐藏变量 [vx] [vy] 将 [重力] 设为 [-0.5] 将 [反弹系数] 设为 [0.8] 将 [地面_y] 设为 [-150] 重复 (5) 次 // 克隆5个小球 创建 [自己] 的克隆体 end 当作为克隆体启动时 将 [vx] 设为 (在 (-5) 到 (5) 之间取随机数) // 给一个随机的初始水平速度 将 [vy] 设为 (在 (5) 到 (15) 之间取随机数) // 给一个向上的初始速度 显示 重复执行 将 [vy] 增加 (重力) 将 x 增加 (vx) 将 y 增加 (vy) // 地面碰撞 如果 (y) (地面_y) 那么 将 y 设为 (地面_y) 将 [vy] 设为 ((vy) * (-反弹系数)) end // 与平台碰撞 检测与 [平台] 的碰撞 :: custom 如果 (检测结果) [1] 那么 ... (碰撞响应代码同上) ... end // 与墙壁碰撞 检测与 [墙壁] 的碰撞 :: custom 如果 (检测结果) [1] 那么 将 [vx] 设为 ((vx) * (-反弹系数)) // 水平反弹 end // 与其他球克隆体的碰撞简易版可选 // 这里需要更复杂的循环检测所有克隆体暂不展开 end4.3 添加用户交互我们可以让平台由鼠标控制增加趣味性。为“平台”角色添加代码当 ⚑ 被点击 重复执行 定位到 [鼠标指针 x] (y) // 只跟随鼠标x坐标y坐标固定 end4.4 运行与效果点击绿旗运行。你会看到多个小球以随机的初速度抛出在重力作用下运动并与地面、鼠标控制的平台以及墙壁发生碰撞和反弹。一个生动的物理模拟场景就构建完成了。5. 常见问题与优化思路在实现过程中你可能会遇到以下问题5.1 物体穿透或抖动问题现象球有时会卡进平台内部或者在地面附近高频抖动。原因与解决速度过大在一帧内物体移动的距离超过了其自身尺寸导致“穿越”了碰撞体。解决方案是进行“连续碰撞检测(CCD)”或在每一帧更新位置前先计算与碰撞体的距离确保不会穿越。一个简单的方法是在检测到碰撞后将物体沿碰撞法线方向移出到刚好不重叠的位置。响应计算不精确我们的响应逻辑如直接取反速度过于简单。优化方法是根据碰撞边界更精确地修正物体的位置如上文代码中将球置于平台顶部。5.2 性能问题问题现象克隆体很多时游戏变卡。原因与解决减少碰撞检测次数不是每一对物体都需要检测。可以引入“空间划分”思想比如只检测彼此距离在一定范围内的物体。优化自定义积木确保自定义积木勾选了“运行时不刷新屏幕”。简化造型使用简单的几何图形作为碰撞体而不是复杂的位图。5.3 物理效果不真实问题现象反弹角度奇怪滚动不自然。原因与解决引入摩擦力创建变量摩擦力在物体与地面/平台接触时让vx乘以一个小于1的系数如0.98来模拟速度衰减。区分碰撞面实现更精细的碰撞方向判断顶部、底部、左侧、右侧并给予不同的响应顶部/底部影响vy左侧/右侧影响vx。考虑旋转高级的物理引擎会计算角速度和扭矩。在Scratch中可以根据vx的差值粗略地让角色右转或左转一定角度来模拟滚动。6. 最佳实践与项目扩展建议6.1 代码组织最佳实践模块化将碰撞检测、运动更新、碰撞响应分别做成自定义积木使主循环清晰易懂。使用列表管理对象对于大量物理对象可以使用列表来统一管理它们的属性如所有球_x所有球_y所有球_vx等然后用循环统一处理比管理多个克隆体的变量更清晰。常量分离将重力、反弹系数、摩擦力等物理参数设为“适用于所有角色”的变量方便统一调整。6.2 项目扩展方向这个简易引擎可以作为起点扩展出许多有趣的项目弹球游戏用平台接住不断弹跳的球击打上方的砖块。多米诺骨牌模拟创建多个矩形角色为它们添加简单的碰撞和连锁反应逻辑。抛体运动模拟器添加角度和力量输入模拟炮弹轨迹。简单车辆模拟为车辆角色添加两个轮子作为碰撞点模拟基于悬挂和摩擦力的运动。6.3 从Scratch到真实编程的桥梁在Scratch中手动实现物理引擎是理解游戏开发底层逻辑的绝佳方式。当你未来学习如Python的Pygame、JavaScript的Canvas或Unity/Cocos等专业引擎时你会发现核心概念是相通的游戏循环、时间增量(deltaTime)、向量运算、碰撞体与刚体。在Scratch中养成的“每一帧更新状态”的思维模式是游戏编程的基础。通过这个“五分钟”的实践你不仅学会在Scratch中制作物理效果更重要的是理解了模拟物理世界的核心循环与算法。尝试调整重力大小、反弹系数或者增加空气阻力、浮力等新的力观察系统的变化这是探索物理和编程乐趣的最佳途径。