☰
Pygame飞机大战项目实战:从精灵组到碰撞检测的完整实现
2026/10/17 19:08:51 网站建设 项目流程

简介:一份基于Pygame实现的Python飞机大战游戏开发资源,面向希望通过小型游戏项目巩固面向对象编程、事件驱动机制和图形界面设计的初学者。资源详细展示了Pygame环境初始化、窗口创建、主循环结构、键盘输入处理、画布刷新等基础流程,并给出飞机移动、子弹发射、碰撞检测、音效播放、分数统计等关键功能的实现思路;同时涉及生命值归零和敌机清空等结束判定,提示可扩展道具、关卡和简单AI增强可玩性。通过整体代码逻辑与分段示例,读者能理解类与对象、pygame.Rect矩形碰撞判断在实际游戏中的应用,也能体会事件驱动、状态更新与渲染刷新构成的典型主循环运作方式,并顺利迁移到自己的飞行射击项目中。压缩包为zip格式,包体约1.66MB,整体轻量,平台目前暂未收录具体文件清单,但不影响学习和动手复现。目前已有395人学习浏览,适合高校Python课程设计、Pygame入门实践或希望快速搭建飞行射击游戏原型的开发者。

1. 飞机大战项目定位:用pygame复刻经典弹幕玩法的完整样本

你是不是也有过这样的阶段——学了Python基础语法,看了几篇面向对象教程,但一到写项目就不知道从哪下手?飞机大战就是那个最适合拿来开刀的项目:它有循环、有事件、有碰撞、有资源加载,一个小游戏把pygame的核心机制几乎全踩了一遍。这个项目用纯Python实现,核心依赖只有pygame一个库,玩家通过键盘控制战机躲避从屏幕上方不断涌出的敌机,击落敌机得分,被击中则扣血,血条归零即游戏结束。适合想通过完整游戏实战夯实pygame开发的初学者,也适合需要快速拿一个可跑的demo做二次开发模板的熟手。它不依赖任何游戏引擎,所有代码摊开都在一个可以直接读懂的逻辑闭环里。

2. 项目结构与技术选型:主循环、精灵组与事件系统怎么组织

写pygame项目最怕一上来就把所有逻辑塞进一个main.py,几百行代码堆在一起,改一个功能牵扯全身。这个飞机大战项目在结构上做了一个比较清爽的分层,虽然体量不大,但目录划分遵循了「入口、配置、资源、逻辑」分离的常见做法。

2.1 整体目录结构:assets、config、main模块的分层

拿到项目后第一件事是看目录,它长这样:

plane_war/ ├── assets/ │ ├── images/ │ │ ├── player.png │ │ ├── enemy.png │ │ └── bullet.png │ └── sounds/ │ ├── shoot.wav │ └── explosion.wav ├── config.py ├── main.py ├── plane_sprites.py └── requirements.txt

assets目录专门放图片和音效资源,这样不会和代码混在一起,换皮肤时只动images文件夹。config.py把屏幕尺寸、帧率、颜色、初始血量、敌机生成速度这些参数集中管理,而不是散落在各个模块里。plane_sprites.py定义了所有游戏对象的类——玩家飞机、敌机、子弹,它们都继承自pygame.sprite.Sprite。main.py只负责初始化游戏并启动主循环。

这样的分层带来的直接好处是:想调整游戏难度,只需要改config.py里的数值;想新增一个敌人类型,直接在plane_sprites.py里新增一个类并注册到主循环中;完全不用关心图片从哪加载、窗口怎么创建。我第一次拿到这个项目时,只用了十分钟就定位到了敌机生成频率的参数位置,这种"配置与逻辑分离"的设计对二次开发者来说非常省心。

2.2 技术选型:为什么用pygame而不是PyQt或cocos

很多初学者会问,做个飞机大战为什么不用PyQt或者cocos?PyQt虽然也能做界面,但它的核心是桌面应用组件,做游戏时的帧循环、精灵管理、碰撞检测都需要自己从零实现,且性能远不如专业游戏库。cocos和unity则引入了引擎概念,学习曲线陡增,还要处理跨平台打包。pygame的优势在于它把所有游戏基础设施都准备好了:显示窗口、加载图片、播放声音、处理键盘输入、绘制图形,都不用额外造轮子。

这个项目选pygame还有一个现实原因:依赖极轻。requirements.txt里只有一行:

