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

1,143 阅读4分钟

Python编程:从入门到实践 第13章课后习题。

13-1 星星:找一幅星星图像,并在屏幕上显示一系列整齐排列的星星。

13-2 更逼真的星星 :为让星星的分布更逼真,可随机地放置星星。本书前面说过,可像下面这样来生成随机数:

from random import randint 
random_number = randint(-10,10)

效果:

1632724275(1).png

import sys
import pygame
from random import randint
from pygame.sprite import Sprite, Group

# 参见b站一个台湾人写法,不用def run_game(),直接写。
pygame.init()
# 设置画布大小
canvas_width = 1200
canvas_height = 900
# 参见台湾人的写法,他将所有页面上的元素都归到一个sprite里面,然后在while里面全部刷新。
all_sprites = Group()
# 设置画布
canvas = pygame.display.set_mode((canvas_width, canvas_height))


class Star(Sprite):
    def __init__(self):
        super(Star, self).__init__()
        self.image = pygame.image.load('star.svg')
        # 参见另外一个同学的写法,在这里随机缩小星星大小
        random_size = randint(10, 30)
        self.image = pygame.transform.smoothscale(self.image,
                                                  (random_size, random_size))
        self.rect = self.image.get_rect()

def create_star():
    # 创建一个对象
    star = Star()
    # 随机看x轴和y轴能创建多少个(数量)
    horizontal_star_num = int(canvas_width / (randint(5, 10) * star.rect.width))
    vertical_star_num = int(canvas_height / (randint(5, 10) * star.rect.height))
    # print(horizontal_star_num, vertical_star_num)
    for y in range(vertical_star_num):
        for x in range(horizontal_star_num):
            # 在画布范围内随机星星的x轴,y轴的位置
            random_x_axis = randint(0, canvas_width)
            random_y_axis = randint(0, canvas_height)
            star = Star()
            star.rect.x = random_x_axis
            star.rect.y = random_y_axis
            all_sprites.add(star)

create_star()

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()

    all_sprites.draw(canvas)

    pygame.display.update()

在页面元素不多,或者元素大多都是sprites的情况下,把所有元素丢到一个sprites里面,然后更新我觉得有道理啊,否则每次都要记得在刷新画布的地方更新,容易乱。

13-3 雨滴:寻找一幅雨滴图像,并创建一系列整齐排列的雨滴。让这些雨滴往下落,直到到达屏幕底端后消失。 13-4 连绵细雨:修改为完成练习13-3而编写的代码,使得一行雨滴消失在屏幕底端后,屏幕顶端又出现一行新雨滴,并开始往下落。 效果:

1632802834(1).png

setting.py

class Setting():
    def __init__(self):
        self.canvas_width = 1200
        self.canvas_height = 900

rain.py

import pygame
from pygame.sprite import Sprite
from random import randint


class Rain(Sprite):
    def __init__(self):
        super(Rain, self).__init__()
        self.image = pygame.image.load("images/raindrop.svg")
        rain_random_size = randint(5, 25)
        self.image = pygame.transform.smoothscale(
            self.image, (rain_random_size, rain_random_size))
        self.rect = self.image.get_rect()

    def update(self, *args, **kwargs) -> None:
        random_drop_speed = randint(1, 5)
        self.rect.y += random_drop_speed / 3

game_function.py

import sys
import pygame
from random import randint
from rain import Rain


def check_events():
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()


def available_spaces(settings):
    """确认canvas的x,y轴能放多少雨滴"""
    # 需要用到每个雨滴的rect,因此生成
    raindrop = Rain()
    # randint可调整雨滴疏密
    axis_x_avail_num = int(settings.canvas_width / (
            raindrop.rect.width * randint(1, 10)))
    axis_y_avail_num = int(settings.canvas_height / (
            raindrop.rect.height * randint(10, 20)))
    print(axis_x_avail_num, axis_y_avail_num)
    return axis_x_avail_num, axis_y_avail_num


def create_a_raindrop(raindrops, settings):
    """生成一颗雨滴"""
    # 随机生成一颗雨滴的x,y坐标
    rain_axis_x = randint(0, settings.canvas_width)
    rain_axis_y = randint(0, settings.canvas_height)
    # 生成雨滴
    raindrop = Rain()
    # 将随机x,y坐标带入新生成的雨滴
    raindrop.rect.x = rain_axis_x
    raindrop.rect.y = rain_axis_y
    raindrops.add(raindrop)


