资讯动态

python飞机大战游戏实例讲解

发布时间:2026/10/17 4:37:48 来源:尧图企业网站定制
前言「飞机大战」是学习游戏开发时最经典的练手项目玩家控制一架战机在屏幕底部左右移动、向上开火敌机从顶部不断下落撞上就结束。它麻雀虽小却把游戏循环的五个核心环节全占了——处理输入、更新状态、检测碰撞、绘制画面、控制帧率。把这五个环节想清楚换任何题材都只是换素材。本文用pygame来实现。这里必须先说明一件事pygame 是第三方库不在标准库之内需要先执行python -m pip install pygame。本机没有安装它所以下面的代码是结构性示例你需要在装好 pygame 的环境里运行。为了免去准备素材图片的麻烦本文所有的战机、子弹、敌机都用pygame.draw现场绘制不读取任何外部图片文件复制过去就能跑。还有一个常见的说法是「游戏就是一个while True循环」。方向对但不完整。如果只用while True而不限制帧率循环会把 CPU 单核跑满而且游戏速度会随机器性能剧烈变化——在快机器上敌机瞬移在慢机器上一卡一卡。所以主循环里必须有一次clock.tick(FPS)。另外提醒一句Python 2.7 已于 2020 年 1 月 1 日停止维护pygame 的新版本也早已只面向 Python 3。网上print xxx的老教程不要直接抄。一、游戏循环的骨架任何实时游戏的主循环都是这五步顺序不能乱步骤做什么本文对应处理事件读取键盘、关闭窗口等pygame.event.get()处理输入状态读取按住的键pygame.key.get_pressed()更新逻辑移动、生成、销毁sprite.update()检测碰撞判断谁碰到了谁groupcollide/spritecollide绘制先清屏再画fill()→draw()→flip()其中「清屏」一步最容易被新手漏掉。如果每帧不重置背景上一帧画的东西会一直留在屏幕上形成拖影。二、精灵与分组pygame 的pygame.sprite.Sprite是官方推荐的游戏对象基类它约定对象要有两个属性image一个Surface即这个对象长什么样rect一个Rect即这个对象在哪、多大pygame.sprite.Group是它们的容器能一次性update()和draw()。注意一个硬约束Group.update()会把传进去的参数原样转给每个精灵的update而本文的玩家需要读取按键状态。如果玩家的update(self, keys)和敌机的update(self)混在同一个组里就会TypeError。解决办法是分组管理玩家的更新单独调用。碰撞检测有两个常用接口pygame.sprite.spritecollide(sprite, group, dokill)判断一个精灵和一个组pygame.sprite.groupcollide(group1, group2, dokill1, dokill2)判断两个组。dokill为True时碰到的一方会被自动移出所有组。三、开火为什么要冷却如果只判断空格键是否按住按住不动就会每帧生成一颗子弹60 FPS 下等于每秒 60 发屏幕瞬间被子弹糊满。所以玩家对象里要维护一个冷却计数器开火后设为若干帧每帧减一为零才能再开火。这是射击游戏里最基础也最有效的节奏控制手段。四、完整实现下面是一份完整可读的飞机大战需要 Python 3.8 且已安装 pygame# 需要 Python 3.8 与第三方库 pygamepip install pygameimport randomimport sysimport pygameWIDTH, HEIGHT 480, 640FPS 60BACKGROUND (10, 10, 30)WHITE (255, 255, 255)BLUE (70, 130, 220)RED (220, 60, 60)YELLOW (250, 220, 60)class Player(pygame.sprite.Sprite):def __init__(self, bullet_group):super().__init__()self.image pygame.Surface((40, 40), pygame.SRCALPHA)pygame.draw.polygon(self.image, BLUE, [(20, 0), (0, 40), (40, 40)])self.rect self.image.get_rect(midbottom(WIDTH // 2, HEIGHT - 10))self.speed 6self.bullets bullet_groupself.cooldown 0 # 剩余冷却帧数def update(self, keys): # 注意这个 update 带参数if keys[pygame.K_LEFT]:self.rect.x - self.speedif keys[pygame.K_RIGHT]:self.rect.x self.speedif keys[pygame.K_UP]:self.rect.y - self.speedif keys[pygame.K_DOWN]:self.rect.y self.speedself.rect.clamp_ip(pygame.Rect(0, 0, WIDTH, HEIGHT))if self.cooldown 0:self.cooldown - 1def shoot(self):if self.cooldown 0:self.bullets.add(Bullet(self.rect.midtop))self.cooldown 8class Bullet(pygame.sprite.Sprite):def __init__(self, pos):super().__init__()self.image pygame.Surface((4, 12))self.image.fill(YELLOW)self.rect self.image.get_rect(midbottompos)self.speed 10def update(self):self.rect.y - self.speedif self.rect.bottom 0: # 飞出屏幕就销毁self.kill()class Enemy(pygame.sprite.Sprite):def __init__(self, extra_speed0):super().__init__()size random.choice([30, 40, 50])self.image pygame.Surface((size, size), pygame.SRCALPHA)pygame.draw.circle(self.image, RED, (size // 2, size // 2), size // 2)self.rect self.image.get_rect(midtop(random.randint(size, WIDTH - size), -size))self.speed random.randint(2, 4) extra_speeddef update(self):self.rect.y self.speedif self.rect.top HEIGHT:self.kill()def main():pygame.init()screen pygame.display.set_mode((WIDTH, HEIGHT))pygame.display.set_caption(飞机大战)clock pygame.time.Clock()font pygame.font.SysFont(None, 32)bullets pygame.sprite.Group()enemies pygame.sprite.Group()player Player(bullets)score 0spawn_timer 0game_over Falserunning Truewhile running:clock.tick(FPS) # 限帧同时让循环休眠# 1. 事件for event in pygame.event.get():if event.type pygame.QUIT:running Falseelif event.type pygame.KEYDOWN and event.key pygame.K_ESCAPE:running Falsekeys pygame.key.get_pressed()if not game_over:# 2/3. 更新player.update(keys)if keys[pygame.K_SPACE]:player.shoot()bullets.update()enemies.update()# 4. 碰撞子弹打敌机hits pygame.sprite.groupcollide(enemies, bullets, True, True)score len(hits) * 10# 碰撞敌机撞玩家if pygame.sprite.spritecollide(player, enemies, True):game_over True# 定时生成敌机随分数提高变快spawn_timer 1if spawn_timer 30:spawn_timer 0enemies.add(Enemy(extra_speedscore // 100))# 5. 绘制screen.fill(BACKGROUND)enemies.draw(screen)bullets.draw(screen)screen.blit(player.image, player.rect)screen.blit(font.render(Score: {}.format(score), True, WHITE), (10, 10))if game_over:over font.render(GAME OVER, True, RED)screen.blit(over, over.get_rect(center(WIDTH // 2, HEIGHT // 2)))pygame.display.flip()pygame.quit()if __name__ __main__:main()常见坑点1. 主循环里不限帧❌while running: ...没有clock.tick()✅clock.tick(FPS)放在循环体开头 不限帧会跑满一个 CPU 核心而且游戏速度取决于机器性能完全不可控。2. 把带参数的update混进Group.update()❌all_sprites.add(player); all_sprites.update()而Player.update(self, keys)✅ 玩家的更新单独调用player.update(keys)敌机和子弹照常用group.update()Group.update()不传参时要求每个精灵的update都能零参数调用。3. 忘写super().__init__()❌class Enemy(pygame.sprite.Sprite):的__init__里直接写self.image ...✅ 第一行就super().__init__()Sprite.__init__负责初始化分组相关的内部映射跳过后add()/kill()行为会不正常。4. 用轮询按键实现按一下开一枪❌if keys[pygame.K_SPACE]: shoot()且不加冷却 ✅ 要么在KEYDOWN事件里开火要么像本文一样加冷却计数器 按住空格每帧都判定为真一秒钟能打出几十发。5. 每帧不清屏❌ 只画东西不写screen.fill(...)✅ 每帧先screen.fill(BACKGROUND)再画 否则上一帧的图像会残留成拖影。6. 忘了从组里移除出界的对象❌ 子弹和敌机只有移动没有self.kill()✅ 出界就kill()同时可以考虑self.rect.top HEIGHT对象只增不减内存和碰撞检测开销都会一直涨。7. 用相对路径加载图片❌pygame.image.load(assets/plane.png)✅os.path.join(os.path.dirname(os.path.abspath(__file__)), assets, plane.png)相对路径取决于启动时的工作目录换个目录启动就FileNotFoundError。本文干脆不读外部文件回避了这个问题。8. 把dokill参数搞反或漏写❌pygame.sprite.spritecollide(player, enemies)漏了第三个参数 ✅pygame.sprite.spritecollide(player, enemies, True)表示碰撞后销毁敌机groupcollide则是两个dokill分别对应第一组和第二组。总结环节关键 API易错点限帧clock.tick(FPS)忘记导致跑满 CPU事件pygame.event.get()不处理QUIT关不掉窗口输入pygame.key.get_pressed()用它做单次触发会连发更新Group.update()传参不一致引发TypeError碰撞groupcollide/spritecollidedokill参数漏写或写反绘制fill→draw→flip漏掉fill产生拖影游戏开发的门槛不在语法而在每帧都要从零重画整个世界这个思维方式。理解了事件—更新—碰撞—绘制—限帧这条流水线飞机大战可以变成打砖块、贪吃蛇结构几乎不用改。

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

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

免费获取报价 →
↑