一、pygame入门

1、准备工作

安装pygame:

$ sudo pip3 install pygame

验证是否安装:

$ python3 -m pygame.examples.aliens

游戏思路:

  • 把一些 静止的图像 绘制到 游戏窗口

  • 根据 用户的交互 或其他情况,移动 这些图像,产生动画效果

  • 根据 图像之间 是否发生重叠,判断 敌机是否被摧毁 等其他情况

 

2、初始化和退出

要使用 pygame 提供的所有功能之前,需要调用 init 方法

在游戏结束前需要调用一下 quit 方法

方法说明
pygame.init() 导入并初始化所有 pygame 模块,使用其他模块之前,必须先调用 init 方法
pygame.quit() 卸载所有 pygame 模块,在游戏结束之前调用!
import pygame

pygame.init()

print("游戏内容")

pygame.quit()

 

3、游戏中的坐标系

原点 在 左上角 (0, 0)

x 轴 水平方向向 右,逐渐增加

y 轴 垂直方向向 下,逐渐增加

在游戏中,所有可见的元素 都是以矩形区域来描述位置的

 

要描述一个矩形区域有四个要素:(x, y) (width, height)

pygame 专门提供了一个类 pygame.Rect 用于描述 矩形区域

Rect(x, y, width, height) -> Rect

pygame.Rect 是一个比较特殊的类,内部只是封装了一些数字计算,不执行 pygame.init() 方法同样能够直接使用

描述英雄:

import pygame

hero_rect = pygame.Rect(100, 500, 120, 125)

print("英雄的原点:%d %d" % (hero_rect.x, hero_rect.y))
print("英雄的尺寸:%d %d" % (hero_rect.width, hero_rect.height))
# size属性会返回矩形的(宽,高)元组
print("英雄大小:%d %d" % hero_rect.size)

 

4、创建游戏主窗口

pygame提供了pygame.display模块用于创建、管理游戏窗口

方法说明
pygame.display.set_mode() 初始化游戏显示窗口
pygame.display.update() 刷新屏幕内容显示

set_mode 方法:set_mode(size=(0,0), flags=0, depth=0) -> Surface

  • 作用 —— 创建游戏显示窗口

  • 参数

    • size 指定屏幕的 ,默认创建的窗口大小和屏幕大小一致

    • flags 参数指定屏幕的附加选项,例如是否全屏等等,默认不需要传递

    • depth 参数表示颜色的位数,默认自动匹配

  • 返回值

    • surface,暂时 可以理解为 游戏的屏幕游戏的元素 都需要被绘制到 游戏的屏幕

  • 注意:必须使用变量记录 set_mode 方法的返回结果!因为:后续所有的图像绘制都基于这个返回结果

import pygame

pygame.init()

# 创建游戏窗口,并指定屏幕的宽高
screen = pygame.display.set_mode((480, 700))

# 游戏循环,保证窗口不会因为代码继续向下执行而退出
while True:
    pass

pygame.quit()

 

5、图像绘制

保存在磁盘上的图像文件首先应该被加载到内存:

要在屏幕上 看到某一个图像的内容,需要按照三个步骤:

  使用 pygame.image.load(file_path) 加载图像的数据

  使用 游戏屏幕 对象,调用 blit(图像,位置) 方法 将图像绘制到指定位置

  调用 pygame.display.update() 方法更新整个屏幕的显示

 

(1)绘制背景图像

  加载background.png创建背景

  将背景绘制在屏幕的(0,0)位置

  更新屏幕显示

import pygame

pygame.init()

screen = pygame.display.set_mode((480, 700))

# 加载图像数据
bg = pygame.image.load("./images/background.png")

# blit绘制图像
screen.blit(bg, (0, 0))

# 更新屏幕显示
pygame.display.update()

while True:
    pass

pygame.quit()

 

(2)绘制英雄图像

  加载me1.png创建英雄飞机

  将飞机绘制在(200,500)位置

  调用屏幕更新显示飞机图像

import pygame

pygame.init()

screen = pygame.display.set_mode((480, 700))

hero = pygame.image.load("./images/me1.png")

screen.blit(hero, (200, 500))

pygame.display.update()

while True:
    pass

pygame.quit()

png 格式的图像是支持透明的,在绘制图像时,透明区域不会显示任何内容。但是如果下方已经有内容,会透过透明区域显示出来;

 

(3)案例调整

可以在 screen 对象完成 所有 blit 方法之后,统一调用一次 display.update 方法,同样可以在屏幕上 看到最终的绘制结果

使用display.set_mode()创建的screen对象是一个内存中的屏幕数据对象,

screen.blit方法可以在上面绘制很多图像(这些图像有可能会彼此重叠或覆盖),

display.update()会将画布的最终结果绘制在屏幕上,这样可以提高屏幕绘制效率,增加游戏的流畅度;

import pygame

pygame.init()