pygame>=2.0.0

不像某些游戏框架动辄几十个依赖包,pygame在Windows、macOS、Linux上都有官方预编译包,pip install直接装,不会出现编译失败这类劝退问题。对于教学和快速验证想法来说,pygame是最折中的选择——它让你不用处理底层图形API,又能完全掌控游戏循环。

2.3 依赖文件与运行环境的细节说明

requirements.txt虽然只有一行,但版本号需要稍微注意。pygame从2.0版本开始重构了API,而且对Python 3.11+的支持更稳定。如果你用的是Python 3.10以下的老版本,装pygame 1.9.x也能跑,但部分API会有细微差异,比如显示模式初始化、音频驱动的默认值。我一般建议直接装2.0以上版本,这个项目里用到的pygame.sprite.Group、pygame.key.get_pressed()等接口在2.x下表现更稳定。

另外一个容易忽略的点是:项目根目录下没有看到单独的utils模块,而是把工具函数直接写进了plane_sprites.py。这种写法在小型游戏里是合理的,但如果你打算后续扩展成较大的项目,建议把资源加载路径封装成一个独立的asset_manager模块,避免每个类都自己拼接相对路径。

3. 核心模块拆解:一个子弹从发射到命中的完整链路

这一章是项目的核心价值所在。飞机大战的全部逻辑其实可以浓缩成一句话:游戏循环不断地处理输入、更新对象位置、检查碰撞、绘制画面。但怎么把这些步骤优雅地组织起来,就是代码功力了。

3.1 主入口main.py:游戏初始化与主循环

main.py是项目的启动入口,它的结构非常标准,先看代码:

import pygame import config from plane_sprites import Player, Enemy, Bullet def main(): pygame.init() # 设置窗体尺寸和标题 screen = pygame.display.set_mode((config.SCREEN_WIDTH, config.SCREEN_HEIGHT)) pygame.display.set_caption("飞机大战") clock = pygame.time.Clock() # 创建精灵组 all_sprites = pygame.sprite.Group() enemies_group = pygame.sprite.Group() bullets_group = pygame.sprite.Group() player = Player() all_sprites.add(player) running = True while running: # 控制帧率,防止CPU占用过高 clock.tick(config.FPS) for event in pygame.event.get(): if event.type == pygame.QUIT: running = False # 更新所有精灵状态 all_sprites.update() bullets_group.update() enemies_group.update() # 绘制背景和所有精灵 screen.fill(config.BG_COLOR) all_sprites.draw(screen) bullets_group.draw(screen) enemies_group.draw(screen) pygame.display.flip() pygame.quit() if __name__ == "__main__": main()

这里的核心是while循环。游戏循环的第一件事是clock.tick(config.FPS),它的作用是让每帧间隔时间固定。假设FPS设为60,那么tick方法会保证每秒最多执行60次循环,这样游戏速度在不同性能的机器上保持一致。如果不加这一行,游戏在高端电脑上会跑得飞快,敌机移动速度看起来像是瞬移,这就是很多新手说"游戏速度忽快忽慢"的根本原因。

事件处理部分非常简洁——只监听了QUIT事件,也就是点击窗口关闭按钮。实际游戏中玩家的上下左右按键动作并不靠event遍历来捕获,而是用pygame.key.get_pressed()在精灵内部获取键盘状态,这种区分很重要:event用于离散动作像点击关闭、按下开火键,而get_pressed适合处理持续按住的方向移动。

你会发现这里把精灵分成了三个Group:all_sprites负责玩家绘制和更新,bullets_group管理和绘制子弹,enemies_group管理和绘制敌机。这样分开的原因在碰撞检测时就会体现出来——你只需要让bullet精灵和enemy精灵所在的组做碰撞检查,不需要把玩家也卷进去。

3.2 玩家飞机精灵设计:移动边界与键盘响应

接下来看plane_sprites.py中的玩家类:

