Pygame实战教程:从零开发打砖块游戏,掌握游戏循环与碰撞检测
1. 为什么我用Pygame做第一个游戏而不是直接上游戏引擎先交代一个背景很多人说学编程要有项目练手但真到了“做点什么”的时候反而一脑袋浆糊——写爬虫怕被封、做网站要学前端后端一大套、搞数据分析又觉得跟工作生活没关联。游戏不一样游戏天生自带反馈你写一行代码屏幕上就多一个会动的东西这种即时成就感是其他项目很难给的。我最初接触Pygame是在一个周末起因其实很简单想给当时正在学Python的A同学做个能在自己电脑上跑起来的小玩意儿验证一下他学的基础语法到底有没有用。结果那个周末我一口气写了三个小demo一个接球游戏、一个会变色的方块堆叠、一个没有完整实现就打消念头的地图编辑器。Pygame给我的感觉是它不像游戏引擎那么庞大不要求你先学会资源管理、场景树、材质贴图这一堆概念但又能让你认真体验一次“游戏是怎么跑起来的”。Pygame本质上是SDLSimple DirectMedia Layer的Python封装SDL负责跟操作系统底层打交道处理窗口、音频、输入设备这些Pygame则把这些能力包成一个相对友好的Python接口。你可以把它理解为一间“已经铺好水电的毛坯房”——你不用自己挖地基、拉电线但墙面怎么刷、房间怎么隔、家具怎么摆全部自己来。对于第一次做游戏的人来说这个自由度刚刚好不会因为太多现成工具而无从下手也不会因为全是底层代码而劝退。这篇文章适合谁看过Python基础语法变量、循环、函数、类哪怕都不太熟想通过一个完整项目把知识串起来的人。不需要你有美术功底不需要你写过游戏甚至不需要你想清楚“到底做什么游戏”。跟着把一整个流程走完你会对“游戏开发”这个神秘领域祛魅也会发现它其实就是一套循环和事件的组合拳。顺带说一个很多新手会问的点为什么不直接用Unity或者Godot我的看法是工具选择的本质是“学习成本”与“结果天花板”的权衡。Unity功能强大但你得先学会它的编辑器、预制体、场景管理这些概念本身就是一门课而Pygame的学习曲线是平的你只需要理解“画布、循环、事件”三个词就能写出东西。等你用Pygame搞明白了游戏逻辑的底层套路再上手引擎很多概念会变得特别顺。反着学也行但那种“眼前一黑”的概率大得多。2. 写游戏之前必须想清楚的三件事2.1 游戏循环所有游戏的心脏几乎所有游戏不管多么复杂核心骨架都是同一套结构循环 事件 状态更新 画面重绘。这个概念我觉得可以用翻页动画来类比你小时候可能见过那种在书角画上小人然后快速翻页让小人“动”起来的玩具。每一帧画面就是书的一页翻页速度够快大脑就把它脑补成了连贯的运动。游戏循环做的就是这件事每秒钟重复“读一次玩家输入→算一下新状态→画一帧画面”这组动作。Pygame里这个循环通常长这样running True while running: for event in pygame.event.get(): if event.type pygame.QUIT: running False # 更新游戏逻辑 # 绘制画面 pygame.display.flip()注意这里有个“陷阱”新手容易踩你怎么控制翻页速度如果这个循环跑得飞快游戏就变成“150帧的子弹时间”如果跑得慢又变成幻灯片。答案是用一个Clock对象让它把循环钉在每秒60次这个节奏上这个我们后面实操里会具体写。2.2 坐标系和运动用小学算术让物体动起来Windows里的坐标跟数学课上的坐标系不一样它左上角是(0, 0)x轴向右为正y轴向下为正。也就是说在画布上数字越大越往下走。刚上手时很多人会在这个地方懵一下我写y - 5为什么方块往上走了因为坐标原点在上方y减小的方向就是视觉上的“向上”。只要记住这一点让物体动起来就跟小学算术一样了你只需要在每次循环里把物体的x或y坐标加上一个“速度偏移量”就行。比如球的位置是ball_x, ball_y每次循环执行ball_x speed_x; ball_y speed_y球就按照(speed_x, speed_y)这个向量方向飞出去了。这里有个概念叫“向量”听着高大上其实就是“横着走多快、竖着走多快”而已。速度也有正负之分正数往右/往下负数往左/往上。理解了这两行代码你已经会做弹球游戏的一半了。2.3 图像和碰撞像素之间怎么叠加Pygame里有两个核心概念Surface和Rect。Surface你可以理解为一张“图层”它上面可以画东西、贴图片、写字。主窗口是一个Surface你可以在上面叠别的Surface。坐标就是Surface上的位置。Rect是矩形区域存着x, y, width, height四个核心数字。游戏里判断两个物体是否相撞本质上就是判断两个矩形是否重叠方法就一行if rect1.colliderect(rect2): # 相撞了这是Pygame给新手最好的礼物。在真实游戏引擎里碰撞检测是个可能需要写几百行代码的话题但在这个库中新手阶段一行代码就搞定了。我用生活类比来说Rect就像你贴在纸上的便签纸colliderect就是看两张便签纸有没有重叠的部分重叠就说明碰上了。3. 环境准备与第一个可运行的窗口3.1 安装Pygame如果你用的是Python 3环境安装就一个命令pip install pygame装完建议顺手验证一下版本和安装状态python -m pygame.examples.aliens如果能看到一个飞船躲外星人的演示窗口跑起来那说明环境完全正常。这条命令是我每次在新电脑配环境时都会跑的它同时验证了Python、Pygame安装、图形驱动三样东西是否OK比自己在命令行里print(pygame.version.ver)靠谱得多。有的系统上如果你的Python是多方管理的比如同时装了Anaconda和官方Python安装完却导入不了大概率是pip和python指向的不是同一个环境。解决办法就是用python -m pip install pygame的方式来装这样能保证装进当前这个python解释器对应的环境里。这个坑我替不少人踩过写在这儿。3.2 创建窗口和画点先写一个最精简的程序目的只有一个弹出一个窗口在窗口里画一个方块import pygame pygame.init() screen pygame.display.set_mode((640, 480)) pygame.display.set_caption(我的第一个Pygame窗口) WHITE (255, 255, 255) BLUE (0, 0, 255) running True clock pygame.time.Clock() while running: for event in pygame.event.get(): if event.type pygame.QUIT: running False screen.fill(WHITE) pygame.draw.rect(screen, BLUE, (300, 230, 40, 40)) pygame.display.flip() clock.tick(60) pygame.quit()先看两个关键点第一pygame.display.set_mode((640, 480))这行里的元组是“窗口宽度和高度”。Pygame的坐标系原点在左上角所以(300, 230, 40, 40)这个矩形参数的四个值分别表示左边缘距窗口左边300像素、上边缘距窗口顶部230像素、矩形宽度40像素、矩形高度40像素。第二clock.tick(60)是帧率控制器“60”表示每秒最多循环60次。这句有两个作用一是限制游戏速度不至于因为CPU性能不同而跑得忽快忽慢二是天然成了一个计时基准后面做倒计时、判定之类逻辑时按照帧数来算就行。关于颜色RGB三个分量分别管红绿蓝每个通道0到255。(255, 255, 255)是白色(0, 0, 0)是黑色(255, 0, 0)是红色。这个三元组说起来是元组用的时候也确实是元组但新手最好把它单独提出来命名比如WHITE (255, 255, 255)一是避免到处都是魔法数字二是后面做颜色变化时只需要改这一处。3.3 理解事件系统游戏怎么知道你按了键盘事件就是“系统通知你‘发生了什么’”。用户按键了、移动鼠标了、点窗口关闭按钮了操作系统都会把这些行为打包成一条事件放进Pygame的事件队列里你的代码通过pygame.event.get()取出这些事件然后挨个检查、判断、响应。有一种说法我特别认同写游戏其实写的就是“如果发生了什么事就做什么反应”。所以游戏主循环里最常出现的就是一堆if。比如if event.type pygame.KEYDOWN: if event.key pygame.K_LEFT: player_speed_x -5注意这里“按住”和“按一下”是有本质区别的。代码里写的是KEYDOWN意思是“按下这个键的瞬间触发一次”。如果你想要“按住就一直移动”的效果不能只靠事件。更常见的做法是用状态在KEYDOWN时把方向变量设成“朝左”在KEYUP时再把它设成“停止”然后主循环里根据这个方向变量每一帧都移动一次。这个区别很多人到写第二个游戏时才会意识到我现在先给你打个预防针。4. 实战做一个完整的打砖块游戏4.1 项目规划和代码结构下面做一个经典到不能再经典的打砖块游戏底部有一个挡板左右移动接住反弹的球上面排列着若干砖块球碰砖块砖块消失砖块全部消失就赢球掉到底部就输一命。为什么选打砖块而不是贪吃蛇或者俄罗斯方块因为打砖块的核心逻辑覆盖了Pygame里几乎所有基础能力键盘输入、碰撞检测、列表管理砖块是一组对象、运动学和边界判定。代码量又不大一个文件放得下适合新手一次性读懂全局。我建议你新建一个项目文件夹比如brick_breaker/里面放一个main.py。虽然Pygame项目后期通常要拆成sprites.py、settings.py之类但第一个游戏就勇敢地写单文件看懂全局比追求优雅结构重要得多。代码我拆成几个部分来讲解。先是常量设置import pygame import sys # 游戏设置 SCREEN_WIDTH 800 SCREEN_HEIGHT 600 FPS 60 # 颜色定义 WHITE (255, 255, 255) BLACK (0, 0, 0) RED (255, 0, 0) BLUE (0, 0, 255) GREEN (0, 255, 0) YELLOW (255, 255, 0) # 挡板设置 PADDLE_WIDTH 100 PADDLE_HEIGHT 15 PADDLE_SPEED 8 # 球设置 BALL_RADIUS 8 BALL_SPEED_X 4 BALL_SPEED_Y -4 # 砖块设置 BRICK_WIDTH 70 BRICK_HEIGHT 20 BRICK_ROWS 5 BRICK_COLS 9这里重点解释一下球的初始速度为什么是(4, -4)而不是(1, 1)。BALL_SPEED_Y -4意味着球初始往“上”飞因为y是负数即屏幕往上。横竖速度都是4刚好45度角斜飞既不会太慢让人等得着急也不会快到反应不过来。如果你把FPS设成60球在一秒内位移是4 * 60 240像素在800像素宽的画面里大概3秒多横穿一遍节奏不错。如果换成30帧球就会慢一倍那是另一个配速思路了。然后是初始化全局实例pygame.init() screen pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) pygame.display.set_caption(打砖块——练手作) clock pygame.time.Clock()4.2 定义挡板和球的角色Pygame里有个pygame.sprite.Sprite类专门用来做游戏对象的基类。初学者可能觉得这个词很唬人但说白了它就是一个帮你封装了“图像”和“位置”的方便容器。一个Sprite通常有两个固定属性rect代表它的位置和大小和image代表长什么样。我建议直接继承它哪怕只是为了养成好习惯class Paddle(pygame.sprite.Sprite): def __init__(self): super().__init__() self.image pygame.Surface((PADDLE_WIDTH, PADDLE_HEIGHT)) self.image.fill(BLUE) self.rect self.image.get_rect() self.rect.midbottom (SCREEN_WIDTH // 2, SCREEN_HEIGHT - 30) self.speed PADDLE_SPEED def update(self): keys pygame.key.get_pressed() if keys[pygame.K_LEFT] and self.rect.left 0: self.rect.x - self.speed if keys[pygame.K_RIGHT] and self.rect.right SCREEN_WIDTH: self.rect.x self.speed这里说一个容易掉坑的点self.image.fill(BLUE)之后一定要用self.image.get_rect()来获取矩形而不是自己手写一个pygame.Rect(...)。因为get_rect会自动读取self.image的宽高保证两者始终一致。而且注意后续移动时只要改self.rect.x的值就行——rect是引用类型改了它绘制时读取self.rect的位置就是新的位置。挡板移动我用了pygame.key.get_pressed()它跟事件模式的区别是get_pressed()返回的是所有键的“当前按下状态”适合做持续移动KEYDOWN事件只触发一次适合做“按一下跳一格”的操作。如果你的挡板用后者按一下只动一下那游戏就没法玩了。这是个经典的经验教训写在这儿。球的类稍微复杂一点因为涉及反弹逻辑class Ball(pygame.sprite.Sprite): def __init__(self): super().__init__() self.image pygame.Surface((BALL_RADIUS * 2, BALL_RADIUS * 2), pygame.SRCALPHA) pygame.draw.circle(self.image, YELLOW, (BALL_RADIUS, BALL_RADIUS), BALL_RADIUS) self.rect self.image.get_rect() self.reset() def reset(self): self.rect.center (SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2) self.speed_x BALL_SPEED_X self.speed_y BALL_SPEED_Y def update(self): self.rect.x self.speed_x self.rect.y self.speed_y # 左右墙壁反弹 if self.rect.left 0 or self.rect.right SCREEN_WIDTH: self.speed_x -self.speed_x # 上墙壁反弹 if self.rect.top 0: self.speed_y -self.speed_ypygame.Surface((BALL_RADIUS * 2, BALL_RADIUS * 2), pygame.SRCALPHA)这行的SRCALPHA参数可以让Surface支持透明通道这样画出来的圆形周围不会有黑色方框底。这个细节如果你漏了游戏里会出现一个圆外面套着一圈黑方块丑得令人崩溃而且一时半会儿还查不出原因。球的极简物理就这么朴素碰到左右墙把x方向速度取反碰到上墙把y方向速度取反。速度取反就是在前面加个负号小学乘法。等你想做更真实的反弹比如打挡板不同位置角度不同再在这个基础上加逻辑就行。砖块的类更简单它只要有一个位置和颜色就够了class Brick(pygame.sprite.Sprite): def __init__(self, x, y, color): super().__init__() self.image pygame.Surface((BRICK_WIDTH, BRICK_HEIGHT)) self.image.fill(color) self.rect self.image.get_rect() self.rect.topleft (x, y)4.3 碰撞检测和游戏逻辑这一节是打砖块的精华所在。看完你就懂为什么我说Pygame碰撞检测“1行顶几百行”。先要把三类对象放到各自的编组里all_sprites pygame.sprite.Group() # 所有对象用于统一绘制 bricks_group pygame.sprite.Group() # 所有砖块用于碰撞检测 paddle Paddle() ball Ball() all_sprites.add(paddle, ball) # 生成砖块墙 for row in range(BRICK_ROWS): for col in range(BRICK_COLS): x 10 col * (BRICK_WIDTH 5) y 50 row * (BRICK_HEIGHT 5) # 按行给不同颜色 if row 0: color RED elif row 1: color YELLOW elif row 2: color GREEN else: color BLUE brick Brick(x, y, color) all_sprites.add(brick) bricks_group.add(brick)砖块间距为什么是5像素因为BRICK_WIDTH是70、列间隔是75总宽度9列 * 75 - 5 670放进800像素的窗口两边各留65像素空隙视觉上比较居中不会顶到墙或者右边空一大块。这个算好游戏画面才好看。主循环里的碰撞检测段落running True score 0 lives 3 while running: for event in pygame.event.get(): if event.type pygame.QUIT: running False # 更新所有对象 all_sprites.update() # 球和挡板碰撞 if ball.rect.colliderect(paddle.rect): if ball.speed_y 0: # 球正在往下走才反弹 ball.speed_y -ball.speed_y ball.rect.bottom paddle.rect.top # 避免球嵌进挡板 # 球和砖块碰撞 hit_bricks pygame.sprite.spritecollide(ball, bricks_group, True) if hit_bricks: ball.speed_y -ball.speed_y score len(hit_bricks) * 10 # 球掉出底部 if ball.rect.top SCREEN_HEIGHT: lives - 1 ball.reset() if lives 0: running False # 砖块清空则胜利 if len(bricks_group) 0: running False # 这里标记胜利稍后我们在循环外判断 # 绘制 screen.fill(BLACK) all_sprites.draw(screen) # 简单显示分数 font pygame.font.SysFont(None, 36) score_text font.render(fScore: {score}, True, WHITE) screen.blit(score_text, (10, 10)) lives_text font.render(fLives: {lives}, True, WHITE) screen.blit(lives_text, (10, 50)) pygame.display.flip() clock.tick(FPS) pygame.quit()为什么球和砖块碰撞时只是简单地把speed_y取反而不是像真实物理那样按撞击点算角度答案是为了先把游戏做出来。真实角度的砖块反弹需要判断“球撞到的是砖块的顶/底/左/右”需要比较球的运动方向和重叠区域复杂度会翻好几倍。第一版游戏先接受“球永远上下翻转”这个简化设定能玩就行。等你觉得“这个反弹好假”的时候恭喜你你已经进入下一个阶段了。4.4 胜利和失败状态处理上面的主循环有个瑕疵循环退出之后玩家不知道自己是赢是输。在真实游戏里退出主循环不等于游戏程序结束你还需要一个“结算画面”。我习惯用一个状态变量来区分而不是靠while条件分支太多# 定义状态常量 PLAYING 0 WON 1 LOST 2 game_state PLAYING # 主循环里改成 if ball.rect.top SCREEN_HEIGHT: lives - 1 ball.reset() if lives 0: game_state LOST running False if len(bricks_group) 0: game_state WON running False # 循环结束后显示结果 if game_state WON: print(你赢了所有砖块都被清空。) elif game_state LOST: print(游戏结束。再接再厉)这是我从实际经验里总结出的一条建议游戏逻辑里尽量不要用裸的running False因为当你后面要增加更多结束条件的时候裸退出会让你搞不清到底为什么结束了。有一个明确的状态变量哪怕现在只用来区分输赢也值得花两分钟写上。它对你后续加“再来一局”功能非常关键——你只要把状态设回PLAYING、重置所有对象位置就能无缝重开。5. 常见问题与排查技巧实录5.1 运行时高频报错速查我在带新手做Pygame项目的过程中发现有几类报错出现频率特别高把它们做成了一张排查表报错信息常见原因解决办法pygame.error: video system not initialized忘记调用pygame.init()或窗口被关闭后还继续画图在创建窗口和主循环前先调用pygame.init()检查代码顺序NameError: name xxx is not defined缩进错误导致类内变量被当成全局变量或拼写错误检查变量定义的位置和拼写类内属性必须用self.xxx访问TypeError: rect is not a valid keyword argument尝试用pygame.Rect的私有方法名确认方法大小写比如rect.bottom而不是rect.Bottom游戏窗口一闪而过自动关闭主循环没有正确运行或直接退出检查while running:是否被缩进错误或者pygame.quit()是否被提前执行画面卡住不动事件循环里没调用pygame.event.get()或者绘制代码被条件语句挡住确认事件获取和绘制代码在主循环内的顶层位置球移动速度在不同电脑上不一样没用clock.tick(FPS)控制帧率在主循环末尾加上clock.tick(FPS)有一条最微妙的球在运行几秒后突然消失或者出现闪烁。这通常不是逻辑问题而是球已经移动到一个超出画面边界的位置但因为没有显示因而感觉像“消失”了。比如球速是(4, -4)碰到挡板后speed_y反转成4如果同时挡板还在快速右移可能球被挡板挤到右下角然后嵌进边界速度反复取反导致它在两帧之间“瞬移”。解决办法之一是像我前面写的反弹时强制设置位置ball.rect.bottom paddle.rect.top这句的意思是把球的底部“按”到挡板的顶部不给它嵌入的机会。用一句代码解决异常状态这种小技巧就是老手和新手的差距所在。5.2 帧率和性能的认知误区新手看到“帧率”往往焦虑是不是游戏会卡其实Pygame对2D小游戏的性能是完全够用的普通的几百个砖块和精灵同时运动60帧轻松跑满。真正需要性能优化的场景是全屏粒子特效、大量图片同时缩放、或者每帧做超级复杂的计算。打砖块远达不到这个量级。但有一个问题你必须理解clock.tick(60)只能限制“最快速度”它不能保证“每帧耗时一样”。如果某帧里碰撞检测和绘制耗时超过了1/60秒帧间隔就会拉长游戏表现为卡顿一下。这时候你可以用简单的方法自测# 在窗口标题里显示实时FPS fps clock.get_fps() pygame.display.set_caption(f打砖块 — FPS: {fps:.1f})先观察卡顿时的帧率变化再定位是哪些代码耗时。根据我的经验九成卡顿来自加载大图片、每帧创建字体对象和渲染文字把font.render放到循环外面做缓存而不是游戏逻辑本身。一个特别容易忽略的坑如果你在主循环里每帧都调用pygame.font.SysFont(None, 36)创建一个新字体对象即使画面不复杂也会因为反复分配资源导致偶发卡顿。正确姿势是在循环外面创建字体和文字Surface循环里只用blit贴上去。文字变化时才重新render比如分数变了才执行渲染否则用上一帧的缓存。这个优化效果立竿见影。5.3 新手最容易忽略的三个编程习惯第一个习惯先让代码跑起来再想着“完善”。很多第一次写游戏的人总想在开始前把所有功能都想全——要音效、要开场动画、要特效粒子。我的建议是第一版只做一个能玩的核心循环哪怕画面丑到只有一个白框和一个小球。动手跑通之后再慢慢加东西出问题才更容易定位。我在带人写项目时反复强调这句话“完美是改出来的不是写出来的。”第二个习惯用print()做临时调试而不是猜。游戏逻辑出问题时在可疑位置加一行print(ball position:, ball.rect.x, ball.rect.y)比你想破脑袋快得多。特别是在判断“球到底有没有碰到挡板”时打印一帧位置就能看出碰撞检测是不是真的生效了。这个方法朴素到很多人不爱用但它是排查问题效率最高的方式之一。第三个习惯隔一段时间就备份“能跑的版本”。哪怕就在本地复制一份main_v2.py也行我见过太多人改了几行代码之后游戏彻底跑不起来了又忘了原来的版本是什么样的只能从头写。版本备份不是装逼是给自己留后悔药。6. 从打砖块到真正游戏开发下一步怎么走如果你已经完成了上面的打砖块恭喜你你已经掌握了Pygame里最核心的80%能力。但是打砖块只是“起跑线”真正让人成长的是你接下来往哪个方向走。根据我自己的经历给你三条进阶路径。第一条路把打砖块做“深”。这个游戏里值得深挖的方向实在太多了。一个最简单的音效添加就把游戏体验拉高一个档次# 加载一个音效文件wav或ogg格式 hit_sound pygame.mixer.Sound(hit.wav) # 在碰撞代码里播放 hit_sound.play()然后可以加道具系统砖块被打碎时有概率掉落一个道具比如挡板变宽、球速加快、多一颗球这就涉及“随机刷新、自由下坠、与挡板碰撞”三个新逻辑把弹幕游戏的雏形就做出来了。再往后可以加关卡系统、存档系统、计分榜一个看似简单的打砖块实际上是一个可以无限延伸的小宇宙。第二条路换一种游戏类型练手。做完打砖块你对Sprite、碰撞检测、事件系统都有体感了可以试着做一个横版飞机射击游戏玩家控制战机发射子弹敌机从上方出现子弹打中敌机就爆炸。这里的核心技术点是“Bullet”类管理——每按一次空格就创建一个子弹对象放进一个列表里逐帧更新它的位置超出画面就删除。这个模式跟砖块的管理思路一样但逻辑更独立你会发现自己的OOP思维在悄悄成型。第三条路尝试加入资源管理比如图片和外置配置文件。把游戏常量写进一个单独的config.py或JSON文件把图片素材放assets/images/把音效放assets/sounds/。这听起来不酷但它是从“写脚本”向“做产品”转变的关键一步。我第一次把打砖块项目拆成模块化结构时虽然代码量没变多少但改一个挡板颜色不用去代码里搜了心里那个爽感是真的。这里稍微聊一下为什么很多教程不建议你一开始就上大型游戏引擎。我见过不少同学从Unity开始折腾一周还在跟编辑器界面较劲游戏逻辑反而一句没写。而用Pygame一周时间已经能做出三四个能玩的小项目了。这种“快速出作品”的体验在学习的早期几乎比学习内容本身还重要——它让你有动力继续下去。有了Pygame的基础你再去学Unity至少GameObject和Sprite的关系、场景切换和游戏循环的对应关系你都门儿清。我个人的体会是Pygame最好的用法不是用它做最终产品而是把它当成游戏开发学习里的“自行车”——你不需要骑一辈子自行车但如果没有它你直接学开汽车游戏引擎很容易手足无措而一旦你靠自己蹬出了“游戏是如何运转”的肌肉记忆后面换什么工具都不会怂。打砖块这个代码你完全可以把它当玩具继续改造。我的建议是你每学会一个新概念就回头往这个游戏里塞一点东西。今天学了随机数就加个随机颜色的砖块明天学了列表就做一排连续生成的砖块墙。游戏开发练的不是一个“大招”而是无数个小技巧的排列组合而这个游戏就是你练习大招的靶场。最后分享一个操作层面的小建议如果你卡在某个功能上超过二十分钟先停下来把问题用一句话写下来比如“球为什么在碰到底部后没有反弹而是直接穿过”然后再带着这句话去看对应代码。很多时候问题还没写完你就已经找到线索了。编程和写游戏都是这样大部分时候差的不是智商而是动手之前有没有把问题想清楚。

