Python游戏开发实战:Pygame飞机大战从入门到精通

发布时间:2026/7/11 21:08:43

Python游戏开发实战:Pygame飞机大战从入门到精通 1. 项目概述从零到一构建你的第一个Python游戏几年前我刚开始学Python的时候总感觉光看语法、做练习题有点枯燥直到我决定用Pygame动手做一个“飞机大战”游戏。这个决定让我真正理解了循环、事件、面向对象这些抽象概念到底是怎么在屏幕上“活”起来的。今天我就把这个过程完整地拆解一遍无论你是刚学完Python基础语法的新手还是想找个有趣项目练手的朋友跟着这篇教程你都能亲手做出一个能玩、能炫的飞机大战游戏。这不仅仅是一个教程更像是一次完整的游戏开发实战演练你会接触到游戏循环、精灵管理、碰撞检测这些游戏开发的核心概念而它们全都被封装在一个不到500行代码的项目里。飞机大战这类2D射击游戏结构清晰逻辑直观是入门游戏开发的绝佳选择。我们将使用Pygame这个经典的2D游戏开发库它足够轻量API也相对友好。整个项目我们会分步实现先是创建一个窗口并让背景动起来然后加入玩家控制的飞机让它能响应键盘移动并发射子弹接着我们让敌机从屏幕上方不断生成并下落最后实现子弹与敌机的碰撞检测和爆炸效果。听起来步骤不少但每一步我都会配上详细的代码和“为什么这么做”的解释确保你不仅能复制代码更能理解背后的设计思路。过程中我们会遇到一些典型的坑比如游戏循环的帧率控制、图片加载的路径问题、碰撞检测的精度调整等这些我都会作为“实战心得”分享出来帮你避开我当年踩过的雷。2. 开发环境搭建与Pygame初探2.1 Python与Pygame安装避坑指南工欲善其事必先利其器。第一步是确保你的电脑上有一个可用的Python环境。我强烈推荐使用Python 3.7或以上的版本太老的版本可能会在后续安装某些库时遇到兼容性问题。如果你还没有安装Python可以去其官方网站下载安装包安装时务必记得勾选“Add Python to PATH”这个选项这能省去后续在命令行中手动配置环境变量的麻烦。安装好Python后我们就需要通过pip来安装Pygame了。打开你的命令行终端Windows上是CMD或PowerShellmacOS或Linux上是Terminal输入安装命令。这里有一个非常常见的坑也是很多新手会卡住的地方直接使用pip install pygame在某些系统上可能会失败尤其是Windows系统可能会报错提示缺少Visual C构建工具或error: failed to build ‘pygame’ when getting requirements to build wheel。注意遇到上述构建错误通常是因为Pygame需要编译一些C语言扩展而你的系统缺少必要的编译环境。对于绝大多数新手最稳妥的解决方案是去Pygame的官方网站找到与你的Python版本和操作系统32位或64位对应的预编译的.whl文件进行安装。例如对于Python 3.9的64位Windows系统可以下载pygame-2.5.2-cp39-cp39-win_amd64.whl文件然后在命令行中导航到该文件所在目录执行pip install pygame-2.5.2-cp39-cp39-win_amd64.whl。这种方法几乎可以保证百分之百成功。安装成功后可以写一个简单的测试脚本验证一下。创建一个名为test_pygame.py的文件输入以下代码import pygame import sys pygame.init() screen pygame.display.set_mode((800, 600)) pygame.display.set_caption(“Pygame Test”) print(“Pygame 安装成功窗口已创建。”) running True while running: for event in pygame.event.get(): if event.type pygame.QUIT: running False screen.fill((0, 0, 0)) # 用黑色填充屏幕 pygame.display.flip() # 更新屏幕显示 pygame.quit() sys.exit()运行这个脚本如果弹出一个黑色的800x600像素的窗口并且关闭窗口后程序正常退出恭喜你环境搭建成功。这个测试脚本虽然简单但已经包含了Pygame程序最核心的三要素初始化、游戏主循环、退出清理。2.2 代码编辑器与项目管理对于编辑器你可以选择任何你熟悉的。PyCharm对Python项目支持非常完善有智能提示和调试功能适合大型项目。VS Code搭配Python插件也是一个轻量且强大的选择启动快速。如果你追求极简甚至用系统自带的文本编辑器如Notepad、Sublime Text也可以。我的建议是初期不必在工具选择上纠结太久先用起来在过程中自然会发现适合自己的工具。在开始写游戏代码前我建议先规划好项目目录。创建一个专属的文件夹比如plane_war在里面再创建几个子文件夹images用于存放所有的游戏图片玩家飞机、敌机、子弹、背景、爆炸图sounds用于存放音效文件fonts用于存放字体文件。把资源文件分门别类放好是保持代码清晰的好习惯能避免后期一堆文件混在一起找不到的尴尬。我们的主游戏代码文件比如main.py就直接放在plane_war这个根目录下。3. 游戏核心架构与主循环设计3.1 理解游戏循环一切动起来的基础游戏为什么能动核心就在于“游戏循环”Game Loop。你可以把它想象成电影放映机它以一帧一帧的速度快速播放静态画面由于人眼的视觉暂留效应我们就看到了连续的动作。游戏循环就是不断重复做四件事处理用户输入比如按键、更新游戏状态比如计算飞机的新位置、渲染绘制把最新的状态画到屏幕上、控制循环速度保证游戏在不同电脑上速度一致。在Pygame中我们通常用一个while循环来实现这个主循环。循环的退出条件通常是用户点击了窗口的关闭按钮。一个最基础的游戏循环骨架如下clock pygame.time.Clock() FPS 60 # 设定帧率为每秒60帧 running True while running: # 1. 控制循环速度 clock.tick(FPS) # 2. 处理事件输入 for event in pygame.event.get(): if event.type pygame.QUIT: running False # 这里还可以处理按键事件等 # 3. 更新游戏状态逻辑 # 例如player.update() # 4. 绘制渲染 screen.fill((0, 0, 0)) # 清屏用黑色填充背景 # 例如player.draw(screen) pygame.display.flip() # 更新整个屏幕clock.tick(FPS)这行代码至关重要。它通过计算两次调用之间的时间差来确保循环每秒最多运行FPS次这里是60次。没有它你的游戏在性能好的电脑上会快得像闪电在性能差的电脑上又会慢得像幻灯片。pygame.display.flip()则是将我们在内存中绘制好的这一帧画面一次性更新到显示器上类似于翻页动画。3.2 面向对象设计用类来组织游戏元素对于飞机大战这样的游戏面向对象编程OOP是天然的绝配。我们把游戏中的每个独立实体都设计成一个类玩家飞机是一个类敌机是一个类子弹也是一个类。这样做的好处是数据和行为被封装在一起管理起来非常清晰。例如我们设计一个Player类class Player(pygame.sprite.Sprite): def __init__(self): super().__init__() # 加载飞机图片convert_alpha()用于支持透明背景 self.image pygame.image.load(“images/player.png”).convert_alpha() self.rect self.image.get_rect() # 获取图片的矩形区域用于定位和碰撞检测 self.rect.centerx SCREEN_WIDTH // 2 # 初始位置水平居中 self.rect.bottom SCREEN_HEIGHT - 10 # 初始位置靠近屏幕底部 self.speed 5 # 飞机的移动速度 def update(self, keys): 根据按键更新飞机位置 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 if keys[pygame.K_UP] and self.rect.top 0: self.rect.y - self.speed if keys[pygame.K_DOWN] and self.rect.bottom SCREEN_HEIGHT: self.rect.y self.speed def draw(self, screen): 将飞机绘制到屏幕上 screen.blit(self.image, self.rect)这里有几个关键点第一我们让Player类继承自pygame.sprite.Sprite。这是Pygame精灵模块的基类它为我们提供了很多便利比如后面会用到精灵组进行批量管理和碰撞检测。第二每个精灵类通常都有image外观和rect位置和碰撞区域这两个核心属性。第三我们把飞机的移动逻辑封装在update方法里把绘制逻辑封装在draw方法里。这样在主循环中我们只需要调用player.update()和player.draw(screen)代码会非常整洁。4. 游戏资源加载与场景构建4.1 图像与音效资源的处理技巧游戏离不开视觉和听觉效果。对于图像我们主要使用pygame.image.load()来加载。这里有一个重要的性能优化技巧对于不带透明通道的背景图使用.convert()对于带有透明通道如PNG格式的飞机、爆炸效果的图片使用.convert_alpha()。这个方法会将图片转换成与当前屏幕显示格式一致的内部格式大幅提升后续blit绘制操作的速度。# 加载背景图 background pygame.image.load(“images/background.jpg”).convert() # 加载带透明通道的飞机图片 player_img pygame.image.load(“images/player.png”).convert_alpha()音效方面Pygame支持WAV和MP3等格式。对于短促的射击音效、爆炸音效使用pygame.mixer.Sound()加载为Sound对象。对于背景音乐这种较长的音频可以使用pygame.mixer.music.load()和pygame.mixer.music.play(-1)来循环播放。# 加载音效 shoot_sound pygame.mixer.Sound(“sounds/shoot.wav”) explosion_sound pygame.mixer.Sound(“sounds/explosion.wav”) # 播放音效例如在发射子弹时 shoot_sound.play()实操心得资源文件的路径问题是个高频坑点。如果你的代码文件和images文件夹在同一目录使用相对路径“images/xxx.png”是没问题的。但如果你直接双击运行打包后的exe文件当前工作目录可能发生变化导致找不到图片。一种更稳健的做法是使用os.path模块来构建绝对路径import os BASE_DIR os.path.dirname(__file__) # 获取当前文件所在目录 IMAGE_PATH os.path.join(BASE_DIR, “images”, “player.png”) player_img pygame.image.load(IMAGE_PATH).convert_alpha()这样无论从何处运行脚本都能正确定位到资源文件。4.2 实现滚动背景与视觉层次静态背景对于飞行射击游戏来说太单调了。我们可以让背景向下缓慢滚动营造出飞机在不断向前飞行的错觉。实现原理是使用两张相同的背景图让它们首尾相接连续向下移动当一张图完全移出屏幕底部时就立刻将其重置到屏幕顶部如此循环。class Background: def __init__(self, image_path, screen_height): self.image1 pygame.image.load(image_path).convert() self.image2 self.image1.copy() # 复制一份 self.rect1 self.image1.get_rect() self.rect2 self.image2.get_rect() self.rect2.top -self.rect1.height # 第二张图放在第一张的正上方 self.speed 1 # 背景滚动速度 def update(self): 更新两张背景图的位置 self.rect1.y self.speed self.rect2.y self.speed # 如果任何一张图完全移出屏幕底部就将其重置到顶部 if self.rect1.top SCREEN_HEIGHT: self.rect1.bottom 0 if self.rect2.top SCREEN_HEIGHT: self.rect2.bottom 0 def draw(self, screen): 绘制两张背景图 screen.blit(self.image1, self.rect1) screen.blit(self.image2, self.rect2)在主循环中我们先绘制背景再绘制飞机、子弹等精灵。由于绘制顺序是后绘制的覆盖先绘制的所以背景必须最先画游戏元素画在它上面这样才有正确的视觉层次感。5. 玩家操控与射击系统实现5.1 平滑的键盘控制与边界检测在上一节的Player类update方法中我们已经实现了基础的键盘控制。但那里有一个小问题我们是通过持续检测按键是否被按下来移动的如果只是简单地在事件循环里处理KEYDOWN事件飞机只会移动一次。更常用的方法是使用pygame.key.get_pressed()来获取当前所有按键的状态字典然后在update方法中查询这个字典。这样只要按住按键飞机就会持续移动操作更跟手。边界检测的逻辑也很重要我们要防止玩家把飞机开出屏幕外。代码if keys[pygame.K_LEFT] and self.rect.left 0:就是在判断如果按下了左键并且飞机的左边界还没有碰到屏幕左边缘坐标大于0才允许向左移动。其他方向的判断逻辑类似。5.2 子弹系统的设计与发射逻辑子弹是玩家攻击的手段。我们同样为子弹创建一个类Bullet。子弹通常由玩家飞机的位置发射向上直线运动。class Bullet(pygame.sprite.Sprite): def __init__(self, x, y): super().__init__() self.image pygame.image.load(“images/bullet.png”).convert_alpha() self.rect self.image.get_rect() self.rect.centerx x # 子弹初始x坐标由传入的玩家飞机位置决定 self.rect.centery y # 子弹初始y坐标 self.speed -10 # 负数表示向上飞 def update(self): 更新子弹位置飞出屏幕后自动销毁 self.rect.y self.speed if self.rect.bottom 0: # 如果子弹的底部已经超出屏幕顶部 self.kill() # 从所有精灵组中移除自己这里的关键是self.kill()方法它是pygame.sprite.Sprite自带的方法用于将精灵从它所属的所有精灵组中删除。这样飞出屏幕的子弹会被自动清理避免无用的对象一直占用内存。那么如何管理屏幕上可能同时存在的多发子弹呢Pygame提供了一个非常强大的工具精灵组pygame.sprite.Group()。我们可以创建一个bullets_group来管理所有活跃的子弹。bullets_group pygame.sprite.Group()当玩家按下射击键比如空格时我们就在玩家飞机的位置创建一个新的Bullet实例并将其添加到bullets_group中。同时播放射击音效。if keys[pygame.K_SPACE]: # 控制射击频率避免子弹过于密集 current_time pygame.time.get_ticks() if current_time - last_shot_time SHOOT_DELAY: # SHOOT_DELAY是射击间隔例如200毫秒 new_bullet Bullet(player.rect.centerx, player.rect.top) bullets_group.add(new_bullet) shoot_sound.play() last_shot_time current_time在主循环的更新阶段我们只需要调用bullets_group.update()组内所有子弹的update方法都会被自动调用。在绘制阶段调用bullets_group.draw(screen)组内所有子弹都会被绘制到屏幕上。精灵组极大地简化了对多个同类对象的管理。6. 敌机生成、运动与碰撞检测6.1 敌机的多样化生成策略敌机是游戏挑战的来源。我们需要让敌机从屏幕上方随机位置不断生成并以不同的速度下落。首先创建Enemy类class Enemy(pygame.sprite.Sprite): def __init__(self): super().__init__() # 可以随机选择几种敌机外观之一 enemy_type random.choice([“enemy1”, “enemy2”, “enemy3”]) self.image pygame.image.load(f“images/{enemy_type}.png”).convert_alpha() self.rect self.image.get_rect() # 初始位置x坐标随机y坐标在屏幕顶部上方 self.rect.x random.randrange(0, SCREEN_WIDTH - self.rect.width) self.rect.y random.randrange(-100, -40) # 从屏幕外上方随机位置出现 self.speed_y random.randrange(1, 5) # 随机下落速度 self.speed_x random.randrange(-2, 2) # 可能有的左右晃动速度 def update(self): self.rect.y self.speed_y self.rect.x self.speed_x # 如果敌机完全飞出屏幕底部或者左右晃出屏幕则销毁它 if self.rect.top SCREEN_HEIGHT or self.rect.left SCREEN_WIDTH or self.rect.right 0: self.kill()为了让敌机的出现不那么规律我们可以使用一个定时器。在主循环中每隔一定时间比如500到1000毫秒就生成一个新的敌机并添加到enemies_group中。enemies_group pygame.sprite.Group() last_enemy_spawn_time 0 ENEMY_SPAWN_DELAY 800 # 敌机生成间隔单位毫秒 # 在主循环中 current_time pygame.time.get_ticks() if current_time - last_enemy_spawn_time ENEMY_SPAWN_DELAY: new_enemy Enemy() enemies_group.add(new_enemy) last_enemy_spawn_time current_time6.2 精确的碰撞检测实现游戏最核心的互动之一就是子弹击中敌机。Pygame的精灵组为我们提供了非常便捷的碰撞检测方法。最常用的是pygame.sprite.groupcollide()它可以检测两个精灵组之间所有精灵的碰撞。# 检测子弹组和敌机组之间的碰撞 collisions pygame.sprite.groupcollide(bullets_group, enemies_group, True, True)这个函数会返回一个字典其中键是发生碰撞的子弹对象值是与该子弹发生碰撞的敌机对象列表。后两个布尔参数True, True分别表示碰撞发生后是否从第一个组子弹组中删除精灵以及是否从第二个组敌机组中删除精灵。这里我们都设为True意味着子弹和敌机相撞后双双消失。但这只是第一步。为了让碰撞体验更好我们还需要在碰撞发生时播放爆炸音效、增加玩家分数并可能显示一个爆炸动画。if collisions: explosion_sound.play() # 播放爆炸音效 player_score len(collisions) * 10 # 每击毁一架敌机加10分 # 这里可以遍历collisions字典在每一个碰撞点生成一个爆炸动画精灵除了子弹与敌机的碰撞我们还需要检测玩家飞机与敌机的碰撞这决定了游戏是否结束。if pygame.sprite.spritecollideany(player, enemies_group): # 玩家与任何敌机发生碰撞 game_over True # 播放玩家爆炸音效和动画spritecollideany函数会检查单个精灵玩家是否与精灵组敌机组中的任何一个精灵发生了碰撞如果有则返回第一个碰撞到的敌机精灵否则返回None。注意事项默认的碰撞检测是基于精灵的rect属性进行的矩形检测。如果精灵图片不是规则的矩形比如飞机有翅膀矩形检测会不够精确可能出现“看起来没碰到但判定为碰撞”的情况。对于要求更高的游戏Pygame还支持基于掩码mask的像素级精确碰撞检测使用pygame.sprite.collide_mask。但矩形检测在大多数情况下已经足够且性能开销小得多。在实现初期建议先用矩形检测如果对视觉效果有更高要求再考虑升级为掩码检测。7. 游戏状态、UI与音效整合7.1 分数、生命值与游戏状态管理一个完整的游戏需要有状态管理。我们至少需要记录玩家的分数和生命值或生命数并区分游戏进行中、游戏结束等不同状态。# 游戏状态变量 player_score 0 player_lives 3 game_over False font pygame.font.Font(None, 36) # 使用默认字体大小36在游戏主循环的绘制阶段我们需要将分数和生命值渲染到屏幕上。# 渲染分数文本 score_text font.render(f“Score: {player_score}”, True, (255, 255, 255)) # 白色文字 screen.blit(score_text, (10, 10)) # 在屏幕左上角(10,10)位置绘制 # 渲染生命值文本 lives_text font.render(f“Lives: {player_lives}”, True, (255, 255, 255)) screen.blit(lives_text, (10, 50))当玩家与敌机相撞时除了将game_over设为True更常见的做法是先减少生命值生命值耗尽后才游戏结束。if pygame.sprite.spritecollideany(player, enemies_group, pygame.sprite.collide_mask): player_lives - 1 if player_lives 0: game_over True else: # 玩家还有生命重置玩家位置清空敌机给予短暂无敌时间等 player.rect.center (SCREEN_WIDTH//2, SCREEN_HEIGHT-50) enemies_group.empty() # 可以设置一个无敌状态的计时器当game_over为True时主循环可以进入一个结束画面循环显示最终分数和“Game Over”字样并等待玩家按R键重新开始或按ESC键退出。7.2 音效与背景音乐的集成音效是游戏体验不可或缺的一环。我们已经知道如何加载和播放音效。合理地规划音效触发点能让游戏更有沉浸感发射子弹时播放一个短促、清脆的音效。击中敌机时播放一个爆炸音效。玩家被击中时播放一个不同的、更沉重的爆炸或受损音效。游戏结束时播放一段特定的音乐或音效。背景音乐则用于烘托整体氛围。我们可以使用pygame.mixer.music模块来加载和播放一个MP3文件并设置为循环播放。pygame.mixer.music.load(“sounds/background.mp3”) pygame.mixer.music.set_volume(0.5) # 设置音量0.0到1.0之间 pygame.mixer.music.play(-1) # -1表示无限循环播放实操心得音效文件的格式和大小需要注意。WAV格式无压缩音质好但文件大MP3格式有压缩文件小但加载时可能有轻微延迟。对于短促的音效建议使用WAV对于长的背景音乐可以使用MP3以减小游戏体积。另外同时播放太多音效可能会造成卡顿或混音问题可以通过pygame.mixer设置同时播放的声道数上限或者对频繁播放的音效如子弹音效进行管理避免同一帧内播放太多次。8. 性能优化、打包与扩展思路8.1 性能优化与常见问题排查当游戏中的精灵越来越多比如满屏子弹和敌机你可能会发现帧率下降游戏变卡。这时就需要进行性能优化。1. 使用精灵组pygame.sprite.Group的批量操作我们已经用了这是最重要的优化。group.update()和group.draw()的内部实现是高度优化的比手动遍历列表调用每个精灵的方法要快得多。2. 图像转换前面提到的对加载的图片使用.convert()或.convert_alpha()是另一个关键优化点务必做到。3. 避免在循环中加载资源所有图片、音效、字体都应该在游戏初始化阶段主循环之前加载好。绝对不要在主循环的每一帧里都去pygame.image.load()。4. 控制精灵数量及时销毁不再需要的精灵。我们的子弹和敌机在飞出屏幕后都调用了kill()这就是在控制总量。5. 使用“脏矩形”更新Dirty Rectangles默认的pygame.display.flip()会更新整个屏幕。如果游戏画面只有一小部分区域在变化比如只是飞机移动我们可以只更新变化的那部分矩形区域大幅提升性能。但这属于进阶优化初期可以不用。如果游戏运行时出现卡顿可以打印出帧率来监控clock pygame.time.Clock() FPS 60 while running: clock.tick(FPS) # ... 游戏逻辑 ... current_fps clock.get_fps() if frame_count % 60 0: # 每60帧打印一次 print(f“FPS: {current_fps:.2f}”) frame_count 1如果FPS远低于设定的60就需要检查上面的优化点。8.2 将游戏打包为可执行文件当你完成游戏开发想分享给没有安装Python的朋友时就需要将其打包成一个独立的exe文件Windows或app文件macOS。最常用的工具是PyInstaller。首先安装PyInstallerpip install pyinstaller。 然后在命令行中导航到你的游戏主文件main.py所在的目录执行打包命令。一个基础的命令如下pyinstaller --onefile --windowed --add-data “images;images” --add-data “sounds;sounds” main.py--onefile将所有依赖打包成一个单独的exe文件。--windowed运行时不显示命令行控制台窗口对于图形游戏很重要。--add-data “images;images”告诉PyInstaller将本地的images文件夹复制到打包后的程序中并保持同样的目录结构。分号前面是源路径后面是打包后的目标路径。在macOS/Linux上分号要改为冒号:。打包完成后会在dist文件夹下找到生成的可执行文件。你可以把这个文件和images、sounds等资源文件夹一起压缩发给别人。踩坑记录打包后最常见的错误是“找不到图片/音效文件”。这是因为打包后程序的运行路径变了。我们之前用os.path构建绝对路径的方法在这里就派上用场了。你需要修改资源加载代码使其能兼容开发环境和打包后的环境。通常使用sys._MEIPASS这个PyInstaller创建的临时目录import sys import os def resource_path(relative_path): “”“获取资源的绝对路径。兼容开发环境和PyInstaller打包后的环境。”“” try: # PyInstaller创建的临时文件夹路径 base_path sys._MEIPASS except Exception: base_path os.path.abspath(“.”) return os.path.join(base_path, relative_path) # 加载图片时 player_img pygame.image.load(resource_path(“images/player.png”)).convert_alpha()8.3 项目扩展与进阶方向完成基础版本的飞机大战后你可以尝试添加更多功能来挑战自己多种敌机与BOSS战设计几种行为不同的敌机比如有的会直线下落有的会左右摆动有的会追踪玩家。还可以设计一个血量厚、会发射子弹的BOSS在特定分数或时间后出现。道具系统击毁特定敌机后掉落道具如火力增强、生命恢复、护盾等玩家飞机接触到后获得相应能力。关卡与难度递增随着分数增加敌机生成速度加快敌机血量变厚移动方式更复杂。粒子爆炸效果用许多小色块或小图片来模拟更华丽的爆炸而不是简单的切换一张爆炸图。本地高分记录使用文件如JSON或简单的数据库如SQLite来保存玩家的最高分数记录。移植到手机虽然Pygame本身主要面向桌面但有一些社区项目如Pygame Subset for Android可以尝试将游戏移植到安卓平台。这个项目最大的价值不在于复现了一个经典游戏而在于你亲手走完了从环境搭建、逻辑设计、代码实现、调试排错到最终打包的完整开发流程。过程中遇到的每一个问题解决的每一个bug都会让你对编程和游戏开发有更深刻的理解。我个人最深的体会是理论学习十遍不如动手做一遍。当你看到自己写的代码让屏幕上的元素按照你的意志互动起来时那种成就感是无与伦比的。现在打开你的编辑器从创建那个800x600的窗口开始吧。

相关新闻