def create_raindrops(raindrops, settings):
    """生成大雨"""
    axis_x_avail_num, axis_y_avail_num = available_spaces(settings)
    for y in range(axis_y_avail_num):
        for x in range(axis_x_avail_num):
            create_a_raindrop(raindrops, settings)


def update_raindrops(canvas, raindrops, settings):
    """更新雨滴位置"""
    canvas_rect = canvas.get_rect()
    for raindrop in raindrops.sprites():
        if raindrop.rect.top > canvas_rect.bottom:
            raindrops.remove(raindrop)
            # print(len(raindrops))
            create_a_raindrop(raindrops, settings)
    raindrops.update()

main.py

import pygame
from pygame.sprite import Group
import game_function as gf
from setting import Setting


def run_game():
    pygame.init()
    settings = Setting()
    canvas = pygame.display.set_mode(
        (settings.canvas_width, settings.canvas_height))

    raindrops= Group()
    gf.create_raindrops(raindrops, settings)

    while True:
        canvas.fill((100,100,100))
        gf.check_events()

        gf.update_raindrops(canvas,raindrops, settings)
        raindrops.draw(canvas)

        pygame.display.flip()


run_game()

还是分成几个文件吧。这个好像比较吃资源啊,单位的台式机不是特行,如果生成的雨滴多的话,居然还卡一下~~

13-5 抓球:创建一个游戏,在屏幕底端放置一个玩家可左右移动的角色。让一个球出现在屏幕顶端,且水平位置是随机的,并让这个球以固定的速度往下落。如果角色与球发生碰撞(表示将球抓住了),就让球消失。每当角色抓住球或球因抵达屏幕底端而消失后,都创建一个新球。

13-6 游戏结束:在为完成练习13-5而编写的代码中,跟踪玩家有多少次未将球接着。在未接着球的次数到达三次后,结束游戏。

settings.py

class Settings():
    def __init__(self):
        # 画布设置
        self.canvas_width = 1200
        self.canvas_height = 900
        # 接球人移动速度
        self.catcher_speed_factor = 1.2
        # 球下落速度
        self.ball_drop_speed_factor = 0.3
        # 错过了多少球的统计
        self.ball_missed = 0

catcher.py

import pygame


class Catcher():
    def __init__(self, canvas):
        # 画布设置
        self.canvas = canvas
        self.image = pygame.image.load("images/catcher.svg")
        self.image = pygame.transform.smoothscale(self.image.convert_alpha(),
                                                  (80, 80))
        # 获取外接矩形
        self.rect = self.image.get_rect()
        self.canvas_rect = canvas.get_rect()
        # 定位小人
        self.rect.centerx = self.canvas_rect.centerx
        self.rect.bottom = self.canvas_rect.bottom
        # 运用浮点坐标
        self.center_x = float(self.rect.centerx)
        self.center_y = float(self.rect.centery)
        # 按住键盘方向的ticker
        self.move_right_ticker = False
        self.move_left_ticker = False
        self.move_up_ticker = False
        self.move_down_ticker = False

    def update_catcher(self, settings):
        if self.move_right_ticker and self.rect.right < \
                self.canvas_rect.right:
            self.center_x += settings.catcher_speed_factor
        if self.move_left_ticker and self.rect.left > 0:
            self.center_x -= settings.catcher_speed_factor
        if self.move_up_ticker and self.rect.top > 0:
            self.center_y -= settings.catcher_speed_factor
        if self.move_down_ticker and self.rect.bottom < \
                self.canvas_rect.bottom:
            self.center_y += settings.catcher_speed_factor

        self.rect.centerx = self.center_x
        self.rect.centery = self.center_y

    def blit_catcher(self):
        # 显示小人
        self.canvas.blit(self.image, self.rect)

ball.py

import pygame
from pygame.sprite import Sprite
from random import randint