import pygame import config class Player(pygame.sprite.Sprite): def __init__(self): super().__init__() # 加载图片并缩小到合适尺寸 self.image = pygame.image.load("assets/images/player.png") self.image = pygame.transform.scale(self.image, (60, 80)) self.rect = self.image.get_rect() self.rect.midbottom = (config.SCREEN_WIDTH // 2, config.SCREEN_HEIGHT - 20) self.speed = config.PLAYER_SPEED self.hp = config.PLAYER_HP def update(self): # 获取当前按下的键盘键位 keys = pygame.key.get_pressed() if keys[pygame.K_a] or keys[pygame.K_LEFT]: self.rect.x -= self.speed if keys[pygame.K_d] or keys[pygame.K_RIGHT]: self.rect.x += self.speed if keys[pygame.K_w] or keys[pygame.K_UP]: self.rect.y -= self.speed if keys[pygame.K_s] or keys[pygame.K_DOWN]: self.rect.y += self.speed # 水平边界限制 if self.rect.left < 0: self.rect.left = 0 if self.rect.right > config.SCREEN_WIDTH: self.rect.right = config.SCREEN_WIDTH # 垂直边界限制 if self.rect.top < 0: self.rect.top = 0 if self.rect.bottom > config.SCREEN_HEIGHT: self.rect.bottom = config.SCREEN_HEIGHT

每个精灵都是一个pygame.sprite.Sprite子类,必须重写update方法,这个方法会被精灵组的update()自动调用。注意update方法没有返回值,它只修改自身的rect属性。rect是pygame管理矩形区域的类,保存了精灵的位置和尺寸,所有移动本质上都是改变rect的x和y坐标。

移动控制用的是键盘状态检测而不是事件驱动,这里有个性能层面的考量:事件队列在游戏运行时会持续堆积,如果用event类型判断按键,那么一个按住动作可能会产生多个KEYDOWN事件,导致移动速度不均匀。get_pressed()返回的是一个布尔元组,表示当前每一帧中哪些键处于按下状态,这样在60FPS下,每帧移动speed个像素,速度就非常平滑。

speed和hp都是直接从config读取的,这样改难度时不需要碰逻辑代码。边界判断用了四组if而不是elif,因为玩家可能同时按了上和左,如果只用if-elif,斜向移动会被吞掉一个方向。这个细节值得留意。

3.3 子弹与敌机碰撞:Group碰撞检测的内在逻辑

敌方子弹与飞机的碰撞是飞机大战中最高频的逻辑,看下子弹和敌机的设计:

class Bullet(pygame.sprite.Sprite): def __init__(self, x, y): super().__init__() self.image = pygame.image.load("assets/images/bullet.png") self.image = pygame.transform.scale(self.image, (10, 20)) self.rect = self.image.get_rect() self.rect.centerx = x self.rect.bottom = y self.speed = config.BULLET_SPEED def update(self): self.rect.y -= self.speed # 超出屏幕后移除子弹 if self.rect.bottom < 0: self.kill() class Enemy(pygame.sprite.Sprite): def __init__(self): super().__init__() self.image = pygame.image.load("assets/images/enemy.png") self.image = pygame.transform.scale(self.image, (50, 60)) self.rect = self.image.get_rect() self.rect.x = random.randint(0, config.SCREEN_WIDTH - self.rect.width) self.rect.y = random.randint(-100, -50) self.speed = random.randint(config.ENEMY_SPEED_MIN, config.ENEMY_SPEED_MAX) def update(self): self.rect.y += self.speed if self.rect.top > config.SCREEN_HEIGHT: self.kill()

子弹从飞机机头位置生成,每帧向上移动;敌机从屏幕上方随机的负坐标区域生成,每帧向下移动。击中检测放在main.py的主循环里,用pygame.sprite.groupcollide:

hits = pygame.sprite.groupcollide(bullets_group, enemies_group, True, True) for hit_enemy in hits.values(): score += 100

groupcollide是pygame内置的碰撞检测方法,它会遍历第一个Group中的所有精灵,检查每个精灵的rect是否与第二个Group中的任一精灵的rect发生重叠。参数中的第三个True表示碰撞后自动移除子弹,第四个True表示自动移除敌机,也就是"一碰就消失"的标准弹幕效果。返回的hits是一个字典,键是子弹精灵,值是被击中的敌机精灵列表。通过遍历字典的values来累计分数。

这种碰撞检测的精度是矩形包围盒,对于飞机大战这种俯视射击游戏完全够用。但要注意,pygame的rect碰撞是基于矩形重叠,如果子弹速度过快,可能一帧内穿越敌机没有发生重叠,这就是所谓的「穿透」问题。常见解法是把子弹设计成足够长,或者把移动拆分成子步进,但在这个项目里,只要子弹速度不超过敌机宽度的FPS倍,就不会出问题。config中BULLET_SPEED默认是15,FPS是60,敌机宽度50,所以理论上能稳定检测。

