python 外星人入侵游戏 学习总结 以及完整代码

  • 从三月开始看《python:从入门到实践》自学python,踏入编程的世界,已经过去两个多月了,学完基础知识之后开始学习游戏项目《外星人入侵》。
    外星人入侵:在游戏《外星人入侵》中,玩家控制着一艘最初出现在屏幕底部中央的飞船。玩家可以使用箭头键左右移动飞船,还可使用空格键进行射击。游戏开始时,一群外星
    人出现在天空中,他们在屏幕中向下移动。玩家的任务是射杀这些外星人。玩家将所有外星人都消灭干净后,将出现一群新的外星人,他们移动的速度更快。只要有
    外星人撞到了玩家的飞船或到达了屏幕底部,玩家就损失一艘飞船。玩家损失三艘飞船后,游戏结束。
    通过学习这样一个项目,我学到了pygame的运用,对之前学的类、函数、方法有了更深的认识,开始思考项目背后运行的原理。
    项目分为10个文件,9个py文件和1个json文件。
    py文件分别储存了游戏的各个要素,具体作用我会在代码的注释中详述。json文件会被 scorebroad.py 中的代码自动创建,用于储存游戏中出现的最高分。
    作为新手,免不了要出错,在写这个项目的过程我也遇到了不少问题,也向很多大神求助过,在这里感谢帮助过我的前辈。我把几个遇到的问题都写在了之前在出错的地方。
    总结一下教训:
    1.写代码的时候一定要专注,不然很多时候怎么出错都不知道。
    2.调用方法函数的时候记得加上括号,不然会报错,这是个细节问题。
    3.一定要注意缩进问题,有时候不应该的缩进会导致一些莫名其妙的问题,找代码出错的时候记得看一看缩进是否正确。
    游戏界面如图:
    在这里插入图片描述

在这里插入图片描述

项目代码如下:

settings.py

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 Settings:
    def __init__(self):
        #储存游戏初始化数据
        #初始化游戏的静态设置
        self.screen_width = 1400
        self.screen_height = 750
        self.bg_color = (200,200,200)
        #设置飞船数量
        self.ship_limit = 3
        #创建关于子弹的属性
        self.bullet_width = 15
        self.bullet_height = 30
        self.bullet_bg_color = (60,0,0)
        #设置限制子弹数量
        self.bullets_allowed = 3
        #设置外星人下降值
        self.alien_drop_factor = 2
        #设置速度提升倍数
        self.speedup_scale = 1.1
        #设置外星人点数的提高速度
        self.score_scale = 1.5
        #初始化速度值
        self.initialize_dynamic_settings()

    def initialize_dynamic_settings(self):
        #初始化动态设置
        # 设置外星人移动速度
        self.alien_speed_factor = 0.5
        # 设置外星人群移动值,向右为1,向左为-1
        self.fleet_direction = 1
        #设置外星人分数
        self.alien_points = 50
        #设置子弹移动速度
        self.bullet_speed_factor = 5
        # 设置飞船移动速度
        self.ship_speed_factor = 3

    def increase_speed(self):
        #提升速度值
        self.ship_speed_factor *= self.speedup_scale
        self.bullet_speed_factor *= self.speedup_scale
        self.alien_speed_factor*= self.speedup_scale
        #提升分数
        self.alien_points = int(self.alien_points * self.score_scale)