相关新闻

Gradle依赖统一管理:Version Catalog与BOM实战避坑指南

Gradle依赖统一管理:Version Catalog与BOM实战避坑指南

简介:这份资源聚焦 Android 工程中 Gradle 依赖的统一管理,面向需要规范多模块依赖版本、减少重复配置的 Android 开发者,尤其适合已掌握 Gradle 基础、希望进一步优化构建脚本的中级工程师。压缩包共 34 个文件,约 100KB&#xf…

2026/10/14 3:49:40 阅读更多 →
高校成绩可视化分析平台的设计与实现:从数据清洗到图表呈现

高校成绩可视化分析平台的设计与实现:从数据清洗到图表呈现

前阵子帮某高校的教务部门做了一套成绩统计分析平台,项目名称就叫“基于大数据校园大学生成绩可视化分析平台设计与实现”。听起来是个很重的系统,实际上只要把需求拆开看,就三件事:全量成绩数据入库、多维交叉统计、图表化呈现。…

2026/10/14 3:49:40 阅读更多 →
同一视频在不同播放器内容不同?拆解多流结构与默认轨道选择原理

同一视频在不同播放器内容不同?拆解多流结构与默认轨道选择原理

同一个视频文件,用不同的播放器播放,结果画面、声音、字幕甚至时长都不一样?这不是魔术,也不是文件被替换,而是视频文件本身的“多流结构”与播放器解析策略差异导致的。本文以安卓端影现播放器出现该问题为例&#xf…

