首页/新闻资讯/正文详情

Python打砖块实战:物理碰撞、子步长与实时循环调优

发布时间:2026/11/3 4:11:44 来源:云帆数科 栏目:资讯中心
Python打砖块实战:物理碰撞、子步长与实时循环调优
1. 为什么老游戏是练手的最佳题材某套入门课程讲到第 95 课的时候突然插了一段几十年前的旧事有个年轻人揣着一份简历去一家做街机的小公司应聘人家问他会什么他说“什么都会”于是被留了下来后来还真的亲手参与了一款打砖块类游戏的外围设计。这个故事的版本在坊间流传得很多细节早就糊了但作为常年写代码的人我从里面读出来的东西特别朴素在一个实际项目里能被立刻派上用场的技能永远比简历上罗列二十行的名词更值钱。这也是我一直推荐新手拿打砖块这类经典街机游戏做Python练手项目的原因。它足够小小到一个人两三天就能跑通它又足够完整完整到把物理碰撞、状态管理、关卡数据、帧率控制这些真问题全都塞进去了。你在一个四十行的贪吃蛇里学不到东西因为没地方出错但打砖块会逼着你面对“球速加到 900 像素每秒之后开始穿砖”这种真实故障。这篇文章适合谁我的判断是三类人第一类是刚学完 Python 基本语法、能写循环能写类但一提“做个东西”就卡壳的朋友第二类是想给孩子或学生找一个有即时反馈的编程题目的老师第三类是做后端或者做数据、想补一点实时循环和碰撞检测直觉的工程师。前两类人从前到后照着敲就行第三类可以直接跳到第 3 章看物理碰撞的处理细节。我先把结论摊开打砖块的代码量大概在三百到五百行之间但真正决定它好不好玩的是不到三十行的几个参数和公式。把这几行搞懂比你多写两百行花哨的特效有意义得多。下面我按“为什么这么设计—关键细节怎么算—完整代码怎么拼—出问题怎么查”的顺序拆中间会夹一些我自己踩过的坑这些内容你在普通的教程里基本看不到。1.1 那段求职轶事里被忽略的技术细节大多数人听那个故事记住的是“胆大”和“运气”。但我关注的其实是另一层那时候做一台街机公司规模极小一个硬件工程师可能同时要画电路、调电源、写游戏逻辑、测试摇杆的响应延迟。没有分工所以每个人都必须是全栈这不是什么浪漫的说法而是资源约束下的必然结果。把这个逻辑搬到现在你会发现它一点都不过时。你一个人做打砖块复刻也要同时扮演好几个角色你是玩法策划得决定砖块排布和难度曲线你是物理程序员得处理球和挡板的反弹角度你是渲染工程师得管好每秒 60 帧的刷新你还是测试得自己找出“球卡在砖缝里来回抖”这种边界 bug。这个项目最大的价值不在于它是个游戏而在于它是个微缩的完整工程。再往深一层说早期街机游戏的硬件约束极其苛刻。可用的内存以 KB 计CPU 主频以 MHz 计屏幕分辨率固定精灵数量有上限甚至连“同屏能画多少个活动对象”都是硬指标。在这种约束下做设计逼出来的东西反而特别干净砖块是规则网格而不是自由摆放因为网格可以用索引算而不用存坐标球的运动是恒定速度而不是带加速度因为加速要额外做积分计分是查表而不是动态计算因为查表快。这些选择后来变成了打砖块这一类型的“标准形态”很多人在复刻的时候照着抄却不知道为什么抄出来的东西手感就很怪。所以我在讲这个项目的时候会花很大篇幅解释“为什么是网格”“为什么球速要用像素每秒而不是每帧”“为什么挡板的反弹角度要按撞击位置算”。这几个“为什么”搞清楚了你再去写别的实时小游戏思路是通的。1.2 一个只有四类物体的游戏复杂度藏在哪把打砖块摊开看屏幕上永远只有四类东西一块挡板、一颗球、一堆砖、一个分数。听起来简单到无聊但复杂度从来不看物体数量看的是物体之间的交互关系。挡板只跟球交互球跟挡板、砖块、四面墙都交互砖块只跟球交互。看似只有三条交互边问题在于球同时可能跟多个砖块接触。这就是第一个坑。你可能觉得“一次撞一块砖”很合理但球在高速运动下一帧之内移动的距离可能超过砖块厚度于是它可能在这一帧里穿过了两块砖。如果你只处理第一个碰撞就结束第二块砖相当于被穿墙了如果你把两块都处理了球会同时被两个方向反弹产生一个莫名其妙的反向速度。这种 bug 在低帧率或者高球速下频繁出现而且极难复现因为取决于你电脑当时卡没卡。第二个坑是挡板边缘的反弹。新手最常写的版本是“撞到挡板就 vy -vy”横向速度不变。这样写出来的游戏能玩但极其无聊因为球永远从同一个角度出去玩家只要站在中间就能无限弹。真正的手感来自撞击点越靠挡板边缘出射角越斜。这一条公式把整个游戏的策略性拉满——玩家不是在挡球是在“瞄准”。第三个坑是球被卡住。当你把球限制在一个很小的速度范围内、又要求反弹角度有足够的垂直分量时某些角度下球会在两面墙之间来回抖看起来像卡顿。这不是物理引擎的错是参数没调好。四类物体三个坑每一个都能让人卡半天。我个人的经验是先写下这三个坑的触发条件再开始写代码写着写着你会主动避开它们比事后 debug 划算太多。1.3 复刻它能练到的四类真功夫我把这个项目能带来的收益列成一张表方便你判断值不值得花时间。能力项具体练到什么在真实项目里的对应实时循环思维帧与时间的分离、delta time、固定步长游戏、音视频、监控系统碰撞检测直觉AABB、圆-矩形最近点、法线反射图形界面、机器人避障、GIS状态与数据分离关卡用数据描述逻辑用代码解释配置驱动开发、规则引擎调试方法论从“看得见的现象”倒推“看不见的状态”线上问题定位第一项最容易被忽略。很多人写 Python 小游戏习惯写成“每帧移动 5 像素”跑起来没问题但换一台刷新率高的显示器就变快换一台慢的就变慢。这个习惯一旦养成后面做任何跟时间相关的东西都要返工。我在第 2 章会专门讲为什么要把速度定义成“像素每秒”并且在主循环里乘上dt。第二项的碰撞检测说实话大多数人写游戏只用到最粗的矩形相交判断。但只要你动手算一次圆和矩形的最近点你会发现它比想象中简单而且正确率高出一大截。这套思路搬到 UI 的拖拽判定、地图上的点位吸附都是一样的。第三项是我最看重的。关卡数据必须能被非程序员编辑这是一条硬标准。你把二十行砖块布局写成二十个Brick(...)调用改一次要半天你写成一组字符串任何人一眼就能看出形状改起来是几秒钟的事。这个习惯从小项目就养成受益很大。第四项不用多说游戏是最适合练调试的场景——因为现象全在屏幕上你可以一眼看到球卡住了然后去查状态。2. 动手之前先把游戏拆成能计算的东西我见过太多人一上来就import pygame然后开始写while True写到两百行发现结构烂了、改不动删掉重来。两次之后就觉得“我不适合做游戏”。问题不在能力在于没有先分层。写任何有实时状态的东西动手前的十分钟规划能把后面的返工时间省掉一大半。2.1 五个实体的职责边界我说四类物体但代码层面我会切成五个类多出来的那个是“游戏世界”本身。挡板只负责三件事记录自己的矩形位置、响应键盘输入、把自己限制在屏幕横坐标范围内。它不关心球在哪也不关心砖块剩几块。球负责位置、速度、半径以及“移动一步”和“跟矩形碰撞后反射”这两件事它不负责判断撞到的是砖还是挡板那是外部传进来告诉它的。砖块最轻只有矩形和生命值有些砖要打两下它甚至不需要update方法。关卡是一份数据负责描述砖块怎么摆、这一关球速是多少。游戏世界是胶水层它持有上面所有对象每个循环里按顺序调用它们并且负责判断“球掉下去了”“砖全没了”这类全局事件。这么切的好处是每一层都能单独测。你可以不启动图形界面直接构造一个球和一个矩形调用碰撞函数看返回值对不对。这一点在实际开发里太重要了——用眼睛盯着屏幕找碰撞 bug效率极低。提醒一句不要为了“面向对象”而过度拆分。我见过把球的横坐标和纵坐标拆成两个类的那不叫设计那是灾难。判断标准很简单一个类如果只有一个字段、没有任何行为它就该合并掉。还有一个边界要划清楚得分和生命值属于世界不属于球。新手容易把score塞进球里因为球是“打砖的人”但这样你后面加第二颗球的时候就崩了。谁的生命周期最长状态就归属谁。2.2 坐标原点、单位与帧率约定这三件事必须在写第一行代码前定死后面所有计算都依赖它。坐标原点定在左上角x 向右为正y 向下为正。这是图形库的通用约定好处是跟画布像素一一对应坏处是“向上”在数学上是 y 减小写的时候容易反。我的建议是接受它不要试图翻转翻转之后所有渲染坐标都要做一次减法纯给自己找麻烦。长度单位就是像素不做抽象。小游戏不需要世界单位屏幕上 960 像素宽就是 960 像素宽。速度单位必须是“像素每秒”不是“像素每帧”。这是新手最容易犯的错也是我前面说的返工源头。理由很直接你的主循环在clock.tick(60)的约束下实际每帧耗时可能是 16.6 毫秒也可能是 18 毫秒某帧稍微卡了一下。如果速度写成每帧 7 像素卡顿那一帧球就少走了长期累积下来球的位置就跟理论值偏了更严重的是如果玩家换了台 144Hz 的屏幕你的游戏整体会快 2.4 倍完全没法玩。正确做法是在主循环里拿到dt单位秒然后ball.x ball.vx * dt ball.y ball.vy * dt这样无论帧率多少一秒内球一定走vx个像素。dt从clock.tick(60) / 1000.0拿记得转成秒pygame.time.Clock.tick()返回的是毫秒。dt还要设一个上限防止窗口被拖动、系统休眠之后返回一个巨大值导致球一帧飞出屏幕dt min(clock.tick(FPS) / 1000.0, 0.05)0.05 秒就是 50 毫秒相当于允许最多掉到 20 帧。这个封顶是必须的我早期没写alt-tab 一下回来球就没了。2.3 数据结构什么时候列表就够什么时候要对象池砖块数量最多也就一百多个用普通的list完全没问题。有人会问要不要用四叉树或者网格加速我的回答是这个规模下不要。一百个矩形两两检测也就一万次现代 CPU 一毫秒都不到你引入空间划分反而增加出错概率。但有一个地方值得用二维索引砖块的排布和“打穿后留空”。如果你用一维列表删除中间元素会让后面全部前移还容易在遍历时漏掉如果你用bricks[row][col] None这种二维结构位置关系一目了然碰撞之后直接置空遍历时跳过None即可。这就是“关卡用数据描述”的落地方式。至于对象池这个项目完全不需要。球和挡板全程只有一个砖块不重生弹幕粒子最多几百个创建销毁的开销可以忽略。不要为了炫技引入复杂度这是我做了几年实时系统之后最深的体会——能简单解决的事情简单解决。3. 物理与碰撞这才是打砖块的真难点前面两章讲的是结构从这一章开始讲真正让人卡住的地方。我可以很直接地说你的打砖块好不好玩90% 取决于第 3.1 节那不到十行的反弹公式。代码写对只是及格公式调好才算能玩。3.1 挡板反弹一行角度公式决定全部手感先把错误示范摆出来。绝大多数教程这么写if ball.rect.colliderect(paddle.rect): ball.vy -abs(ball.vy)这段代码能运行你也能玩但玩两分钟就腻。因为球的出射角度永远等于入射角镜像反射玩家只要把挡板放在球的落点上球就会一直垂直上下弹游戏变成机械运动。正确的做法是用撞击点相对挡板中心的偏移量来决定出射角。设挡板宽度为W中心横坐标为cx球心横坐标为bx定义归一化偏移t (bx - cx) / (W / 2) # t 落在 [-1, 1] t max(-1.0, min(1.0, t)) # 夹紧防止球从侧面擦到时溢出t -1表示撞在最左边缘t 0表示正中间t 1表示最右边缘。然后出射角按线性映射max_angle math.radians(60) # 边缘最大出射角 angle t * max_angle # 角度制下就是 -60° 到 60° vx math.sin(angle) * speed vy -math.cos(angle) * speed # 向 up所以取负这里我用“相对竖直方向的角度”来描述t 0时球垂直向上t ±1时球以 60° 斜飞出去。60 度这个值是我实测下来最舒服的超过 70 度球会贴着墙横飞玩家根本追不到低于 45 度策略性不够站位几乎不影响结果。用这套公式玩家会主动去“用挡板边缘蹭球”来改变方向策略性立刻出来了。这就是我前面说的参数比代码重要。实操心得如果你想再提升一点手感可以让max_angle随关卡递增比如第一关 50 度、第五关 65 度。难度曲线不用靠加快球速硬拉靠角度就够了。还有一个必须处理的细节球从侧面或者从下方撞到挡板。上面的公式假设球是从上方来的。如果球刚好从挡板左侧擦过按公式算出来的角度可能指向下方球就直接钻地了。我的处理是只有当vy 0球在向下运动时才应用这套反弹逻辑其余情况按普通的水平反射处理。3.2 高速球的穿透问题与子步长这是打砖块最经典的 bug。假设球速 900 像素每秒帧率 60那么一帧位移是 15 像素。如果砖块高度是 20 像素、球半径 7 像素理论上一帧还穿不过去。但当帧率掉到 40位移变成 22.5 像素加上球半径就可能出现“这一帧球在砖上方下一帧球在砖下方”的情况中间的碰撞完全被跳过。解决办法叫子步长sub-stepping把一帧的位移拆成若干小步每步都做一次碰撞检测。def move_with_collision(ball, dt, world): dx ball.vx * dt dy ball.vy * dt steps max(1, int(math.ceil(max(abs(dx), abs(dy)) / (ball.r * 0.8)))) for _ in range(steps): ball.x dx / steps ball.y dy / steps world.resolve_collisions(ball)步长的选择依据是不超过球半径的 0.8 倍。因为球半径是 7步长最多 5.6 像素这样球在任何一步内都不可能完整跨过一块砖砖高 20碰撞一定会被捕捉到。这个思路很通用只要物体有速度就存在穿透风险子步长是最便宜可靠的解法。我在做一个物流轨迹回放的小工具时也用了同样的方法判断车辆是否经过某个围栏区域直接一帧一帧判断会漏拆步之后就准了。代价当然有每帧多跑几次碰撞检测。但打砖块的碰撞对象最多一百个拆十步也就一千次矩形判断完全无感。性能不是问题的时候优先选正确性。3.3 砖块命中一个球撞两块砖的判定顺序子步长解决了“穿透”但没解决“一帧撞两块”。即使拆了步某一步里球仍有可能同时与两块相邻的砖相交比如撞在两块砖的交界处。我的处理原则是一步之内只处理第一个命中处理完立刻结束这一步的后续检测。def resolve_collisions(self, ball): # 先跟墙壁、挡板 ... # 再跟砖块 for brick in self.bricks: if brick is None or brick.hp 0: continue if hit_circle_rect(ball, brick.rect): normal normal_circle_rect(ball, brick.rect) ball.reflect(normal) brick.hp - 1 self.score 10 break # 关键命中一块就退出break那一行是重点。如果不 break球会连续被两个法线方向不同的砖反射两次速度方向会变得完全不可预测玩家会看到球“抽搐”一下然后飞向奇怪的方向。但这里有个取舍只处理一块意味着另一块砖在这一步“被穿过了”要等下一步才会被撞。因为下一步球已经反射离开了所以那块砖这个回合就躲过了。视觉上你会看到球贴着砖缝飞过去只打掉一块。这个现象在经典版本里也存在玩家基本察觉不到。我认为这个取舍是对的宁可少打一块也不要出现诡异的抽搐。至于碰撞检测本身我推荐用圆与矩形的最近点法而不是把球当成矩形def hit_circle_rect(ball, rect): cx max(rect.left, min(ball.x, rect.right)) cy max(rect.top, min(ball.y, rect.bottom)) dx ball.x - cx dy ball.y - cy return dx * dx dy * dy ball.r * ball.r法线就是(dx, dy)归一化。如果dx和dy都是 0球心正好落在矩形内部说明穿透很深这时候退化成按最小穿透轴分离比较球心到矩形四条边的距离取最近的那条边作为法线方向。这套方法比 AABB 准得多特别是在砖块的四个角上。用 AABB 的话球从角上擦过会被判定为“正面撞击”反弹方向完全错误。反射公式用向量形式写def reflect(self, nx, ny): dot self.vx * nx self.vy * ny self.vx - 2 * dot * nx self.vy - 2 * dot * ny self.enforce_min_vertical()enforce_min_vertical()是我强制加的一层保护保证|vy|不低于球速的 28%。原因前面提过球如果几乎水平飞行会在两面墙之间来回弹很久玩家看着干着急。这个函数会重新分配速度分量保持总速度不变。4. 完整实现把上面这些东西拼成能跑的程序理论讲完落地才有意义。这一章我给一份可以直接跑的完整代码大概四百行按模块拆开讲。环境用pygamepip install pygame就行Python 3.9 以上都没问题。4.1 环境准备与最小骨架先把常量集中在一个文件顶部所有魔法数字都不许散落在代码里。这条规矩在小项目里看起来小题大做但你调参的时候就知道值了——改一个数字全局生效。# config.py WIDTH, HEIGHT 960, 640 FPS 60 BG_COLOR (12, 14, 22) PADDLE_COLOR (235, 235, 245) BALL_COLOR (255, 214, 102) BRICK_COLORS [(255, 99, 99), (255, 165, 89), (255, 214, 102), (137, 221, 255), (178, 148, 255)] PADDLE_W, PADDLE_H 130, 14 PADDLE_SPEED 780 # 像素/秒 BALL_R 7 BALL_SPEED_BASE 420 # 像素/秒 BALL_SPEED_MAX 900 PADDLE_MAX_ANGLE math.radians(60) BRICK_W, BRICK_H 68, 24 BRICK_GAP 6 BRICK_TOP 90 BRICK_LEFT 40算一下砖块布局屏幕宽 960左边距 40右边距 40可用宽度 880。每块砖宽 68 加间隙 6 是 74880 / 74 ≈ 11.9所以一排 11 块实际占用11 * 74 - 6 808右边留白 112看起来会偏左。要居中左边距应该改成(960 - 808) / 2 76。这种计算我建议每次都在纸上算一遍靠猜调出来的布局永远是歪的。4.2 核心循环与状态机主循环里最容易被写乱的是“什么时候该干什么”。我用一个极简状态机来管READY球粘在挡板上等待发射、PLAYING、PAUSED、GAME_OVER、WIN。def run(self): clock pygame.time.Clock() while self.running: dt min(clock.tick(FPS) / 1000.0, 0.05) self.handle_events() if self.state self.PLAYING: self.update(dt) self.draw()READY状态很关键很多新手直接让球开局就飞玩家还没反应过来就掉血了第一印象极差。正确做法是球贴在挡板上跟着移动按空格才发射。这个细节能显著提升体验成本几乎为零。update(dt)里的顺序也有讲究我固定的顺序是挡板跟随鼠标或键盘移动并夹紧到边界球按子步长移动并处理碰撞判断砖块是否清空、球是否掉落更新粒子和音效状态。顺序错了会出问题。比如先移动球再移动挡板那么当挡板快速追球时可能出现“球已经离开挡板位置挡板才移过来”的情况明明撞上了却没反弹玩家会觉得挡板有延迟。4.3 关卡数据怎么组织前面强调过关卡必须是数据。我用字符串列表来写每个字符代表一种砖块LEVELS [ [ ..........., ..1111111.., ..1222221.., ..1233321.., ..1222221.., ..1111111.., ], [ 1.1.1.1.1.1, .2.2.2.2.2., 3.3.3.3.3.3, .2.2.2.2.2., 1.1.1.1.1.1, ], ].是空位数字是砖块的生命值打几下碎数字也顺便决定颜色。这么写的好处是改关卡不用碰代码你甚至可以让别人帮你画。加载逻辑就是双重循环def load_level(self, index): self.bricks [] pattern LEVELS[index % len(LEVELS)] for row, line in enumerate(pattern): for col, ch in enumerate(line): if ch .: continue hp int(ch) x BRICK_LEFT col * (BRICK_W BRICK_GAP) y BRICK_TOP row * (BRICK_H BRICK_GAP) self.bricks.append(Brick(x, y, hp))难度曲线我是这么设计的球速随击碎砖块数量递增但不做线性递增而是分档。击碎 0-20 块时 42021-40 块时 52041 块以上 620另外每进入新一关再整体加 40封顶 900。为什么不线性因为线性的难度曲线在前期太缓、后期太陡。分档的体感是“打一会儿突然变快一点”玩家的注意力会被重新调动。这个设计是我试了几十个版本后定的档位之间的差值在 80-100 之间体感最明显低于 50 玩家几乎察觉不到高于 150 会觉得莫名其妙变难。4.4 手感调优清单代码跑起来只是开始真正花时间的是调。我列一份自己常用的清单你可以照着试参数初始值调节方向影响挡板宽度130减小 → 变难容错空间挡板速度780提高 → 变顺手追球能力最大出射角60°提高 → 更斜策略性球速基数420降低 → 更从容整体节奏最小垂直分量28%提高 → 更少横飞观感砖块间隙6增大 → 视觉更清晰可读性我特别想说一下“挡板跟随鼠标”这个选项。键盘控制更“街机”鼠标控制更“现代”。实测下来鼠标控制的玩家在第五关之后的存活率明显更高因为鼠标的定位精度远高于按键。如果你的目标是让新手能玩下去用鼠标如果想保留挑战感用键盘加一点惯性。别两个都开玩家会混乱。还有一个很便宜的小改动球撞到挡板时让挡板轻微“压扁”一下宽度加 4、高度减 3持续 80 毫秒。这纯粹是视觉反馈但能显著提升“打击感”。类似的还有球撞砖时的粒子飞散我一般用 6 个小方块初速度随机、带重力、寿命 0.3 秒。特效不需要多需要的是“即时”。5. 跑起来之后才会暴露的问题这一章全部来自实际跑起来之后踩的坑你在静态看代码的时候永远发现不了。5.1 常见故障速查表先上表遇到问题可以直接对号入座。现象最可能的原因处理方式球穿过砖块不反弹单帧位移过大加子步长步长 ≤ 球半径 × 0.8球在两块砖之间抖动同时处理多个碰撞碰撞后立即 break球水平来回弹不停垂直分量过小强制换高刷屏后整体变快速度按帧定义全部改成每秒乘 dt切窗口回来球消失dt 异常大dt 上限 0.05挡板追不上球挡板速度低提升到球的 1.8 倍以上球卡在挡板侧面侧向碰撞未单独处理只在 vy 0 时用角度反弹砖块打完后不结束判定漏了 None遍历时统计剩余数量第三行那个问题我卡了挺久。当时的现象是球在左右墙之间弹了七八个来回才落下来看起来很“呆”。我一开始以为是速度设置问题后来才发现是反射后vy变成了一个极小值。加上最小垂直分量之后立刻正常。第七行也值得说。球从挡板侧面擦过的时候如果用统一的法线反射球会被“弹”向下方直接判死。正确做法是判断球心在挡板的哪个方位如果球心在挡板上下边界之内、只在左右两侧那是水平法线如果球心在挡板之上才用角度反弹。5.2 我踩过的三个坑第一个坑是把渲染和逻辑混在一起。我最初把碰撞检测直接写在绘制函数里因为“反正每帧都要画”。结果后面想加个暂停功能发现暂停之后球的位置还在更新——因为绘制还在跑。后来把所有update抽出来draw只读不写问题消失。规则绘制函数里不许修改任何状态这一条我到现在还守着。第二个坑是音效延迟。我给撞砖加了个音效结果密集连击的时候声音会叠成一片噪音。pygame.mixer默认的通道数不多多个音效同时触发会互相抢占。解决方法是限制同一音效的最小间隔我设的是 40 毫秒也就是每秒最多 25 次。这个数字是实测的低于 30 毫秒听起来还是糊高于 60 毫秒击打感就没了。第三个坑是分数显示模糊。我用系统默认字体画分数在高分屏上糊成一团。后来改成显式加载一个字体文件并指定大小同时把分数渲染成“先画一遍深色再做偏移再画一遍亮色”做出了轻微的立体感。这种小细节属于“不做也没事做了质感不一样”的类型。5.3 从单文件走向可扩展代码能跑之后你大概会长出新的想法加道具、加多球、加 Boss。这时候单文件就不够用了。我的拆分方式是game/ config.py 常量与调参 entities.py 挡板、球、砖块 world.py 游戏世界、碰撞、状态机 levels.py 关卡数据 main.py 启动拆的依据是变化频率。config.py改得最勤levels.py次之entities.py和world.py相对稳定。把变化频率不同的东西放一起是维护成本的主要来源。加道具的时候有个设计原则值得记住道具只是给球或挡板临时改参数不要引入新的物理规则。比如“加宽挡板”就是把PADDLE_W乘 1.5 并记录持续时间“多球”就是往球的列表里再塞一个拷贝。如果你给每个道具都写一套独立的更新逻辑代码会迅速失控。最后提一句难度平衡。加了道具之后原来的难度曲线必然被打乱。我的做法是先关掉所有道具跑十遍确认基础难度合适再开启道具跑十遍看两次的通关率差多少。如果差得太多说明道具太强砍掉一半效果。这个过程听起来笨但比凭感觉调靠谱得多。我个人在这个项目上最大的体会是代码写得多不等于学到了东西把参数调到“自己愿意连玩五把”才叫完成。我第一版写完玩了三十秒就关掉了改到第七版才终于有那种“再来一把”的冲动。从三十秒到想再来一把中间没有加任何新功能全是在调速度、角度、反馈延迟这些数字。如果你也在做类似的东西别急着加内容先把现有的东西调到顺手那才是真正的分水岭。