alien.py

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
#创建一个关于外星人的模块
import pygame
from pygame.sprite import Sprite
class Alien(Sprite):

    '''表示单个外星人的类'''
    def __init__(self,ai_settings,screen):
        #继承精灵类
        super().__init__()
        self.ai_settings = ai_settings
        self.screen = screen
        #返回外星人图像并矩形化
        self.image = pygame.image.load('image/humor.png')
        self.rect = self.image.get_rect()
        #每个外星人最初都在屏幕左上角附近
        self.rect.x = self.rect.width
        self.rect.y = self.rect.height
        #储存外星人的准确位置
        self.x = float(self.rect.x)


    def blitme(self):
        '''在指定位置绘制外星人'''
        self.screen.blit(self.image,self.rect)

    def update(self):
        #外星人坐标为速度乘以方向值,当方向值为负时方向改变
        self.x += (self.ai_settings.alien_speed_factor *
                    self.ai_settings.fleet_direction)
        self.rect.x = self.x
        '''当方向值为1时,x 坐标增加,向右移动。方向值为-1时,x 坐标减少,向左移动'''
    def check_edges(self):
        #检查外星人是否碰到了边缘
        #矩形化屏幕
        screen_rect = self.screen.get_rect()
        #当外星人贴图右边缘大于或等于屏幕矩形右边缘,返回正确
        if self.rect.right >= screen_rect.right:
            return True
        #当外星人贴图左边缘小于或等于屏幕矩形左边缘(小于等于0),返回正确
        elif self.rect.left <= screen_rect.left:
            return True

bullet.py

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
#创建子弹类
import pygame
from pygame.sprite import Sprite
class Bullet(Sprite):
    '''一个对飞船发射的子弹进行管理的类'''
    def __init__(self,ai_settings,screen,ship):
        #通过函数super()来继承sprite
        super().__init__()
        self.screen = screen
        #在(0,0)处创建一个代表子弹的矩形,再设置正确的位置,参数为初识坐标,子弹的宽和高
        self.rect = pygame.Rect(0,0,ai_settings.bullet_width,ai_settings.bullet_height)
        #根据飞船位置来确定子弹位置
        self.rect.centerx = ship.rect.centerx
        self.rect.top = ship.rect.top
        #储存用小数表示的子弹位置
        '''由于子弹是直线向下移动,所以我们只考虑在y轴上坐标的变化,
        而x轴的坐标是固定的,这由发射子弹时飞船的x轴坐标决定,
        因为我们在前面定义了self.rect.centerx = ship.rect.centerx'''
        self.y = float(self.rect.y)
        #储存子弹颜色
        self.color = ai_settings.bullet_bg_color
        #储存速度
        self.speed_factor = ai_settings.bullet_speed_factor
    def update(self):
        #让子弹向上移动
        self.y -= self.speed_factor
        #更新子弹rect的位置
        self.rect.y = self.y
    def draw_bullet(self):
        #在屏幕上绘制子弹
        pygame.draw.rect(self.screen,self.color,self.rect)

ship.py

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
import pygame
from pygame.sprite import Sprite
class Ship(Sprite):
    def __init__(self,ai_settings,screen):
        '''初始化飞船并设置其初始位置'''
        #继承精灵
        super().__init__()
        self.screen = screen
        self.ai_settings = ai_settings
        #加载飞船图像,并获取其外接矩形
        #load返回贴图所在文件
        self.image = pygame.image.load('image/shot.PNG')
        #矩形属性rect为贴图的矩形对象
        #get_rect()是一个处理矩形图像的方法,返回值包含矩形的居中属性( center centerx centery )
        #get_rect须加上(),否则后续程序在运行时会出现无法引用centerx和bottom的错误
        self.rect = self.image.get_rect()
        #先将表示屏幕的矩形储存在属性screen—rect中
        self.screen_rect = screen.get_rect()
        '''将每艘新飞船放在屏幕底部中央'''
        #再将self.rect.centerx设置为表示屏幕的矩形属性centerx
        #再将self.rect.bottom设置为表示屏幕的矩形属性bottom
        #后面使用这个rect属性来放置飞船在屏幕底部中间
        self.rect.centerx = self.screen_rect.centerx
        self.rect.bottom = self.screen_rect.bottom
        #在飞船属性center中储存小数点
        self.center = float(self.rect.centerx)
        #添加属性,设置一个飞船向右移动的标志
        self.moving_right = False
        #添加一个飞船向左移动的标志
        self.moving_left = False
    def blitme(self):
        #方法blit 位块传输 将指定的像素内容传输到指定位置
        self.screen.blit(self.image,self.rect)
    def update(self):
        #根据标志来向右移动飞船
        #更新飞船的center值而不是rect(rect已被浮点化储存到center中)
        '''添加条件,防止飞船移动到屏幕外'''
        #以屏幕矩形的右边界为界,当按键事件(标志正确)以及飞船外接矩形坐标小于右边界时可向右移动
        if self.moving_right and self.rect.right < self.screen_rect.right:
            self.center += self.ai_settings.ship_speed_factor
        #根据标志向左移动飞船
        #以屏幕矩形的左边界为准,即x轴坐标的0,当按键事件(标志正确)以及飞船外接矩形坐标大于0时可向左移动
        if self.moving_left and self.rect.left > 0:
            self.center -= self.ai_settings.ship_speed_factor
        '''此处使用两个if来应对左右方向按键,两个条件同时检测,当同时按下左右键时,飞船纹丝不动,
        这有利于精准控制,如使用if—elif格式的话,if优先,故右键优先,同时按两个键时无法同时抵消,则向右移动'''
        #根据center来更新rect
        self.rect.centerx = self.center

    def center_ship(self):
        #让飞船底部居中
        self.center = self.screen_rect.centerx