screen = pygame.display.set_mode((480, 700))

bg = pygame.image.load("./images/background.png")
hero = pygame.image.load("./images/me1.png")

screen.blit(bg, (0, 0))
screen.blit(hero, (200, 500))

pygame.display.update()

while True:
    pass

pygame.quit()

 

6、游戏循环和游戏时钟

电影的原理类似,游戏中的动画效果,本质上是快速的在屏幕上绘制图像,每次绘制的结果被称为 帧Frame;

一般在电脑上每秒绘制60次,就能达到非常连续、高品质的动画效果;

 

(1)游戏循环

游戏循环的开始就意味着游戏的真正开始。

游戏的两个重要组成部分:

  游戏初始化(设置游戏窗口、绘制图像初始位置、设置游戏时钟)

  游戏循环(设置刷新帧率、检测用户交互、更新所有图像位置、更新屏幕显示)

游戏循环的作用:

  保证游戏 不会直接退出

  变化图像位置 —— 动画效果

    每隔 1 / 60 秒 移动一下所有图像的位置

    调用 pygame.display.update() 更新屏幕显示

  检测用户交互 —— 按键、鼠标等...

 

(2)游戏时钟

pygame提供了pygame.time.Clock可以非常方便地设置屏幕绘制速度——刷新帧率

要使用时钟对象需要两步:

  在游戏初始化创建一个时钟对象

  在游戏循环中让时钟对象调用tick(帧率)方法

tick方法会根据上次被调用的时间,自动设置游戏循环中的延时

clock = pygame.time.Clock()

i = 0
while True:
    # 可以指定循环体内部的代码执行的频率(每秒60次)
    clock.tick(60)
    print(i)
    i += 1

 

7、英雄的简单动画实现

需求:

在 游戏初始化 定义一个 pygame.Rect 的变量记录英雄的初始位置

在 游戏循环 中每次让 英雄 的 y - 1 —— 向上移动

y <= 0 将英雄移动到屏幕的底部

import pygame

pygame.init()

screen = pygame.display.set_mode((480, 700))

bg = pygame.image.load("./images/background.png")
hero = pygame.image.load("./images/me1.png")

screen.blit(bg, (0, 0))
screen.blit(hero, (150, 300))

pygame.display.update()

clock = pygame.time.Clock()

# 定义Rect记录飞机的初始位置
hero_rect = pygame.Rect(150, 300, 102, 126)

while True:
    clock.tick(60)

    # 修改飞机的位置
    hero_rect.y -= 1
    # 当飞机完全飞出屏幕时,重置y值
    if hero_rect.y + hero_rect.height <= 0:
        hero_rect.y = 700

    # 重新绘制背景图像,遮挡住上一次绘制的飞机图案
    screen.blit(bg, (0, 0))
    screen.blit(hero, hero_rect)

    pygame.display.update()

pygame.quit()

每一次调用 update() 方法之前,需要把所有的游戏图像都重新绘制一遍,而且应该最先重新绘制背景图像

 

8、在游戏循环中监听事件

事件Event:就是游戏启动后,用户针对游戏所做的操作,例如点击关闭按钮、点击鼠标、按下键盘

监听:在游戏循环中,判断用户具体的操作,捕获到用户具体的操作,才能有针对性的做出响应

在pygame中通过pygame.event.get()可以获得用户当前所做动作的事件列表(用户可以同一时间做很多事情)

  这段代码非常的固定,几乎所有的pygame游戏都大同小异

import pygame

pygame.init()

screen = pygame.display.set_mode((480, 700))

bg = pygame.image.load("./images/background.png")
hero = pygame.image.load("./images/me1.png")

screen.blit(bg, (0, 0))
screen.blit(hero, (150, 300))

pygame.display.update()

clock = pygame.time.Clock()

hero_rect = pygame.Rect(150, 300, 102, 126)

while True:
    clock.tick(60)

    # 获取动作事件列表
    event_list = pygame.event.get()
    for event in event_list:
        # 判断事件类型是否是用户点击了关闭按钮
        if event.type == pygame.QUIT:
            print("退出游戏")
            pygame.quit()
            # 直接退出系统
            exit()

    hero_rect.y -= 1
    if hero_rect.y + hero_rect.height <= 0:
        hero_rect.y = 700

    screen.blit(bg, (0, 0))
    screen.blit(hero, hero_rect)

    pygame.display.update()

pygame.quit()

 

9、精灵和精灵组

 在刚刚完成的案例中,图像加载、位置变化、绘制图像都需要程序员编写代码分别处理。

  游戏初始化(设置游戏窗口、绘制图像初始位置、设置游戏时钟)

  游戏循环(设置刷新帧率、检测用户交互、更新所有图像位置、更新屏幕显示)

为了简化开发步骤,pygame提供了两个类:

 

