用 Python 实现微信版飞机大战

2013 年微信 5.0 版本内置了一款经典小游戏-灰黑色版飞机大战,被称为微信经典飞机大战,在这个版本中微信甚至将欢迎设置了成了这款小游戏,用户首次登陆将会直接进入飞机大战小游戏,游戏一经推出也是火爆异常,当时还出现了许多经典语录。

我们要实现飞机大战这款小游戏,主要需要用到的是第三方模块 pygame,安装使用 pip install pygame 即可。

角色 model

飞机大战这款小游戏中角色相对比较简单,主要包括三部分:玩家、敌人和子弹。

我们先来定义一个子弹类,类中主要包括子弹样式、移动速度等,代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
# 子弹类
class Bullet(pygame.sprite.Sprite):
    def __init__(self, img, pos):
        pygame.sprite.Sprite.__init__(self)
        self.image = img
        # 设置图片的区域
        self.rect = self.image.get_rect()
        self.rect.midbottom = pos
        self.speed = 10
    def move(self):
        self.rect.top -= self.speed

接下来我们来定义敌人类,类中主要包括敌机样式、移动速度、出现位置等,代码实现如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 敌人类
class Enemy(pygame.sprite.Sprite):
    def __init__(self, img, explosion_img, pos):
        pygame.sprite.Sprite.__init__(self)
        self.image = img
        self.rect = self.image.get_rect()
        self.rect.topleft = pos
        self.explosion_img = explosion_img
        self.speed = 2
        # 设置击毁序列
        self.explosion_index = 0
    def move(self):
        # 敌人的子弹只能一直向下
        self.rect.top += self.speed

我们再接着来定义玩家类,类中主要包括飞机初始位置、移动方法、是否被敌机击中等,代码实现如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 玩家类
class Player(pygame.sprite.Sprite):
    def __init__(self, img, rect, pos):
        pygame.sprite.Sprite.__init__(self)
        self.image = []
        # 将飞机图片部分分隔
        for i in range(len(rect)):
            self.image.append(img.subsurface(rect[i]).convert_alpha())
        # 获取飞机的区域
        self.rect = rect[0]
        self.rect.topleft = pos
        self.speed = 8
        # 生成精灵组实例
        self.bullets = pygame.sprite.Group()
        self.img_index = 0
        # 判断飞机是否被打中
        self.is_hit = False
    def shoot(self, img):
        bullet = Bullet(img, self.rect.midtop)
        # 添加子弹实例到玩家的子弹组
        self.bullets.add(bullet)
    def moveUp(self):
        # 当遇到顶部时,设置上顶部为0
        if self.rect.top <= 0:
            self.rect.top = 0
        else:
            self.rect.top -= self.speed
    def moveDown(self):
        # 当遇到底部时,设置一直为常值
        if self.rect.top >= SCREEN_HEIGHT - self.rect.height:
            self.rect.top = SCREEN_HEIGHT - self.rect.height
        else:
            self.rect.top += self.speed
    def moveLeft(self):
        # 当遇到左边时,一直停靠在左边
        if self.rect.left <= 0:
            self.rect.left = 0
        else:
            self.rect.left -= self.speed
    def moveRight(self):
        # 当遇到右边时, 停靠右边
        if self.rect.left >= SCREEN_WIDTH - self.rect.width:
            self.rect.left = SCREEN_WIDTH - self.rect.width
        else:
            self.rect.left += self.speed

运行逻辑

