14-1 按P开端新游戏 :鉴于游戏《外星人侵略》使用键盘来操控飞船,最好让玩家也能够经过按键来开端游戏。请增加让玩家在按P时开端游戏的代码。或许这样做 会有所协助:将check_play_button() 的一些代码提取出来,放到一个名为start_game() 的函数中,并在check_play_button() 和check_keydown_events() 中调用这个函数。

game_function.py

def start_game(ai_settings, screen, stats,ship, aliens,bullets):
    # 躲藏光标
    pygame.mouse.set_visible(False)
    # 重置游戏信息
    stats.reset_stats()
    stats.game_active = True
    # 清空外星人列表和子弹
    aliens.empty()
    bullets.empty()
    # 创立一群新外星人,并让飞船居中
    create_fleet(ai_settings, screen, ship, aliens)
    ship.center_ship()
def check_play_button(ai_settings, screen, stats, play_button, ship, aliens,
                      bullets, mouse_x, mouse_y):
    """在玩家点击Play按钮时开端新游戏"""
    button_clicked = play_button.rect.collidepoint(mouse_x, mouse_y)
    if button_clicked and not stats.game_active:
        start_game(ai_settings, screen, stats, ship, aliens, bullets)
def check_keydown_events(event, ai_settings, screen, stats, ship, aliens,
                         bullets):
    """呼应按键"""
    if event.key == pygame.K_RIGHT:
        ship.moving_right = True
    elif event.key == pygame.K_LEFT:
        ship.moving_left = True
    elif event.key == pygame.K_SPACE:
        fire_bullet(ai_settings, screen, ship, bullets)
    elif event.key == pygame.K_p:
        if not stats.game_active:
            start_game(ai_settings, screen, stats, ship, aliens, bullets)
    elif event.key == pygame.K_q:
        sys.exit()
def check_events(ai_settings, screen, stats, play_button, ship, aliens,
                 bullets):
    """呼应按键和鼠标事情"""
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()
        elif event.type == pygame.KEYDOWN:
            check_keydown_events(event, ai_settings, screen, stats, ship,
                                 aliens,
                                 bullets)
        elif event.type == pygame.KEYUP:
            check_keyup_events(event, ship)
        elif event.type == pygame.MOUSEBUTTONDOWN:
            mouse_x, mouse_y = pygame.mouse.get_pos()
            check_play_button(ai_settings, screen, stats, play_button, ship,
                              aliens, bullets, mouse_x, mouse_y)

alien_invasion.py

while True:
    gf.check_events(ai_settings, screen, stats, play_button, ship, aliens,
             bullets)

主要是把def check_play_button()里边的一些东西拿出来弄个新函数,再适当的调整呼号内的形参。但这个形参调整起来杂乱啊,是不是能够括号内的形参,都依照字母顺序排列。。。

14-2 射击操练 :创立一个矩形,它在屏幕右边际以固定的速度上下移动。然后,在屏幕左边际创立一艘飞船,玩家可上下移动该飞船,并射击前述矩形方针。增加一个用于开端游戏的Play按钮,在玩家三次未击中方针时结束游戏,并从头显现Play按钮,让玩家能够经过单击该按钮来从头开端游戏。

14-3 有一定难度的射击操练:以你为完结操练14-2而做的工作为基础,让标靶的移动速度随游戏进行而加快,并在玩家单击Play按钮时将其重置为初始值。

14-4 前史最高分:每当玩家封闭并从头开端游戏《外星人侵略》时,最高分都将被重置。请修复这个问题,调用sys.exit() 前将最高分写入文件,并当在GameStats 中初始化最高分时从文件中读取它。

这几问算是给整个pygame章节做了总结,一并写了。

  • 这个答复用的单纯的色彩,没有选择图片,看起来不怎样样但是不同的色块反而把不同的sprites显现的比较好。

  • 由于这个游戏我觉得还蛮难的,因而子弹设置的很大。当然这也能显现一颗子弹击中两个及以上方针时的成果。

  • 但目前还不知道怎样把左上角的剩下子弹的色彩给改了,暂时没找到办法,书上也没写这个。StackOverflow上这个还不好弄关键词搜索。

  • 这个答案同网络上其他的答复不一样的是右边的方块没有用一个小方块而是做了一个随机生成个数的sprite,我看有的答案是击中一个方块后就从头生成一个方块,这个答复是击中一切的之后从头生成。

初始画面:

Python编程:从入门到实践 第14章 pygame

游戏中画面:

Python编程:从入门到实践 第14章 pygame

setting.py