pygame.sprite.Sprite 存储图像image和位置rect的对象

  属性:

  image 要显示的图像

  rect 图像要显示在屏幕的位置

  方法:

  update(*args) 在每次刷新屏幕时,更新精灵位置

  kill() 从所有组中删除

注意pygame.sprite.Sprite 并没有提供 imagerect 两个属性,需要程序员从 pygame.sprite.Sprite 派生子类,并在 子类 的 初始化方法 中,设置 imagerect 属性

 

pygame.sprite.Group 精灵组,可以包含多个精灵对象

  方法:

  __init__(self, *精灵)

  add(*sprites) 向组中增加精灵

  sprites() 返回所有精灵列表

  update(*args) 让组中所有精灵自动调用update方法

  draw(Surface) 将组中所有精灵的image,绘制到Surface的rect位置

注意:仍然需要调用 pygame.display.update() 才能在屏幕看到最终结果

 

游戏初始化:创建精灵、创建精灵组

游戏循环:精灵组.update()、精灵组.draw()、pygame.display.update()

 

10、派生精灵子类

定义GameSprite继承自pygame.sprite.Sprite

在重写初始化方法时,一定要先 super() 一下父类的 __init__ 方法,保证父类中实现的 __init__ 代码能够被正常执行

GameSprite

  属性:

  image 精灵图像,使用image_name加载

  rect 精灵大小,默认使用图像大小

  speed 精灵移动速度,默认为1

  方法:

  __init__(self, image_name, speed=1)

  update(self) 每次更新屏幕时在游戏内循环调用,让精灵的self.rect.y += self.speed

注意:imageget_rect() 方法,可以返回 pygame.Rect(0, 0, 图像宽, 图像高) 的对象 

import pygame


class GameSprite(pygame.sprite.Sprite):
    """飞机大战游戏精灵"""
    
    def __init__(self, image_name, speed=1):
        # 调用父类初始化方法
        super().__init__(self)
        # 定义对象的属性
        self.image = pygame.image.load(image_name)
        self.rect = self.image.get_rect()
        self.speed = speed
    
    def update(self, *args):
        # 在屏幕的垂直方向上移动
        self.rect.y += self.speed

 

11、使用精灵和精灵组创建敌机

使用 from 导入 plane_sprites 模块,from 导入的模块可以直接使用

在游戏初始化创建精灵对象和精灵组对象

在游戏循环中让精灵组分别调用 update()draw(screen) 方法

from plane_sprites import *
import pygame

pygame.init()

screen = pygame.display.set_mode((480, 700))

bg = pygame.image.load("./images/background.png")
hero = pygame.image.load("./images/me1.png")

screen.blit(bg, (0, 0))
screen.blit(hero, (150, 300))

pygame.display.update()

# 创建游戏时钟对象
clock = pygame.time.Clock()
# 记录英雄初始位置
hero_rect = pygame.Rect(150, 300, 102, 126)

# 创建敌机的精灵
enemy1 = GameSprite("./images/enemy1.png")
enemy2 = GameSprite("./images/enemy1.png", 2)
# 创建敌机的精灵组
enemy_group = pygame.sprite.Group(enemy1, enemy2)

while True:
    # 设置刷新频率
    clock.tick(60)

    # 检测用户交互
    event_list = pygame.event.get()
    for event in event_list:
        if event.type == pygame.QUIT:
            print("退出游戏...")
            pygame.quit()
            quit()

    # 修改飞机位置
    hero_rect.y -= 1
    # 判断飞机位置
    if hero_rect.y + hero_rect.height <= 0:
        hero_rect.y = 700

    # 重新绘制图像
    screen.blit(bg, (0, 0))
    screen.blit(hero, hero_rect)

    # 让精灵组调用两个方法
    # update - 让组中的所有精灵更新位置
    enemy_group.update()
    # draw - 在screen上绘制所有的精灵
    enemy_group.draw(screen)

    pygame.display.update()


pygame.quit()

 

二、飞机大战

1、框架搭建

游戏主程序可以分为:

  游戏初始化:

  设置游戏窗口

  创建游戏时钟

  创建精灵、精灵组

  游戏循环:

  设置刷新频率

  事件监听

  碰撞检测

  更新/绘制精灵组

  更新屏幕显示

 

根据明确的职责,设计PlaneGame类

  属性:

  screen

  clock

  精灵组或精灵

  方法:

  __init__(self):

  __create_sprites(self):

  start_game(self):

  __event_handler(self):

  __check_collide(self):

  __update_sprites(self):

  __game_over():

 

  • 游戏初始化 —— __init__() 会调用以下方法:

方法职责
__create_sprites(self) 创建所有精灵和精灵组
  • 游戏循环 —— start_game() 会调用以下方法:

方法职责
__event_handler(self) 事件监听
__check_collide(self) 碰撞检测 —— 子弹销毁敌机、敌机撞毁英雄
__update_sprites(self) 精灵组更新和绘制
__game_over() 游戏结束

 