game_stats.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#创建游戏统计类
class GameStats():
    #跟踪游戏的统计信息
    def __init__(self,ai_settings):
        #初始化统计信息
        self.ai_setting = ai_settings
        #设置结束游戏的标志
        #修改标志,让游戏一开始处于非活动状态
        self.game_active = False
        #设置最高分,最高分不会被重置,所以在init初始化
        self.high_score = 0
        #在初始化init中调用方法reset_stats
        #在创建实例时能妥善地设置统计信息
        #在后续也可以调用
        self.reset_stats()
    def reset_stats(self):
        #重置统计
        #初始化游戏运行期间可能变化的统计信息
        #储存飞船数量
        self.ships_left = self.ai_setting.ship_limit
        #初始化得分
        self.score = 0
        #初始化等级
        self.level = 1

button.py

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
#创建按钮类
#导入模块ftfont(font),它能将文本渲染到屏幕上
import pygame.ftfont
class Button():
    def __init__(self,ai_settings,screen,msg):
        #初始化
        self.screen = screen
        self.screen_rect = screen.get_rect()
        #设置按钮尺寸和颜色
        self.wdith,self.height = 200,50
        self.button_color = (0,50,0)
        #指定文本颜色
        self.text_color = (255,255,255)
        #None 指定了使用默认字体渲染文本,48为字号
        self.ftfont = pygame.ftfont.SysFont(None,48)
        #创建一个rect对象
        self.rect = pygame.Rect(0,0,self.wdith,self.height)
        #rect对象位置居中
        self.rect.center = self.screen_rect.center
        #调用按钮标签(只需创建一次)
        self.prep_msg(msg)

    def prep_msg(self,msg):
        #创建渲染文本方法
        '''将msg渲染为图像,并使其在按钮上居中'''
        #将文本渲染为图像,第一个实参为文本,第二个实参为布尔值(用于指定图像边缘是否开启反锯齿功能,此处的Ture为开启)
        #第三个实参为文本颜色,第四个实参为按钮颜色(即图像的背景色,当没有指定时为透明)
        self.msg_image = self.ftfont.render(msg,True,self.text_color,self.button_color)
        #矩形化文本图像
        self.msg_image_rect = self.msg_image.get_rect()
        #文本图像位置居中,即在按钮中居中(按钮矩形的中央位置)
        self.msg_image_rect.center = self.rect.center

    def draw_button(self):
        #绘制一个用颜色填充的按钮,再绘制文本
        self.screen.fill(self.button_color,self.rect)
        self.screen.blit(self.msg_image,self.msg_image_rect)