2026/10/14 3:49:40 阅读更多 →

最新新闻

dplyr group_by后只剩一行?五步定位数据丢失根因

dplyr group_by后只剩一行?五步定位数据丢失根因

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

2026/10/14 4:31:52 阅读更多 →
线性回归与逻辑回归:从原理到实战

线性回归与逻辑回归:从原理到实战

机器学习线性模型学习笔记——线性回归与逻辑回归 一、线性回归 1.概念 线性回归是回归任务的基础线性模型,用于预测连续型数值。它假设目标值与特征之间存在线性关系,通过特征的线性组合来输出预测结果。 模型表达式: y^w1x1w2x2⋯wnxnb \ha…

2026/10/14 4:31:52 阅读更多 →
前端工程化必备:用corepack统一pnpm版本,终结CI疑难杂症

前端工程化必备:用corepack统一pnpm版本,终结CI疑难杂症

打开任何一个维护超过半年的前端仓库,你大概率会在某次CI失败日志里见过这种提示:ERR_PNPM_UNEXPECTED_PACKAGE_MANAGER,或者某个本地依赖装好、线上却装不出的诡异场景。排查到最后,绝大多数时候都是同一个原因——团队里pnpm版本…

2026/10/14 4:31:52 阅读更多 →
删掉重型架构后,我的Agent效果反而全面变好