plane_main文件:游戏主程序(封装主游戏类、创建游戏对象、启动游戏)

plane_sprites文件:封装所有精灵子类、提供游戏相关工具

 

plane_main.py

import pygame
from plane_sprites import *


class PlaneGame(object):
    """飞机大战主游戏"""
    def __init__(self):

        print("游戏初始化...")

        # 创建游戏的窗口
        self.screen = pygame.display.set_mode(SCREEN_RECT.size)

        # 创建游戏时钟
        self.clock = pygame.time.Clock()

        # 调用私有方法,创建精灵和精灵组
        self.__create_sprites()

    def __create_sprites(self):

        pass

    def start_game(self):

        print("游戏开始...")

        while True:
            # 1.设置刷新帧率
            self.clock.tick(FRAME_PER_SEC)

            # 2.事件监听
            self.__event_handler()

            # 3.碰撞检测
            self.__check_collide()

            # 4.精灵组更新绘制
            self.__update_sprites()

            # 5.更新显示
            pygame.display.update()

    def __event_handler(self):
        for event in pygame.event.get():
            # 判断是否退出游戏
            if event.type == pygame.QUIT:
                PlaneGame.__game_over()

    def __check_collide(self):
        pass

    def __update_sprites(self):
        pass

    @staticmethod
    def __game_over():
        print("游戏结束...")
        pygame.quit()
        exit()


if __name__ == '__main__':

    # 创建游戏对象
    game = PlaneGame()

    # 启动游戏
    game.start_game()

plane_sprites.py

import pygame

# 屏幕大小的常量
SCREEN_RECT = pygame.Rect(0, 0, 480, 700)
# 刷新的帧率
FRAME_PER_SEC = 60


class GameSprite(pygame.sprite.Sprite):
    """飞机大战游戏精灵"""

    def __init__(self, image_name, speed=1):
        # 调用父类初始化方法
        super().__init__()
        # 定义对象的属性
        self.image = pygame.image.load(image_name)
        self.rect = self.image.get_rect()
        self.speed = speed

    def update(self, *args):
        # 在屏幕的垂直方向上移动
        self.rect.y += self.speed

 

2、背景图像

交替滚动实现思路:

创建两张背景图像精灵,两张图像一起向下方移动

  self.rect.y += self.speed

当任意背景精灵的 rect.y>=屏幕的高度,说明已经移动到屏幕下方,将这张图片设置到屏幕的上方

  rect.y = -rect.height

 

GameSprite的update方法没有针对移出屏幕进行判断,我们需要派生子类,重写方法:

class Background(GameSprite):
    """游戏背景精灵"""
    def update(self):
        # 1.调用父类的方法实现
        super().update()
        # 2.判断是否移出屏幕
        if self.rect.y >= SCREEN_RECT.height:
            self.rect.y = -self.rect.height

 

    def __create_sprites(self):

        # 创建背景精灵和精灵组
        bg1 = Background("./images/background.png")
        bg2 = Background("./images/background.png")
        bg2.rect.y = -bg2.rect.height
        self.back_group = pygame.sprite.Group(bg1, bg2)
    def __update_sprites(self):

        self.back_group.update()
        self.back_group.draw(self.screen)

上方代码存在问题:

在主程序中,创建的两个背景精灵,传入了相同的图像文件路径

创建 第二个背景精灵 时,在主程序中设置背景精灵的图像位置,而根据面向对象设计原则,应该由精灵自己负责

利用初始化方法,简化背景精灵的创建:

  • 直接指定 背景图片

  • is_alt 判断是否是另一张图像

    • False 表示 第一张图像,需要与屏幕重合

    • True 表示 另一张图像,在屏幕的正上方

    def __create_sprites(self):

        # 创建背景精灵和精灵组
        bg1 = Background()
        bg2 = Background(True)
        self.back_group = pygame.sprite.Group(bg1, bg2)
class Background(GameSprite):
    """游戏背景精灵"""
    def __init__(self, is_alt=False):
        # 调用父类方法实现精灵的创建
        super().__init__("./images/background.png")
        # 判断是否是替换图像,如果是,设置初始位置
        if is_alt:
            self.rect.y = -self.rect.height

    def update(self):
        # 1.调用父类的方法实现
        super().update()
        # 2.判断是否移出屏幕
        if self.rect.y >= SCREEN_RECT.height:
            self.rect.y = -self.rect.height

 

3、定时器

定时器:

pygame 中可以使用 pygame.time.set_timer() 来添加定时器

set_timer(eventid, milliseconds) -> None

所谓定时器,就是每隔一段时间,去执行一些动作

set_timer 可以创建一个事件,可以在游戏循环的事件监听方法中捕获到该事件

第 1 个参数事件代号需要基于常量 pygame.USEREVENT 来指定,USEREVENT 是一个整数,再增加的事件可以使用 USEREVENT + 1 指定,依次类推...