scorebroad.py

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
import pygame.ftfont
from pygame.sprite import Group
from ship import Ship
#导入json用于写入最高分
import json
#创建计分板类
class Scoreboard():
    def __init__(self,screen,ai_settings,stats):
        #初始化得分涉及的属性
        self.screen = screen
        self.screen_rect = self.screen.get_rect()
        self.ai_settings = ai_settings
        self.stats = stats
        #设置计分板的字体
        self.text_color = (30,30,30)
        #默认字体,48字号
        self.ftfont = pygame.ftfont.SysFont(None,48)
        #准备初始得分图像和最高分图像、等级、飞船剩余图像
        self.prep_images()

    def prep_images(self):
        # 整合初始化图像内容
        self.prep_score()
        self.prep_high_score()
        self.prep_level()
        self.prep_ships()

    def prep_score(self):
        '''将得分转换为渲染的图像'''
        #  得分为得分为10的整数倍,并将逗号用作千分位分隔符
        rounded_score = int(round(self.stats.score, -1))
        score_str = "{:,}".format(rounded_score)
        self.image = self.ftfont.render(score_str,True,self.text_color,self.ai_settings.bg_color)
        #将得分放在屏幕右上角
        #将文字渲染成的图像矩形化,指定位置
        self.score_rect = self.image.get_rect()
        self.score_rect.right = self.screen_rect.right - 20
        self.score_rect.top = 20

    def prep_high_score(self):
        '''将最高得分渲染为图像'''
        #打开json文件读取最高分来圆整规范以及渲染为图像,当出现 FileNotFoundError 异常时创建json文件,并写入初始化最高分
        # (这一步仅需执行一次,也可应对文件丢失情况)

        try:
            with open('high_score.json') as f_obj:
                high_score_json = json.load(f_obj)
        except FileNotFoundError:
            with open('high_score.json','w') as f_obj:
                json.dump(self.stats.high_score,f_obj)

        # 最高得分为10的整数倍,并将逗号用作千位分隔符
        high_score = int(round(high_score_json,-1))
        high_score_str = "{:,}".format(high_score)


        self.high_score_image = self.ftfont.render(high_score_str,True,self.text_color,self.ai_settings.bg_color)
        #矩形化图像,并指定位置为屏幕顶端中央
        self.high_score_rect = self.high_score_image.get_rect()
        self.high_score_rect.centerx = self.screen_rect.centerx
        self.high_score_rect.top = self.score_rect.top

    def prep_level(self):
        '''将等级转化为渲染的图像'''
        self.level_image = self.ftfont.render(str(self.stats.level),True,self.text_color,self.ai_settings.bg_color)
        #将等级放在得分下方
        self.level_rect = self.level_image.get_rect()
        self.level_rect.right = self.score_rect.right
        self.level_rect.top = self.score_rect.bottom + 10

    def prep_ships(self):
        '''显示还剩余多少飞船'''
        self.ships = Group()
        #循环剩下的飞船数,并根据数量生成位置
        for ship_numbers in range(self.stats.ships_left):
            ship = Ship(self.ai_settings,self.screen)
            ship.rect.x = 10 + ship_numbers * (2 * ship.rect.width)
            ship.rect.y = -30
            self.ships.add(ship)




    def show_score(self):
        #在屏幕上显示得分和最高分
        self.screen.blit(self.image,self.score_rect)
        self.screen.blit(self.high_score_image,self.high_score_rect)
        self.screen.blit(self.level_image,self.level_rect)
        #绘制飞船
        self.ships.draw(self.screen)

game_functions.py

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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
#将管理事件的代码部分转移到此模块中,以简化主程序逻辑
import sys
import pygame
#导入子弹类
from bullet import Bullet
from alien import Alien
#导入json用于记录最高分
import json
#导入sleep来实现游戏暂停
from time import sleep

def fire_bullet(ai_settings,screen,ship,bullets):
    # 创建一个子弹,并将其加入到编组中去
    # 设置条件,限制子弹数量
    #当符合条件时,按下空格发射子弹
    if len(bullets) < ai_settings.bullets_allowed:
        new_bullet = Bullet(ai_settings, screen, ship)
        bullets.add(new_bullet)

