资讯动态

用 Python + Pygame 从零手写一个 Flappy Bird(附完整代码+粒子特效)

发布时间:2026/8/4 22:40:54 来源:尧图企业网站定制
# 用 Python Pygame 从零手写一个 Flappy Bird附完整代码粒子特效![运行效果](运行效果.gif) 只用了 **400 行纯 Python**就做出了带视差背景、粒子爆炸、屏幕震动、小鸟旋转的 Flappy Bird。文末附完整可运行代码和打包 exe 的方法建议收藏。---## 一、先看效果这个版本和网上的极简复刻不一样它带有| 特效 | 说明 ||---|---|| 视差背景 | 远山 / 近丘 / 地面三层不同速度滚动有纵深感 || ✨ 粒子系统 | 翅膀拍动喷白粒、死亡爆炸 28 粒、得分爆金粒 || 屏幕震动 白闪 | 撞管瞬间的打击感 || 小鸟旋转 残影 | 上升抬头、俯冲低头还拖着尾迹 |**核心机制不变**空格 / 鼠标点击控制小鸟上下翻飞穿过管道缝隙得分。## 二、环境准备bashpip install pygame只需要这一个依赖Python 3.8 即可。## 三、整体框架游戏循环所有 Pygame 游戏的骨架都是一样的——**死循环里不断更新逻辑 → 绘制画面**pythondef run(self):while True:self.clock.tick(FPS) # 锁定 60 帧for ev in pygame.event.get(): # 处理事件if ev.type pygame.QUIT:pygame.quit(); sys.exit()if ev.type pygame.KEYDOWN:if ev.key pygame.K_r: self.reset(); continueif ev.key in (pygame.K_SPACE, pygame.K_UP, pygame.K_w):if self.state dead and self.dtimer 22: self.restart()else: self.flap()if ev.type pygame.MOUSEBUTTONDOWN and ev.button 1:if self.state dead and self.dtimer 22: self.restart()else: self.flap()self.update() # 更新逻辑self.draw() # 绘制画面几个设计点- **状态机**游戏有 ready / play / dead 三种状态update() 里按状态分支处理逻辑清晰不混乱。- **update() 和 draw() 分离**逻辑和渲染解耦这是游戏代码能长期维护的关键。- **操作统一**空格、W、↑、鼠标左键都能触发 flap()死亡后按 R 或任意键重开。## 四、小鸟物理重力 跳跃 旋转### 1. 重力与跳跃物理学就两个公式**每帧加速度 → 累加速度 → 改变位置**。python# 常量GRAVITY 0.5 # 重力加速度FLAP_FORCE -8.5 # 每次扇翅的上升力负值向上python# 每帧更新play 状态self.bv GRAVITY # 速度 重力self.by self.bv # 位置 速度python# 扇翅直接把速度设成上升力def flap(self):if self.state ready:self.state play; self.bv FLAP_FORCE; self.flap_t 8self.ptimer 55; self.trail []; returnif self.state play:self.bv FLAP_FORCE; self.flap_t 8### 2. 旋转动画最容易忽略的细节光有上下移动太生硬真实的小鸟是**上升时抬头、下落时低头**。这里用一个「目标角度 缓动」实现平滑转向python# 目标角度由当前速度决定速度越负向上→ 抬头越正向下→ 低头tgt max(-35, min(80, -self.bv * 3.5))self.bang (tgt - self.bang) * 0.18 # 每帧向目标角度靠近 18%* 0.18 是缓动系数——角度永远追着目标走但不会瞬间到达于是产生了自然的回摆效果。绘制时用 pygame.transform.rotate 旋转即可。## 五、管道系统生成、移动、计分### 1. 定时生成用计时器 ptimer 控制管道间隔每次在屏幕右侧随机生成一对上管 下管pythonself.ptimer 1if self.ptimer 82: # 每 82 帧生成一对self.ptimer 0gy random.randint(85, H - GROUND_H - 85 - PIPE_GAP)self.pipes.append({x: W 10, gy: gy, ok: False})for p in self.pipes: p[x] - PIPE_SPEED # 统一左移self.pipes [p for p in self.pipes if p[x] -PIPE_W - 10] # 出屏即回收管道用一个 dict 存 **位置 x 缺口中心 gy 计分标记 ok**数据比 OOP 的类更轻量。### 2. 计分防重复是精髓ok 字段是计分的关键——每根管道**只会被计一次分**用 p[x] PIPE_W self.bx 判断管道已完全经过小鸟pythonfor p in self.pipes:if not p[ok] and p[x] PIPE_W self.bx:p[ok] True; self.score 1if self.score self.high_score: self.high_score self.score# 顺便在屏幕顶部爆出一串金色粒子庆祝for _ in range(10):self.sparts.append({x: self.bx random.randint(-10, 30),y: 40 random.randint(-10, 10),vx: random.uniform(-3, 3), vy: random.uniform(-5, -1),life: 25, size: random.randint(2, 4),color: random.choice([C_GOLD, C_WHITE, (255, 255, 150)])})### 3. 碰撞检测四重判定更真实小鸟本体是一个 Rect管道则拆成**4 个判定区**上管身、下管身、上下管口的凸缘。凸缘比管身宽 8px、高 22px这样撞到管口边缘也会判死手感更严谨pythonbr pygame.Rect(self.bx - 11, self.by - 9, 22, 18)if self.by 9 H - GROUND_H or self.by - 9 0: # 撞地 / 撞顶self._die(); returnfor p in self.pipes:px, gy p[x], p[gy]top pygame.Rect(px, 0, PIPE_W, gy) # 上管身bot pygame.Rect(px, gy PIPE_GAP, PIPE_W, H) # 下管身top_c pygame.Rect(px - 4, gy - 20, PIPE_W 8, 22) # 上管口凸缘bot_c pygame.Rect(px - 4, gy PIPE_GAP - 2, PIPE_W 8, 22) # 下管口凸缘if br.colliderect(top) or br.colliderect(bot) or \br.colliderect(top_c) or br.colliderect(bot_c):self._die(); return## 六、 特效进阶这个版本的重头戏下面的几个技巧能把能玩的游戏升级成有手感的游戏。### 1. 视差背景三层滚动造纵深远山、近丘、地面分别以不同速度移动人眼会产生景深错觉。核心是把一组随机点画成多边形山脊再整体偏移pythondef _draw_parallax(self, pts, color, offset, brightness):画一条可无缝循环的山脊线c tuple(int(v * brightness) for v in color)strip_w max(x for x, _ in pts) 80poly []for px, py in pts:x (px - offset) % strip_w - 40poly.append((x, py))poly.sort(keylambda p: p[0])full [(0, H - GROUND_H)] poly [(W, H - GROUND_H)]if len(full) 3:pygame.draw.polygon(self.screen, c, [(int(x), int(y)) for x, y in full])配合 % 取模实现**无缝循环**——山脊滚出屏幕左侧后从右侧原样接回来肉眼看不出来。滚动速度由三处变量控制| 图层 | 速度 | 深度感 ||---|---|---|| 远山 mtn_x | 0.25 | 最远最慢 || 近丘 hill_x | 0.55 | 中间 || 地面 gx | -2每 48px 重复 | 最近最快 |### 2. 粒子系统一份数据三种用途粒子本质就是**一群带速度、带生命值的小圆点**结构很简单pythondef _add_part(self, x, y, vx, vy, life, color, size):self.parts.append({x: x, y: y, vx: vx, vy: vy,life: life, color: color, size: size})每帧统一更新位置 速度、速度 重力、生命 - 1生命耗尽就移除pythonfor p in self.parts:p[x] p[vx]; p[y] p[vy]; p[vy] 0.15; p[life] - 1self.parts [p for p in self.parts if p[life] 0]同一套机制驱动了三种效果- **翅膀拍动**每次 flap() 往身后喷 3 粒小白点- **死亡爆炸**_die() 时沿 360° 均匀撒出 28 粒彩色碎片带随机速度- **得分庆祝**计分时在顶部爆 10 粒金星### 3. 屏幕震动 白闪 残影打击感三件套python# 震动绘制前给所有物体加随机偏移if self.shake 0:ox random.randint(-self.shake_s, self.shake_s)oy random.randint(-self.shake_s, self.shake_s)python# 白闪半透明白色全屏覆盖逐帧淡出if self.flash 0:s pygame.Surface((W, H), pygame.SRCALPHA)s.fill((255, 255, 255, 30 * self.flash))self.screen.blit(s, (0, 0)); self.flash - 1python# 残影记录最近 10 帧位置画成渐小渐透明的圆self.trail.append((self.bx, self.by))if len(self.trail) 10: self.trail.pop(0)撞管瞬间 self.flash 8; self.shake 10一击即爆、屏幕震颤——玩家能明显感觉到自己死了。## 七、完整代码可直接运行pythonFlappy Bird — Pygameimport pygameimport randomimport sysimport math# W, H 400, 600FPS 60GRAVITY 0.5FLAP_FORCE -8.5PIPE_W 58PIPE_GAP 145PIPE_SPEED 3GROUND_H 80# ColorsC_SKY_TOP (78, 175, 235)C_SKY_BOT (145, 210, 245)C_PIPE (80, 190, 65)C_PIPE_SH (60, 155, 48)C_PIPE_HI (115, 215, 100)C_PIPE_CA (70, 170, 55)C_GROUND_G (85, 175, 55)C_GROUND_D (65, 140, 40)C_DIRT (125, 90, 55)C_DIRT_D (100, 70, 40)C_WHITE (255, 255, 255)C_BLACK (0, 0, 0)C_GOLD (255, 215, 55)C_SILVER (200, 200, 215)C_RED (240, 75, 75)C_BIRD_Y (245, 215, 60)C_BIRD_Y2 (250, 235, 130)C_BIRD_W (215, 180, 40)C_BIRD_BEAK (225, 105, 45)C_MTN1 (90, 140, 80)C_MTN2 (70, 115, 60)C_HILL (80, 150, 65)# def lerp_color(a, b, t):return tuple(int(x (y - x) * t) for x, y in zip(a, b))def make_clouds():return [{x: random.randint(0, W), y: random.randint(30, 200),w: random.randint(55, 120), speed: random.uniform(0.15, 0.4)}for _ in range(5)]def make_mountains():Return list of (x, peak_y) for one repeatable mountain strip.pts []x -40while x W 120:pts.append((x, H - GROUND_H - random.randint(50, 120)))x random.randint(35, 70)return ptsdef make_hills():pts []x -30while x W 100:pts.append((x, H - GROUND_H - random.randint(25, 55)))x random.randint(20, 45)return pts# class FlappyBird:def __init__(self):pygame.init()self.screen pygame.display.set_mode((W, H))pygame.display.set_caption(Flappy Bird)self.clock pygame.time.Clock()self.font_lg pygame.font.SysFont(consolas, 44, boldTrue)self.font_md pygame.font.SysFont(consolas, 26, boldTrue)self.font_sm pygame.font.SysFont(consolas, 17)self.high_score 0self.clouds make_clouds()self.mtn_pts make_mountains()self.hill_pts make_hills()self.stars [(random.randint(0, W), random.randint(0, H - GROUND_H - 30))for _ in range(50)]self.reset()def reset(self):self.bx 80.0self.by H / 2self.bv 0.0self.bang 0.0self.flap_t 0self.pipes []self.score 0self.state ready # ready / play / deadself.dtimer 0self.flash 0self.shake 0self.shake_s 0self.parts [] # particlesself.sparts [] # score particlesself.trail []self.gx 0.0self.mtn_x 0.0self.hill_x 0.0self.ptimer 0self.t 0.0# ---- helpers ----def _add_part(self, x, y, vx, vy, life, color, size):self.parts.append({x:x,y:y,vx:vx,vy:vy,life:life,color:color,size:size})# ---- update ----def update(self):self.t 1for c in self.clouds:c[x] - c[speed]if c[x] c[w] -20:c[x] W random.randint(10, 60)c[y] random.randint(30, 200)if self.state ! dead:self.gx (self.gx - 2) % 48self.mtn_x (self.mtn_x 0.25)self.hill_x (self.hill_x 0.55)if self.shake 0:self.shake - 1# particles always tickfor p in self.parts:p[x] p[vx]; p[y] p[vy]; p[vy] 0.15; p[life] - 1self.parts [p for p in self.parts if p[life] 0]for p in self.sparts:p[x] p[vx]; p[y] p[vy]; p[vy] 0.1; p[life] - 1self.sparts [p for p in self.sparts if p[life] 0]if self.state ready:self.by H/2 10 * math.sin(self.t * 0.06)returnif self.state dead:self.bv GRAVITYself.by self.bvself.bang min(90, self.bang 4)gy H - GROUND_H - 14if self.by gy:self.by gy; self.bv 0self.dtimer 1return# ---- playing ----self.bv GRAVITYself.by self.bvtgt max(-35, min(80, -self.bv * 3.5))self.bang (tgt - self.bang) * 0.18if self.flap_t 0: self.flap_t - 1self.trail.append((self.bx, self.by))if len(self.trail) 10: self.trail.pop(0)# pipesself.ptimer 1if self.ptimer 82:self.ptimer 0gy random.randint(85, H - GROUND_H - 85 - PIPE_GAP)self.pipes.append({x: W 10, gy: gy, ok: False})for p in self.pipes: p[x] - PIPE_SPEEDself.pipes [p for p in self.pipes if p[x] -PIPE_W - 10]# scorefor p in self.pipes:if not p[ok] and p[x] PIPE_W self.bx:p[ok] True; self.score 1if self.score self.high_score: self.high_score self.scorefor _ in range(10):self.sparts.append({x: self.bx random.randint(-10, 30),y: 40 random.randint(-10, 10),vx: random.uniform(-3, 3), vy: random.uniform(-5, -1),life: 25, size: random.randint(2, 4),color: random.choice([C_GOLD, C_WHITE, (255,255,150)])})# collisionbr pygame.Rect(self.bx - 11, self.by - 9, 22, 18)if self.by 9 H - GROUND_H or self.by - 9 0:self._die(); returnfor p in self.pipes:px, gy p[x], p[gy]top pygame.Rect(px, 0, PIPE_W, gy)bot pygame.Rect(px, gy PIPE_GAP, PIPE_W, H)top_c pygame.Rect(px - 4, gy - 20, PIPE_W 8, 22)bot_c pygame.Rect(px - 4, gy PIPE_GAP - 2, PIPE_W 8, 22)if br.colliderect(top) or br.colliderect(bot) or \br.colliderect(top_c) or br.colliderect(bot_c):self._die(); returndef _die(self):self.state dead; self.flash 8; self.shake 10; self.shake_s 5; self.bv -5for _ in range(28):a random.uniform(0, 6.28)s random.uniform(2, 8)self._add_part(self.bx, self.by,math.cos(a)*s, math.sin(a)*s - 2,random.randint(20, 42),random.choice([C_BIRD_Y, C_BIRD_W, C_BIRD_BEAK, C_WHITE, C_RED]),random.randint(2, 5))def flap(self):if self.state ready:self.state play; self.bv FLAP_FORCE; self.flap_t 8self.ptimer 55; self.trail []; returnif self.state play:self.bv FLAP_FORCE; self.flap_t 8for _ in range(3):self._add_part(self.bx - 6, self.by 8,random.uniform(-2, -0.5), random.uniform(1, 3),random.randint(8, 15), C_WHITE, random.randint(1, 3))def restart(self):if self.state dead: self.reset()# ---- drawing ----def draw(self):ox oy 0if self.shake 0:ox random.randint(-self.shake_s, self.shake_s)oy random.randint(-self.shake_s, self.shake_s)# sky gradientfor r in range(H - GROUND_H):c lerp_color(C_SKY_TOP, C_SKY_BOT, r / (H - GROUND_H))pygame.draw.line(self.screen, c, (0, r), (W, r))# sunself._draw_sun()# cloudsfor c in self.clouds:self._draw_cloud(c)# mountains (parallax)self._draw_parallax(self.mtn_pts, C_MTN1, self.mtn_x % (W 120), 0.75)self._draw_parallax(self.hill_pts, C_HILL, self.hill_x % (W 100), 0.85)# second layer offsetself._draw_parallax(self.mtn_pts, C_MTN2, (self.mtn_x 80) % (W 120), 0.65)# pipesfor p in self.pipes:self._draw_pipe(p[x] ox, p[gy] oy)# trailfor i, (tx, ty) in enumerate(self.trail):a int(35 * i / len(self.trail)) if self.trail else 0r int(2 3 * i / len(self.trail)) if self.trail else 2if a 5:s pygame.Surface((r*2, r*2), pygame.SRCALPHA)pygame.draw.circle(s, (245, 215, 60, a), (r, r), r)self.screen.blit(s, (int(tx) - r ox, int(ty) - r oy))# groundself._draw_ground(ox)# particlesfor p in self.sparts self.parts:pygame.draw.circle(self.screen, p[color],(int(p[x] ox), int(p[y] oy)), max(1, p[size]))# birdself._draw_bird(ox, oy)# flashif self.flash 0:s pygame.Surface((W, H), pygame.SRCALPHA)s.fill((255, 255, 255, 30 * self.flash))self.screen.blit(s, (0, 0)); self.flash - 1# scoreself._text_center(str(self.score), self.font_lg, C_WHITE, 48, shadowTrue)# readyif self.state ready:self._text_center(Flappy Bird, self.font_lg, C_WHITE, H//2 - 55, shadowTrue)self._text_center(Click / Space, self.font_sm, C_WHITE, H//2 15)if self.high_score 0:self._text_center(fBest: {self.high_score}, self.font_sm, C_GOLD, H//2 42)# game overif self.state dead and self.dtimer 22:ov pygame.Surface((W, H), pygame.SRCALPHA)ov.fill((0, 0, 0, min(90, (self.dtimer - 22) * 3)))self.screen.blit(ov, (0, 0))self._text_center(GAME OVER, self.font_lg, C_RED, H//2 - 50, shadowTrue)self._text_center(fScore: {self.score}, self.font_md, C_WHITE, H//2 - 5)if self.score self.high_score and self.score 0:self._text_center(NEW BEST!, self.font_sm, C_GOLD, H//2 30)else:self._text_center(fBest: {self.high_score}, self.font_sm, C_SILVER, H//2 30)self._text_center(Click / Space, self.font_sm, C_WHITE, H//2 60)pygame.display.flip()def _text_center(self, txt, font, color, y, shadowFalse):s font.render(txt, True, color)r s.get_rect(center(W//2, y))if shadow:self.screen.blit(font.render(txt, True, C_BLACK), (r.x2, r.y2))self.screen.blit(s, r)def _draw_sun(self):cx, cy int(W * 0.8), 55t pygame.time.get_ticks() / 1000for i in range(10):a t / 2 i * math.pi / 5ex cx int(math.cos(a) * 32)ey cy int(math.sin(a) * 32)pygame.draw.line(self.screen, (255, 235, 120), (cx, cy), (ex, ey), 2)pygame.draw.circle(self.screen, (255, 245, 120), (cx, cy), 18)pygame.draw.circle(self.screen, (255, 250, 190), (cx - 3, cy - 3), 14)def _draw_cloud(self, c):s pygame.Surface((c[w], 36), pygame.SRCALPHA)w c[w]col (255, 255, 255, 80)pygame.draw.ellipse(s, col, (0, 10, w//2, 22))pygame.draw.ellipse(s, col, (w//4, 0, w//2, 30))pygame.draw.ellipse(s, col, (w//2, 8, w//2, 24))self.screen.blit(s, (int(c[x]), c[y]))def _draw_parallax(self, pts, color, offset, brightness):Draw a mountain/hill strip that repeats.c tuple(int(v * brightness) for v in color)strip_w max(x for x, _ in pts) 80poly []for px, py in pts:x (px - offset) % strip_w - 40poly.append((x, py))poly.sort(keylambda p: p[0])full [(0, H - GROUND_H)] poly [(W, H - GROUND_H)]if len(full) 3:pygame.draw.polygon(self.screen, c, [(int(x), int(y)) for x, y in full])def _draw_pipe(self, px, gy):px, gy int(px), int(gy)bot_y gy PIPE_GAP# top pipepygame.draw.rect(self.screen, C_PIPE, (px, 0, PIPE_W, gy))pygame.draw.rect(self.screen, C_PIPE_HI, (px 5, 0, 7, gy))pygame.draw.rect(self.screen, C_PIPE_SH, (px PIPE_W - 9, 0, 9, gy))tc pygame.Rect(px - 4, gy - 20, PIPE_W 8, 22)pygame.draw.rect(self.screen, C_PIPE_CA, tc)pygame.draw.rect(self.screen, C_PIPE_HI, (tc.x4, tc.y2, 7, tc.h-4))pygame.draw.rect(self.screen, C_PIPE_SH, (tc.right-9, tc.y2, 7, tc.h-4))pygame.draw.rect(self.screen, C_PIPE_SH, tc, 2)# bottom pipepygame.draw.rect(self.screen, C_PIPE, (px, bot_y, PIPE_W, H - bot_y))pygame.draw.rect(self.screen, C_PIPE_HI, (px 5, bot_y, 7, H - bot_y))pygame.draw.rect(self.screen, C_PIPE_SH, (px PIPE_W - 9, bot_y, 9, H - bot_y))bc pygame.Rect(px - 4, bot_y - 2, PIPE_W 8, 22)pygame.draw.rect(self.screen, C_PIPE_CA, bc)pygame.draw.rect(self.screen, C_PIPE_HI, (bc.x4, bc.y2, 7, bc.h-4))pygame.draw.rect(self.screen, C_PIPE_SH, (bc.right-9, bc.y2, 7, bc.h-4))pygame.draw.rect(self.screen, C_PIPE_SH, bc, 2)def _draw_ground(self, ox):gy H - GROUND_Hpygame.draw.rect(self.screen, C_GROUND_G, (0, gy, W, 15))pygame.draw.rect(self.screen, C_GROUND_D, (0, gy 15, W, 10))pygame.draw.rect(self.screen, C_DIRT, (0, gy 25, W, 30))pygame.draw.rect(self.screen, C_DIRT_D, (0, gy 55, W, GROUND_H - 55))for x in range(-48, W 48, 10):gx x int(self.gx) oxpygame.draw.line(self.screen, (95, 185, 60), (gx, gy), (gx 2, gy - 5), 2)def _draw_bird(self, ox, oy):x, y int(self.bx ox), int(self.by oy)surf pygame.Surface((32, 24), pygame.SRCALPHA)pygame.draw.ellipse(surf, C_BIRD_Y, (2, 3, 26, 18))pygame.draw.ellipse(surf, C_BIRD_Y2, (6, 11, 18, 9))wy 9 if self.flap_t 4 else (13 if self.flap_t 0 else 12)pygame.draw.ellipse(surf, C_BIRD_W, (1, wy, 17, 9))pygame.draw.circle(surf, C_WHITE, (22, 7), 4)pygame.draw.circle(surf, C_BLACK, (24, 7), 2)pygame.draw.polygon(surf, C_BIRD_BEAK, [(26, 9), (32, 12), (26, 15)])pygame.draw.ellipse(surf, C_BLACK, (2, 3, 26, 18), 1)rot pygame.transform.rotate(surf, self.bang)rect rot.get_rect(center(x, y))self.screen.blit(rot, rect)# ---- main ----def run(self):while True:self.clock.tick(FPS)for ev in pygame.event.get():if ev.type pygame.QUIT:pygame.quit(); sys.exit()if ev.type pygame.KEYDOWN:if ev.key pygame.K_r: self.reset(); continueif ev.key in (pygame.K_SPACE, pygame.K_UP, pygame.K_w):if self.state dead and self.dtimer 22: self.restart()else: self.flap()if ev.type pygame.MOUSEBUTTONDOWN and ev.button 1:if self.state dead and self.dtimer 22: self.restart()else: self.flap()self.update(); self.draw()if __name__ __main__:FlappyBird().run()把上面代码保存为 flappy_bird.py直接运行即可。**无需任何图片素材**——小鸟、管道、背景全部用 Pygame 的绘图 API 画出来的。## 八、打包成 exe发给朋友玩用 PyInstaller 一键打包bashpip install pyinstallerpyinstaller -F -w -n flappy-bird flappy_bird.py- -F打包成单个 exe- -w不显示黑色控制台窗口打包完成后 exe 在 dist/ 目录双击即玩朋友电脑上不需要装 Python。## 九、还可以怎么玩这个框架已经是麻雀虽小五脏俱全顺着往下加功能门槛很低| 扩展方向 | 实现思路 ||---|---|| 音效 | 扇翅/得分/撞管各加一个 pygame.mixer.Sound || 难度递增 | 管道速度 PIPE_SPEED 随分数动态提升间距 PIPE_GAP 逐步收紧 || 本地排行榜 | 把最高分写入 JSON 文件做 Top5 榜单 || 换肤 | 天空渐变颜色、管道配色做成可配置参数一键换主题 || 开场动画 | 加一个标题菜单 过渡动画 |## 十、小结这篇文章里最值得收藏的其实不是 Flappy Bird 本身而是四个能复用到任何游戏里的技巧1. **状态机**ready / play / dead——让游戏逻辑不纠缠2. **视差滚动**——廉价但效果最好的高级感来源3. **粒子系统**——一份数据驱动所有特效4. **打击感三件套**震动 白闪 缓动——决定手感好坏的分水岭用同样的思路你还能做出马里奥、飞机大战、打砖块……欢迎评论区交流或告诉我你想看哪个游戏的教程点赞多就安排 觉得有用记得 **点赞 收藏**你的支持是我更新的动力

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价