class Ball(Sprite):
    def __init__(self, canvas, settings):
        super(Ball, self).__init__()
        self.canvas = canvas
        self.image = pygame.image.load("images/ball.svg")
        self.image = pygame.transform.smoothscale(
            self.image.convert_alpha(), (50, 50))
        self.rect = self.image.get_rect()
        self.canvas_rect = canvas.get_rect()
        # 将球定位在画布顶
        self.rect.top = self.canvas_rect.top
        # 随机设置球在画布顶的位置,两边要各去掉一个球的宽度
        self.rect.centerx = randint(
            self.rect.width, settings.canvas_width - self.rect.width)

        self.center_y = float(self.rect.centery)

    def update(self, settings, **kwargs) -> None:
        self.center_y += settings.ball_drop_speed_factor
        self.rect.centery = self.center_y

game_function.py

import sys
import time

import pygame

from ball import Ball


def update_canvas(ball_sprite, canvas, catcher, settings):
    canvas.fill((100, 100, 100))
    ##更新小人
    catcher.blit_catcher()
    catcher.update_catcher(settings)
    ##更新球
    ball_sprite.draw(canvas)
    ball_sprite.update(settings)


def create_a_ball(ball_sprite, canvas, settings):
    # 创建一个球
    ball = Ball(canvas, settings)
    ball_sprite.add(ball)


def create_balls(ball_sprite, canvas, catcher, settings):
    # 创建循环球
    if len(ball_sprite) == 0:
        create_a_ball(ball_sprite, canvas, settings)
    # 如果球和人碰撞,则球的sprite清空
    elif pygame.sprite.spritecollideany(catcher, ball_sprite):
        ball_sprite.empty()

    for ball in ball_sprite.sprites():
        if ball.rect.bottom > settings.canvas_width:
            ball_sprite.remove(ball)
            # print(len(ball_sprite))
            settings.ball_missed += 1
            print("You have missed "
                  + str(settings.ball_missed)
                  + " balls, game will be over if 3 balls are missed")
            check_missed(settings)
            # 每次miss后停半秒
            time.sleep(0.5)


def check_missed(settings):
    if settings.ball_missed == 3:
        print("Maximum missed, game over.")
        sys.exit()
    else:
        pass


def check_events(catcher):
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()
        elif event.type == pygame.KEYDOWN:
            check_keydown_event(event, catcher)
        elif event.type == pygame.KEYUP:
            check_keyup_event(event, catcher)


def check_keydown_event(event, catcher):
    if event.key == pygame.K_RIGHT:
        catcher.move_right_ticker = True
    elif event.key == pygame.K_LEFT:
        catcher.move_left_ticker = True
    elif event.key == pygame.K_UP:
        catcher.move_up_ticker = True
    elif event.key == pygame.K_DOWN:
        catcher.move_down_ticker = True
    elif event.key == pygame.K_q:
        sys.exit()


def check_keyup_event(event, catcher):
    if event.key == pygame.K_RIGHT:
        catcher.move_right_ticker = False
    elif event.key == pygame.K_LEFT:
        catcher.move_left_ticker = False
    elif event.key == pygame.K_UP:
        catcher.move_up_ticker = False
    elif event.key == pygame.K_DOWN:
        catcher.move_down_ticker = False

main.py

import pygame
import game_functions as gf
from pygame.sprite import Group
from catcher import Catcher
from settings import Settings


def run_game():
    pygame.init()
    # 设置一个游戏开始的ticker
    running = True
    # 导入设置
    settings = Settings()
    # 设置画布
    canvas = pygame.display.set_mode(
        (settings.canvas_width, settings.canvas_height))
    # 生成人物和小球
    catcher = Catcher(canvas)
    ball_sprite = Group()

    while running:
        gf.check_events(catcher)

        gf.update_canvas(ball_sprite, canvas, catcher, settings)

        gf.create_balls(ball_sprite, canvas, catcher, settings)

        pygame.display.flip()

        # print(len(ball_sprite))


run_game()

今晚时间有限,所以没有如教材上的那样用game stats.py来控制球,直接print了结果。

学习的过程中在StackOverflow上发现,导入图片最好用self.image.convert_alpha(),或者.convert,这样能提高效率。

回头可以尝试一下,把小人也用sprite来生成,这样应该可以练习groupcollide

先过国庆去了。