第 2 个参数是事件触发间隔的毫秒值(1000毫秒=1秒)

 

定时器事件的监听:

通过 pygame.event.get() 可以获取当前时刻所有的 事件列表

遍历列表 并且判断 event.type 是否等于 eventid,如果相等,表示 定时器事件 发生

 

定义并监听创建敌机的定时器事件

pygame 的 定时器 使用套路非常固定:

  定义 定时器常量 —— eventid

# 创建敌机的定时器常量
CREATE_ENEMY_EVENT = pygame.USEREVENT

  在 初始化方法 中,调用 set_timer 方法 设置定时器事件

        # 设置定时器事件
        pygame.time.set_timer(CREATE_ENEMY_EVENT, 1000)

  在 游戏循环 中,监听定时器事件

    def __event_handler(self):
        for event in pygame.event.get():
            # 判断是否退出游戏
            if event.type == pygame.QUIT:
                PlaneGame.__game_over()
            # 监听创建敌机的定时器事件
            elif event.type == CREATE_ENEMY_EVENT:
                print("敌机出场...")

 

4、设计敌机类

敌机的特点:

每隔一秒出现一架

向屏幕下方飞行,速度各不相同

敌机出现的水平位置也不相同

敌机从屏幕下方飞出,不会再回到屏幕中

 

初始化方法:

指定敌机图片

随机敌机的初始位置和初始速度

update():

判断是否飞出屏幕,如果是,从精灵组删除

class Enemy(GameSprite):
    """敌机精灵"""
    def __init__(self):
        # 调用父类方法,创建敌机精灵,同时指定敌机图片
        super().__init__("./images/enemy1.png")
        # 指定敌机的初始随机速度
        
        # 指定敌机的初始随机位置

    def update(self):
        # 调用父类方法,保持垂直方向的飞行
        super().update()
        # 判断是否飞出屏幕,需要删除飞出的精灵
        if self.rect.y > SCREEN_RECT.height:
            print("飞出屏幕...")

 

__create_sprites,添加 敌机精灵组。敌机是定时被创建的,因此在初始化方法中,不需要创建敌机

        # 创建敌机精灵组
        self.enemy_group = pygame.sprite.Group()

__event_handler,创建敌机,并且 添加到精灵组。调用 精灵组 的 add 方法可以 向精灵组添加精灵

            elif event.type == CREATE_ENEMY_EVENT:
                # 创建敌机精灵
                enemy = Enemy()
                # 将敌机精灵添加到敌机精灵组
                self.enemy_group.add(enemy)

__update_sprites,让 敌机精灵组调用 updatedraw 方法

        self.enemy_group.update()
        self.enemy_group.draw(self.screen)

 

实现随机敌机位置和速度:

    def __init__(self):
        # 调用父类方法,创建敌机精灵,同时指定敌机图片
        super().__init__("./images/enemy1.png")
        # 指定敌机的初始随机速度
        self.speed = random.randint(1, 3)
        # 指定敌机的初始随机位置
        self.rect.x = random.randint(0, SCREEN_RECT.width-self.rect.width)
        # bottom属性:底边bottom=y+height
        # 可以把bottom设为0,实现敌机从-height位置飞入的效果
        self.rect.bottom = 0

模块导入顺序

1) 官方标准模块导入
2) 第三方模块导入
3)应用程序模块导入

销毁飞出屏幕的敌机:

    def update(self):
        # 调用父类方法,保持垂直方向的飞行
        super().update()
        # 判断是否飞出屏幕,需要删除飞出的精灵
        if self.rect.y > SCREEN_RECT.height:
            self.kill()  # 从所有的精灵组中移除并自动销毁

    # 会在对象被销毁时调用
    def __del__(self):
        print("敌机飞出 %s" % self.rect)

 

5、设计英雄和子弹类

英雄需求:

游戏启动后,英雄出现在屏幕的水平中间位置,距离屏幕底部 120 像素

英雄每隔 0.5 秒发射一次子弹,每次连发三枚子弹

英雄默认不会移动,需要通过 左/右 方向键,控制英雄在水平方向移动

子弹需求:

子弹从英雄的正上方发射沿直线向上方飞行

飞出屏幕后,需要从精灵组中删除

 

Hero

bullets属性:

  记录所有子弹精灵

初始化方法:

  指定英雄图片

  初始速度=0

  定义bullets子弹精灵组保存子弹精灵

重写update:

  英雄需要水平移动

  保证不能移出屏幕

fire方法:

  用于发射子弹

 

Bullet

初始化方法:

  指定子弹图片

  初始速度=-2(子弹向上方飞行)

重写update:

  判断是否飞出屏幕,飞出就从精灵组删除

 

绘制英雄:

class Hero(GameSprite):
    """英雄精灵"""
    def __init__(self):
        # 调用父类方法,设置英雄图像和速度
        super().__init__("./images/me1.png", 0)
        # 设置英雄的初始位置
        self.rect.centerx = SCREEN_RECT.centerx
        self.rect.bottom = SCREEN_RECT.height - 120
        # 创建英雄的精灵和精灵组
        self.hero = Hero()  # 要在其他方法中使用,所以定义成属性
        self.hero_group = pygame.sprite.Group(self.hero)
        self.hero_group.update()
        self.hero_group.draw(self.screen)

 

6、键盘按键捕获

在Python中针对键盘按键的捕获有两种方式:

1)判断event.type == pygame.KEYDOWN(KEYDOWN事件表示用户按下了某个键)(K_RIGHT表示向右方向键)

elif event.type == pygame.KEYDOWN and event.key == pygame.K_RIGHT:
    print("向右移动...")

2)首先使用pygame.key.get_pressed()返回所有按键元组,再通过键盘常量,判断元组中某一个键是否被按下(如果被按下,对应数值为1,没有按下就是0)

# 返回所有按键的元组,如果某个键被按下,对应的值会是1
keys_pressed = pygame.key.get_pressed()
# 判断是否按下了方向键
if keys_pressed[pygame.K_RIGHT]:
    print("向右移动...")

 

第一种方式用户必须要抬起按键才算一次按键事件,操作灵活性大打折扣;

第二种方式用户可以按住方向键不放,实现朝某个方向持续移动;

 

7、英雄移动

在Hero类中重写update方法:

  让英雄的rect.x与速度speed叠加

  不需要调用父类方法,父类方法只实现了垂直运动

    def update(self):
        # 英雄在水平方向移动
        self.rect.x += self.speed

在__event_handler方法中根据左右方向键设置英雄的速度

  向右 2

  向左-2

  没有按键或其他0

    def __event_handler(self):
        for event in pygame.event.get():
            # 判断是否退出游戏
            if event.type == pygame.QUIT:
                PlaneGame.__game_over()
            # 监听创建敌机的定时器事件
            elif event.type == CREATE_ENEMY_EVENT:
                # 创建敌机精灵
                enemy = Enemy()
                # 将敌机精灵添加到敌机精灵组
                self.enemy_group.add(enemy)
            # elif event.type == pygame.KEYDOWN and event.key == pygame.K_RIGHT:
            #     print("按下右方向键")

        # 返回所有按键元组
        keys_pressed = pygame.key.get_pressed()
        # 如果被按下,值为1
        if keys_pressed[pygame.K_RIGHT]:
            self.hero.speed = 2
        elif keys_pressed[pygame.K_LEFT]:
            self.hero.speed = -2
        else:
            self.hero.speed = 0

英雄边界控制:

    def update(self):
        # 英雄在水平方向移动
        self.rect.x += self.speed

        # 控制英雄不能离开屏幕
        if self.rect.x < 0:
            self.rect.x = 0
        elif self.rect.right > SCREEN_RECT.right:
            self.rect.right = SCREEN_RECT.right

 

8、发射子弹

英雄每隔0.5秒发射一次,每次连发三枚子弹

pygame的定时器使用套路:

  定义定时器常量 eventid

  在初始化方法中,set_timer设置定时器事件

  在游戏循环中,监听定时器事件

    def fire(self):
        print("发射子弹")
# 英雄发射子弹的定时器常量
HERO_FIRE_EVENT = pygame.USEREVENT + 1
        pygame.time.set_timer(HERO_FIRE_EVENT, 500)
            elif event.type == HERO_FIRE_EVENT:
                # 监听发射子弹事件
                self.hero.fire()

Bullet类:

  初始化方法:指定图片和初始速度-2

  update:子弹飞出屏幕就删除

class Bullet(GameSprite):
    """子弹精灵"""
    def __init__(self):
        # 调用父类方法设置子弹图片,设置初始速度
        super().__init__("./images/bullet1.png", -2)
    
    def update(self):
        # 调用父类方法,让子弹沿垂直方向飞行
        super().update()
        # 判断子弹是否飞出
        if self.rect.bottom < 0:
            self.kill()
    
    def __del__(self):
        print("子弹销毁")

 

Hero 的 初始化方法 中创建 子弹精灵组 属性

    def __init__(self):
        # 调用父类方法,设置英雄图像和速度
        super().__init__("./images/me1.png", 0)
        # 设置英雄的初始位置
        self.rect.centerx = SCREEN_RECT.centerx
        self.rect.bottom = SCREEN_RECT.height - 120
        # 创建子弹的精灵组
        self.bullets = pygame.sprite.Group()

修改 plane_main.py__update_sprites 方法,让 子弹精灵组 调用 updatedraw 方法

        self.hero.bullets.update()
        self.hero.bullets.draw(self.screen)