3.4 游戏状态切换与计分逻辑

游戏进行中还需要处理玩家血量、得分和结束判定。这部分常常会被新手写成一坨全局变量,但这个项目做了一个比较好的状态管理:

class GameState: def __init__(self): self.score = 0 self.hp = config.PLAYER_HP self.running = True self.over = False def update_score(self, points): self.score += points def damage(self, value): self.hp -= value if self.hp <= 0: self.over = True

把游戏状态封装成一个独立的GameState类,而不是把score和hp散落在main函数里,这样后续加暂停、加菜单、加多关卡都只需要扩展这个类。计分规则的逻辑也在这里集中管理,修改得分系数时只需要改update_score的调用点。

主循环中的判断逻辑是:

if player.rect.colliderect(enemy.rect): state.damage(10) enemy.kill()

这里直接使用两个精灵的rect碰撞方法,因为玩家和敌机不需要像子弹和敌机那样批量检测,单独判断性能更好。注意血量扣除后判断了state.over,如果为True则跳出主循环。这种状态的转移是显式的,比用自定义event来通知更可控。

4. 运行与调试:从克隆到第一局通关的完整记录

这部分写给想立刻跑起来的读者。按照下面的步骤操作,五分钟内就能看到游戏窗口弹出来。

4.1 环境准备与依赖安装

项目要跑起来的前提是Python环境,推荐使用Python 3.8以上的版本,太老或太新的Python版本都可能导致pygame的wheel包不兼容。另一个人在Windows上遇到过Python 3.13还没发布pygame对应轮子时安装失败的情况,所以如果卡在安装pygame,直接把Python换到3.10或3.11。

第一步先创建一个虚拟环境(推荐,避免污染全局Python):

python -m venv venv # Windows激活 venv\Scripts\activate # mac/Linux激活 source venv/bin/activate

然后安装依赖:

pip install -r requirements.txt

如果你的网络环境比较慢,可以使用国内镜像源,例如清华的PyPI镜像。但要注意,镜像源只影响下载速度,不会改变包的API。安装完成后检查一下:

python -m pygame.examples.aliens

如果这个示例游戏能正常弹窗,说明pygame的环境和显示驱动都没问题。这一步非常重要,很多初学者项目本身写对了,但运行环境有问题,然后误以为是代码bug。

4.2 启动游戏与传入参数调优

项目设计时把配置都集中在config.py,这意味着运行游戏完全不需要命令参数,直接启动即可:

python main.py

以下是config.py中的几个关键可调参数:

参数名默认值作用
SCREEN_WIDTH480窗口宽度,调大后需要重新设计坐标边界
SCREEN_HEIGHT700窗口高度
FPS60帧率,影响游戏速度和流畅度
PLAYER_SPEED5玩家飞机每帧移动的像素数
BULLET_SPEED15子弹每秒上升的像素数
ENEMY_SPEED_MIN2敌机最小下落速度
ENEMY_SPEED_MAX6敌机最大下落速度
PLAYER_HP5玩家初始生命值

调优的方法是:如果觉得游戏难度太高,把ENEMY_SPEED_MIN降到1,把PLAYER_HP加到8;如果觉得画面太卡,把分辨率调小成(400, 600)。改完config.py后不需要改任何代码,重新运行main.py即可生效。这种参数与逻辑分离的设计对于快速试验不同手感非常有用,甚至可以把这些参数配置为命令行参数,比如python main.py --speed 6,但项目没有内置argparse,有需要的读者可以自行扩展。

4.3 常见运行报错与log排查

运行阶段最容易遇到的三类报错,这里提前说明。

第一类是pygame.error: Unable to open file,这类问题95%以上是工作目录不对。当你从项目根目录以外的路径运行main.py时,代码中相对路径assets/images/player.png会拼接成一个错误的路径。解决办法是在项目根目录下运行,或者在代码中把资源路径改成基于__file__的绝对路径:

import os BASE_DIR = os.path.dirname(os.path.abspath(__file__)) IMAGE_PATH = os.path.join(BASE_DIR, "assets", "images", "player.png")

