likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 79

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()
转载自:https://juejin.cn/post/7015784574579900453
评论
请登录