文章目录
在win10下安装第三方库pygame:
pip install pygame
文中代码均参考自python从入门到实践这一书,文中所用到的图片可到本书的官网python从入门到实践下载获取
或者用百度网盘下载:
链接:https://pan.baidu.com/s/1aU5WvduNPlxXmU33KT6Tkg
提取码:4ijn
创建文件夹,可以任意命名,这里我把它命名成alien_invasion_game,后面项目所创建的模块都要统一放到plane_game中。
创建飞船模块 ship.py:
import pygame
from pygame.sprite import Sprite
class Alien(Sprite):
def __init__(self, ai_settings, screen):
super().__init__()
self.screen = screen
self.ai_settings = ai_settings
self.image = pygame.image.load('images/alien.bmp')
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 check_edges(self):
screen_rect = self.screen.get_rect()
if self.rect.right >= screen_rect.right:
return True
elif self.rect.left <= 0:
return True
def update(self):
self.x += (self.ai_settings.alien_speed_factor*self.ai_settings.fleet_direction)
self.rect.x = self.x
Sprite精灵组就是一个列表,里面存储了许多个相同的飞船(后面的外星人,子弹的精灵组也是一样),精灵组便于对里面的元素进行统一的管理
创建子弹模块 bullet.py:
import pygame
from pygame.sprite import Sprite
class Bullet(Sprite):
def __init__(self, ai_setting, screen, ship):
"""在飞机所处位置创建子弹对象"""
super().__init__()
self.screen = screen
#在(0, 0)处创建一个表示子弹的矩形,再设置其正确的位置
self.rect = pygame.Rect(0, 0, ai_setting.bullet_width, ai_setting.bullet_height)
# 子弹并非基于图像,就像是从游戏窗口中挖出的一部分像素,因此我们必须使用pygame.Rect()类从空白处
# 开始创建一个矩形,必须提供矩形左上角的x,y坐标,和矩形的宽度和高度
self.rect.centerx = ship.rect.centerx
self.rect.top = ship.rect.top
#存储子弹的位置
self.y = float(self.rect.y)
self.color = ai_setting.bullet_color
self.speed_factor = ai_setting.bullet_speed_factor
#持续发射子弹
self.shoot_unstop = False
def update(self):
"""更新子弹的位置"""
self.y -= self.speed_factor
self.rect.y = self.y
def draw_bullet(self):
pygame.draw.rect(self.screen, self.color, self.rect)
# 与飞船在屏幕上显示的方式不同:子弹本身就是游戏窗口中的一部分(不是从游戏窗口外的图像导入的),
# 使用pygame.draw.rect()方法来填充表示子弹的rect占据的屏幕部分
创建外星人模块 alien.py:
import pygame
from pygame.sprite import Sprite
class Alien(Sprite):
def __init__(self, ai_settings, screen):
super().__init__()
self.screen = screen
self.ai_settings = ai_settings
self.image = pygame.image.load('images/alien.bmp')
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 check_edges(self):
screen_rect = self.screen.get_rect()
if self.rect.right >= screen_rect.right:
return True
elif self.rect.left <= 0:
return True
def update(self):
self.x += (self.ai_settings.alien_speed_factor*self.ai_settings.fleet_direction)
self.rect.x = self.x
创建模块 settings.py :
用来管理游戏设置(管理游戏窗口的颜色,宽度,高度,可用飞船的艘树等等)
class Settings():
def __init__(self):
self.screen_width = 1000
self.screen_height = 600
self.bg_color = (230, 230, 230)
self.ship_limit = 3 # 可用飞船的总数
self.bullet_width = 1000
self.bullet_height = 15
self.bullet_color = (60, 60, 60)
self.bullets_allowed = 5 # 可让最多个子弹在同一个屏幕上
self.fleet_drop_speed = 100
self.speed_scale = 1.1
self.score_scale = 1.5 # 随着外星人速度的增加,其分值也随之增加
self.initialize_dynamic_settings()
def initialize_dynamic_settings(self):
"""随着游戏进行,这些属性会发生变化"""
self.ship_speed_factor = 1.5
self.bullet_speed_factor = 2
self.alien_speed_factor = 1
self.fleet_direction = 1 #表示飞船的运行方向,为什么不用文本 right, left
#来描述?按照书中所述:若用文本表示,就需要使用if...else...来判断外形人的移
#动方向,鉴于只有两个方向(左或者右),我们可使用 1 和 -1 来分别代表他们,两
#者进行转换的时候只需 乘 -1,非常的方便
self.alien_points = 50
def increase_speed(self):
self.ship_speed_factor *= self.speed_scale
self.bullet_speed_factor *= self.speed_scale
self.alien_speed_factor *= self.speed_scale
self.alien_points = int(self.alien_points * self.alien_speed_factor)
创建 game_stats.py :
用来统计当前的分数,游戏历史的最高分数,此模块用来跟踪游戏的统计信息:
class GameStats():
"""跟踪游戏的统计信息"""
def __init__(self, ai_settings):
self.ai_settings = ai_settings
self.reset_stats()
self.game_active = False
self.score = 0
self.high_score = 0
def reset_stats(self):
#当使用完一开始设定好艘数的飞船时,我们要重置游戏,使其恢复到最初状态
self.ship_left = self.ai_settings.ship_limit
self.score = 0
self.level = 1
创建scoreboard.py 模块 :
把所统计的分数,当前飞船等级显示在游戏窗口中
import pygame.font
from pygame.sprite import Group
from ship import Ship
class Scoreboard():
def __init__(self, ai_settings, screen, stats):
self.screen = screen
self.screen_rect = screen.get_rect()
self.ai_settings = ai_settings
self.stats = stats
self.text_color = (30, 30, 30)
self.font = pygame.font.SysFont(None, 35)
self.prep_score()
self.prep_high_score()
self.prep_level()
self.prep_ships()
def prep_score(self):
"""把文字转换成图像"""
rounded_score = int(round(self.stats.score, -1))
score_str = "{:,}".format(rounded_score)
self.score_image = self.font.render(score_str, True, self.text_color, self.ai_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 = int(round(self.stats.high_score, -1))
high_score_str = "{:,}".format(high_score)
self.high_score_image = self.font.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.top = 10
self.high_score_rect.centerx = self.screen_rect.centerx
def prep_level(self):
self.level_image = self.font.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
def prep_ships(self):
self.ships = Group()
for ship_number in range(self.stats.ship_left):
ship = Ship(self.ai_settings, self.screen)
ship.rect.x = 10 + ship_number*ship.rect.width
ship.rect.y = 10
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) #貌似精灵组都是用draw???
创建 button.py模块:
当游戏处于非活跃状态时,我么需要通过点击屏幕上的特定位置来使游戏启动
import pygame.font
class Button():
def __init__(self, ai_settings, screen, msg):
self.screen = screen
self.screen_rect = screen.get_rect()
self.width, self.height = 200, 50
self.button_color = (0, 162, 232)
self.text_color = (255, 255, 255)
self.font = pygame.font.SysFont(None, 48)
# 创建按钮的rect对象,并使其居中(相当于一个容纳字符串的容器)
self.rect = pygame.Rect(0, 0, self.width, self.height)
self.rect.center = self.screen_rect.center
self.pre_msg(msg)
def pre_msg(self, msg):
"""将要显示的字符串 渲染为图像来处理文本,并将字符串在按钮中居中显示"""
self.msg_image = self.font.render(msg, True, self.text_color, self.button_color) # 使用font.render将文本转换成图像
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)
创建 game_functions.py 模块:
为了使主模块函数(也就是启动游戏的函数)变得更简洁和更容易拓展并使其逻辑更容易理解,统一的管理游戏运行的函数
import sys
import pygame
from bullet import Bullet
from alien import Alien
from time import sleep
def check_keydown_events(event, ai_setting, screen, ship, bullets):
#if event.type == pygame.KEYDOWN: #若事件类型是按下方向键盘的按键,但是当玩家持续按住方向键时
if event.key == pygame.K_RIGHT:
# ship.rect.x += 1 #飞船并不能持续移动
ship.moving_right = True
elif event.key == pygame.K_LEFT:
ship.moving_left = True
elif event.key == pygame.K_SPACE:
# if len(bullets) < ai_setting.bullets_allowed:
# new_bullet = Bullet(ai_setting, screen, ship)
# bullets.add(new_bullet)
fire_bullet(ai_setting, screen, ship, bullets)
elif event.key == pygame.K_q:
sys.exit()
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_play_button(stats, play_button, mouse_x, mouse_y, ai_settings, screen, sb, ship, aliens, bullets):
button_clicked = play_button.rect.collidepoint(mouse_x, mouse_y)
if button_clicked and not stats.game_active:
pygame.mouse.set_visible(False)
ai_settings.initialize_dynamic_settings()
#重置游戏统计信息
stats.reset_stats()
stats.game_active = True
#重置记分牌图像
sb.prep_level()
sb.prep_score()
sb.prep_high_score()
aliens.empty()
bullets.empty()
create_fleet(ai_settings, screen, ship, aliens)
ship.center_ship()
def check_events(ai_setting, screen, sb, ship, bullets, aliens, stats, play_button):
for event in pygame.event.get():
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, ai_setting, screen, sb, ship, aliens, bullets)
elif event.type == pygame.KEYDOWN: # 表示按下键盘
check_keydown_events(event, ai_setting, screen, ship, bullets)
elif event.type == pygame.KEYUP:
check_keyup_events(event, ship)
def update_bullets(ai_settings, screen,stats, sb, ship, aliens, bullets):
bullets.update()
for bullet in bullets.copy(): #子弹越过游戏窗口后并没有从内存中消失,只是其y轴坐标不断减小,
# 在屏幕中没有显示而已, 必须要将越过游戏窗口的子弹从内存中删除,
if bullet.rect.bottom <=0: # 注意删除列表的方式!!!
bullets.remove(bullet)
check_bullet_alien_collision(ai_settings, screen,stats, sb, ship, aliens, bullets)
# 检查是否有子弹集中了外形人
# collisions = pygame.sprite.groupcollide(bullets, aliens, False, True)
#这行代码先遍历bullets中的每颗子弹,再遍历aliens中的每个外星人,当有子弹和外星人的rect重叠时,groupcollide()
#就在它返回的字典中添加一个键-值对,两个实参True表示要将子弹和外星人删除
def check_bullet_alien_collision(ai_settings, screen, stats, sb, ship, aliens, bullets):
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
if collisions:
# stats.score += ai_settings.alien_points #这种情况会导致宽子弹击中多个外星人时分数也只能增加击中
# sb.prep_score() #击中一个外星人的分数
for aliens in collisions.values():
stats.score += ai_settings.alien_points * len(aliens)#collisions是一个字典,bullet是字典中的键
sb.prep_score() #键对应的值是一个列表,列表内存储的是该bullet所击中的外星人
check_high_score(stats, sb)
if len(aliens) == 0:
bullets.empty()
ai_settings.increase_speed()
stats.level += 1
sb.prep_level()
create_fleet(ai_settings, screen, ship, aliens)
def fire_bullet(ai_setting, screen, ship, bullets):
if len(bullets) < ai_setting.bullets_allowed:
new_bullet = Bullet(ai_setting, screen, ship)
bullets.add(new_bullet)
def update_screen(ai_setting, screen, ship, bullets, aliens, play_button, stats, sb):
screen.fill(ai_setting.bg_color) # 该方法只接受颜色这一个参数,每次循环都重回屏幕
for bullet in bullets: # 不能按照书本中的 for bullet in bullets.sprites()
bullet.draw_bullet()
sb.show_score()
ship.blitme() # 飞船必须放在背景色的后面,否则,每次屏幕被刷新后,飞机都会被覆盖
aliens.draw(screen)
if not stats.game_active:
play_button.draw_button()
pygame.display.flip() #该命令只显示 Pygame 最近绘制的屏幕,该命令在 while 循环中,使得屏幕不断更新,每次都是显示
#元素的最新位置,从而营造出平滑移动的效果
def get_number_aliens_x(ai_settings, alien_width):
availabe_space_x = ai_settings.screen_width - 2*alien_width
num_alien_x = int(availabe_space_x / (2*alien_width))
return num_alien_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
alien.x = alien_width + 2*alien_number*alien_width
alien.rect.x = alien.x
alien.rect.y = alien.rect.height + 2*alien.rect.height*row_number
aliens.add(alien)
def create_fleet(ai_settings, screen, ship, aliens):
# alien = Alien(ai_setting, screen) # 只是为了获得外星人的高度与宽度等属性,它并不是外星人群的成员
alien = Alien(ai_settings, screen)
number_rows = get_number_rows(ai_settings, ship.rect.height, alien.rect.height)
num_alien_x = get_number_aliens_x(ai_settings, alien.rect.width)
for row_number in range(number_rows):
for alien_number in range(num_alien_x):
create_alien(ai_settings, screen, aliens, alien_number, row_number)
def change_fleet_direction(ai_settings, aliens):
for alien in aliens.sprites():
alien.rect.y += ai_settings.fleet_drop_speed
ai_settings.fleet_direction *= -1
def check_fleet_edges(ai_settings, aliens):
for alien in aliens.sprites():
if alien.check_edges():
change_fleet_direction(ai_settings, aliens)
break
def update_aliens(ai_settings, stats, screen, sb, ship, aliens, bullets):
check_fleet_edges(ai_settings, aliens)
aliens.update()
if pygame.sprite.spritecollideany(ship, aliens):
# spritecollideany接受两个参数:一个精灵组和一个编组,它遍历精灵组是否有成员与编组发生碰撞,有的话就停止遍历
# 并返回与编组发生碰撞的精灵组成员,若没有碰撞则返回None
# 检测子弹和外星人碰撞用的groupcollide,接受的两个参数都是精灵组
ship_hit(ai_settings, stats, screen, sb, ship, aliens, bullets)
check_aliens_bottom(ai_settings, stats, screen, sb, ship, aliens, bullets)
def ship_hit(ai_settings, stats, screen, sb, ship, aliens, bullets):
"""当飞船与外星人碰撞时,就创建一群新的外星人,并将飞船重置到屏幕低端中央"""
if stats.ship_left > 0:
stats.ship_left -= 1
sb.prep_ships()
aliens.empty()
bullets.empty()
create_fleet(ai_settings, screen, ship, aliens)
ship.center_ship()
sleep(0.5)
else:
stats.game_active = False
pygame.mouse.set_visible(True)
def check_aliens_bottom(ai_settings, stats, screen, sb, ship, aliens, bullets):
"""如果外星人的底部移动到了游戏窗口的底部,那么程序就进行与飞船与外星人撞击相同的行为"""
screen_rect = screen.get_rect()
for alien in aliens.sprites():
if alien.rect.bottom >= screen_rect.bottom:
ship_hit(ai_settings, stats, screen, sb, ship, aliens, bullets)
break
def check_high_score(stats, sb):
if stats.score > stats.high_score:
stats.high_score = stats.score
sb.prep_high_score()
创建alien_invasion.py 模块:
然后就是我们的启动游戏函数了,它存放在了 alien_invasion.py 模块中:
"""""
import pygame
from settings import Settings
from ship import Ship
import game_functions as gf
from pygame.sprite import Group
from game_stats import GameStats
from button import Button
from scoreboard import Scoreboard
def run_game():
pygame.init()
ai_setting = Settings()
screen = pygame.display.set_mode((ai_setting.screen_width, ai_setting.screen_height)) #创建一个名为screen的显示窗口
pygame.display.set_caption("Alien Invasion")
play_button = Button(ai_setting, screen, "Play")
ship = Ship(ai_setting, screen)
bullets = Group()
aliens = Group()
gf.create_fleet(ai_setting, screen, ship, aliens) # 这个创建外星人群千万不要放到了while循环内
stats = GameStats(ai_setting)
sb = Scoreboard(ai_setting, screen, stats)
while True:
gf.check_events(ai_setting, screen, sb, ship, bullets, aliens, stats, play_button)
if stats.game_active:
ship.update()
# bullets.uprdate()
#
# for bullet in bullets.copy(): #子弹越过游戏窗口后并没有从内存中消失,只是其y轴坐标不断减小,在屏幕中没有显示而已
# # 必须要将越过游戏窗口的子弹从内存中删除,
# if bullet.rect.bottom <=0:
# bullets.remove(bullet)
gf.update_bullets(ai_setting, screen, stats, sb, ship, aliens, bullets)
gf.update_aliens(ai_setting, stats, screen, sb, ship, aliens, bullets)
# screen.fill(ai_setting.bg_color) # 该方法只接受颜色这一个参数,每次循环都重回屏幕
# ship.blitme() # 飞船必须放在背景色的前面,否则,每次屏幕被刷新后,飞机都会被覆盖
#
# pygame.display.flip() #该命令只显示 Pygame 最近绘制的屏幕,该命令在 while 循环中,使得屏幕不断更新,每次都是显示
# #元素的最新位置,从而营造出平滑移动的效果
gf.update_screen(ai_setting, screen, ship, bullets, aliens, play_button, stats, sb)
run_game()
最终的游戏效果:
最后做个总结的话:我最大的体会就是深刻地认识到了重构这一设计理念,真的是大大地提高了代码的拓展,代码堆在一起确实不好理解,也可能是本人的描述不行,如果各位看官有兴趣的话,你们也可以去找原书仔细阅读,肯定会比看此文收获地更多。