class Setting():
    def __init__(self):
        # 画布设置
        self.canvas_width = 1200
        self.canvas_height = 900
        self.canvas_bg_color = (105, 105, 105)
        # target设置
        self.target_direction = 1
        # self.target_drop_speed = 0.2 放在initialize_speed_settings中
        # ship设置
        # self.ship_speed = 0.5 放在initialize_speed_settings中
        # bullets设置
        self.bullet_width = 15
        self.bullet_height = 300
        self.bullet_color = (139, 0, 139)
        self.bullet_allowed = 1
        self.bullets_limit = 3
        # self.bullet_speed = 1.2 放在initialize_speed_settings中
        # 加快速度的scale
        self.speedup_scale = 1.1
        self.score_scale = 1.5
        # 加载初始设置
        self.initialize_speed_settings()
    def initialize_speed_settings(self):
        self.target_drop_speed = 0.2
        self.ship_speed = 0.5
        self.bullet_speed = 1.2
        self.target_points = 50
    def increase_speed(self):
        # 加速设置
        self.target_drop_speed *= self.speedup_scale
        # self.ship_speed *= self.speedup_scale
        # self.bullet_speed *= self.speedup_scale
        # 加速后得分设置
        self.target_points = int(self.target_points * self.score_scale)

ship.py

import pygame
class Ship():
    def __init__(self, canvas):
        self.canvas = canvas
        self.image = pygame.Surface((40, 40))
        pygame.Surface.fill(self.image, (205, 133, 63))
        self.rect = self.image.get_rect()
        self.canvas_rect = canvas.get_rect()
        # 按住不松移动飞机的ticker
        self.move_up = False
        self.move_down = False
        # 定位飞机方位
        self.rect.centery = self.canvas_rect.centery
        self.rect.left = self.canvas_rect.left
        # 纵坐标浮点
        self.center_y = float(self.rect.centery)
    def center_ship(self):
        # 使飞船居中
        self.center_y = self.canvas_rect.centery
    def blit_ship(self):
        # 画飞船
        self.canvas.blit(self.image, self.rect)
    def update_ship(self, settings):
        # 更新飞船方位
        if self.move_up and self.rect.top > 0:
            self.center_y -= settings.ship_speed
        elif self.move_down and self.rect.bottom < self.canvas_rect.bottom:
            self.center_y += settings.ship_speed
        # 将纵坐标传回center y
        self.rect.centery = self.center_y

target.py

import pygame
from pygame.sprite import Sprite
class Target(Sprite):
    def __init__(self, canvas):
        super(Target, self).__init__()
        # 画面设置
        self.canvas = canvas
        self.image = pygame.Surface((80, 50))
        pygame.Surface.fill(self.image, (60, 179, 113))
        # 获取外接矩形
        self.rect = self.image.get_rect()
        self.canvas_rect = canvas.get_rect()
        # 将target定位在画面右边
        self.rect.x = self.canvas_rect.width - self.rect.width
        self.rect.y = self.rect.height
        # 纵坐标浮点表明
        self.y = float(self.rect.y)
    def check_edge(self):
        # 查看target是否移动到画面边际
        if self.rect.bottom >= self.canvas_rect.bottom:
            return True
        elif self.rect.top <= self.canvas_rect.top:
            return True         # 两个都是return True
    def update(self, settings, *args, **kwargs) -> None:
        # 更新target方位
        self.y += (settings.target_drop_speed * settings.target_direction)
        # 将浮点的纵坐标传回rect.y
        self.rect.y = self.y

scoreboard.py