def check_keydown_events(event,ai_settings,screen,ship,bullets,stats,aliens,sb):
    #响应按键
    if event.key == pygame.K_RIGHT:
        # 当按键事件为按下右箭头时,向右移动标志为正确
        ship.moving_right = True
    elif event.key == pygame.K_LEFT:
        # 当按键事件为按下左箭头时,向左移动标志为正确
        ship.moving_left = True
        #当按下的键为z时
    elif event.key == pygame.K_z:
        #简化keydown,当按下空格时调用函数fire_bullets检查条件发射子弹
        fire_bullet(ai_settings,screen,ship,bullets)
        #设置快捷键q用于退出游戏
    elif event.key == pygame.K_q:
        sys.exit()
        #设置快捷键p用于重置游戏
    elif event.key == pygame.K_p:
        start_game(stats,aliens,bullets,ai_settings,screen,ship,sb)

def check_keyup_events(event,ship):
    #响应松开
    if event.key == pygame.K_RIGHT:
        # 当按键事件为松开按右箭头时,向右移动标志为错误
        ship.moving_right = False
    elif event.key == pygame.K_LEFT:
        # 当按键事件为松开按左箭头时,向左移动标志为错误
        ship.moving_left = False


def check_events(ai_settinngs,ship,screen,bullets,stats,play_button,aliens,sb):
    '''响应按键和鼠标事件'''
    for event in pygame.event.get():
        '''当事件类型为QUIT时,退出游戏'''
        if event.type == pygame.QUIT:
            sys.exit()
        #检测鼠标事件
        elif event.type == pygame.MOUSEBUTTONDOWN:
            #将鼠标点击区域坐标储存到变量中
            mouse_x,mouse_y = pygame.mouse.get_pos()
            check_play_button(stats,play_button,mouse_x,mouse_y,aliens,bullets,ai_settinngs,screen,ship,sb)
       #更新后的check_event直接使用前面定义的函数
        elif event.type == pygame.KEYDOWN:
            #调用时增加形参
            check_keydown_events(event,ai_settinngs,screen,ship,bullets,stats,aliens,sb)
        elif event.type == pygame.KEYUP:
            check_keyup_events(event,ship)
            '''在 KEYDOWN 和 KEYUP 中使用 if-elif 结构处理两种按键标志,因为每个事件只与一个键关联,
            当同时按下左右两键时,即视为两个事件来处理'''


def check_play_button(stats,play_button,mouse_x,mouse_y,aliens,bullets,ai_settings,screen,ship,sb):
    '''在玩家单击play按钮时开始新游戏'''
    #将按钮点击检测储存到变量中
    button_clicked = play_button.rect.collidepoint(mouse_x,mouse_y)
    #当按钮被点击且游戏非运行状态时,按钮点击生效
    if button_clicked:
        #调用重置游戏方法
        start_game(stats,aliens,bullets,ai_settings,screen,ship,sb)

def start_game(stats,aliens,bullets,ai_settings,screen,ship,sb):
    '''创建重置游戏的方法'''
    if not stats.game_active :
        # 隐藏光标
        # 向set_visible()传递参数False来隐藏光标
        pygame.mouse.set_visible(False)
        #重置速度
        ai_settings.initialize_dynamic_settings()
        # 重置统计信息
        stats.reset_stats()
        # 游戏运行标志正确
        stats.game_active = True
        #重置记分牌中的图像
        sb.prep_images()
        # 清空子弹和外星人
        aliens.empty()
        bullets.empty()
        # 创建新外星人群
        create_fleet(ai_settings, screen, ship, aliens)
        # 飞船居中
        ship.center_ship()