相关推荐

代码注释规范:避免多语言陷阱的技术实践
代码注释规范:避免多语言陷阱的技术实践

1. 项目背景与核心问题那天下午的代码评审会,整个技术部门都记得清清楚楚。新来的技术总监王总正对着投影屏幕皱眉,突然整个人僵在了那里——他面前的IDE里,密密麻麻全是西夏文注释的Java代码。会议室里鸦雀无声,只听见空调出风口… · 2026/11/3 4:10:43

测试文档编写指南:从目标定义到最佳实践
测试文档编写指南:从目标定义到最佳实践

1. 项目概述"测试文章标题01"这个看似简单的标题背后,其实蕴含着丰富的可能性。作为从业多年的技术博主,我深知每个项目标题都可能是一个技术宝藏的入口。今天我们就来深度挖掘这个标题背后的技术内涵和应用场景。在实际工作中,我们… · 2026/11/3 4:10:43

HarmonyOS音频焦点管理:音乐播放器开发实战
HarmonyOS音频焦点管理:音乐播放器开发实战

1. 项目概述作为一名在HarmonyOS音频开发领域深耕多年的开发者,今天我想和大家深入探讨一个在音乐播放器开发中至关重要却常被忽视的技术点——音频焦点管理。在HarmonyOS 6环境下,AudioSession机制作为音频焦点管理的核心实现,其设计理念和实… · 2026/11/3 4:10:43