import pygame.font
from pygame.sprite import Group
from bullet import Bullet
class Scoreboard():
    def __init__(self, canvas, settings, ship, stats):
        self.canvas = canvas
        self.canvas_rect = canvas.get_rect()
        self.settings = settings
        self.stats = stats
        # 文本色彩及使用系统默认字体
        self.text_color = (250, 235, 215)
        self.font = pygame.font.SysFont(None, 48)
        # 调用将文本烘托制作成图片的函数
        self.prep_score()
        self.prep_high_score()
        self.prep_level()
        self.prep_bullet(canvas, settings, ship)
    def prep_bullet(self, canvas, settings, ship):
        # 画面左上方画剩下子弹数量,将子弹编组
        self.bullets = Group()
        # 遍历剩下的子弹stats.bullets_left
        for bullet_number in range(self.stats.bullets_left):
            # 遍历一个生成一颗子弹
            bullet = Bullet(canvas, settings, ship)
            # 定位生成的子弹的x,y坐标
            bullet.rect.x = 10 + bullet_number * bullet.rect.width * 5  # 为显现清晰、拉大距离
            bullet.rect.y = 10
            # 将生成的子弹参加编组
            self.bullets.add(bullet)
    def prep_score(self):
        # 将成果取整round()。round()的第二个参数确定小数位数,若负,则准确点小数点前(负)位。
        rounded_score = int(round(self.stats.score, -1))
        # 行将显现的文字内容
        score_str = "Current Score: " + "{:,}".format(rounded_score)
        # current score这两个单词若用中文书写,则显现方框。
        # baidu到解决办法:将一个中文字体文件放入项目的文件夹下:
        # 原来的 self.font=pygame.font.SysFont(None,45)
        # 改成  self.font=pygame.font.Font(‘STZHONGS.TTF’,45) 这儿SysFont要改为Font
        # 烘托文字为图片
        self.score_image = self.font.render(score_str, True, self.text_color,
                                            self.settings.canvas_bg_color)
        # 定位烘托成图片的文字方位
        self.score_rect = self.score_image.get_rect()
        self.score_rect.centerx = self.canvas_rect.centerx
        self.score_rect.top = self.canvas_rect.top + 20
    def prep_high_score(self):
        # 将high score取整
        rounded_high_score = int(round(self.stats.high_score, -1))
        # 行将显现的high score的文字
        high_score_str = "High Score: " + "{:,}".format(rounded_high_score)
        # 烘托high score
        self.high_score_image = self.font.render(high_score_str, True,
                                                 self.text_color,
                                                 self.settings.canvas_bg_color)
        # 定位high score方位
        self.high_score_rect = self.high_score_image.get_rect()
        self.high_score_rect.centerx = self.canvas_rect.centerx
        self.high_score_rect.bottom = self.canvas_rect.bottom - 20
    def prep_level(self):
        # 烘托level的文字为图片
        self.level_image = self.font.render(
            "Current Level: " + str(self.stats.level), True, self.text_color,
            self.settings.canvas_bg_color)
        # 定位level的方位
        self.level_rect = self.level_image.get_rect()
        self.level_rect.centerx = self.canvas_rect.centerx
        self.level_rect.top = self.canvas_rect.top + 60
    def show_score(self):
        # 在canvas上显现烘托成图片的文字
        self.canvas.blit(self.score_image, self.score_rect)
        self.canvas.blit(self.high_score_image, self.high_score_rect)
        self.canvas.blit(self.level_image, self.level_rect)
        self.bullets.draw(self.canvas)

button.py

