项目总结
尽量遵守PEP8编码规范,使代码更加整洁易读;
重点理解内容:
- 坐标系统: pygame中的坐标系统原点位于屏幕的左上角,向右是x轴的正向,向下是y轴的正向。所有对象的位置都是相对于原点的。
- Surface对象:在pygame中,surface是一个代表图像的对象, 它是所有图像(如飞船,外星人,子弹)和图形(如绘制文本)的载体。在项目中使用了
pygame.image.load('image.bmp')
和pygame.font.SysFont.render(str, antialias, str_color, backgroud_color)
来获取相关的surface。surface的返回值形如:<Surface(宽度x高度x颜色深度 渲染方式)>
,比如飞船为:<Surface(48x48x32 SW)>
。
- Rect对象:Rect是Pygame中用于表示矩形区域的对象,它包含了矩形的位置和大小信息。每个surface对象都会有一个对应的rect对象,通过使用rect可以改变surface的位置,以及判断是否碰撞。在项目中使用了
pygame.image.load('image.bmp').get_rect
来获取surface的rect对象。rect的返回值形如<rect(x坐标, y坐标, 宽度, 高度)>
, 比如飞船的rect为:<rect(0, 0, 48, 48)>
。
- 编组:在pygame中,编组是一个特殊的列表,它可以存储游戏中所有的精灵(sprite)。比如可以管理所有的子弹或者外星人。当对编组调用方法时,pygame会自动对编组中的每个精灵调用该方法,比如子类重写的update()可以一次性更新所有精灵的相关状态,父类的draw()可以一次性绘制组中的所有元素(draw()期望的属性名分别是iamge和rect,故属性名不能随意修改)。在项目中使用了
pygame.sprite.Group()
来创建编组对象。
- 绘制空白矩形:项目中的子弹,开始按钮都是通过创建矩形再绘制出来的,代码基本步骤为:首先使用
pygame.Rect(x坐标, y坐标, 宽度, 高度)
创建一个矩形(rect对象), 然后使用位置属性(如center, right, top)修改坐标,最后使用pygame.draw.rect(surface, color, rect)
将矩形以指定color绘制在surface的rect位置上。
- 将字符串绘制到屏幕:项目中的分数、等级、飞船数、按钮框上的提示,都需要绘制字符串,代码基本步骤为:首先使用
font = pygame.font.SysFont(fontname, size)
创建字体对象,然后使用str_image = font.render(str, antialias, str_color, backgroud_color)
将字str渲染为一个surface图形对象, 再使用get_rect()
获取surface()的rect对象,使用位置属性(如center, right, top)修改坐标,最后使用blit(surface, rect)
将图像绘制在rect对象指定的位置上。对于飞船数,由于使用了编组进行管理,因此使用的是飞船Ship类的父类Sprite的draw()对每个精灵(飞船)进行绘制。
- 外星人移动:外星人在屏幕上的移动是按照一定的方向和速度进行的。它们首先持续向右移动,当碰到屏幕的右边缘时开始向下移动指定像素位,并改变方向持续向左移动。同理,当移动到屏幕左边缘时,它们也会向下移动并改变方向开始向右移动。
- 碰撞处理:外星人与飞船或子弹的碰撞处理:使用
pygame.sprite.spritecollideany(sprite, group)
来检测飞船和外星人编组中的每个精灵之间的碰撞,使用pygame.sprite.groupcollide(groupa, groupa, dokilla, dokillb)
来检测子弹编组中的任意精灵和外星人编组中的任意精灵是否发生碰撞。
项目模块
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 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
| import sys import pygame from time import sleep
from settings import Settings from ship import Ship from bullet import Bullet from alien import Alien from game_stats import Gamestats from button import Button from scoreboard import Scoreboard
class AlienInvasion: """管理游戏资源和行文的类""" def __init__(self): pygame.init() self.settings = Settings() self.screen = pygame.display.set_mode((self.settings.screen_width, self.settings.screen_height)) pygame.display.set_caption("Alien Invation") self.clock = pygame.time.Clock() self.ship = Ship(self) self.stats = Gamestats(self) self.bullets = pygame.sprite.Group() self.aliens = pygame.sprite.Group() self._create_fleet() self.game_active = False self.play_button = Button(self, 'Play') self.sb = Scoreboard(self)
def run_game(self): """开始游戏的主循环""" while True: self._check_events()
if self.game_active: self.ship.update() self._update_bullets() self._update_aliens()
self._update_screen() self.clock.tick(60)
def _check_events(self): """响应按键和鼠标事件""" for event in pygame.event.get(): if event.type == pygame.QUIT: sys.exit()
elif event.type == pygame.KEYDOWN: self._check_keydown_events(event)
elif event.type == pygame.KEYUP: self._check_keyup_events(event)
elif event.type == pygame.MOUSEBUTTONDOWN: mouse_pos = pygame.mouse.get_pos() self._check_play_button(mouse_pos)
def _check_play_button(self, mouse_pos): """在玩家单击Play按钮时开始新游戏""" button_cliked = self.play_button.rect.collidepoint(mouse_pos) if button_cliked and not self.game_active: self.settings.initialize_dynamic_settings() self.stats.reset_stats() self.sb.prep_score() self.sb.prep_level() self.sb.prep_ships() self.game_active = True
self.bullets.empty() self.aliens.empty()
self._create_fleet() self.ship.center_ship()
pygame.mouse.set_visible(False)
def _check_keydown_events(self, event): """响应按下""" if event.key == pygame.K_q: sys.exit() elif event.key == pygame.K_RIGHT: self.ship.moving_right = True elif event.key == pygame.K_LEFT: self.ship.moving_left = True elif event.key == pygame.K_SPACE: self._fire_bullet()
def _check_keyup_events(self, event): """响应释放""" if event.key == pygame.K_RIGHT: self.ship.moving_right = False elif event.key == pygame.K_LEFT: self.ship.moving_left = False
def _fire_bullet(self): """检测到按下空格事件后会创建一颗子弹, 并将其加入编组bullets""" if len(self.bullets) < self.settings.bullet_allowed: new_bullet = Bullet(self) self.bullets.add(new_bullet)
def _update_bullets(self): """移动子弹的位置并删除已消失的子弹以及判断子弹是否击中外星人并且是否消灭光""" self.bullets.update()
for bullet in self.bullets.copy(): if bullet.rect.bottom <= 0: self.bullets.remove(bullet)
self._check_bullet_alien_collisions()
def _check_bullet_alien_collisions(self): """检查是否有子弹击中了外星人, 如果是, 就删除相应的子弹和外星人""" collisions = pygame.sprite.groupcollide(self.bullets, self.aliens, False, True)
if collisions: for aliens in collisions.values(): self.stats.score += self.settings.alien_points * len(aliens) self.sb.prep_score() self.sb.check_high_score()
if not self.aliens: self.bullets.empty() self._create_fleet() self.settings.increase_speed()
self.stats.level += 1 self.sb.prep_level()
def _create_fleet(self): """创建一个外星舰队(一屏幕外星人)""" alien = Alien(self) alien_width, alien_height = alien.rect.size
current_x, current_y = alien_width, alien_height while current_y < (self.settings.screen_height - 2 * alien_height): while current_x < (self.settings.screen_width - 2 * alien_width): self._create_alien(current_x, current_y) current_x += 2 * alien_width
current_x = alien.rect.x current_y += alien.rect.y
def _create_alien(self, x_position, y_position): """创建一个外星人并将其加入外星舰队""" new_alien = Alien(self) new_alien.x = x_position new_alien.rect.x = x_position new_alien.rect.y = y_position self.aliens.add(new_alien)
def _update_aliens(self): """检查是否有外星人位于左右屏幕边缘以及是否发生碰撞; 更新外星舰队中所有外星人的位置""" self._check_fleet_edges() self.aliens.update()
if pygame.sprite.spritecollideany(self.ship, self.aliens): self._ship_hit()
self._check_aliens_bottom()
def _check_fleet_edges(self): """在有外星人到达边缘时采取相应的措施""" for alien in self.aliens.sprites(): if alien.check_edges(): self._change_fleet_direction() break
def _change_fleet_direction(self): """将所有外星人向下移动, 并改变他们的方向""" for alien in self.aliens.sprites(): alien.rect.y += self.settings.fleet_drop_speed self.settings.fleet_direction *= -1
def _check_aliens_bottom(self): """检查是否有外星人到达了屏幕下边缘并做出处理;当飞船处于上方外星人被消灭的列时,其他列就有可能被外星人触底""" for alian in self.aliens.sprites(): if alian.rect.bottom >= self.settings.screen_height: self._ship_hit() break
def _ship_hit(self): """响应外星人和飞船或者底部的碰撞, 并将飞船数量减1""" if self.stats.ships_left > 0: self.stats.ships_left -= 1 self.sb.prep_ships() print(f"飞船已战毁1艘,当前还剩下{self.stats.ships_left}艘!") self.bullets.empty() self.aliens.empty()
self._create_fleet() self.ship.center_ship()
sleep(0.3) else: self.game_active = False print("飞船已全部战毁!\n请重新开始游戏或退出。") pygame.mouse.set_visible(True)
def _update_screen(self): """更新屏幕上的图像, 并切换到新屏幕""" self.screen.fill(self.settings.bg_color) for bullet in self.bullets.sprites(): bullet.draw_bullet() self.ship.blitme() self.aliens.draw(self.screen) self.sb.show_score() if not self.game_active: self.play_button.draw_button() pygame.display.flip()
if __name__ == '__main__': ai = AlienInvasion() ai.run_game()
|
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
| import sys import pygame from pygame.sprite import Sprite
class Ship(Sprite): """管理飞船(ship)的类""" def __init__(self, ai_game): """初始化飞船并设置其初始位置""" super().__init__() self.screen = ai_game.screen self.screen_rect = ai_game.screen.get_rect() self.settings = ai_game.settings
try: self.image = pygame.image.load('images/ship.png') except FileNotFoundError: print("未找到飞船图像文件!") sys.exit() self.rect = self.image.get_rect()
self.rect.midbottom = self.screen_rect.midbottom self.x = float(self.rect.x)
self.moving_right = False self.moving_left = False
def update(self): """根据移动标志和活动范围调整飞船的位置""" if self.moving_right and self.rect.right < self.screen_rect.right: self.x += self.settings.ship_speed if self.moving_left and self.rect.left > self.screen_rect.left: self.x -= self.settings.ship_speed
self.rect.x = self.x
def center_ship(self): self.rect.midbottom = self.screen_rect.midbottom self.x = float(self.rect.x)
def blitme(self): """在指定位置绘制飞船""" self.screen.blit(self.image, self.rect)
|
billet.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
| import pygame from pygame.sprite import Sprite
class Bullet(Sprite): def __init__(self, ai_game): """在飞船的当前位置创建一个子弹对象""" super().__init__() self.screen = ai_game.screen self.settings = ai_game.settings self.color = self.settings.bullet_color
self.rect = pygame.Rect(0, 0, self.settings.bullet_width, self.settings.bullet_height) self.rect.midtop = ai_game.ship.rect.midtop
self.y = float(self.rect.y)
def update(self): """向上移动子弹""" self.y -= self.settings.bullet_speed self.rect.y = self.y
def draw_bullet(self): """在屏幕上绘制子弹""" pygame.draw.rect(self.screen, self.color, self.rect)
|
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 42
| import pygame from pygame.sprite import Sprite
class Alien(Sprite): """表示单个外星人的类"""
def __init__(self, ai_game): """初始化外星人并设置其起始位置""" super().__init__() self.screen = ai_game.screen self.settings = ai_game.settings
try: self.image = pygame.image.load("images\\alien.png") except FileNotFoundError: print("未找到外星人图像文件!") 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 check_edges(self): """如果外星人位于屏幕边缘,就返回True""" screen_rect = self.screen.get_rect() if self.rect.right > screen_rect.right: return True elif self.rect.left < screen_rect.left: return True
def update(self): """向右或向左移动外星人""" self.x += self.settings.alien_speed * self.settings.fleet_direction self.rect.x = self.x
|
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
| import pygame.font
class Button: """该类用于创建游戏按钮""" def __init__(self, ai_game, msg): self.screen = ai_game.screen self.screen_rect = self.screen.get_rect()
self.width, self.height = 200, 50 self.button_color = (72, 209, 204) self.text_color = (255, 255, 255) self.font = pygame.font.SysFont(None, 48)
self.rect = pygame.Rect(0, 0, self.width, self.height) self.rect.center = self.screen_rect.center
self._prep_msg(msg)
def _prep_msg(self, msg): """将msg渲染为图形,并使其在按钮上居中""" self.msg_image = self.font.render(msg, True, self.text_color, self.button_color) self.msg_image_rect = self.msg_image.get_rect() self.msg_image_rect.center = self.screen_rect.center
def draw_button(self): """绘制一个浅绿色填充的按钮框,再绘制纯白色的文本""" pygame.draw.rect(self.screen, self.button_color, self.rect) self.screen.blit(self.msg_image, self.msg_image_rect)
|
scoreboard.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
| import pygame from ship import Ship
class Scoreboard: """显示得分信息的类""" def __init__(self, ai_game): """初始化显示得分涉及的属性""" self.ai_game = ai_game self.screen = ai_game.screen self.screen_rect = self.screen.get_rect() self.settings = ai_game.settings self.stats = ai_game.stats
self.text_color = (30, 30, 30) self.font = pygame.font.SysFont(None, 20)
self.prep_score() self.prep_high_score() self.prep_level() self.prep_ships()
def prep_score(self): """将当前得分渲染成图像""" round_score = round(self.stats.score, -1) score_str = f"Score: {round_score:,}" self.score_image = self.font.render(score_str, True, self.text_color, self.settings.bg_color)
self.score_rect = self.score_image.get_rect() self.score_rect.right = self.screen_rect.right - 20 self.score_rect.top = 10
def prep_high_score(self): """将最高分渲染成图像""" high_score = round(self.stats.high_score, -1) high_score_str = f"High Score: {high_score:,}" self.high_score_image = self.font.render(high_score_str, True, self.text_color, self.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 check_high_score(self): """检查是否创造了更高分""" if self.stats.score > self.stats.high_score: self.stats.high_score = self.stats.score self.prep_high_score()
def prep_level(self): """将等级渲染成图像""" level_str = str(f"Level: {self.stats.level}") self.level_image = self.font.render(level_str, True, self.text_color, self.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 = pygame.sprite.Group() for ship_number in range(self.stats.ships_left): ship = Ship(self.ai_game) ship.rect.x = 10 + ship_number * ship.rect.width
ship.rect.y = 0 self.ships.add(ship)
def show_score(self): """在屏幕上绘制得分""" self.screen.blit(self.score_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_stats
1 2 3 4 5 6 7 8 9 10 11 12 13
| class Gamestats: """跟踪游戏的统计信息""" def __init__(self, ai_game): self.settings = ai_game.settings self.reset_stats() self.high_score = 0
def reset_stats(self): """初始化在游戏运行期间可能变化的统计信息""" self.ships_left = self.settings.ship_limit self.score = 0 self.level = 1
|
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 = 800 self.screen_height = 500 self.bg_color = "cadetblue4"
self.ship_limit = 3
self.bullet_width = 3 self.bullet_height = 15 self.bullet_color = (220, 220, 220) self.bullet_allowed = 30
self.fleet_drop_speed = 5.0
self.speedup_scale = 1.2 self.score_scale = 1.5
self.initialize_dynamic_settings()
def initialize_dynamic_settings(self): """初始化随游戏进行而变化的设置""" self.ship_speed = 9.5 self.bullet_speed = 8.5 self.alien_speed = 1.2 self.fleet_direction = 1 self.alien_points = 50
def increase_speed(self): """提高速度""" self.ship_speed *= self.speedup_scale self.bullet_speed *= self.speedup_scale self.alien_speed *= self.speedup_scale
self.alien_points = int(self.alien_points * self.score_scale) print(self.alien_points)
|
参考:
https://book.douban.com/subject/36365320/
https://www.pygame.org/docs/ref/color_list.html