NineData实现MySQL到openGauss PostgreSQL数据复制
NineData实现MySQL到openGauss PostgreSQL数据复制

1. 项目概述:NineData新增MySQL到openGauss PostgreSQL兼容版数据复制链路最近NineData平台上线了一个让数据库管理员们眼前一亮的新功能——支持MySQL到openGauss PostgreSQL兼容版的数据复制链路。这个功能解决了不少企业级用户在实际业务中遇到的数据库迁移难题。… · 2026/11/3 4:33:49

医院医疗设备管理系统微信小程序开发实践
医院医疗设备管理系统微信小程序开发实践

1. 项目概述这个医院医疗设备管理系统微信小程序毕设项目,是我去年指导计算机专业学生完成的典型毕业设计案例。整套系统包含完整的前后端代码、数据库设计文档和技术说明文档,实现了医疗设备的全生命周期管理。从实际运行效果来看,系统日均能… · 2026/11/3 4:33:49

代码注入与Hook技术:原理、实现与应用解析
代码注入与Hook技术:原理、实现与应用解析

1. 代码注入与Hook技术核心概念解析在软件开发和安全研究领域,代码注入和Hook技术就像外科医生的手术刀——用得好可以治病救人,用不好则会造成严重伤害。这两种技术本质上都是通过干预程序正常执行流程来实现特定功能,但它们的实现原理和应用… · 2026/11/3 4:33:49