现在我们已经定义好游戏的角色 model 了,接下来我们可以先对角色 model、图片素材及一些常量等进行一些初始化操作,代码实现如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# 设置屏幕的宽度
SCREEN_WIDTH = 450
# 设置屏幕的高度
SCREEN_HEIGHT = 600
# 初始化窗口
pygame.init()
# 设置窗口标题
pygame.display.set_caption("飞机大战")
# 设置屏幕大小
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT), 0, 32)
# 隐藏光标
pygame.mouse.set_visible(False)
# 设置背景
bg = pygame.image.load("resources/image/bg.png")
# 设置游戏结束的图片
bg_game_over = pygame.image.load("resources/image/bg_game_over.png")
# 加载飞机资源图片
img_plane = pygame.image.load("resources/image/shoot.png")
img_start = pygame.image.load("resources/image/start.png")
img_pause = pygame.image.load("resources/image/pause.png")
img_icon = pygame.image.load("resources/image/plane.png").convert_alpha()
# 顺便设置窗口
pygame.display.set_icon(img_icon)
# 初始化飞机区域
player_rect = []
player_rect.append(pygame.Rect(0, 99, 102, 126))
player_rect.append(pygame.Rect(165, 360, 102, 126))
# 玩家爆炸图片
player_rect.append(pygame.Rect(165, 234, 102, 126))
player_rect.append(pygame.Rect(330, 624, 102, 126))
player_rect.append(pygame.Rect(330, 498, 102, 126))
player_rect.append(pygame.Rect(432, 624, 102, 126))
# 初始化位置
player_pos = [200, 450]
# 生成玩家类
player = Player(img_plane, player_rect, player_pos)
# 设置子弹框
bullet_rect = pygame.Rect(1004, 987, 9, 21)
# 加载子弹图片
bullet_img = img_plane.subsurface(bullet_rect)
# 设置敌人框
enemy_rect = pygame.Rect(534, 612, 57, 43)
# 设置敌人图片
enemy_img = img_plane.subsurface(enemy_rect)
# 设置敌人被击图片
enemy_explosion_imgs = []
enemy_explosion_imgs.append(img_plane.subsurface(pygame.Rect(267, 347, 57, 43)))
enemy_explosion_imgs.append(img_plane.subsurface(pygame.Rect(873, 697, 57, 43)))
enemy_explosion_imgs.append(img_plane.subsurface(pygame.Rect(267, 296, 57, 43)))
enemy_explosion_imgs.append(img_plane.subsurface(pygame.Rect(930, 697, 57, 43)))
# 设置敌机精灵组
enemies = pygame.sprite.Group()
# 设置敌机被击精灵组
enemies_explosion = pygame.sprite.Group()
# 设置射击频率
shoot_frequency = 0
# 设置敌机频率
enemy_frequency = 0
# 设置玩家被击的图片顺序
player_explosion_index = 16
score = 0
running = True
is_pause = False
is_game_over = False
clock = pygame.time.Clock()