实现 fire() 方法:

  创建子弹精灵

  设置初始位置 —— 在 英雄的正上方

  子弹 添加到精灵组

    def fire(self):
        # 创建子弹精灵
        bullet = Bullet()
        # 设置精灵位置
        bullet.rect.bottom = self.rect.y - 20
        bullet.rect.centerx = self.rect.centerx
        # 将精灵添加到精灵组
        self.bullets.add(bullet)

一次发射三枚子弹:

    def fire(self):
        for i in (0, 1, 2):
            # 创建子弹精灵
            bullet = Bullet()
            # 设置精灵位置
            bullet.rect.bottom = self.rect.y - i * 20
            bullet.rect.centerx = self.rect.centerx
            # 将精灵添加到精灵组
            self.bullets.add(bullet)

 

9、碰撞检测

1)pygame.sprite.groupcollide():两个精灵组 中 所有的精灵 的碰撞检测

groupcollide(group1, group2, dokill1, dokill2, collided = None) -> Sprite_dict

  • 如果将 dokill 设置为 True,则 发生碰撞的精灵将被自动移除

  • collided 参数是用于 计算碰撞的回调函数

    • 如果没有指定,则每个精灵必须有一个 rect 属性

# 子弹摧毁敌机
pygame.sprite.groupcollide(self.hero.bullets, self.enemy_group, True, True)

 

2)pygame.sprite.spritecollide():判断 某个精灵 和 指定精灵组 中的精灵的碰撞

spritecollide(sprite, group, dokill, collided = None) -> Sprite_list

  • 如果将 dokill 设置为 True,则 指定精灵组 中 发生碰撞的精灵将被自动移除

  • collided 参数是用于 计算碰撞的回调函数

    • 如果没有指定,则每个精灵必须有一个 rect 属性

  • 返回 精灵组 中跟 精灵 发生碰撞的 所有精灵列表

    def __check_collide(self):
        # 子弹摧毁敌机
        pygame.sprite.groupcollide(self.hero.bullets, self.enemy_group, True, True)
        # 敌机撞毁英雄
        enemies = pygame.sprite.spritecollide(self.hero, self.enemy_group, True)
        # 判断列表是否有内容
        if len(enemies) > 0:
            # 让英雄牺牲
            print("英雄牺牲了...")
            self.hero.kill()
            # 结束游戏
            PlaneGame.__game_over()

 

三、飞机大战代码

plane_main.py

import pygame
from plane_sprites import *


class PlaneGame(object):
    """飞机大战主游戏"""
    def __init__(self):

        print("游戏初始化...")

        # 创建游戏的窗口
        self.screen = pygame.display.set_mode(SCREEN_RECT.size)

        # 创建游戏时钟
        self.clock = pygame.time.Clock()

        # 调用私有方法,创建精灵和精灵组
        self.__create_sprites()

        # 设置定时器事件
        pygame.time.set_timer(CREATE_ENEMY_EVENT, 1000)
        pygame.time.set_timer(HERO_FIRE_EVENT, 500)

    def __create_sprites(self):

        # 创建背景精灵和精灵组
        bg1 = Background()
        bg2 = Background(True)
        self.back_group = pygame.sprite.Group(bg1, bg2)

        # 创建敌机精灵组
        self.enemy_group = pygame.sprite.Group()

        # 创建英雄的精灵和精灵组
        self.hero = Hero()  # 要在其他方法中使用,所以定义成属性
        self.hero_group = pygame.sprite.Group(self.hero)

    def start_game(self):

        print("游戏开始...")

        while True:
            # 1.设置刷新帧率
            self.clock.tick(FRAME_PER_SEC)

            # 2.事件监听
            self.__event_handler()

            # 3.碰撞检测
            self.__check_collide()

            # 4.精灵组更新绘制
            self.__update_sprites()

            # 5.更新显示
            pygame.display.update()

    def __event_handler(self):
        for event in pygame.event.get():
            # 判断是否退出游戏
            if event.type == pygame.QUIT:
                PlaneGame.__game_over()
            # 监听创建敌机的定时器事件
            elif event.type == CREATE_ENEMY_EVENT:
                # 创建敌机精灵
                enemy = Enemy()
                # 将敌机精灵添加到敌机精灵组
                self.enemy_group.add(enemy)
            # elif event.type == pygame.KEYDOWN and event.key == pygame.K_RIGHT:
            #     print("按下右方向键")
            elif event.type == HERO_FIRE_EVENT:
                # 监听发射子弹事件
                self.hero.fire()

        # 返回所有按键元组
        keys_pressed = pygame.key.get_pressed()
        # 如果被按下,值为1
        if keys_pressed[pygame.K_RIGHT]:
            self.hero.speed = 2
        elif keys_pressed[pygame.K_LEFT]:
            self.hero.speed = -2
        else:
            self.hero.speed = 0

    def __check_collide(self):
        # 子弹摧毁敌机
        pygame.sprite.groupcollide(self.hero.bullets, self.enemy_group, True, True)
        # 敌机撞毁英雄
        enemies = pygame.sprite.spritecollide(self.hero, self.enemy_group, True)
        # 判断列表是否有内容
        if len(enemies) > 0:
            # 让英雄牺牲
            print("英雄牺牲了...")
            self.hero.kill()
            # 结束游戏
            PlaneGame.__game_over()

    def __update_sprites(self):

        self.back_group.update()
        self.back_group.draw(self.screen)

        self.enemy_group.update()
        self.enemy_group.draw(self.screen)

        self.hero_group.update()
        self.hero_group.draw(self.screen)

        self.hero.bullets.update()
        self.hero.bullets.draw(self.screen)

    @staticmethod
    def __game_over():
        print("游戏结束...")
        pygame.quit()
        exit()


