黑马python入门(6):Python基础(pygame介绍)

Pygame 官方文档 - 中译版(感谢分享)

https://blog.csdn.net/Enderman_xiaohei/article/details/87708373


pygame模块


pygame的初始化和退出

import pygame  # 导入
pygame.init()  # 初始化
# 主代码内容
this_screen = pygame.Rect(100, 120, 400, 600)
print("this_screen.x:[{}]".format(this_screen.x))

pygame.quit()  # 退出 执行到这里就会把所有pygame相关模块都彻底清理


pygame的Rect的介绍

作用:是存储和操作一个pygame的矩形对象
定义:Rect(left, top, width, height)   Rect((left, top), (width, height))   Rect(object)  定义需要获取坐标和宽高四个数值
常见属性: x,y(就是获取了矩形坐标和宽高 自动计算出来下面的这些属性 也可以直接修改这些属性来调整矩形的位置
top, left, bottom, right
topleft(返回左上坐标元组), bottomleft, topright, bottomright
midtop, midleft, midbottom, midright
center, centerx, centery
size, width, height
w,h
常见方法
pygame.Rect.copy - 复制矩形(rectangle)
pygame.Rect.move - 移动矩形
pygame.Rect.move_ip - 就在原地位置移动矩形
pygame.Rect.inflate - 增大或缩小矩形大小
pygame.Rect.inflate_ip - 就在原地位置增大或缩小矩形大小
pygame.Rect.clamp - 将矩形移动到另一个内部
pygame.Rect.clamp_ip - 将矩形就在原地位置移动到另一个内部
pygame.Rect.clip - 在另一个内部裁剪一个矩形
pygame.Rect.union - 将两个矩形连接成一个
pygame.Rect.union_ip - 将两个矩形就在原地位置连接成一个
pygame.Rect.unionall - 许多矩形的并集
pygame.Rect.unionall_ip - 许多矩形就在原地位置并集
pygame.Rect.fit - 使用纵横比调整大小并移动的矩形
pygame.Rect.normalize - 更正负大小
pygame.Rect.contains - 测试一个矩形是否在另一个矩形内
pygame.Rect.collidepoint - 测试一个点是否在矩形内
pygame.Rect.colliderect - 测试两个矩形是否重叠
pygame.Rect.collidelist - 测试列表中的一个矩形是否相交
pygame.Rect.collidelistall - 测试列表中的所有矩形是否相交
pygame.Rect.collidedict - 测试字典中的一个矩形是否相交
pygame.Rect.collidedictall - 测试字典中的所有矩形是否相交
相关操作
1.获取这个图片的Rect对象 pygame.image.load('images/ship2.bmp').get_rect()
2.通过图片的Rect对象的x y 属性的变化来移动图片对象

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# author:albert time:2020/9/17
import pygame
# import os
pygame.init()  # 初始化

# 参数配置
win_x = 480  # 游戏窗口大小
win_y = 700
FPS = 50  # FPS常量

# 需要的图片路径
me1_path = "images/me1.png"  # 我方飞机图的路径
enemy1_path = "images/enemy1.png"  # 敌机图的路径
background_path = "images/background.png"  # 背景图的路径

# 把需要用到的图片都载入进来作为Surface对象 备用
me1_surface = pygame.image.load(me1_path)
enemy1_surface = pygame.image.load(enemy1_path)
background_surface = pygame.image.load(background_path)

# 获取每个图像的Rect对象 以便后面控制图像变动位置等等
me1_rect = me1_surface.get_rect()
enemy1_rect = enemy1_surface.get_rect()
background_rect = background_surface.get_rect()

# 设置各个对象的初始坐标 从Surface对象那边获取的宽高是对的 但是坐标是0,0 所以这里要设置我机的初始坐标
print("x:{},y:{},w:{},h:{}".format(me1_rect.x, me1_rect.y, me1_rect.w, me1_rect.h))
me1_rect.x = int((win_x - me1_rect.w)/2)  # 我机起始在最下面那行的中间位置开始
me1_rect.y = win_y - me1_rect.h
print("x:{},y:{},w:{},h:{}".format(me1_rect.x, me1_rect.y, me1_rect.w, me1_rect.h))

# 创建游戏窗口 本质还是创建图像或者surface对象
windows1 = pygame.display.set_mode((win_x, win_y), 0, 32)

# 一开始的游戏界面绘图
windows1.blit(background_surface, (0, 0))  # 描绘背景
windows1.blit(me1_surface, me1_rect)  # 描绘我机
pygame.display.update()  # 刷新窗口界面

# 游戏流程开始
clock = pygame.time.Clock()  # 声明一个时钟对象
i = 0
while True:

    clock.tick(FPS)  # 每次执行到这里就会自动重置

    # 无限循环留下一个退出的通道
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()

    # 描绘背景
    windows1.blit(background_surface, (0, 0))
    # 计算我机本次的移动位置 到了最上面就自动回到原点重新跑
    me1_rect.y -= 1
    if me1_rect.y + me1_rect.h <= 0:
        me1_rect.y = win_y - me1_rect.h
    # 移动本机
    windows1.blit(me1_surface, me1_rect)
    pygame.display.update()  # 刷新界面

    print("[{}]:耗费{}毫秒,飞机坐标为({}, {})".format(i, clock.get_time(), me1_rect.x, me1_rect.y))
    i += 1

pygame.quit()  # 退出

pygame.Surface简单理解

是用来存储和表示pygame的图像对象。 游戏窗口从本质上说也是我们绘制出来的图像 在游戏窗口上运行的游戏 其实不过是 在一个大图像上的各种小图像相互作用的效果

(1)pygame.Surface.blit() 把图像绘制到另外一个图像上

pygame.Surface.blit(source, dest, area=None, special_flags = 0) –> Rect

source 参数指定的 Surface 对象绘制到该对象上。dest 参数指定绘制的位置,dest 的值可以是 source 的左上角坐标。如果传入一个 Rect 对象给 dest,那么 blit() 会使用它的左上角坐标,而不关 Rect 的大小什么事_可选参数 area 是一个 Rect 对象,表示限定 source 指定的 Surface 对象的范围。

(2)pygame.Surface.get_rect() 获取surface对象的Rect

get_rect(**kwargs) –> Rect

注意返回的Rect对象可以通过操纵该对象的参数来自由的移动对应的图像对象


pygame.image简单理解

包含用于加载和保存图片的函数,以及将Surface对象转换为其他包可用的格式
(1)pygame.image.load(filename) –> Surface  返回的也是surface对象  从文件中加载新图像(最常用的函数)
pygame.image.load(fileobj, namehint="") –> Surface


Pygame.display简单理解

Pygame中用于控制窗口和屏幕显示的模块

(1)pygame.display.set_mode 初始化一个窗口

pygame.display.set_mode(resolution=(0,0), flags=0, depth=0) –> Surface

第一个参数是元组 描绘图像的宽高 其他两个可以省略 不用管

(2)pygame.display.update 更新界面显示

你在窗口上描绘了很多图像 但是这还不够 还需要调用update刷新界面  这些描绘才能显示出来

pygame.display.update(rectangle=None) –> None  一般而言不用加参数
pygame.display.update(rectangle_list) -> None


pygame.time的简单介绍

pygame中用于管理时间的模块

(1)帧数概念

动画的本质是什么  就是一组连续的画顺序播放才形成了视觉上的动画  我们做的游戏 动画 很多时候 原理就是这么简单

帧数:我们一秒钟播放这动画100张图 就是帧数为100 一秒钟播放这动画50张图 就是帧数为50 每张图消耗的时间就是200ms

(2)pygame.time.Clock() 创建一个时钟对象帮助跟踪时间 Clock对象还提供了几个函数来帮助控制游戏的帧速率 注意Clock是大写C

Clock 按照写法规定 这是一个类 其下的类方法有

pygame.time.Clock.tick - 更新clock对象
pygame.time.Clock.tick_busy_loop - 更新clock对象
pygame.time.Clock.get_time - 上一个tick中使用的时间
pygame.time.Clock.get_rawtime - 上一个tick中使用的实际时间
pygame.time.Clock.get_fps - 计算clock对象的帧率

常用的形式:

clock = pygame.time.clock()  # 声明一个clock类的实例

while true:  # 一个无限循环

     # 主代码部分 负责在游戏窗口上绘制里面的图像的变化

     clock.tick(50)   # 实例的tick方法 参数是表示当前最高的帧数 一秒钟刷新多少多少次游戏窗口的各个图像对象 比如这个设置为50就是1秒钟最高刷新50次游戏窗口 也就是200毫秒刷新一次 或者200毫秒循环一次 如果本次循环主代码消耗时间不够200毫秒 执行到了clock.tick(50)这里 会根据情况自动补足200毫秒 当然了 如果消耗超过200毫秒 clock.tick(50)什么也不干 直接下个循环

clock = pygame.time.Clock()  # 声明一个时钟对象
i = 0
while True:

    clock.tick(FPS)  # 每次执行到这里就会自动重置

    # 无限循环留下一个退出的通道 避免无限循环卡顿
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()

    # # 描绘背景
    # windows1.blit(background_surface, (0, 0))
    # # 计算我机本次的移动位置 到了最上面就自动回到原点重新跑
    # me1_rect.y -= 1
    # if me1_rect.y + me1_rect.h <= 0:
    #     me1_rect.y = win_y - me1_rect.h
    # # 移动本机
    # windows1.blit(me1_surface, me1_rect)
    # pygame.display.update()  # 刷新界面
    # 
    # print("[{}]:耗费{}毫秒,飞机坐标为({}, {})".format(i, clock.get_time(), me1_rect.x, me1_rect.y))
    i += 1

(3)pygame的定时器  就是其他语言里面的时钟触发  设置一个定时器定时有规律的重复执行某个操作 比较常见的定时器使用是配合自定义事件

设置一个自定义事件 使用pygame.USEREVENT来创建 类似端口号之类的 我们自定义的事件都要在这个pygame.USEREVENT这个常量之后的数字才不会和固有事件产生冲突

在代码初始化中设置一个定时器和相关操作(这里是触发自定义事件)

在事件监听代码中监听指定的自定义事件并对他做出反应

import pygame
pygame.init()
# 定义自己的事件 注意一般都是常量
MY_EVENT1 = pygame.USEREVENT + 1

# 初始化中设置定时器 参数1是要定时触发的事件id 后面是间隔多久触发一次 这里是2秒一次
pygame.time.set_timer(MY_EVENT1, 2000)

clock = pygame.time.Clock()  # 声明一个时钟对象
# 接收事件
while True:
    clock.tick(10)  # fps
    # 避免卡顿的固定用法
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
        # 监听自定义事件
        if event.type == MY_EVENT1:
            print("自定义触发了")
pygame.quit()


pygame.event的简单介绍

事件队列 pygame.event.get()  所有的事件都在这个函数里面以字典的形式被捕捉了 我们要找特定的事件只需要遍历这个字典来查找即可

pygame.event.wait()这个用的不多 是让整个代码都停下来等待用户触发一个事件才会继续后续

常用事件


QUIT 用户按下关闭按钮 none
ATIVEEVENT Pygame被激活或者隐藏 gain, state
KEYDOWN 键盘被按下 unicode, key, mod
KEYUP 键盘被放开 key, mod
MOUSEMOTION 鼠标移动 pos, rel, buttons
MOUSEBUTTONDOWN 鼠标按下 pos, button
MOUSEBUTTONUP 鼠标放开 pos, button
VIDEORESIZE Pygame窗口缩放 size, w, h
USEREVENT 触发了一个用户事件 code

[<Event(1-ActiveEvent {'gain': 1, 'state': 1})>

自定义事件

常常和定时器配合使用

import pygame
pygame.init()
# 定义自己的事件 注意一般都是常量
MY_EVENT1 = pygame.USEREVENT + 1

# 初始化中设置定时器 参数1是要定时触发的事件id 后面是间隔多久触发一次 这里是2秒一次
pygame.time.set_timer(MY_EVENT1, 2000)

clock = pygame.time.Clock()  # 声明一个时钟对象
# 接收事件
while True:
    clock.tick(10)  # fps
    # 避免卡顿的固定用法
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
        # 监听自定义事件
        if event.type == MY_EVENT1:
            print("自定义触发了")
pygame.quit()


常用鼠标事件

[<Event(4-MouseMotion {'pos': (197, 101), 'rel': (-1, 2), 'buttons': (0, 0, 0), 'window': None})>] 鼠标移动反馈

[<Event(4-MouseMotion {'pos': (226, 298), 'rel': (-1, 1), 'buttons': (1, 0, 0), 'window': None})>] 点击鼠标左键反馈

[<Event(4-MouseMotion {'pos': (268, 312), 'rel': (0, 1), 'buttons': (0, 0, 1), 'window': None})>] 点击鼠标右键反馈

[<Event(5-MouseButtonDown {'pos': (268, 312), 'button': 2, 'window': None})>] 鼠标按下反馈

[<Event(6-MouseButtonUp {'pos': (234, 319), 'button': 1, 'window': None})>] 鼠标松开

MOUSEMOTION事件会在鼠标动作的时候发生,它有三个参数:

  • buttons – 一个含有三个数字的元组,三个值分别代表左键、中键和右键,1就是按下了。
  • pos – 就是位置了……
  • rel – 代表了现在距离上次产生鼠标事件时的距离

和MOUSEMOTION类似的,我们还有MOUSEBUTTONDOWNMOUSEBUTTONUP两个事件,看名字就明白是什么意思了。很多时候,你只需要知道鼠标点下就可以了,那就可以不用上面那个比较强大(也比较复杂)的事件了。它们的参数为:

  • button – 看清楚少了个s,这个值代表了哪个按键被操作
  • pos – 和上面一样。


利用鼠标各个事件实现一个鼠标按住拖拽图像移动 松开了放开图像的操作

原理

鼠标按住事件中:检测到鼠标按住的时候 通过鼠标按住事件获取到按下鼠标的坐标 然后判断鼠标位置是否在图像的范围内 不在图像范围内自然就不用实现拖拽功能,如果在图像范围内 则设置一个开关变量 在鼠标按住时间中设置为True 允许拖拽 

鼠标松开事件中:在鼠标松开事件中开关变量设置为False 不允许拖拽。

鼠标移动事件中:具体拖拽行为的实现是要在鼠标移动事件里面,先检测从鼠标按住事件和鼠标松开事件检测过的开关变量的值为真为假  是否允许拖拽图像,得到允许后 使用鼠标移动事件获取当前的鼠标坐标  修改图像对象的Rect属性来调整位置 实现拖拽移动图像

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# author:albert time:2020/9/17
import pygame

# import os
pygame.init()  # 初始化

# 参数配置
win_x = 480  # 游戏窗口大小
win_y = 700
FPS = 60  # FPS常量

# 需要的图片路径 照理说这里应该检测下文件是否存在 文件格式等等 不过为了演示不纠结这个
me1_path = "images/me1.png"  # 我方飞机图的路径
enemy1_path = "images/enemy1.png"  # 敌机图的路径
background_path = "images/background.png"  # 背景图的路径

# 把需要用到的图片都载入进来作为Surface对象 备用
me1_surface = pygame.image.load(me1_path)
enemy1_surface = pygame.image.load(enemy1_path)
background_surface = pygame.image.load(background_path)

# 获取每个图像的Rect对象 以便后面控制图像变动位置等等
me1_rect = me1_surface.get_rect()
enemy1_rect = enemy1_surface.get_rect()
background_rect = background_surface.get_rect()

# 设置各个对象的初始坐标 从Surface对象那边获取的宽高是对的 但是坐标是0,0 所以这里要设置我机的初始坐标
print("x:{},y:{},w:{},h:{}".format(me1_rect.x, me1_rect.y, me1_rect.w, me1_rect.h))
me1_rect.x = int((win_x - me1_rect.w) / 2)  # 我机起始在最下面那行的中间位置开始
me1_rect.y = win_y - me1_rect.h
print("x:{},y:{},w:{},h:{}".format(me1_rect.x, me1_rect.y, me1_rect.w, me1_rect.h))

# 创建游戏窗口 本质还是创建图像或者surface对象
windows1 = pygame.display.set_mode((win_x, win_y), 0, 32)

# 一开始的游戏界面绘图
windows1.blit(background_surface, (0, 0))  # 描绘背景
windows1.blit(me1_surface, me1_rect)  # 描绘我机
pygame.display.update()  # 刷新窗口界面

# 游戏流程开始
clock = pygame.time.Clock()  # 声明一个时钟对象
i = 0  # 区别每个循环的计数君
is_move = False  # (不能声明在循环内)这个是一个是否允许拖拽图像的开关变量 按下鼠标 判断当前鼠标位置在图像内 则允许拖拽 松开鼠标则关闭拖拽 具体的拖拽行为由移动鼠标事件负责


while True:

    clock.tick(FPS)  # fps

    # 描绘背景
    windows1.blit(background_surface, (0, 0))

    # 临时坐标 用来接收鼠标位置
    tmp_x = 0
    tmp_y = 0

    # 开始遍历事件做针对处理 同时避免卡顿
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()

        # 如果当前鼠标按下并且鼠标位置在图像内 就表示可以拖拽了 开关变量为True
        if event.type == pygame.MOUSEBUTTONDOWN:  # 按下鼠标

            tmp_x, tmp_y = event.pos  # 记录鼠标当前位置并判断是否在图像范围内 是则可以拖拽 不在则不可以
            if ((tmp_x >= me1_rect.x)
                    and (tmp_x <= me1_rect.x + me1_rect.w)
                    and (tmp_y >= me1_rect.y)
                    and (tmp_y <= me1_rect.y + me1_rect.h)):
                is_move = True
                print("[{}] =按下鼠标 开关变量:True=".format(i))

            else:
                print("[{}] <按下鼠标 开关变量:False>".format(i))

        # 松开鼠标表示无法拖拽了
        if event.type == pygame.MOUSEBUTTONUP:  # 松开鼠标
            print("[{}] |松开鼠标 开关变量:False|".format(i))
            is_move = False

        # 如果允许拖拽了 实际操作就在这个事件里面实现
        if event.type == pygame.MOUSEMOTION:
            if is_move:
                tmp_x, tmp_y = event.pos  # 接收当前鼠标坐标
                # 判断下坐标是否合法 简单说是否超出范围 超出则归位
                if tmp_x < 0:
                    me1_rect.x = 0
                if tmp_x > win_x - me1_rect.w:
                    me1_rect.x = win_x - me1_rect.w
                if tmp_y < 0:
                    me1_rect.y = 0
                if tmp_y > win_y - me1_rect.h:
                    me1_rect.y = win_y - me1_rect.h

                # 保证鼠标处于图像的中心位置
                me1_rect.x = tmp_x - int(me1_rect.w / 2)
                me1_rect.y = tmp_y - int(me1_rect.h / 2)
                print("[{}] ==鼠标移动 开关变量:True 坐标({}, {})==".format(i, me1_rect.x, me1_rect.y))

            else:
                print("[{}] 鼠标移动 False".format(i))

    # 移动本机 在事件里面调整要变动的图像坐标  在这里描绘出来
    print("[{}] 移动本机 坐标({}, {})".format(i, me1_rect.x, me1_rect.y))
    windows1.blit(me1_surface, me1_rect)
    pygame.display.update()  # 刷新界面


    i += 1

pygame.quit()  # 退出


常用键盘事件

[<Event(2-KeyDown {'unicode': '', 'key': 115, 'mod': 0, 'scancode': 31, 'window': None})>]

[<Event(3-KeyUp {'key': 115, 'mod': 0, 'scancode': 31, 'window': None})>]


键盘事件的3个常用例子

1.检测键盘按键 实现上下左右的移动

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# author:albert time:2020/9/17
import pygame

# import os
pygame.init()  # 初始化

# 参数配置
win_w = 480  # 游戏窗口大小
win_h = 700
FPS = 60  # FPS常量

# 需要的图片路径
me1_path = "images/me1.png"  # 我方飞机图的路径
enemy1_path = "images/enemy1.png"  # 敌机图的路径
background_path = "images/background.png"  # 背景图的路径

# 把需要用到的图片都载入进来作为Surface对象 备用
me1_surface = pygame.image.load(me1_path)
enemy1_surface = pygame.image.load(enemy1_path)
background_surface = pygame.image.load(background_path)

# 获取每个图像的Rect对象 以便后面控制图像变动位置等等
me1_rect = me1_surface.get_rect()
enemy1_rect = enemy1_surface.get_rect()
background_rect = background_surface.get_rect()

# 设置各个对象的初始坐标 从Surface对象那边获取的宽高是对的 但是坐标是0,0 所以这里要设置我机的初始坐标
print("x:{},y:{},w:{},h:{}".format(me1_rect.x, me1_rect.y, me1_rect.w, me1_rect.h))
me1_rect.x = int((win_w - me1_rect.w) / 2)  # 我机起始在最下面那行的中间位置开始
me1_rect.y = win_h - me1_rect.h
print("x:{},y:{},w:{},h:{}".format(me1_rect.x, me1_rect.y, me1_rect.w, me1_rect.h))

# 创建游戏窗口 本质还是创建图像或者surface对象
windows1 = pygame.display.set_mode((win_w, win_h), 0, 32)

# 一开始的游戏界面绘图
windows1.blit(background_surface, (0, 0))  # 描绘背景
windows1.blit(me1_surface, me1_rect)  # 描绘我机
pygame.display.update()  # 刷新窗口界面


def check_pic_position(rect, tmp_win_w, tmp_win_h):
    """
    funtion:为了处理窗口内的图像不能超出窗口范围 超出了则回到窗口内
    :param rect: 图像的rect对象
    :param tmp_win_w: 窗口的宽
    :param tmp_win_h: 窗口的高
    :return:返回一个rect对象
    """
    if rect.x < 0:
        rect.x = 0
    if rect.x > tmp_win_w - rect.w:
        rect.x = tmp_win_w - rect.w
    if rect.y < 0:
        rect.y = 0
    if rect.y > tmp_win_h - rect.h:
        rect.y = tmp_win_h - rect.h
    return rect


# 游戏流程开始
clock = pygame.time.Clock()  # 声明一个时钟对象
i = 0  # 区别每个循环的计数君

while True:

    clock.tick(FPS)  # fps

    # 描绘背景
    windows1.blit(background_surface, (0, 0))
    # print(pygame.event.get())
    # 开始遍历事件做针对处理 同时避免卡顿
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()

        if event.type == pygame.KEYDOWN:

            if event.key == pygame.K_UP:  # 判断按键可以使用pygame的按键常量 也可以使用按键码
            # if event.key == 273:

                print("向上")
                me1_rect.y -= 10
            elif event.key == 274:
                print("向下")
                me1_rect.y += 10
            elif event.key == 276:
                print("向左")
                me1_rect.x -= 10
            elif event.key == 275:
                print("向右")
                me1_rect.x += 10
            else:
                print(event.key)

    # 描绘背景
    windows1.blit(background_surface, (0, 0))

    # 保证rect的坐标变动不会超出窗口范围 用一个独立函数来处理
    me1_rect = check_pic_position(me1_rect, win_w, win_h)

    # 移动本机
    windows1.blit(me1_surface, me1_rect)
    pygame.display.update()  # 刷新界面

    # print("[{}]:耗费{}毫秒,飞机坐标为({}, {})".format(i, clock.get_time(), me1_rect.x, me1_rect.y))

    i += 1

pygame.quit()  # 退出

按键码 https://www.jianshu.com/p/ffefa2af6d47

问题是 长按上下左右效果无法实现 只能点击松开点击松开

2.按键一直按下的实现

使用pygame.key.get_pressed()实现的 上下左右的移动 很平滑 可以一直按下去 注意的是 pygame.key.get_pressed() 不要写在pygame.event.get()里面 很容易出现闪退的情况   pygame.key.get_pressed() 和 pygame.event.get()是平级的

# 游戏流程开始
clock = pygame.time.Clock()  # 声明一个时钟对象
i = 0  # 区别每个循环的计数君

while True:

    clock.tick(FPS)  # fps

    # 描绘背景
    windows1.blit(background_surface, (0, 0))
    # 使用 pygame.key.get_pressed 来控制上下左右 可以按住移动 很平滑
    keys = pygame.key.get_pressed()
    mods = pygame.key.get_mods()

    if keys[pygame.K_UP]:
        print("向上")
        me1_rect.y -= 10
    elif keys[pygame.K_DOWN]:
        print("向下")
        me1_rect.y += 10
    elif keys[pygame.K_LEFT]:
        print("向左")
        me1_rect.x -= 10
    elif keys[pygame.K_RIGHT]:
        print("向右")
        me1_rect.x += 10
    # 开始遍历事件做针对处理 同时避免卡顿
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()

    # 描绘背景
    windows1.blit(background_surface, (0, 0))

    # 保证rect的坐标变动不会超出窗口范围 用一个独立函数来处理
    me1_rect = check_pic_position(me1_rect, win_w, win_h)

    # 移动本机
    windows1.blit(me1_surface, me1_rect)
    pygame.display.update()  # 刷新界面

    # print("[{}]:耗费{}毫秒,飞机坐标为({}, {})".format(i, clock.get_time(), me1_rect.x, me1_rect.y))

    i += 1

3 组合键 不知道为啥 查了资料说是要使用 pygame.key.get_mods()配合使用 但是 实际测试中只需要 pygame.key.get_pressed() 就够了 足矣实现组合键 但是注意按键常量写法和get_mods() 有变化

keys = pygame.key.get_pressed()
    mods = pygame.key.get_mods()

    if keys[pygame.K_UP]:
        print("向上")
        me1_rect.y -= 10
    elif keys[pygame.K_DOWN]:
        print("向下")
        me1_rect.y += 10
    elif keys[pygame.K_LEFT]:
        print("向左")
        me1_rect.x -= 10
    elif keys[pygame.K_RIGHT]:
        print("向右")
        me1_rect.x += 10
    elif keys[pygame.K_F4] and keys[pygame.K_LALT]:  # 点击alt+f4 表示关闭
        print("即将关闭")
        pygame.quit()

窗口未响应问题(窗口卡顿严重 出现未响应标题)

原因

pygame中必须在循环体中对pygame.event.get()做出响应,不然系统就会认为窗口没有响应,鼠标就会一直转等待响应,而且点了以后,窗口会显示无响应

解决办法
在while循环中加上对事件的响应即可

无限循环加入此代码 即可解决

# 无限循环留下一个退出的通道
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()


创建主窗口和绘图

创建窗口(创建surface对象 或者创建图像)

# 参数配置
win_x = 480  # 游戏窗口大小
win_y = 700
FPS = 50  # FPS常量

# 需要的图片路径
background_path = "images/background.png"  # 背景图的路径

# 把需要用到的图片都载入进来作为Surface对象 备用
background_surface = pygame.image.load(background_path)

# 获取每个图像的Rect对象 以便后面控制图像变动位置等等
background_rect = background_surface.get_rect()


# 创建游戏窗口 本质还是创建图像或者surface对象
windows1 = pygame.display.set_mode((win_x, win_y), 0, 32)

# 游戏界面绘图
windows1.blit(background_surface, (0, 0))  # 描绘背景
pygame.display.update()  # 刷新窗口界面


创建窗口 绘图 和 事件触发 移动图像的整体例子

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# author:albert time:2020/9/17
import pygame
# import os
pygame.init()  # 初始化

# 参数配置
win_x = 480  # 游戏窗口大小
win_y = 700
FPS = 50  # FPS常量

# 需要的图片路径
me1_path = "images/me1.png"  # 我方飞机图的路径
enemy1_path = "images/enemy1.png"  # 敌机图的路径
background_path = "images/background.png"  # 背景图的路径

# 把需要用到的图片都载入进来作为Surface对象 备用
me1_surface = pygame.image.load(me1_path)
enemy1_surface = pygame.image.load(enemy1_path)
background_surface = pygame.image.load(background_path)

# 获取每个图像的Rect对象 以便后面控制图像变动位置等等
me1_rect = me1_surface.get_rect()
enemy1_rect = enemy1_surface.get_rect()
background_rect = background_surface.get_rect()

# 设置各个对象的初始坐标 从Surface对象那边获取的宽高是对的 但是坐标是0,0 所以这里要设置我机的初始坐标
print("x:{},y:{},w:{},h:{}".format(me1_rect.x, me1_rect.y, me1_rect.w, me1_rect.h))
me1_rect.x = int((win_x - me1_rect.w)/2)  # 我机起始在最下面那行的中间位置开始
me1_rect.y = win_y - me1_rect.h
print("x:{},y:{},w:{},h:{}".format(me1_rect.x, me1_rect.y, me1_rect.w, me1_rect.h))

# 创建游戏窗口 本质还是创建图像或者surface对象
windows1 = pygame.display.set_mode((win_x, win_y), 0, 32)

# 一开始的游戏界面绘图
windows1.blit(background_surface, (0, 0))  # 描绘背景
windows1.blit(me1_surface, me1_rect)  # 描绘我机
pygame.display.update()  # 刷新窗口界面

# 游戏流程开始
clock = pygame.time.Clock()  # 声明一个时钟对象
i = 0
while True:

    clock.tick(FPS)  # 每次执行到这里就会自动重置

    # 无限循环留下一个退出的通道
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()

    # 描绘背景
    windows1.blit(background_surface, (0, 0))
    # 计算我机本次的移动位置 到了最上面就自动回到原点重新跑
    me1_rect.y -= 1
    if me1_rect.y + me1_rect.h <= 0:
        me1_rect.y = win_y - me1_rect.h
    # 移动本机
    windows1.blit(me1_surface, me1_rect)
    pygame.display.update()  # 刷新界面

    print("[{}]:耗费{}毫秒,飞机坐标为({}, {})".format(i, clock.get_time(), me1_rect.x, me1_rect.y))
    i += 1

pygame.quit()  # 退出


残影问题的处理

我们在循环移动图像的时候 有的时候会沿着移动路径出现一连串的残影 这个残影是怎么发生的呢

image

代码部分:

clock = pygame.time.Clock()  # 声明一个时钟对象
i = 0
while True:

    clock.tick(FPS)  # 每次执行到这里就会自动重置

    # 无限循环留下一个退出的通道 避免无限循环卡顿
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()

    # 描绘背景
    # windows1.blit(background_surface, (0, 0))
    # 计算我机本次的移动位置 到了最上面就自动回到原点重新跑
    me1_rect.y -= 50
    if me1_rect.y + me1_rect.h <= 0:
        me1_rect.y = win_y - me1_rect.h
    # 移动本机
    windows1.blit(me1_surface, me1_rect)
    pygame.display.update()  # 刷新界面

    print("[{}]:耗费{}毫秒,飞机坐标为({}, {})".format(i, clock.get_time(), me1_rect.x, me1_rect.y))
    i += 1
问题:代码上看 每次循环 都会描绘一次移动后的飞机图像 上一次循环描绘的飞机图像并不是随着这一次的描绘就消失了而是依然存在窗口上 所有才导致这个问题

解决办法:每次循环 先优先描绘一次背景图 让背景图把上一次循环描绘的东西全部覆盖掉 然后在新的背景上描绘本次循环要描绘的所有图片对象


精灵和精灵组

https://blog.csdn.net/Enderman_xiaohei/article/details/88218773

精灵的作用:方便快捷的批量描述游戏对象 

比如我们之前游戏窗口有个飞机在移动 先是image.load加载该图片对象 然后获取该对象的rect 然后再循环的铺背景和控制rect来实现该图片对象的变动 实现了这个图像对象的动画效果 但是呢 如果有上百个类似的图像都要在这个游戏窗口运动怎么办 大批量的处理这些图像对象就可以吧每个图像对象都设置为一个精灵子类的实例 然后这些精灵子类实例分门别类的归到各个精灵组里面 每个精灵组内的精灵可以统一的协调的运动 并且里面封装了一批游戏对象常用的方法 比如碰撞测试

精灵组的作用:把一系列的游戏对象归到一个或者几个组 让他们统一操作


如何使用精灵和精灵租

1.根据你的实际需要来构建精灵子类 重写update方法来描述该子类的实例对象的具体行为

继承精灵的子类需要注意的地方:

  • 要在子类的__init__里面再次调用父类的初始化方法
  • 需要配置类实例属性Sprite.image Sprite.rect 一般也都是在__init__里面定义好的
  • 重写update方法 因为在父类里面这个方法其实是个空方法 当前这个子类的实例对象想要实现什么操作  比如上下有规则移动 都可以在这个类实例方法中描述
class EnemyClass(pygame.sprite.Sprite):
    """
    为敌人飞机提供精灵子类
    """
    # 按照规范要在sprite的子类的__init__里面载入pygame.sprite.Sprite的init方法
    # 并且要在这个sprite的子类的__init__里面给类实例属性image和rectify赋初值
    def __init__(self, image_path, start_x=0, start_y=0):
        pygame.sprite.Sprite.__init__(self)  # 载入pygame.sprite.Sprite的init方法
        # 类实例属性image和rectify赋初值
        self.image = pygame.image.load(image_path)
        self.rect = self.image.get_rect()
        self.rect.x = start_x  # 修正初始坐标 从get_rect获取的坐标默认是0 0
        self.rect.y = start_y
        print("敌人实例x:{},y:{},w:{},h:{}".format(me1_rect.x, me1_rect.y, me1_rect.w, me1_rect.h))

    # update是继承自精灵父类的 但是精灵父类里面是个空壳子 需要我们重写update方法来赋值这个精灵子类的行为模式
    def update(self, *args):
        print("敌人实例的update触发")
        self.rect.y += 10
        # 到底了就从上面重新开始
        if self.rect.y >= win_h - self.rect.h:
            self.rect.y = 0

2.添加精灵子类实例进精灵组

enemy_group = pygame.sprite.Group()  # 定义一个敌人精灵组用来存储 敌人类的实例
enemy_1 = EnemyClass(enemy1_path, 0, 100)  # 实例化一个敌人
enemy_2 = EnemyClass(enemy1_path, 200, 100)  # 实例化一个敌人
enemy_group.add(enemy_1, enemy_2)  # 把多个敌人实例添加到敌人精灵组里面

3 精灵组统一控制精灵

# 游戏流程开始
clock = pygame.time.Clock()  # 声明一个时钟对象
enemy_group = pygame.sprite.Group()  # 定义一个敌人精灵组用来存储 敌人类的实例
enemy_1 = EnemyClass(enemy1_path, 0, 100)  # 实例化一个敌人
enemy_2 = EnemyClass(enemy1_path, 200, 100)  # 实例化一个敌人
enemy_group.add(enemy_1, enemy_2)  # 把多个敌人实例添加到敌人精灵组里面

i = 0  # 区别每个循环的计数君

while True:

    clock.tick(FPS)  # fps

    enemy1_rect.y += 10  # 敌人的飞机移动

    # 开始遍历事件做针对处理 同时避免卡顿
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()

    # 敌人
    enemy_group.update()  # 精灵组来统一触发组内所有精灵的update方法
    enemy_group.draw(windows1)  # 精灵组统一把组内精灵描绘到游戏窗口上

    pygame.display.update()  # 刷新界面

    i += 1

4 从精灵组删除精灵

精灵组.remove(精灵名)  没什么可说的

5 精灵 精灵组之间检测碰撞


精灵与精灵之间的碰撞检测
pygame.sprite.collide_rect(left, right) -> bool
测试两个精灵之间的碰撞。 使用pygame rect colliderect函数计算碰撞。 作为碰撞回调函数传递给*collide函数。 精灵必须具有“rect”属性。

精灵与精灵组的碰撞检测
pygame.sprite.spritecollide(sprite, group, dokill, collided = None) -> Sprite_list
返回一个列表,其中包含与另一个Sprite相交的Group中的所有Sprite。 通过比较每个Sprite的Sprite.rect属性来确定交集。
dokill参数是一个布尔值。 如果设置为True,则将从组中删除所有碰撞的Sprite。
碰撞参数是一个回调函数,用于计算两个精灵是否发生碰撞。 它应该将两个精灵作为值,并返回一个bool值,指示它们是否发生碰撞。 如果未传递碰撞,则所有精灵必须具有“rect”值,该值是sprite区域的矩形,将用于计算碰撞。

两个精灵组之间发生碰撞
pygame.sprite.groupcollide(group1, group2, dokill1, dokill2, collided = None) -> Sprite_dict
这将在两组中找到所有精灵之间的碰撞。 通过比较每个Sprite的Sprite.rect属性或使用碰撞函数(如果它不是None)来确定碰撞。
group1中的每个Sprite都被添加到返回字典中。 每个项的值是group2中相交的Sprite列表。
如果dokill参数为True,则将从各自的组中删除碰撞的Sprite。
碰撞参数是一个回调函数,用于计算两个精灵是否发生碰撞。 它应该将两个精灵作为值并返回一个bool值,指示它们是否发生碰撞。 如果未传递碰撞,则所有精灵必须具有“rect”值,该值是精灵区域的矩形,将用于计算碰撞。

posted @ 2020-09-19 18:34  点-滴  阅读(1180)  评论(0编辑  收藏  举报