第二类是没有声音但程序正常跑,这往往是pygame在初始化混音器时选择了错误的音频驱动。可以尝试在代码开头加入pygame.mixer.pre_init(44100, -16, 2, 512),在pygame.init()之前调用,能让音频初始化更稳定。

第三类是运行时出现TypeError: 'pygame.event.Event' object is not subscriptable,这通常是因为你尝试用event['key']来获取按键,但应该用event.key。新老版本pygame的差异坑,这个在后面避坑章节里会详细说。

5. 避坑指南:pygame开发中五个最容易翻车的地方

这一章写的是我在实际跑这个项目以及做二次开发时踩过的坑。每一条都是真实会遇到的,而且报错信息往往非常误导人。

5.1 坑一:图片加载不报错但窗口全黑

现象:main.py正常启动,窗口弹出来了,但画面完全黑色,没有任何报错,甚至player精灵也没显示。

原因:资源加载使用了相对路径,而启动命令是在其他目录下执行的。虽然pygame在加载失败时会抛出pygame.error,但如果你在做初始化时图片路径写错了,比如assets/images/player.png写成了assets/images/player.PNG,由于Windows文件系统不区分大小写,可能不会报错,但加载到的是一个空surface。另一种情况是,你在pygame.init()里没有初始化显示模块,虽然大多数时候pygame会自动处理,但某些环境下会导致图片surface没有正确创建。

解决:先打印image对象确认是否为空,再检查路径大小写和目录层级。最稳妥的做法是使用os.path拼接绝对路径,并把资源加载封装成一个独立的函数。我在验证这个项目时,直接把所有资源路径切换为基于当前文件目录的绝对路径,问题立刻消失。

5.2 坑二:FPS没有限制导致CPU占用100%

现象:游戏运行后风扇狂转,任务管理器一看Python进程CPU占用率接近100%,而且游戏速度时快时慢。

原因:主循环里没有调用clock.tick(FPS)。没有帧率限制时,while循环以每秒几千次的速度疯狂执行,每帧都去绘制和更新,不仅CPU耗尽,而且所有精灵移动速度变成「极速模式」,游戏完全没法玩。这是一个非常经典的pygame新手错误,因为不报错,只表现为性能问题。

解决:在循环开始处添加clock.tick(config.FPS)。如果这样做了仍然CPU占用高,检查是否在循环中重复加载图片、重复转换surface,比如在update里调用pygame.image.load()就是灾难级错误。图片和声音资源必须在初始化时加载一次,之后只复用。

5.3 坑三:groupcollide碰撞检测漏判子弹

现象:子弹明明飞向敌机,但有时能击中,有时直接穿过,而且穿过去的子弹和敌机都一切正常。

原因:子弹速度过快,导致前后两帧之间子弹与敌机的矩形没有任何时刻相交。假设子弹每帧移动30像素,敌机宽度只有30像素,那么子弹在某一帧刚好在敌机上方,下一帧已经跑到敌机下方,两帧之间没有重叠帧,碰撞检测就漏过去了。这不是pygame的bug,而是离散碰撞检测的固有缺陷。

解决:控制子弹速度在合理范围内,原则是子弹每帧位移小于敌机的最小尺寸。如果必须高速子弹,可以把子弹的image拉长,比如做一个宽度8但长度30的长条子弹图片,让矩形前后帧都有覆盖;或者手动做连续碰撞检测,用上一帧位置到当前位置做线段与矩形相交判断。对飞机大战来说,最简单的方法是调整BULLET_SPEED不超过15,我实测在FPS=60下,这个速度不会出问题。

5.4 坑四:子弹击穿敌机后kill()引发字典遍历错误

现象:运行一段时间后,程序抛出RuntimeError: dictionary changed size during iteration,崩溃位置在groupcollide之后的for循环里。

原因:groupcollide返回的字典hits会在成员移除时动态变化。当你在循环中处理多个子弹时,某个子弹触发kill()后,hits字典内部也同步移除了这个键,但你的for循环还在遍历原始键视图,导致迭代异常。这属于并发修改(更准确地说是迭代中修改)的经典错误。

解决:不直接遍历hits字典,而是调用hits = pygame.sprite.groupcollide(...)之后,把hits.items()强制转成list再进行遍历:

for bullet, enemies in list(hits.items()): score += 100 * len(enemies)