import pygame
class Button():
    # 开端按钮类
    def __init__(self, canvas, msg):
        self.canvas = canvas
        self.canvas_rect = canvas.get_rect()
        # 开端按钮的巨细
        self.width = 200
        self.height = 50
        # 开端按钮的按钮背景色
        self.button_color = (219, 112, 147)
        # 开端按钮上文字色彩
        self.text_color = (255, 255, 255)
        # 开端按钮的字体
        self.font = pygame.font.SysFont(None, 48)
        # 在0,0处生成开端按钮
        self.rect = pygame.Rect(0, 0, self.width, self.height)
        # 将按钮移动到画面中心
        self.rect.center = self.canvas_rect.center
        # 调用prep_msg进行文字至图片的烘托
        self.prep_msg(msg)
    def prep_msg(self, 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.rect.center
    def draw_button(self):
        # 制作一个用色彩填充的按钮,然后制作按钮上的文本
        self.canvas.fill(self.button_color, self.rect)
        self.canvas.blit(self.msg_image, self.msg_image_rect)

bullet.py

import pygame
from pygame.sprite import Sprite
class Bullet(Sprite):
    def __init__(self, canvas, settings, ship):
        super(Bullet, self).__init__()
        self.canvas = canvas
        # bullet假如直接用pygame.Rect,则无法直接制作剩下子弹的图画。
        # 提示Rect方针没有image
        self.image = pygame.Surface(
            (settings.bullet_width, settings.bullet_height))
        self.rect = self.image.get_rect()
        # 定位子弹中轴在ship的中轴、右部在ship的右部。
        self.rect.centery = ship.rect.centery
        self.rect.right = ship.rect.right
        # 子弹色彩设置
        self.color = settings.bullet_color
        # 子弹的横坐标浮点
        self.x = float(self.rect.x)
    def draw_bullet(self):
        # 在canvas上画子弹
        pygame.draw.rect(self.canvas, self.color, self.rect)
    def update(self, settings, *args, **kwargs) -> None:
        # 更新子弹移动方位
        self.x += settings.bullet_speed
        # 将浮点的横坐标代入rect x
        self.rect.x = self.x

game_stats.py

class GameStats():
    def __init__(self, settings):
        self.settings = settings
        # 引入rest_stats的设置
        self.reset_stats()
        # 初始时画面停止
        self.game_active = False
        self.high_score = 0
    def reset_stats(self):
        self.bullets_left = self.settings.bullets_limit
        self.score = 0
        self.level = 1

game_function.py

import sys
import pygame
from random import randint
from time import sleep
from bullet import Bullet
from target import Target
def update_canvas(canvas, play_button, sb, settings, ship, stats, targets):
    """更新画布上的内容"""
    # 填充色彩
    canvas.fill(settings.canvas_bg_color)
    # 显现飞船
    ship.blit_ship()
    # 显现targets的sprite
    targets.draw(canvas)
    # 显现记分牌
    sb.show_score()
    # 假如游戏处于非活动状况,则制作按钮
    if not stats.game_active:
        play_button.draw_button()
def create_target(canvas, settings, targets):
    """创立画面右侧标靶"""
    target = Target(canvas)
    target_height = target.rect.height
    # 测算画面能放多少行target
    num_row = int((settings.canvas_height - 2 * target.rect.height) / (
            2 * target_height))
    # 在1和num_row之间随机生成方针target
    for target_number in range(randint(1, num_row)):
        target = Target(canvas)
        # 每一个target的方位
        target.y = target_height + 2 * target_height * target_number
        # 将target.y的浮点值赋予rect.y
        target.rect.y = target.y
        # 将生成的target参加sprites编组
        targets.add(target)
def check_target_sprite_edges(settings, targets):
    """侦测子弹和标靶的磕碰"""
    # 查看sprites是否碰到边际
    for target in targets.sprites():
        if target.check_edge():
            # 假如碰到,则设置setting中的target_direction变换方向
            settings.target_direction *= -1
            break
def update_targets(settings, targets):
    """改写画面右侧标靶"""
    # 查看target是否碰到边际
    check_target_sprite_edges(settings, targets)
    # 碰到边际之后更新target方向
    targets.update(settings)
def update_bullets(bullets, canvas, sb, settings, ship, stats, targets):
    """改写子弹"""
    # 按住space开战之后在canvas上制作每一个bullet
    for bullet in bullets.sprites():
        bullet.draw_bullet()
    # # 删去画面外的bullet
    # 遍历bullets副本中的每一颗子弹
    for bullet in bullets.copy():
        # 若子弹的左边超过了canvas的宽度
        if bullet.rect.left > settings.canvas_width:
            # 从编组中移除这颗子弹
            bullets.remove(bullet)
            # stats中剩下子弹减1(用于检测游戏结束)
            stats.bullets_left -= 1
            # 每打飞一颗子弹,停顿0.5秒
            sleep(0.5)
            # 每打飞一颗子弹,则从头制作画面左上方的剩下子弹的图案
            sb.prep_bullet(canvas, settings, ship)
        # 假如子弹数量为0
        if stats.bullets_left == 0:
            # 游戏状况改为false
            stats.game_active = False
            # 从头在画面内显现鼠标
            pygame.mouse.set_visible(True)
    # 每发射一颗子弹则调用函数查看子弹和方针是否右磕碰
    check_bullet_target_collision(bullets, canvas, sb, settings, stats,
                                  targets)
    # 更新bullets 的移动轨迹
    bullets.update(settings)
def check_high_score(sb, stats):
    """查询当时成果和最好成果"""
    # 假如当时成果大于最好成果
    if stats.score > stats.high_score:
        # 将当时成果赋予最好成果
        stats.high_score = stats.score
        # 从头制作画面上的最好成果
        sb.prep_high_score()
def check_bullet_target_collision(bullets, canvas, sb, settings, stats,
                                  targets):
    """查看子弹和target是否磕碰"""
    collisions = pygame.sprite.groupcollide(bullets, targets, True, True)
    if collisions:
        # 在check_bullet_alien_collisions() 中,与外星人磕碰的子弹都是字典
        # collisions 中的一个键;而与每颗子弹相关的值都是一个列表,其中包含该子弹撞到的
        # 外星人。我们遍历字典collisions确保将消灭的每个外星人的点数都记入得分:
        # {key: value} 为 {bullet:[击中的外星人1, 击中的2,击中的3... 击中的n]}
        for x in collisions.values():
            stats.score += settings.target_points * len(x)
            sb.prep_score()
        check_high_score(sb, stats)
        # 若无以上的遍历,则有可能一颗子弹打中两个但只显现一个分数。该字典中每颗子弹为key,而
        # value为其击中的target。因而遍历该字典的value,找出一颗子弹击中好几个target的情况。
    # 若target悉数击中,剩下0
    if len(targets) == 0:
        # 清空子弹
        bullets.empty()
        # 增大游戏速度
        settings.increase_speed()
        # level加1
        stats.level += 1
        # 从头制作canvas上的level
        sb.prep_level()
        # 从头生成一批target
        create_target(canvas, settings, targets)
def check_events(bullets, canvas, play_button, sb, settings, ship, stats,
                 targets):
    """获取游戏事情"""
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()
        elif event.type == pygame.KEYDOWN:
            check_keydown_events(bullets, canvas, event, settings, ship)
        elif event.type == pygame.KEYUP:
            check_keyup_events(event, ship)
        elif event.type == pygame.MOUSEBUTTONDOWN:
            # 获取鼠标的x, y坐标
            mouse_x, mouse_y = pygame.mouse.get_pos()
            # 调用函数
            check_play_button(bullets, canvas, mouse_x, mouse_y, play_button,
                              sb, settings, ship, stats, targets)
def check_play_button(bullets, canvas, mouse_x, mouse_y, play_button, sb,
                      settings, ship, stats, targets):
    """获取按下Play按钮后的事情"""
    # 查看play button(经过button()生成)和鼠标的x,y坐标是否有交会,回来True or False
    button_clicked = play_button.rect.collidepoint(mouse_x, mouse_y)
    # 假如有交汇(点击)且游戏未进行
    if button_clicked and not stats.game_active:
        # 游戏初始化
        settings.initialize_speed_settings()
        # 鼠标躲藏不可见
        pygame.mouse.set_visible(False)
        # 重置stats内的数据
        stats.reset_stats()
        # 游戏状况改为True,开端游戏
        stats.game_active = True
        # 从头制作记分牌
        sb.prep_score()
        sb.prep_high_score()
        sb.prep_level()
        sb.prep_bullet(canvas, settings, ship)
        # 清空targets和子弹
        targets.empty()
        bullets.empty()
        # 创立一组target
        create_target(canvas, settings, targets)
        # 将飞船至于中心
        ship.center_ship()
def check_keydown_events(bullets, canvas, event, settings, ship):
    if event.key == pygame.K_DOWN:
        ship.move_down = True
    elif event.key == pygame.K_UP:
        ship.move_up = True
    elif event.key == pygame.K_SPACE:
        fire_bullet(bullets, canvas, settings, ship)
def fire_bullet(bullets, canvas, settings, ship):
    # 在按下空格后、且画面上的子弹未超数量限制时
    if len(bullets) < settings.bullet_allowed:
        # 生成一颗新子弹,并参加编组
        new_bullet = Bullet(canvas, settings, ship)
        bullets.add(new_bullet)
def check_keyup_events(event, ship):
    if event.key == pygame.K_DOWN:
        ship.move_down = False
    elif event.key == pygame.K_UP:
        ship.move_up = False

main.py

import pygame
from pygame.sprite import Group
from button import Button
from game_stats import GameStats
from scoreboard import Scoreboard
from setting import Setting
from ship import Ship
import game_function as gf
def run_game():
    pygame.init()
    # 导入setting
    settings = Setting()
    # 生成画布
    canvas = pygame.display.set_mode(
        (settings.canvas_width, settings.canvas_height))
    pygame.display.set_caption(""Python编程:从入门到实践"课后习题14-2,14-3,14-4")
    # 导入开端按钮,按钮上的字为play
    play_button = Button(canvas, "Play")
    # 导入ship
    ship = Ship(canvas)
    # 导入stat
    stats = GameStats(settings)
    # 导入score board
    sb = Scoreboard(canvas, settings, ship, stats)
    # 将bullet编组
    bullets = Group()
    # 将方针编组
    targets = Group()
    # 创立一组target
    gf.create_target(canvas, settings, targets)
    while True:
        gf.check_events(bullets, canvas, play_button, sb, settings, ship,
                        stats, targets)
        gf.update_canvas(canvas, play_button, sb, settings, ship, stats,
                         targets)
        if stats.game_active:
            # 更新ship
            ship.update_ship(settings)
            # 更新targets
            gf.update_targets(settings,
                              targets)  # targets.update() 放在了本函数下的update_targets
            gf.update_bullets(bullets, canvas, sb, settings, ship, stats,
                              targets)  # bullets.update() 放在了本函数下的update_bullets
        pygame.display.flip()
run_game()