删掉重型架构后,我的Agent效果反而全面变好

最近一年我大部分精力都放在 Agent 的落地项目上,从零搭过框架,也接手过别人留下的框架,还帮团队拆过好几套跑不动的“重型架构”。如果要我说这一年里对效果提升最大的一次改动是什么,我的答案很直接:把架构删了。做 …

2026/10/14 4:31:52 阅读更多 →
z-index 为什么有时不生效?层叠上下文是怎么决定遮挡顺序的?

z-index 为什么有时不生效?层叠上下文是怎么决定遮挡顺序的?

z-index 为什么有时不生效?层叠上下文是怎么决定遮挡顺序的? z-index 要放在层叠上下文里理解。同一上下文中的元素按相应绘制规则比较,子上下文作为整体参与父级排序,内部子元素不能靠更大的 z-index 越过这个整体的层级。 例如…

2026/10/14 4:31:52 阅读更多 →
SQL基础查询指南:从查询本质到实际应用,零基础也能快速上手

SQL基础查询指南:从查询本质到实际应用,零基础也能快速上手

想要入门数据分析、后端开发、运维,甚至只是想把报表做得灵活一点,SQL 基础查询语句都是绕不开的第一关。我遇到过不少零基础的同学,花一周把网上流传的“命令大全”抄了一遍,结果面对一张真实的订单表时,连“查询金额…

2026/10/14 4:30:52 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/12 18:11:17 阅读更多 →