这样迭代的是原始快照,不会被运行时修改影响。另一个更好的做法是封装一个fight_collide函数,专门负责碰撞后的事件处理,避免在主循环里写太多递归逻辑。

5.5 坑五:中文字体显示成方块

现象:使用pygame.font.SysFont('simhei', 24)设置中文字体,但屏幕上的文字全部变成「口口口」。

原因:pygame的SysFont依赖于系统字体名称,不同操作系统对中文字体的命名不一样。在Windows下是simhei,macOS下是pychinese或STHeiti,Linux下可能是wqy-microhei,SysFont找不到指定字体时回退到默认字体,默认字体不包含中文字符集,于是显示为方块。

解决:将字体文件放到assets/fonts目录下,然后使用pygame.font.Font("assets/fonts/simhei.ttf", 24)加载,这样完全绕开系统字体依赖。如果没有现成ttf文件,也可以下载一个开源中文字体放进去,这是游戏项目中很常见的做法。我另一个项目里就是靠这个办法解决了Linux服务器上字体方块问题,之后再也没在字体上翻过车。

6. 二次开发思路:把方块飞机大战扩展成弹幕射击原型

这个项目留了很舒服的扩展位,下面几个方向是我认为性价比最高的改造路径。

6.1 增加武器强化与道具掉落

现在玩家每次只能单发子弹,升级到弹幕游戏最简单的方式是给玩家加「子弹等级」。在Player类里增加一个bullet_level属性,每次射出发射三发子弹,中间一发直行,左右两发稍微偏移角度。实现方式是在Bullet类里增加speed_x参数,在初始化时给不同子弹不同的横向速度。另外可以用pygame.sprite.Group来管理道具,敌机被击落后有一定概率掉落一个「能量块」精灵,玩家触碰到能量块后提升子弹等级。这只需要复用现有的rect碰撞检测逻辑,扩展门槛很低。

6.2 引入状态机管理菜单、游戏、结束

当前游戏没有暂停和菜单,主循环在running跳出后直接退出。我一般会建立一个GameState枚举,管理MENU、PLAYING、PAUSED、GAMEOVER四个状态。主循环中不同状态绘制不同界面,例如GAMEOVER时绘制分数和重新开始按钮,点击按钮重置GameState对象并清空所有精灵组。这个状态机的核心改动在main.py,把while running内部的逻辑拆成三个函数:handle_events、update、draw,每个函数内部按state分支执行。这种改造带来的最大价值是不需要重复启动pygame.init和加载资源,整个生命周期内窗口只初始化一次。

6.3 音效与背景音乐的接入

项目assets/sounds目录里已经预留了shoot.wav和explosion.wav,但主循环里没有接播放逻辑。接入非常简单,在Player类中emit子弹的位置调用pygame.mixer.Sound.play(shoot_sound),在碰撞检测命中时播放explosion_sound。注意pygame的mixer最多同时播放多个声音,如果射击频率太高,需要控制每次射击之间的最小间隔。背景音乐可以使用pygame.mixer.music.load,然后music.play(-1)循环播放。这里有个优化点:不要把Sound对象放在循环内反复创建,应该在模块顶层加载一次。

6.4 扩展双人对战模式的大致思路

如果想做成双人同屏对战,需要新增一个Player2类,使用不同的按键控制,例如WASD控制玩家一,方向键控制玩家二。两个玩家共用一台电脑,每个人的子弹只对自己之外的精灵生效,这就需要在碰撞检测时把两个玩家的子弹分到不同的Group。最难的点是敌机既要撞击玩家也要被子弹消灭,但玩家的子弹不能攻击另一个玩家。其实pygame的groupcollide本身支持传入不同的组,只要把玩家二和玩家一放到不同的Group即可。不过这个扩展需要改动的地方较多,适合已经把这个项目完全吃透后动手。

最后说一点自己的习惯:每次在这个项目上做改动,我都会先跑一遍原有的测试流程——启动游戏、射击一个敌人、确认分数增加、被撞一次、确认血量减少、死亡后重开。这个小循环五步不到十秒,却能把百分之九十以上的回归问题拦在门外。后来不管改哪个游戏项目,我都强制自己先列一个手动冒烟用例清单再动手。希望你也能从这种朴素的验证方法里受益,把飞机大战跑起来,再改成自己的版本。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询