蒙特卡洛模拟从原理到实战:工期、库存与风险估算
蒙特卡洛模拟从原理到实战:工期、库存与风险估算

上周某位做项目管理的朋友问了我一个问题:手上这个研发项目,合同里写死了六个月交付,可每个环节的工期都是估出来的,有的乐观有的悲观,最后按期完成的概率到底有多大?他手上有一张 Excel 表,每个… · 2026/11/3 4:33:49

8cm等于多少像素?厘清PPI、CSS像素与印刷分辨率的换算逻辑
8cm等于多少像素?厘清PPI、CSS像素与印刷分辨率的换算逻辑

做设计、写前端、跑印刷的人,几乎都被同一个问题绊过一下:8cm等于多少像素。这个问题看着像小学算术,实际上一句话根本答不完。我在带新人的时候,最常听到的两句抱怨,一句是"我按8cm做的图,打印出来矮… · 2026/11/3 4:33:49

V2X技术实现卡车自动编队行驶:原理与应用
V2X技术实现卡车自动编队行驶:原理与应用

1. 项目概述:当卡车在高速上自动排成"火车"去年夏天,我在G15沈海高速上看到一列特殊的"货运火车"——五辆重型卡车以不到10米的间距匀速行驶,头车司机偶尔调整方向盘,后车就像被无形绳索牵引般同步跟随。这正… · 2026/11/3 4:32:49

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 4:42:45

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 5:17:18

STM32F411RE与MAX14431实现SPI隔离:工业现场抗共模干扰实战
STM32F411RE与MAX14431实现SPI隔离:工业现场抗共模干扰实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59

ESP32+大模型AI硬件开发:从Demo到产品的8个工程问题
ESP32+大模型AI硬件开发:从Demo到产品的8个工程问题

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59

基于MAX14431BAEE+与PIC18F85J10的SPI隔离方案设计与实践
基于MAX14431BAEE+与PIC18F85J10的SPI隔离方案设计与实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 4:42:45

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 5:17:18

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码