def update_bullets(bullets,aliens,ai_settings,screen,ship,stats,sb):
    # 对编组中的每一个子弹调用update
    #更新子弹位置
    bullets.update()
    # 删除已消失的子弹
    # 在for循环中不应从列表或编组删除条目,所以使用方法copy()遍历编组的副本,然后修改编组
    # 遍历副本的原因:由于编组内的子弹是动态变化的,遍历副本后根据副本元素来删除编组中对应的元素,做到精准删除
    # 注意:这里删除的是原编组里的子弹!
    for bullet in bullets.copy():
        if bullet.rect.bottom <= 0:
            bullets.remove(bullet)
    #调用检测外星人子弹碰撞函数
    check_bullet_alien_collisions(bullets,aliens,ai_settings,screen,ship,stats,sb)

def check_bullet_alien_collisions(bullets,aliens,ai_settings,screen,ship,stats,sb):
    #检查是否有子弹击中了外星人
    #如果子弹击中外星人,则删除该子弹和外星人
    collisions = pygame.sprite.groupcollide(bullets,aliens,True,True)
    #当发生碰撞时加分
    if collisions :
        for aliens in collisions.values():
            stats.score += ai_settings.alien_points * len(aliens)
            sb.prep_score()
            #检测是否出现新的最高分
            check_high_score(stats,sb)

    #当外星人团灭时,调用提升等级函数做出相应变化
    if len(aliens) == 0:
        start_new_level(bullets,ai_settings,stats,sb,screen,ship,aliens)

def start_new_level(bullets,ai_settings,stats,sb,screen,ship,aliens):
    # 清空子弹
    bullets.empty()
    # 提升外星人群速度
    ai_settings.increase_speed()
    # 提升等级
    stats.level += 1
    sb.prep_level()
    # 创建外星人
    create_fleet(ai_settings, screen, ship, aliens)

def check_high_score(stats,sb):
    '''检测是否诞生了新的最高分'''
    #读取json中的最高分,将最高分储存在变量,将当前分数与最高分比较
    #当当前分数高于最高分时,将当前最高分储存到新变量,并将新变量写入json文件
    #调用更新最高分函数,显示新的最高分
    with open('high_score.json') as f_obj:
        high_score = json.load(f_obj)
    if stats.score > high_score :
        new_high_score = stats.score
        with open('high_score.json','w') as f_obj :
            json.dump(new_high_score,f_obj)
    sb.prep_high_score()

def get_number_aliens_x(ai_settings,alien_width):
    '''整理函数,增加条理性'''
    '''此函数返回每行可容下的外星人数量'''
    # 按照公式来计算行的有效屏幕空间,需要与边界保持间距,间距为一个外星人矩形的宽,故行有效屏幕空间为屏宽减去两个外星人宽
    # 求一行容纳外星人数量:两个外星人之间需要保留一个外星人宽的间距,即一个外星人需要的宽为两倍外星人矩形宽
    # 那么行容纳外星人数量 = 行有效屏幕空间/两倍外星人矩形宽
    available_space_x = ai_settings.screen_width - 2 * alien_width  # 行外星人可用屏幕容量
    number_aliens_x = int(available_space_x / (2 * alien_width))  # 行容纳外星人数量
    '''这里使用了int来求得整数数量的外星人,舍弃了小部分,防止出现不完整外星人,同时也出于生成的目的,
       函数range()需要一个整数值'''
    return number_aliens_x

def get_number_rows(ai_settings, ship_height, alien_height):
    """计算屏幕可容纳多少行外星人"""
    # 屏幕垂直可用空间为屏幕高度减去第一行外星人高度和飞船高度,以及减去两倍外星人高度作为最底下一行外星人与飞船的距离
    available_space_y = (ai_settings.screen_height - (3 * alien_height) - ship_height)
    # 可容纳行数为屏幕垂直可用空间除以二倍外星人高度(外星人行与行之间保持一个外星人高度的间距)
    #公式中注意计算时的括号问题
    number_rows = int(available_space_y / (2 * alien_height))
    # 返回可容纳行数
    return number_rows

