跳球物理模拟实战:从重力积分到碰撞检测的完整实现

发布时间:2026/10/15 15:26:58

跳球物理模拟实战:从重力积分到碰撞检测的完整实现 1. 跳球项目的整体设计与核心思路1.1 从“跳球”这个名字说起第一次看到“跳球”这个标题很多人脑子里浮现的可能是篮球开场那一下——裁判把球往空中一抛两边的人同时起跳争抢。这个画面感其实非常准确因为“跳球”这个项目本质上就是在模拟一个球体在重力、弹力和外力作用下不断跳跃的过程。它不是一个具体的商业产品而是一类物理模拟小项目的统称常见于游戏开发入门、物理引擎练手、前端动画演示等场景。我最早接触这类项目是在做一个小型互动演示的时候当时需要让一个球在屏幕里自然地弹跳碰到地面会反弹碰到墙壁会折返还要能响应鼠标点击给它一个额外的力。听起来简单但真正动手写的时候才发现要让这个球“跳得自然”并不容易——重力给多少、弹性系数怎么调、什么时候该减速、什么时候该停止每一个参数背后都有讲究。这个项目适合谁呢如果你是刚接触游戏开发或者物理模拟的新手跳球是一个非常好的练手项目因为它涉及的核心概念足够基础但又足够完整。如果你是有经验的开发者想快速验证一个物理引擎的手感跳球也是一个很好的试金石。甚至如果你只是对动画感兴趣想用代码做出一个看起来舒服的弹跳效果这个项目也能给你很多启发。1.2 为什么选择“跳球”作为练手项目在众多物理模拟项目中跳球之所以被反复拿出来做是因为它刚好卡在一个很微妙的位置上。比它简单的项目比如匀速直线运动几乎不需要考虑力的相互作用做完之后学不到什么东西。比它复杂的项目比如多刚体碰撞、布料模拟又需要引入大量的数学工具和优化技巧新手很容易在细节里迷失方向。跳球刚好覆盖了物理模拟最核心的几个要素重力加速度、速度与位移的积分关系、碰撞检测与响应、能量损耗。这四个要素在跳球项目里都能以最直观的方式呈现出来。你调一下重力参数球的下落速度立刻变化你改一下弹性系数球的反弹高度马上不同。这种即时反馈对于理解物理模拟的原理非常有帮助。另外跳球项目还有一个隐藏的好处它天然适合做参数调优的练习。同样一个球重力设成9.8和设成20手感完全不一样弹性系数设成0.9和设成0.6视觉上的“重量感”也截然不同。这种调参的过程其实就是在培养对物理参数的直觉而这种直觉在后续做更复杂的项目时非常关键。1.3 技术选型的考量做跳球项目技术选型的空间其实很大。你可以用纯前端技术栈比如HTML5 Canvas或者WebGL配合JavaScript或者TypeScript来实现也可以用游戏引擎比如Unity、Godot或者 Cocos Creator甚至可以用Python的Pygame或者Processing来做快速原型。我个人的建议是如果你是想理解物理模拟的底层原理那就用最基础的技术栈比如Canvas加原生JavaScript。因为引擎帮你封装了太多东西你调一个参数就能看到效果但你不清楚引擎内部到底做了什么。而自己从零实现一遍虽然麻烦一点但每一个公式、每一次积分、每一次碰撞响应都是你自己写的理解会深刻得多。如果你是想快速出一个可演示的版本或者想把精力放在玩法和交互上那用游戏引擎会更高效。Unity的Rigidbody组件已经内置了完整的物理模拟你只需要给球加一个Collider和一个Rigidbody再调一下Bounciness参数基本就能跑起来。但这样做的代价是你对物理模拟的理解可能停留在“调参”层面而不是“实现”层面。我自己的做法是两条路都走一遍。先用引擎快速搭一个原型感受一下什么样的参数组合手感最好然后再用原生技术栈从零实现一遍把引擎里那些“黑盒”操作拆开来看。这样做的好处是你既有手感上的直觉又有原理上的理解后续遇到问题的时候知道该往哪个方向排查。1.4 核心设计原则在动手写代码之前有几个设计原则需要先明确下来。这些原则看起来简单但实际做的时候很容易忽略导致最后的效果不自然。第一个原则是帧率无关性。很多新手写物理模拟的时候直接把速度加到位置上比如position velocity然后每帧执行一次。这样做的问题在于如果你的帧率不稳定球的运动速度就会忽快忽慢。正确的做法是引入时间步长用position velocity * deltaTime来计算位移。这样无论帧率是30帧还是60帧球的运动速度都是一致的。第二个原则是能量守恒与损耗的平衡。真实的球在弹跳过程中每次碰撞都会损失一部分能量所以反弹高度会逐渐降低直到最后停下来。如果你的弹性系数设成1.0球就会永远弹下去看起来非常假。但如果弹性系数设得太低球又会在几次弹跳后就迅速停下缺乏“活力”。一般来说弹性系数设在0.7到0.9之间比较合适具体数值要根据你想要的手感来调。第三个原则是边界处理的鲁棒性。球在碰到地面或者墙壁的时候需要做碰撞检测和响应。这里最容易出的问题是“穿透”——球的速度太快一帧之内直接穿过了地面导致碰撞检测失效。解决这个问题的方法有两种一种是限制最大速度让球一帧的位移不超过球的半径另一种是做连续碰撞检测在球的位置变化路径上检测是否与边界相交。前者简单但会限制球的最大速度后者复杂但更通用。2. 核心细节解析与实操要点2.1 重力与速度的积分关系跳球项目里最核心的物理概念就是重力。重力本质上是一个恒定的加速度它会让球的速度不断变化。在每一帧里球的速度会根据重力加速度进行更新然后位置再根据速度进行更新。这个过程叫做欧拉积分是最简单也最常用的一种数值积分方法。具体来说假设重力加速度是g时间步长是dt那么每一帧的更新逻辑是这样的velocityY g * dt; positionY velocityY * dt;这里有一个细节需要注意g的方向。如果你把屏幕坐标系的原点设在左上角Y轴向下为正那么重力加速度应该是正值因为球是往下掉的。如果你把原点设在左下角Y轴向上为正那重力加速度就是负值。这个方向问题看起来简单但实际写代码的时候很容易搞混导致球往上飞或者直接消失。另外dt的取值也很关键。如果dt太大比如0.1秒球的运动会显得很跳跃因为每一帧的位移太大了。如果dt太小比如0.001秒计算量会急剧增加但视觉效果上不会有明显提升。一般来说dt取1/60秒约0.0167秒是一个比较平衡的选择对应60帧的刷新率。注意如果你用的是requestAnimationFrame它回调的时间戳精度是毫秒级的需要除以1000转换成秒。而且不同设备的刷新率可能不一样有些是高刷屏120Hz甚至144Hz这时候dt会变小球的运动速度在视觉上会变快。解决办法是固定时间步长比如始终用1/60秒来更新物理渲染的时候再做插值。2.2 碰撞检测与响应碰撞检测是跳球项目里第二个核心问题。最简单的场景是球与地面的碰撞当球的最低点低于地面高度时就认为发生了碰撞。这时候需要做两件事一是把球的位置修正到刚好接触地面的位置二是把球的速度反向并乘以弹性系数。if (positionY radius groundY) { positionY groundY - radius; velocityY -velocityY * restitution; }这里的restitution就是弹性系数取值在0到1之间。如果设成1球会以相同的速度反弹永远不会停下来如果设成0球碰到地面就停住没有任何反弹。实际项目中这个值通常设在0.7到0.9之间。但这里有一个隐藏的问题如果球的速度很小比如每帧只移动0.1个像素那么它可能会在groundY附近反复微弹看起来像是在抖动。解决这个问题的方法是引入一个速度阈值当球的速度小于某个值时直接把它设为0并让球停在地面上。if (Math.abs(velocityY) threshold) { velocityY 0; positionY groundY - radius; }这个阈值一般取0.5到1.0像素每帧比较合适具体数值要根据你的场景尺度来调。如果球很大阈值可以适当放大如果球很小阈值就要相应缩小。除了地面碰撞还有墙壁碰撞。墙壁碰撞的逻辑和地面类似只是方向不同。左右墙壁碰撞影响的是水平速度地面和天花板碰撞影响的是垂直速度。如果你想让球在水平方向也有能量损耗可以在每次墙壁碰撞时也乘以一个弹性系数或者单独设置一个水平方向的阻尼系数。2.3 参数调优的实操经验调参是跳球项目里最花时间但也最有意思的部分。同样的代码参数不同手感可以差出十万八千里。我把自己调参的经验总结成几个关键点供你参考。重力加速度的选择。如果你想让球看起来像在地球上重力加速度可以设成9.8米每二次方秒。但问题是屏幕上的像素和现实中的米之间没有直接的对应关系。所以更实际的做法是先确定球的下落时间然后反推重力加速度。比如你希望球从屏幕顶部落到地面需要1秒钟屏幕高度是600像素那么根据自由落体公式h 0.5 * g * t^2可以算出g 2 * h / t^2 2 * 600 / 1 1200像素每二次方秒。这个数值看起来很大但在像素坐标系里是合理的。弹性系数的选择。弹性系数决定了球的“活力”。如果设成0.9球会弹很多次才停下来看起来很有弹性如果设成0.7球弹几次就停了感觉比较“沉闷”。我个人的偏好是0.8左右既有足够的弹跳次数又不会弹太久。如果你想让球看起来像篮球可以设到0.85如果像铅球可以设到0.5以下。空气阻尼的引入。真实的球在运动过程中会受到空气阻力速度会逐渐衰减。在跳球项目里你可以通过给速度乘以一个小于1的阻尼系数来模拟这个效果。比如每帧velocity * 0.99这样球的速度会缓慢下降最终停下来。阻尼系数越接近1衰减越慢越接近0衰减越快。一般来说0.99到0.995之间比较合适。水平速度的处理。如果你希望球在水平方向也能持续运动可以给球一个初始的水平速度并且在墙壁碰撞时也乘以弹性系数。但要注意如果水平方向的弹性系数和垂直方向一样球可能会在水平方向弹得太久看起来不自然。我的做法是给水平方向单独设一个稍小的弹性系数比如0.9这样球在水平方向会更快地停下来。2.4 视觉表现与交互增强物理模拟做完了接下来要考虑的是视觉表现。一个纯色的球在纯色的背景上弹跳虽然物理上正确但视觉上很单调。你可以通过几个简单的技巧来提升视觉效果。拖尾效果。在球经过的位置留下逐渐淡出的残影可以让球的运动轨迹更明显。实现方法很简单每一帧把球当前的位置和透明度记录下来然后绘制一系列逐渐变淡的圆。透明度从1逐渐降到0半径也可以逐渐缩小。这样球在快速移动的时候会留下一条漂亮的尾巴。阴影效果。在球的正下方绘制一个椭圆形的阴影阴影的大小和透明度根据球的高度动态变化。球越高阴影越大越淡球越低阴影越小越深。这个效果可以极大地增强球的空间感让观众更容易判断球离地面有多远。碰撞反馈。球每次碰到地面或者墙壁的时候可以触发一个短暂的视觉效果比如地面出现一圈扩散的波纹或者球本身闪一下。这种反馈可以让碰撞更有“打击感”提升交互体验。鼠标交互。你可以让鼠标点击的时候给球一个指向点击位置的力或者让球跟随鼠标移动。前者更适合做“弹球”类的游戏后者更适合做“拖拽”类的交互。实现鼠标交互的关键是做好坐标转换把屏幕坐标转换成世界坐标然后计算力的方向和大小。3. 实操过程与核心环节实现3.1 环境搭建与项目初始化我以最基础的HTML5 Canvas加原生JavaScript为例完整走一遍实现过程。你不需要安装任何额外的依赖只需要一个浏览器和一个文本编辑器就能开始。首先创建一个HTML文件里面放一个Canvas元素!DOCTYPE html html head meta charsetUTF-8 title跳球模拟/title style body { margin: 0; display: flex; justify-content: center; align-items: center; height: 100vh; background: #1a1a2e; } canvas { border: 1px solid #333; } /style /head body canvas idgame width800 height600/canvas script srcmain.js/script /body /html然后创建main.js在里面获取Canvas上下文定义球的基本属性const canvas document.getElementById(game); const ctx canvas.getContext(2d); const ball { x: 400, y: 100, radius: 20, vx: 3, vy: 0, color: #e94560 }; const gravity 1200; const restitution 0.8; const groundY 580; const damping 0.995;这里的groundY是地面的Y坐标我留了20像素的边距让地面看起来不那么贴边。damping是空气阻尼系数每帧对速度做一次衰减。3.2 物理更新循环的实现物理更新的核心逻辑放在一个update函数里每一帧调用一次。这个函数负责更新球的速度和位置并处理碰撞。function update(dt) { ball.vy gravity * dt; ball.vx * damping; ball.vy * damping; ball.x ball.vx * dt; ball.y ball.vy * dt; // 地面碰撞 if (ball.y ball.radius groundY) { ball.y groundY - ball.radius; ball.vy -ball.vy * restitution; if (Math.abs(ball.vy) 50) { ball.vy 0; } } // 天花板碰撞 if (ball.y - ball.radius 0) { ball.y ball.radius; ball.vy -ball.vy * restitution; } // 左墙碰撞 if (ball.x - ball.radius 0) { ball.x ball.radius; ball.vx -ball.vx * restitution; } // 右墙碰撞 if (ball.x ball.radius canvas.width) { ball.x canvas.width - ball.radius; ball.vx -ball.vx * restitution; } }这里有一个细节需要注意速度阈值我设的是50像素每秒而不是之前说的0.5像素每帧。因为这里的速度单位是像素每秒而之前说的0.5是像素每帧。换算一下0.5像素每帧乘以60帧每秒就是30像素每秒。所以50这个阈值是合理的甚至稍微偏大一点可以让球更快地停下来。3.3 渲染与动画循环渲染函数负责把球画到Canvas上。除了画球本身我还加了一个简单的阴影效果让球看起来更有立体感。function render() { ctx.clearRect(0, 0, canvas.width, canvas.height); // 绘制地面 ctx.fillStyle #16213e; ctx.fillRect(0, groundY, canvas.width, canvas.height - groundY); // 绘制阴影 const shadowScale 1 - (groundY - ball.y) / 600; ctx.beginPath(); ctx.ellipse(ball.x, groundY, ball.radius * shadowScale, ball.radius * 0.3 * shadowScale, 0, 0, Math.PI * 2); ctx.fillStyle rgba(0, 0, 0, ${0.3 * shadowScale}); ctx.fill(); // 绘制球 ctx.beginPath(); ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2); ctx.fillStyle ball.color; ctx.fill(); ctx.strokeStyle #fff; ctx.lineWidth 2; ctx.stroke(); }阴影的缩放比例是根据球的高度动态计算的。球越高groundY - ball.y越大shadowScale越小阴影就越小越淡。这个效果虽然简单但对空间感的提升非常明显。动画循环用requestAnimationFrame来驱动同时计算时间步长let lastTime 0; function loop(timestamp) { const dt Math.min((timestamp - lastTime) / 1000, 0.05); lastTime timestamp; update(dt); render(); requestAnimationFrame(loop); } requestAnimationFrame(loop);这里用Math.min把dt限制在0.05秒以内防止页面切换或者卡顿的时候dt过大导致球直接穿透地面。这是一个很实用的保护措施建议你在自己的项目里也加上。3.4 鼠标交互的实现让球响应鼠标点击可以增加项目的互动性。我实现的效果是点击画布的时候给球一个指向点击位置的力。canvas.addEventListener(click, (e) { const rect canvas.getBoundingClientRect(); const mouseX e.clientX - rect.left; const mouseY e.clientY - rect.top; const dx mouseX - ball.x; const dy mouseY - ball.y; const distance Math.sqrt(dx * dx dy * dy); if (distance 0) { const force 800; ball.vx (dx / distance) * force; ball.vy (dy / distance) * force; } });这里的force是施加的力的大小我设的是800像素每秒。这个值需要根据你的场景尺度来调如果球的质量感比较强可以适当加大如果球比较轻可以减小。另外getBoundingClientRect是用来获取Canvas在页面中的位置因为鼠标事件的坐标是相对于页面的需要转换成相对于Canvas的坐标。提示如果你想让交互更细腻可以区分鼠标按下和松开按下的时候持续施加力松开的时候停止。这样球会跟随鼠标移动手感更像“拖拽”。实现方法是记录鼠标按下的状态在update函数里根据鼠标位置持续施加力。4. 常见问题与排查技巧实录4.1 球穿透地面或墙壁这是跳球项目里最常见的问题尤其是当球的速度很快的时候。原因很简单球在一帧内的位移超过了它的半径导致碰撞检测的时候球已经在地面以下了。排查方法在update函数里打印球的位置和速度观察球在碰撞前后的数值变化。如果发现球的位置突然跳到了地面以下很多那就是穿透了。解决方案有三种。第一种是限制最大速度让球一帧的位移不超过半径const maxSpeed ball.radius / dt; ball.vx Math.max(-maxSpeed, Math.min(maxSpeed, ball.vx)); ball.vy Math.max(-maxSpeed, Math.min(maxSpeed, ball.vy));第二种是使用连续碰撞检测在球的位置变化路径上采样多个点检测是否与边界相交。这种方法更精确但计算量也更大。第三种是增加物理更新的频率比如每帧做两次物理更新每次用dt/2作为时间步长。这样球一帧的位移就减半了穿透的概率大大降低。我一般用第一种方法简单有效而且限制最大速度对视觉效果的影响很小。4.2 球在地面上抖动球在地面上抖动的原因是球的速度很小但又不为零导致它反复微弹。每次弹起的高度很小视觉上看起来就像在抖动。排查方法观察球停止前的速度变化如果发现速度在0附近反复变化那就是抖动问题。解决方案是引入速度阈值。当球的速度小于某个值时直接把它设为0并让球停在地面上。这个阈值需要根据你的场景尺度来调我一般设50像素每秒。如果你发现球停得太早可以把这个值调小如果球抖动明显可以调大。另外你也可以在球的速度反向之后判断反弹高度是否小于某个值如果小于就直接停止。这样更符合物理直觉因为真实的球在弹跳高度很低的时候确实会很快停下来。4.3 帧率不稳定导致运动不流畅帧率不稳定的原因有很多比如页面里有其他耗时的操作、浏览器标签页切换、设备性能不足等。表现是球的运动速度忽快忽慢看起来不流畅。排查方法在loop函数里记录每一帧的dt观察dt的波动范围。如果dt在0.01到0.05之间波动那就是帧率不稳定。解决方案是固定时间步长。不要把dt直接用于物理更新而是用一个固定的值比如1/60秒。如果实际帧率高于60帧就累积时间够一个固定步长就更新一次物理如果实际帧率低于60帧就做多次物理更新来追赶。这种方法叫做固定时间步长加插值是游戏开发里的标准做法。const fixedDt 1 / 60; let accumulator 0; function loop(timestamp) { const dt Math.min((timestamp - lastTime) / 1000, 0.1); lastTime timestamp; accumulator dt; while (accumulator fixedDt) { update(fixedDt); accumulator - fixedDt; } render(); requestAnimationFrame(loop); }这样无论实际帧率如何变化物理更新的步长都是固定的球的运动速度就稳定了。4.4 常见问题速查表问题现象可能原因排查方法解决方案球穿透地面速度过快一帧位移超过半径打印碰撞前后的位置和速度限制最大速度或使用连续碰撞检测球在地面抖动速度阈值太小反复微弹观察停止前的速度变化增大速度阈值速度小于阈值时直接归零运动速度忽快忽慢帧率不稳定dt波动大记录每帧的dt使用固定时间步长加插值球弹跳次数太少弹性系数太小或阻尼太大检查restitution和damping参数增大弹性系数或减小阻尼球永远停不下来弹性系数为1或阻尼为1检查参数是否等于1把弹性系数调到0.9以下阻尼调到0.995以下鼠标交互方向不对坐标转换错误打印鼠标坐标和球坐标使用getBoundingClientRect做坐标转换阴影位置不对阴影的Y坐标没有跟随地面检查阴影绘制时的Y坐标阴影的Y坐标应该固定在地面高度4.5 独家避坑技巧第一个技巧是用颜色标记调试信息。在开发阶段我习惯把球的速度用颜色表示出来速度越快球的颜色越红速度越慢颜色越蓝。这样一眼就能看出球在什么时候速度快、什么时候速度慢对调参非常有帮助。第二个技巧是录制慢动作回放。如果你觉得球的运动哪里不对劲但又说不清楚可以把dt乘以一个小于1的系数比如0.2让球的运动变成慢动作。这样你可以仔细观察每一次碰撞的细节很容易发现问题所在。第三个技巧是参数面板。在页面上加几个滑块分别控制重力、弹性系数、阻尼系数然后实时调整。这样你可以快速尝试不同的参数组合找到手感最好的那一组。这个技巧在做任何物理模拟项目的时候都适用强烈推荐。第四个技巧是边界可视化。在开发阶段把碰撞边界用半透明的颜色画出来这样你可以直观地看到球和边界的相对位置关系。很多时候球看起来“穿过了”边界其实只是边界的位置和你想象的不一样。5. 跳球项目的扩展方向5.1 多球碰撞单个球的跳球做完了下一步自然是加入多个球让它们之间也能碰撞。多球碰撞的难点在于碰撞检测的复杂度从O(n)变成了O(n²)如果球的数量很多性能会成为瓶颈。解决办法是使用空间分割数据结构比如四叉树或者网格把碰撞检测的范围缩小到相邻的球之间。球与球之间的碰撞响应比球与墙壁的碰撞要复杂一些因为涉及到两个球的速度交换。基本的思路是先计算两个球之间的法线方向然后把两个球的速度分解到法线方向和切线方向法线方向的速度做交换并乘以弹性系数切线方向的速度保持不变。这个计算过程涉及到向量运算如果你对向量不熟悉可以先从一维碰撞开始理解再扩展到二维。5.2 不同形状的碰撞体球是最简单的碰撞体因为它的碰撞检测只需要比较圆心距离和半径之和。如果你想让项目更有挑战性可以尝试加入矩形、多边形甚至任意凸形状的碰撞体。不同形状的碰撞检测算法不一样比如矩形和矩形的碰撞可以用分离轴定理多边形和多边形的碰撞也可以用分离轴定理但计算量会大很多。我的建议是先从矩形开始因为矩形的碰撞检测相对简单而且在实际项目中用得很多。等你对碰撞检测有了更深的理解再尝试更复杂的形状。5.3 加入旋转和摩擦力真实的球在弹跳的时候会旋转旋转会影响球的运动轨迹和碰撞响应。加入旋转需要引入角速度、转动惯量、摩擦力矩等概念复杂度会上升一个台阶。但如果你想让球的运动更真实旋转是绕不开的一步。一个简化的做法是只考虑球与地面碰撞时的摩擦力摩擦力会让球产生旋转旋转又会通过摩擦力影响球的水平速度。这个反馈循环会让球的运动变得更有趣比如球在弹跳几次之后会开始往回滚。5.4 音效与视觉特效物理模拟做完了加上音效和视觉特效可以让项目的完成度提升一个档次。音效方面球每次碰撞的时候播放一个短促的“砰”声音量根据碰撞速度动态调整。视觉特效方面碰撞的时候在碰撞点生成粒子效果粒子向四周飞散并逐渐消失。这些效果虽然不涉及物理模拟的核心逻辑但对用户体验的影响很大。我个人的经验是一个加了音效和粒子的跳球项目看起来会比纯物理模拟的版本“高级”很多即使物理逻辑完全一样。5.5 从跳球到更复杂的物理系统跳球项目虽然简单但它涉及的物理概念是通用的。你在这个项目里学到的重力、速度、积分、碰撞检测、能量损耗等知识在更复杂的物理系统里同样适用。比如做平台跳跃游戏的时候角色的重力、跳跃速度、落地检测本质上和跳球是一样的。做弹球游戏的时候球的碰撞响应和跳球也是同一套逻辑。所以我的建议是不要小看跳球这个项目。把它做透把每一个参数都调到自己满意为止把每一个边界情况都处理好。这个过程积累的经验会在你做更复杂的项目时发挥出意想不到的价值。6. 我个人在实际操作中的体会做跳球项目最大的收获不是学会了怎么写物理模拟代码而是培养了一种“参数直觉”。刚开始的时候我调重力参数完全是靠猜调一次跑一次看效果好不好。但做得多了之后我大概能预判出某个参数值会产生什么样的效果。比如看到重力设成800我就知道球从顶部落到底部大概需要1.2秒左右看到弹性系数设成0.75我就知道球大概会弹5到6次。这种直觉在后续做更复杂的项目时非常有用。因为物理模拟的参数往往不是孤立的重力、弹性、阻尼、摩擦力之间会相互影响。如果你对每个参数的效果都有直觉调参的时候就能更快地找到方向而不是盲目地试。另外一个小技巧是把调好的参数保存下来做成预设。比如“篮球模式”用一组参数“铅球模式”用另一组参数“弹力球模式”再用一组。这样下次做类似项目的时候可以直接从预设开始省去很多调参的时间。最后再分享一个我踩过的坑不要试图一次性把所有功能都做完。我刚开始做跳球的时候想同时加入多球碰撞、旋转、音效、粒子效果结果代码越写越乱最后连基本的弹跳都出了问题。后来我学乖了先把单球弹跳做稳定再加一个功能测试没问题再加下一个。这种增量式的开发方式虽然看起来慢但实际上比一次性堆功能要快得多因为排查问题的时候范围小定位容易。这个项目后续还可以这样扩展把物理引擎抽出来做成一个独立的模块这样你可以在不同的项目里复用同一套物理逻辑。比如做一个弹球游戏用这套引擎做一个平台跳跃游戏也用这套引擎只是参数和碰撞体的形状不同。这种模块化的思路会让你的代码质量提升一个档次。
延伸阅读

更多相关文章

2026/10/15 15:26:58

Java Swing+JDBC水果超市管理系统课设源码解析与避坑指南

简介:面向Java初学者的水果超市管理系统源码包,适合正在学习Java GUI、集合框架与JDBC数据库编程的读者。项目提供两种典型实现:一种基于ArrayList完成内存数据管理,另一种使用MySQL数据库并通过JDBC进行增删改查,帮助…

2026/10/15 15:21:58

计算机基础++:把背过的名词变成能解释现象的地图

我面试过不少候选人,也带过新人,慢慢发现一个共性现象:很多人能把“进程和线程的区别”“TCP和UDP的区别”背得滚瓜烂熟,可你只要把问题稍微换一下,变成“如果让你设计一个聊天软件,你会选 TCP 还是 UDP&am…

2026/10/15 15:21:58

51单片机+DS18B20温控系统完整实战:从硬件接线到PID控制

简介:一套基于AT89C52单片机与DS18B20数字温度传感器的温控系统设计资源,面向单片机初学者及有课程设计、竞赛备赛需求的开发者。RAR压缩包内共有3个文件,包含C语言源码、Proteus仿真工程以及可直接烧录的Hex文件,整体仅28KB&…

2026/10/15 16:17:59

彻底搞懂CSS弹性布局:从轴概念到实战避坑指南

1. 为什么弹性布局值得你花时间彻底搞懂做前端这些年,我见过太多人把 Flex 当成"能居中就行"的工具。问起来就是display: flex加justify-content: center加align-items: center,三件套背得滚瓜烂熟,可一旦遇到子项宽度被压缩、换行…

2026/10/15 16:17:59

Android 移植 OctoPrint 实战:Kotlin 壳嵌入 Python 运行时

简介:这份资源是 Octo4a 项目的完整源码包,面向没有树莓派却希望远程控制 3D 打印机的 Android 用户与 Kotlin 开发者。它把 OctoPrint 主机能力搬到手机端,让用户无需 Linux 基础即可在 Android 设备上完成部署,解决硬件成本高、…

2026/10/15 16:17:59

Spring MVC响应消息处理机制解析:从返回值到HTTP输出的完整链路

1. 响应消息到底在响什么:先搞清楚Spring MVC的“最后一公里”做Java Web开发的兄弟,十有八九都经历过这样的困惑:Controller方法明明写完了return,前端拿到的却经常跟预想的不一样。要么是返回JSON时字段丢了,要么是下…

2026/10/15 16:17:59

纯CSS实现移动端家谱世系图:基于uniapp递归组件实战

家谱世系图这个需求,乍一看很简单,无非就是画一棵树。但真落到移动端、落到 uniapp、还得加一句“纯 CSS 实现”的时候,事情就变得有意思了。我上一个项目正好接到类似需求,要在 App 里展示一个家族从始祖到当前几代人的完整世系关…

2026/10/15 16:17:59

TDC-GP22激光测距开发实战:寄存器配置、标定滤波与避坑指南

简介:这份资源面向嵌入式开发工程师与激光测距方向的学习者,围绕Infineon TDC-GP22时间数字转换器展开,提供从硬件接口到软件驱动的完整工程参考,帮助解决亚纳秒级时间间隔测量与微米级距离解算的实现问题。压缩包共195个文件&…

2026/10/15 16:12:55

TikTok 2026海外营销新趋势:品牌如何通过KOC矩阵解决内容增长难题?

品牌出海TikTok增长怎么做?为什么越来越多企业开始布局KOC矩阵营销?对于已经进入全球市场竞争阶段的消费品牌来说,TikTok正在从一个单纯的短视频曝光渠道,逐渐演变为品牌建立海外用户认知、获取消费者反馈、沉淀内容资产的重要增长…

2026/10/14 11:31:03

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/14 17:41:07

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/15 13:42:16

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/15 0:02:22

防关联浏览器哪个好?三款产品实测差异解析

新手做多店铺,最纠结的就是防关联浏览器选哪个。飞跨、网易、站斧这三款被问得最多,先给结论:要一体化省心、长期便宜,选站斧;要网易招牌和稳,选网易;预算紧做东南亚 Lazada,选飞跨。…

2026/10/15 0:02:22

影刀RPA深度实战:大模型Fine-tuning定制RPA专用Prompt与指令生成

影刀RPA深度实战:大模型Fine-tuning定制RPA专用Prompt与指令生成 一、技术背景 大模型(LLM)在通用场景下表现强大,但当我们将 GPT-4 或 Qwen 直接用于生成影刀RPA操作指令和 XPath 定位时,准确率通常只有 40%-60%。原因…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