最后,我们来看一下游戏运行中最重要的一部分-游戏的运行逻辑,主要包括:敌机的生成运行、子弹的生成运行、生成分数、游戏状态的处理(暂停、重置、结束)等,具体代码实现如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# 开始游戏循环
while running:
    # 设置游戏帧率为 60
    clock.tick(60)
    if not is_pause and not is_game_over:
        if not player.is_hit:
            # 设置连续射击,因为每秒 60 帧,15/60=0.25 秒发一次子弹
            if shoot_frequency % 15 == 0:
                player.shoot(bullet_img)
            shoot_frequency += 1
            # 当设置的射击频率大于 15,置零
            if shoot_frequency >= 15:
                shoot_frequency = 0
        # 控制生成敌机的频率
        if enemy_frequency % 50 == 0:
            # 设置敌机的出现的位置
            enemy_pos = [random.randint(0, SCREEN_WIDTH - enemy_rect.width), 0]
            enemy = Enemy(enemy_img, enemy_explosion_imgs, enemy_pos)
            enemies.add(enemy)
        enemy_frequency += 1
        if enemy_frequency >= 100:
            enemy_frequency = 0
        # 控制子弹的显示运行
        for bullet in player.bullets:
            bullet.move()
            if bullet.rect.bottom < 0:
                player.bullets.remove(bullet)
        # 控制敌机的运行
        for enemy in enemies:
            enemy.move()
            # 判断敌机是否与玩家飞机碰撞
            if pygame.sprite.collide_circle(enemy, player):
                enemies_explosion.add(enemy)
                enemies.remove(enemy)
                player.is_hit = True
                # 设置玩家的飞机被毁
                is_game_over = True
            # 判断敌机是否在界面
            if enemy.rect.top < 0:
                enemies.remove(enemy)
        # 设置敌机与玩家的飞机子弹相碰时,返回被击的敌机实例
        enemy_explosion = pygame.sprite.groupcollide(enemies, player.bullets, 1, 1)
        for enemy in enemy_explosion:
            enemies_explosion.add(enemy)
    # 绘制屏幕
    screen.fill(0)
    # 加入背景图片
    screen.blit(bg, (0, 0))
    # 添加玩家飞机图片到屏幕
    if not player.is_hit:
        screen.blit(player.image[int(player.img_index)], player.rect)
        player.img_index = shoot_frequency / 8
    else:
        if player_explosion_index > 47:
            is_game_over = True
        else:
            player.img_index = player_explosion_index / 8
            screen.blit(player.image[int(player.img_index)], player.rect)
            player_explosion_index += 1
    # 敌机被子弹击中的效果显示
    for enemy in enemies_explosion:
        if enemy.explosion_index == 0:
            pass
        if enemy.explosion_index > 7:
            enemies_explosion.remove(enemy)
            score += 100
            continue
        # 敌机被击时显示图片
        screen.blit(enemy.explosion_img[int(enemy.explosion_index / 2)], enemy.rect)
        enemy.explosion_index += 1
    # 显示子弹
    player.bullets.draw(screen)
    # 显示敌机
    enemies.draw(screen)
    # 分数的显示效果
    score_font = pygame.font.Font(None, 36)
    score_text = score_font.render(str(score), True, (128, 128, 128))
    # 设置文字框
    text_rect = score_text.get_rect()
    # 放置文字的位置
    text_rect.topleft = [20, 10]
    # 显示出分数
    screen.blit(score_text, text_rect)
    left, middle, right = pygame.mouse.get_pressed()
    # 暂停游戏
    if right == True and not is_game_over:
        is_pause = True
    if left == True:
        # 重置游戏
        if is_game_over:
            is_game_over = False
            player_rect = []
            player_rect.append(pygame.Rect(0, 99, 102, 126))
            player_rect.append(pygame.Rect(165, 360, 102, 126))
            player_rect.append(pygame.Rect(165, 234, 102, 126))
            player_rect.append(pygame.Rect(330, 624, 102, 126))
            player_rect.append(pygame.Rect(330, 498, 102, 126))
            player_rect.append(pygame.Rect(432, 624, 102, 126))
            player = Player(img_plane, player_rect, player_pos)
            bullet_rect = pygame.Rect(1004, 987, 9, 21)
            bullet_img = img_plane.subsurface(bullet_rect)
            enemy_rect = pygame.Rect(534, 612, 57, 43)
            enemy_img = img_plane.subsurface(enemy_rect)
            enemy_explosion_imgs = []
            enemy_explosion_imgs.append(img_plane.subsurface(pygame.Rect(267, 347, 57, 43)))
            enemy_explosion_imgs.append(img_plane.subsurface(pygame.Rect(873, 697, 57, 43)))
            enemy_explosion_imgs.append(img_plane.subsurface(pygame.Rect(267, 296, 57, 43)))
            enemy_explosion_imgs.append(img_plane.subsurface(pygame.Rect(930, 697, 57, 43)))
            enemies = pygame.sprite.Group()
            enemies_explosion = pygame.sprite.Group()
            score = 0
            shoot_frequency = 0
            enemy_frequency = 0
            player_explosion_index = 16
        # 继续游戏
        if is_pause:
            is_pause = False
    # 游戏结束
    if is_game_over:
        font = pygame.font.SysFont("微软雅黑", 48)
        text = font.render("Score: " + str(score), True, (255, 0, 0))
        text_rect = text.get_rect()
        text_rect.centerx = screen.get_rect().centerx
        text_rect.centery = screen.get_rect().centery + 70
        # 显示游戏结束画面
        screen.blit(bg_game_over, (0, 0))
        # 显示分数
        screen.blit(text, text_rect)
        font = pygame.font.SysFont("微软雅黑", 40)
        text = font.render("Press Left Mouse to Restart", True, (255, 0, 0))
        text_rect = text.get_rect()
        text_rect.centerx = screen.get_rect().centerx
        text_rect.centery = screen.get_rect().centery + 150
        screen.blit(text, text_rect)
    # 刷新屏幕
    pygame.display.update()
    # 处理游戏退出
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            exit()
    if not is_pause and not is_game_over:
        key = pygame.key.get_pressed()
        if key[K_w] or key[K_UP]:
            player.moveUp()
        if key[K_s] or key[K_DOWN]:
            player.moveDown()
        if key[K_a] or key[K_LEFT]:
            player.moveLeft()
        if key[K_d] or key[K_RIGHT]:
            player.moveRight()

我们来看一下实现效果:

总结

本文我们使用 Python 实现了微信版飞机大战的基本功能,大家有兴趣的可以获取源码自己玩了,如果你对游戏哪个地方实现的不满意,还可以自己进行修改。

示例代码:[py-plane](https://github.com/JustDoPython/python-examples/tree/master/yeke/py-plane

Python Geek Tech wechat
欢迎订阅 Python 技术,这里分享关于 Python 的一切。