|
| 1 | +#!/usr/bin/env python3 |
| 2 | +# -*- coding: utf-8 -*- |
| 3 | +"""霓虹飞鸟 — 赛博朋克 Flappy Bird,粒子爆炸、无尽上头、最高引流神器""" |
| 4 | + |
| 5 | +from scene import * |
| 6 | +import random |
| 7 | +import math |
| 8 | + |
| 9 | +class Particle: |
| 10 | + __slots__ = ('x', 'y', 'vx', 'vy', 'r', 'g', 'b', 'life', 'ml', 'sz') |
| 11 | + def __init__(self, x, y, r, g, b, spread=4, life=0.6): |
| 12 | + a = random.uniform(0, math.pi * 2) |
| 13 | + s = random.uniform(0.5, spread) |
| 14 | + self.x, self.y = x, y |
| 15 | + self.vx, self.vy = math.cos(a) * s, math.sin(a) * s |
| 16 | + self.r, self.g, self.b = r, g, b |
| 17 | + self.life = random.uniform(life * 0.4, life) |
| 18 | + self.ml = self.life |
| 19 | + self.sz = random.uniform(2, 5) |
| 20 | + |
| 21 | +class NeonFlappy(Scene): |
| 22 | + def setup(self): |
| 23 | + self.W, self.H = self.size.w, self.size.h |
| 24 | + self.bird_x = self.W * 0.25 |
| 25 | + self.bird_y = self.H * 0.6 |
| 26 | + self.bird_vy = 0 |
| 27 | + self.bird_angle = 0 |
| 28 | + self.gravity = -0.38 |
| 29 | + self.jump_force = 10.2 |
| 30 | + self.speed = 2.8 |
| 31 | + self.score = 0 |
| 32 | + self.high_score = 0 |
| 33 | + self.game_over = False |
| 34 | + self.frame = 0 |
| 35 | + self.spawn_timer = 0 |
| 36 | + self.pipes = [] |
| 37 | + self.particles = [] |
| 38 | + self.floats = [] |
| 39 | + self.bg_stars = [{'x': random.uniform(0, self.W), 'y': random.uniform(0, self.H), |
| 40 | + 'sp': random.uniform(0.3, 1.2), 'br': random.uniform(0.2, 0.8), |
| 41 | + 'sz': random.uniform(1, 3)} for _ in range(90)] |
| 42 | + self._spawn_pipe() |
| 43 | + self._spawn_pipe() |
| 44 | + |
| 45 | + def touch_began(self, touch): |
| 46 | + if self.game_over: |
| 47 | + self.setup() |
| 48 | + return |
| 49 | + self.bird_vy = self.jump_force |
| 50 | + for _ in range(12): |
| 51 | + self.particles.append(Particle(self.bird_x - 10, self.bird_y - 5, 0.3, 1.0, 0.95, 3, 0.4)) |
| 52 | + |
| 53 | + def _spawn_pipe(self): |
| 54 | + gap_y = random.randint(int(self.H * 0.3), int(self.H * 0.65)) |
| 55 | + gap_size = 155 |
| 56 | + self.pipes.append({'x': self.W + 60, 'gap_y': gap_y, 'gap_size': gap_size, 'passed': False}) |
| 57 | + |
| 58 | + def _die(self): |
| 59 | + self.game_over = True |
| 60 | + if self.score > self.high_score: |
| 61 | + self.high_score = self.score |
| 62 | + for _ in range(35): |
| 63 | + self.particles.append(Particle(self.bird_x, self.bird_y, 1.0, 0.4, 0.2, 7, 1.1)) |
| 64 | + |
| 65 | + def update(self): |
| 66 | + if self.game_over: |
| 67 | + for p in self.particles[:]: |
| 68 | + p.x += p.vx |
| 69 | + p.y += p.vy |
| 70 | + p.vy += 0.15 |
| 71 | + p.life -= 0.025 |
| 72 | + if p.life <= 0: |
| 73 | + self.particles.remove(p) |
| 74 | + return |
| 75 | + |
| 76 | + self.frame += 1 |
| 77 | + |
| 78 | + self.bird_vy += self.gravity |
| 79 | + self.bird_y += self.bird_vy |
| 80 | + self.bird_angle = max(-35, min(35, self.bird_vy * 2.8)) |
| 81 | + |
| 82 | + if self.bird_y < 60 or self.bird_y > self.H - 60: |
| 83 | + self._die() |
| 84 | + |
| 85 | + for p in self.pipes[:]: |
| 86 | + p['x'] -= self.speed |
| 87 | + if p['x'] < -80: |
| 88 | + self.pipes.remove(p) |
| 89 | + continue |
| 90 | + if not p['passed'] and p['x'] + 40 < self.bird_x: |
| 91 | + p['passed'] = True |
| 92 | + self.score += 1 |
| 93 | + self.floats.append({'x': p['x'] + 30, 'y': p['gap_y'], 'text': '+1', 'life': 1.2}) |
| 94 | + for _ in range(6): |
| 95 | + self.particles.append(Particle(p['x'] + 30, p['gap_y'], 0.9, 1.0, 0.3, 2.5, 0.5)) |
| 96 | + if self.score > 0 and self.score % 10 == 0 and self.frame % 90 == 0: |
| 97 | + self.speed = min(5.5, self.speed + 0.15) |
| 98 | + |
| 99 | + self.spawn_timer -= 1 |
| 100 | + if self.spawn_timer <= 0: |
| 101 | + self._spawn_pipe() |
| 102 | + self.spawn_timer = 65 |
| 103 | + |
| 104 | + # 碰撞检测(修复:鸟在缝隙外面才判定死亡) |
| 105 | + bird_r = 18 |
| 106 | + for p in self.pipes: |
| 107 | + gap_bottom = p['gap_y'] - p['gap_size'] // 2 |
| 108 | + gap_top = p['gap_y'] + p['gap_size'] // 2 |
| 109 | + if (abs(self.bird_x - (p['x'] + 30)) < bird_r + 30 and |
| 110 | + (self.bird_y - bird_r < gap_bottom or self.bird_y + bird_r > gap_top)): |
| 111 | + self._die() |
| 112 | + break |
| 113 | + |
| 114 | + for p in self.particles[:]: |
| 115 | + p.x += p.vx |
| 116 | + p.y += p.vy |
| 117 | + p.life -= 0.028 |
| 118 | + if p.life <= 0: |
| 119 | + self.particles.remove(p) |
| 120 | + |
| 121 | + for f in self.floats[:]: |
| 122 | + f['y'] += 1.2 |
| 123 | + f['life'] -= 0.035 |
| 124 | + if f['life'] <= 0: |
| 125 | + self.floats.remove(f) |
| 126 | + |
| 127 | + for s in self.bg_stars: |
| 128 | + s['x'] -= s['sp'] |
| 129 | + if s['x'] < 0: |
| 130 | + s['x'] = self.W |
| 131 | + |
| 132 | + def draw(self): |
| 133 | + background(0.03, 0.0, 0.12) |
| 134 | + |
| 135 | + no_stroke() |
| 136 | + for s in self.bg_stars: |
| 137 | + fill(1, 1, 1, s['br']) |
| 138 | + rect(s['x'], s['y'], s['sz'], s['sz']) |
| 139 | + |
| 140 | + # 管子(霓虹发光) |
| 141 | + for p in self.pipes: |
| 142 | + gb = p['gap_y'] - p['gap_size'] // 2 |
| 143 | + gt = p['gap_y'] + p['gap_size'] // 2 |
| 144 | + # 下管(地面到缝隙下沿) |
| 145 | + no_stroke() |
| 146 | + fill(0.08, 0.5, 0.65) |
| 147 | + rect(p['x'], 0, 64, gb) |
| 148 | + stroke(0.4, 1.0, 1.0) |
| 149 | + stroke_weight(3) |
| 150 | + no_fill() |
| 151 | + rect(p['x'], 0, 64, gb) |
| 152 | + # 上管(缝隙上沿到屏幕顶) |
| 153 | + no_stroke() |
| 154 | + fill(0.08, 0.5, 0.65) |
| 155 | + rect(p['x'], gt, 64, self.H - gt) |
| 156 | + stroke(0.4, 1.0, 1.0) |
| 157 | + stroke_weight(3) |
| 158 | + no_fill() |
| 159 | + rect(p['x'], gt, 64, self.H - gt) |
| 160 | + |
| 161 | + no_stroke() |
| 162 | + |
| 163 | + # 鸟(霓虹粉紫 + 旋转) |
| 164 | + push_matrix() |
| 165 | + translate(self.bird_x, self.bird_y) |
| 166 | + rotate(self.bird_angle) |
| 167 | + fill(1.0, 0.35, 0.9) |
| 168 | + ellipse(-18, -14, 36, 28) |
| 169 | + fill(1.0, 1.0, 0.3) |
| 170 | + ellipse(8, 2, 12, 12) |
| 171 | + fill(0, 0, 0) |
| 172 | + ellipse(12, 8, 6, 8) |
| 173 | + fill(1, 1, 1) |
| 174 | + ellipse(13, 9, 3, 3) |
| 175 | + pop_matrix() |
| 176 | + |
| 177 | + # 粒子 |
| 178 | + for p in self.particles: |
| 179 | + a = p.life / p.ml |
| 180 | + fill(p.r, p.g, p.b, a) |
| 181 | + ellipse(p.x, p.y, p.sz * a, p.sz * a) |
| 182 | + |
| 183 | + # 浮动分数 |
| 184 | + for f in self.floats: |
| 185 | + a = f['life'] |
| 186 | + fill(1, 1, 0.4, a) |
| 187 | + text(f['text'], 'Helvetica-Bold', 36, f['x'], f['y']) |
| 188 | + |
| 189 | + # 分数 |
| 190 | + fill(1, 1, 1) |
| 191 | + text(str(self.score), 'Helvetica-Bold', 72, self.W / 2, self.H * 0.88) |
| 192 | + |
| 193 | + if self.game_over: |
| 194 | + fill(1.0, 0.3, 0.3) |
| 195 | + text("GAME OVER", 'Helvetica-Bold', 64, self.W / 2, self.H * 0.55) |
| 196 | + fill(1, 1, 1) |
| 197 | + text(f"最高分 {self.high_score}", 'Helvetica', 42, self.W / 2, self.H * 0.45) |
| 198 | + text("轻点屏幕重新开始", 'Helvetica', 28, self.W / 2, self.H * 0.3) |
| 199 | + |
| 200 | +run(NeonFlappy()) |
0 commit comments