def create_alien(ai_settings, screen, aliens, alien_number, row_number):
    '''创建单个外星人并将其放在行中'''
    # 先创建单个外星人
    # 外星人间距为外星人宽度
    alien = Alien(ai_settings, screen)
    alien_width = alien.rect.width
    # 外星人位置从左算起,空掉一个外星人矩形作为间隔,每个外星人占据两个外星人矩形大小,
    # x 轴排布位置由for循环的外星人次序决定,故 * number_aliens_x
    alien.x = alien_width + 2 * alien_width * alien_number
    # 储存外星人坐标
    alien.rect.x = alien.x
    # 外星人行的位置需空出一行作为与顶的边界,同时行与行之间空出一个外星人高度作为间距
    # y 轴排布位置由for循环的行数次序决定,故 * row_number
    alien.rect.y = 30 + alien.rect.height + 2 * alien.rect.height * row_number
    # 将外星人添加到编组
    aliens.add(alien)

def create_fleet(ai_settings, screen, ship,aliens):
    '''创建外星人群'''
    # 创建一个外星人,计算每行外星人容量
    alien = Alien(ai_settings, screen)
    # 调用前面计算每行容量的函数,储存在变量中
    number_aliens_x = get_number_aliens_x(ai_settings, alien.rect.width)
    number_rows = get_number_rows(ai_settings, ship.rect.height,alien.rect.height)
    # 创建外星人群
    #遍历可用行空间和行数量,创造出相应数量的一群外星人
    for row_number in range(number_rows):
        for alien_number in range(number_aliens_x):
            create_alien(ai_settings, screen, aliens, alien_number,row_number)


#增加了子弹和外星人形参
def update_screen(ai_settings,ship,screen,aliens,bullets,stats,play_button,sb):
    '''设置、飞船、屏幕为三个实参,在方法中使用'''
    #屏幕颜色填充为设置中设置的颜色
    screen.fill(ai_settings.bg_color)
    #在飞船和外星人后面重绘所有子弹
    #在背景设置完毕后将飞船绘制到指定位置
    ship.blitme()
    #绘制外星人编组中的每一个
    aliens.draw(screen)
    '''遍历编组中的每一个精灵(子弹),并对每一个精灵(子弹)调用draw_bullte'''
    # 这里使用了方法bullets.sprites  它返回子弹编组  注意sprites是复数
    for bullet in bullets.sprites():
        bullet.draw_bullet()
    #显示得分
    sb.show_score()
    #让最近绘制的屏幕可见
    #当游戏处于非活动状态,绘制按钮
    if not stats.game_active :
        play_button.draw_button()
    #让最近绘制的屏幕可见
    pygame.display.flip()

def check_fleet_edges(ai_settings,aliens):
    #遍历每一个外星人,当触碰边缘时调用改变方向函数
    for alien in aliens.sprites():
        if alien.check_edges():
            change_fleet_direction(ai_settings,aliens)
            '''由于之前这里忘记break退出循环,外星人群有时会直接下落导致游戏结束'''
            break

def change_fleet_direction(ai_settings,aliens):
    #遍历每一个外星人,调整每一个外星人的 y 坐标,即下降
    for alien in aliens.sprites():
        alien.rect.y += ai_settings.alien_drop_factor
    #改变方向值
    ai_settings.fleet_direction *= -1

def check_alien_bottom(ai_settings,ship,aliens,stats,bullets,screen,sb):
    #检查外星人是否触底
    #注意!!!
    #get_rect()要记得加上括号,不然会出现如下错误:
    # AttributeError: 'builtin_function_or_method' object has no attribute 'bottom'
    screen_rect = screen.get_rect()
    #遍历外星人编组
    for alien in aliens.sprites():
        #当有外星人触底时,调用ship_hit函数更新外星人和飞船
        if alien.rect.bottom >= screen_rect.bottom:
            ship_hit(stats,aliens,bullets,ai_settings,screen,ship,sb)
            break