if __name__ == '__main__':

    # 创建游戏对象
    game = PlaneGame()

    # 启动游戏
    game.start_game()

 

plane_sprites.py

import random
import pygame

# 屏幕大小的常量
SCREEN_RECT = pygame.Rect(0, 0, 480, 700)
# 刷新的帧率
FRAME_PER_SEC = 60
# 创建敌机的定时器常量
CREATE_ENEMY_EVENT = pygame.USEREVENT
# 英雄发射子弹的定时器常量
HERO_FIRE_EVENT = pygame.USEREVENT + 1


class GameSprite(pygame.sprite.Sprite):
    """飞机大战游戏精灵"""

    def __init__(self, image_name, speed=1):
        # 调用父类初始化方法
        super().__init__()
        # 定义对象的属性
        self.image = pygame.image.load(image_name)
        self.rect = self.image.get_rect()
        self.speed = speed

    def update(self):
        # 在屏幕的垂直方向上移动
        self.rect.y += self.speed


class Background(GameSprite):
    """游戏背景精灵"""
    def __init__(self, is_alt=False):
        # 调用父类方法实现精灵的创建
        super().__init__("./images/background.png")
        # 判断是否是替换图像,如果是,设置初始位置
        if is_alt:
            self.rect.y = -self.rect.height

    def update(self):
        # 1.调用父类的方法实现
        super().update()
        # 2.判断是否移出屏幕
        if self.rect.y >= SCREEN_RECT.height:
            self.rect.y = -self.rect.height


class Enemy(GameSprite):
    """敌机精灵"""
    def __init__(self):
        # 调用父类方法,创建敌机精灵,同时指定敌机图片
        super().__init__("./images/enemy1.png")
        # 指定敌机的初始随机速度
        self.speed = random.randint(1, 3)
        # 指定敌机的初始随机位置
        self.rect.x = random.randint(0, SCREEN_RECT.width-self.rect.width)
        # bottom属性:底边bottom=y+height
        # 可以把bottom设为0,实现敌机从-height位置飞入的效果
        self.rect.bottom = 0

    def update(self):
        # 调用父类方法,保持垂直方向的飞行
        super().update()
        # 判断是否飞出屏幕,需要删除飞出的精灵
        if self.rect.y > SCREEN_RECT.height:
            self.kill()  # 从所有的精灵组中移除并自动销毁

    # 会在对象被销毁时调用
    def __del__(self):
        # print("敌机飞出 %s" % self.rect)
        pass


class Hero(GameSprite):
    """英雄精灵"""
    def __init__(self):
        # 调用父类方法,设置英雄图像和速度
        super().__init__("./images/me1.png", 0)
        # 设置英雄的初始位置
        self.rect.centerx = SCREEN_RECT.centerx
        self.rect.bottom = SCREEN_RECT.height - 120
        # 创建子弹的精灵组
        self.bullets = pygame.sprite.Group()

    def update(self):
        # 英雄在水平方向移动
        self.rect.x += self.speed

        # 控制英雄不能离开屏幕
        if self.rect.x < 0:
            self.rect.x = 0
        elif self.rect.right > SCREEN_RECT.right:
            self.rect.right = SCREEN_RECT.right

    def fire(self):
        for i in (0, 1, 2):
            # 创建子弹精灵
            bullet = Bullet()
            # 设置精灵位置
            bullet.rect.bottom = self.rect.y - i * 20
            bullet.rect.centerx = self.rect.centerx
            # 将精灵添加到精灵组
            self.bullets.add(bullet)


class Bullet(GameSprite):
    """子弹精灵"""
    def __init__(self):
        # 调用父类方法设置子弹图片,设置初始速度
        super().__init__("./images/bullet1.png", -2)

    def update(self):
        # 调用父类方法,让子弹沿垂直方向飞行
        super().update()
        # 判断子弹是否飞出
        if self.rect.bottom < 0:
            self.kill()

    # def __del__(self):
    #     print("子弹销毁")

 

posted on 2019-06-25 23:16  三分天涯  阅读(2046)  评论(1编辑  收藏  举报