def update_aliens(ai_settings,ship,aliens,stats,bullets,screen,sb):
    '''检查外星人是否触碰边缘,做出调整'''
    check_fleet_edges(ai_settings,aliens)
    # 对每个外星人的位置更新
    aliens.update()
    #检查外星人与飞船的碰撞,当碰撞时发出信号(已删去)
    #在碰撞时调用ship_hit,做出相应变化
    if pygame.sprite.spritecollideany(ship,aliens):
        ship_hit(stats,aliens,bullets,ai_settings,screen,ship,sb)
    #调用函数检查是否有外星人触底
    check_alien_bottom(ai_settings,ship,aliens,stats,bullets,screen,sb)


def ship_hit(stats,aliens,bullets,ai_settings,screen,ship,sb):
    #检查飞船数量是否用完,若未用完,暂停后更新飞船和外星人;若用完,标志False,没有更新,游戏结束
    if stats.ships_left > 0 :
        #响应被外星人撞到的飞船
        #飞船数量相应减一
        stats.ships_left -= 1
        #更新飞船剩余
        sb.prep_ships()
        #清空外星人和子弹列表
        aliens.empty()
        bullets.empty()
        #创建一群新的外星人
        create_fleet(ai_settings,screen,ship,aliens)
        #让飞船底部居中
        ship.center_ship
        #暂停0.5秒
        sleep(0.5)
    elif stats.ships_left == 0:
        #当飞船数量用完,标志错误,不执行更新
        stats.game_active = False
        #在游戏处于非运行状态时,光标出现,可以点击按钮继续游戏
        pygame.mouse.set_visible(True)

alien_invasion.py

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
#游戏主程序
#模块sys可用于退出游戏,因为模块game_functions中已经导入sys,主程序中不需要再导入
import pygame
from settings import Settings
from ship import Ship
#导入信息统计类
from game_stats import GameStats
from scoreboard import Scoreboard
#导入编组
from pygame.sprite import Group
#由于不在主程序创建外星人实例,此处不必导入
#导入game_functions,后面的事件管理部分从模块导入
import game_functions as gf
#导入按钮类
from button import Button
def run_game():
    #初始化游戏并创建一个屏幕
    pygame.init()
    ai_settings = Settings()
    screen = pygame.display.set_mode((ai_settings.screen_width,ai_settings.screen_height))
    #设置屏幕对象
    pygame.display.set_caption("alien_invision")
    #创建按钮类 形参msg对应的实参为“play”
    play_button = Button(ai_settings,screen,"Play")
    #创建一个用于储存游戏信息统计的实例
    stats = GameStats(ai_settings)
    #创建记分牌实例
    sb = Scoreboard(screen,ai_settings,stats)
    #创建一艘飞船
    ship = Ship(ai_settings,screen)
    #创建一个子弹编组
    bullets = Group()
    #创建一个外星人编组
    aliens = Group()
    # 使用gf模块中的函数绘制外星人群
    gf.create_fleet(ai_settings,screen,ship,aliens)
    #开始游戏主循环
    while True:
        #使用模块game_funcations中的方法
        #check_events检测按键和鼠标点击事件
        gf.check_events(ai_settings,ship,screen,bullets,stats,play_button,aliens,sb)
        #当标志正确时运行游戏:
        if stats.game_active :
            #方法update()用于确定事件标志以及移动距离和方向
            ship.update()
            #使用更新后的gf的函数用于更新和删除子弹
            #检测外星人与子弹的碰撞,删除外星人与子弹  并创建外星人群
            gf.update_bullets(bullets,aliens,ai_settings,screen,ship,stats,sb )
            #由于外星人会被子弹击中,所以在绘制子弹之后绘制外星人
            gf.update_aliens(ai_settings,ship,aliens,stats,bullets,screen,sb)
        #方法update_screen用于更新绘制屏幕各元素
        gf.update_screen(ai_settings,ship,screen,aliens,bullets,stats,play_button,sb)
#运行游戏
run_game()