一、基础知识
pygame安装
pygame
就是一个Python模块,专为店子游戏设计
安装pygame(Windows):
pip install pygame
验证安装
python -m pygame.examples.aliens
会出现一个游戏界面,则表示安装成功。
pygame快速入门
1、使用pygame创建图形窗口
1.1游戏的初始化和退出
要使用pygame提供的所有功能之前,需要调用init方法
在游戏结束前需要调用quit方法
方法 | 说明 |
---|---|
pygame.init() | 导入并初始化所有pygame模块,使用其他模块之前,必须先调用init方法 |
pygame.quit() | 卸载所有pygame模块,在游戏结束之前调用! |
1.2理解游戏中的坐标
坐标系:
原点在左上角(0,0)
x轴水平方向向右,逐渐增加
y轴垂直方向向下,逐渐增加
在游戏中,所有可见的元素都是以矩形区域来描述位置的:
要描述一个矩形区域有四个要素:(x,y)(width,height)
pygame
专门提供了一个类pygame.Rect
用于描述矩形区域
Rect(x, y , width , height ) --->Rect
提示:
pygame.Rect
是一个比较特殊的类,内部只是封装了一些数字计算
不执行pygame.init()
方法同样能够直接使用
例子:
import pygame
first_rect = pygame.Rect(100,500,120,125)
print(' 第一个矩形区域的原点为: %d %d' %(first_rect.x , first_rect.y))
print(' 第一个矩形区域的尺寸为:%d %d' %(first_rect.width , first_rect.height))
print('尺寸:',first_rect.size)
1.3创建游戏主窗口
pygame专
门提供了一个模块pygame.display
用于创建、管理游戏窗口
方法 | 说明 |
---|---|
pygame.display.set_mode() | 初始化游戏显示窗口 |
pygame.display.update() | 刷新屏幕内容显示 |
set_mode
方法:
set_mode(resolution=(0,0),flags=0) -->Surface
- 作用 —创建游戏显示窗口
- 参数:
resolution
指定屏幕的宽和高,默认创建的窗口大小和屏幕大小一致
flags
参数指定屏幕的附加选项,例如是否全屏等等,默认不需要传递
depth
参数表示颜色的位数,默认自动匹配 - 注意:必须使用变量记录set_mode方法的返回结果。因为:后续所有的图像绘制都基于这个返回结果
#创建游戏主窗口
screen = pygame.display.set_mode((480,700))
1.4简单的游戏循环
- 为了做到游戏程序启动后,不会立即退出,通常会在游戏程序中添加一个游戏循环
- 游戏循环就是一个无限循环
- 在创建游戏串口代码下方,增加一个无限循环(使用 while true)
- 注意:游戏窗口不需要重复创建
2、理解图像并实现图像绘制
- 在游戏中,能够看到的游戏元素大多都是图像
- 图像文件初始是保存在磁盘上的,如果需要使用,第一步就需要被加载到内存中。
- 要在屏幕上看到某一个图像的内容,需要按照三个步骤:
- 1、使用
pygame.image.load()
加载图像的数据 - 2、使用游戏屏幕对象,调用
blit
方法,将图像绘制到指定位置 - 3、调用
pygame.display.update()
方法更新整个屏幕的显示
提示:要想在屏幕上看到绘制的结果,就一定要调用pygame.display.update()方法
例子:
# 绘制背景图像
# 加载图像数据
bg=pygame.image.load("./images/background.png")
hero=pygame.image.load("./images/me1.png")
# blit绘制图像
screen.blit(bg,(0,0))
screen.blit(hero,(200,500))
# update更新屏幕显示
pygame.display.update()
理解update()方法的作用
- 使用display.set_mode()创建的screen对象是一个内存中的屏幕数据对象
- screen.blit方法可以在画布上绘制很多图像
- display.update()会将画布的最终结果绘制在屏幕上,这样可以提高屏幕绘制效率,增加游戏的流畅度。
3、理解游戏循环和游戏时钟
3.1游戏中动画实现原理
- 通过在电脑上每秒绘制60次,就能够到大非常连续高品质的动画效果。每次绘制的结果被成为帧Frame
3.2游戏循环
游戏的两个组成部分:
游戏循环的开始就意味着游戏的正式开始
游戏循环的作用:
1、保证游戏不会直接退出
2、变化图像位置—动画效果
每个1/60 秒移动一下所有图像的位置
调用pygame.display.update()
更新屏幕显示
3、检测用户交互 —按键、鼠标等
3.3游戏时钟
- pygame专门提供了一个类
pygame.time.Clock
可以非常方便的设置屏幕绘制速度—刷新帧率。 - 要使用时钟对象需要两步:
- 1)在游戏初始化创建一个时钟对象
- 2)在游戏循环中让时钟对象调用tick(帧率)方法
tick
方法会根据上次被调用的时间,自动设置游戏循环中的延时
# 创建时钟对象
clock = pygame.time.Clock()
# tick参数可以指定循环体内部循环的频率
clock.tick(60) #在循环体内部使用
3.4简单动画实现
需求:
1、在游戏初始化定义一个pygame.Rect的变量记录英雄的初始位置
2、在游戏循环中每次让英雄的y-1 ----向上移动
3、当y<=0时,将英雄丕东到屏幕的底部
提示:
每一次调用update()方法之前,需要把所有的游戏图像都重新绘制一边
而且应该最先重新绘制背景图像
3.5在游戏循环中监听事件
事件event
-
就是启动游戏后,用户针对游戏所做的操作
-
例如:点击关闭按钮,点击鼠标,按下键盘…
监听 -
在游戏循环中,判断用户具体操作
-
只有捕获到用户具体的操作,才能针对性的做出响应
pygame中通过pygame.event.get()可以获得用户当前所做动作的事件列表。
用户可以同一时间做很多事情
提示:这段代码非常固定,几乎所有的pygame游戏都大同小异。
例子:
for event in pygame.event.get():
#判断用户是否点击了关闭按钮
if event.type == pygame.QUIT:
print("退出游戏...")
pygame.quit()
#直接退出系统
exit()
理解精灵和精灵组
在之前写的案列中,图像加载、位置变化、绘制图像都需要程序员编写代码分别处理。
为了简化开发步骤,pygame提供了两个类
- pygame.sprite.Sprite —存储图像数据image和位置rect对象
- pygame.sprite.Group
精灵(需要派生子类 |
---|
image 记录图像数据 rect 记录在屏幕上的位置 |
update(*args):更新精灵位置 kill():从所有组中删除 |
精灵组 |
---|
init(self , *精灵): add(*sprites):向组中添加精灵 update(*args):让组中所有精灵调用update方法 sprites():返回所有精灵列表 draw(Surface):将组中所有精灵的image,绘制到Surface的rect位置 |
4.1创建游戏精灵:
新建一个plane_sprites.py文件
定义GameSprite类继承自pygame.sprite.Sprite
注意:
- 如果一个类的父类不是object
- 在重写初始化方法时,一定要先super()一下父类的__init__方法
- 保证父类中实现的__init__代码能够被正常执行
属性: - image精灵图像,使用image_name加载
- rect精灵大小,默认使用图像大小
- speed精灵移动速度,默认为1
方法:
update每次更新屏幕时在游戏循环内调用
让精灵的self.rect.x+=self.speed
提示:
image的get_rect()方法,可以返回pygame.Rect(0,0,图像宽,图像高)的对象
实现代码:
class GameSprite(pygame.sprite.Sprite):
"""飞机大战游戏的精灵"""
def __init__(self,image_name,speed=1):
#调用父类的初始化方法
super().__init__()
#定义对象属性:
#这里的image_name是图片的存放路径
self.image = pygame.image.load(image_name)
self.rect = self.image.get_rect()
self.speed = speed
#定义更新
def update(self):
#在屏幕的垂直方向上移动
self.rect.y += self.speed
4.2使用游戏精灵和精灵组创建敌机
步骤
1、使用from导入plane_sprites模块
- from导入的模块可以直接使用
- import 导入的模块需要通过模块名来使用
2、在游戏初始化创建精灵对象和精灵组对象
3、在游戏循环中让精灵组分别调用update()和draw(screen)方法
职责 - 精灵
- 封装图像image、位置rect和速度speed
- 提供update()方法,根据游戏需求,更新位置rect
- 精灵组
- 包含多个精灵对象
- update方法,让精灵组中的所有精灵调用update方法更新位置
- draw(screen)方法,在screen上绘制精灵组中的所有精灵
代码
#创建敌机的精灵
enemy = GameSprite("./images/enmey1.png")
#创建敌机的精灵组
enemy_group = pygame.sprite.Group(enemy)
#游戏循环
while True:
#tick参数可以指定循环体内部循环的频率,记得import time模块
clock.tick(60)
# 捕获事件
for event in pygame.event.get():
# 判断事件类型是否是退出事件
if event.type == pygame.QUIT:
print('游戏退出...')
# quit卸载所有模块
pygame.quit()
# exit() 直接终止当前正在执行的程序
exit()
#让精灵组调用两个方法
#update --让组中的所有精灵更新位置
enemy_group.update()
#draw --在screen上绘制所有的精灵
enemy_group.draw(screen)
#screen是最开始创建的游戏窗口
#screen = pygame.display.set_mode((480,700))
游戏框架搭建
目标—使用面向对象设计飞机大战游戏类
目标:
- 明确主程序职责
- 实现主程序类
- 准备游戏精灵组
01.明确主程序职责
一个游戏程序的职责可以分为两个部分:
- 游戏初始化
- 游戏循环
根据明确的职责,设计PlaneGame类如下:
|PlaneGame|
|:–|
|screen |
PlaneGame |
---|
screen clock 精灵或精灵组… |
__init__(self) :__create_sprites(self): start_game(self): __event_handler(self): __update_sprites(self): __game_over(): |
提示:根据职责封装私有方法,可以避免某一个方法的代码写得太过冗长,如果某一个方法编写的太长,及不好阅读,也不好维护
方法 | 职责 |
---|---|
__event_handler(self) | 事件监听 |
__check_collide(self) | 碰撞检测—子弹销毁敌机、敌机撞毁英雄 |
__update_sprites(self) | 精灵组更新和绘制 |
__game_over() | 游戏结束 |
02.明确文件职责
plane_main
:
1、封装主游戏类
2、创建游戏对象
3、启动游戏plane_sprites
:
1、封装游戏中所有需要使用的精灵子类
2、提供游戏的相关工具
代码实现- 新建plane_main.py文件,并设置为可执行
- 编写基础代码
import pygame
from plane_sprites import *
class PlaneGame(object):
"""飞机大战主游戏"""
def __init__(self)
print("游戏初始化")
def start_game(self):
print("游戏开始")
#游戏循环体
while True:
pass
if __name__=='__main__':
#创建游戏对象
game = PlaneGame()
#启动游戏
game.start_game()
在游戏初始化中需要设置游戏窗口、创建游戏时钟、创建精灵、精灵组。
在PlaneGame类中的__init__函数中进行这些操作:
def __init__(self):
print("游戏初始化")
#1.设置游戏窗口
self.screen = pygame.display.set_mode(SCREEN_RECT.size)
#SCREEN_RECT是定义在plane_sprites.py中的屏幕大小的常量。
#通过设置次常量,当我们需要修改屏幕大小时,直接在plane_spriyes.py文件定义常量的位置修改即可,不需要在主函数中一个一个的去找和去修改值。
#提示:python中并没有真正意义的常量,只是通过命名的约定---所有字母都是大写的就是常量,开发时不要轻易修改。
#2.创建游戏时钟
self.clock = pygame.time.Clock()
#3.调用私有方法,创建精灵和精灵组
self.__create_sprites()
#私有方法,创建精灵和精灵组
def __create_sprites(self):
pass
在游戏循环中需要:设置刷新帧率、事件监听、碰撞检测、更新/绘制精灵组,更新屏幕显示。
代码如下:
def start_game(self):
print("游戏开始")
#游戏循环
while True:
#1.设置刷新帧率
self.clock.tick(FRAME_PRE_SEC)
#这里的FRAME_PRE_SEC同上屏幕大小
#2.事件监听:
self.__event_handle()
#3.碰撞检测:
self.__check_collide()
#4.更新/绘制精灵
self.__update_sprites()
#5.更新显示
pygame.display.update()
#以下是游戏循环中函数的定义
#暂时只定义,不实现,稍后一个一个实现
def __event_handle(self):
#使用pygame.event.get()获得操作事件
#使用for循环遍历获得的操作事件
for event in pygame.event.get():
#判断是否退出游戏
if event.type == pygame.QUIT:
#定义的静态私有方法,不需要创建对象,直接就可以使用
PlaneGame.__game_over()
elif:
pass
def __check_collide(self):
pass
def __update_sprites(self):
pass
#定义退出游戏的静态私有方法
@staticmethod
def __game_over():
print("游戏结束")
pygame.quit()
exit()
03.设计背景类
背景交替滚动的思路
游戏启动后,背景图像会连续不断地向下方移动
在视觉上产生飞机不断向上飞行的错觉:
游戏背景不断变化
游戏的主角位置保持不变
解决方法:
1.创建两张背景图像精灵
第1张完全和屏幕重合
第2张在屏幕的正上方
2.两张图像一起向下方运动
self.rect.y +=self.speed
3.当任意背景精灵的 rect.y >= 屏幕的高度,说明移动到屏幕下方
4.将移动到屏幕下方的这张图像设置到屏幕的正上方
rect.y = -rect.height
设计一个背景类Background,继承于GameSprite类,并且有自己的方法。
-
初始化方法
-
直接指定背景图片
-
is_alt
判断是否是另一张图像 -
False表示第一张图像,需要与屏幕重合
-
True表示另一张图像,在屏幕的正上方。
-
update()方法
-
判断是否移动出屏幕,如果是,将图像设置到屏幕的正上方,从而实现交替滚动。
提示:
继承如果父类提供的方法,不能满足子类的需求:
派生一个子类
在子类中针对特有的需求,重写父类方法,并且进行扩展
实现代码
plane_sprites.py
import pygame
#定义屏幕大小的常量
SCREEN_RECT = pygame.Rect(0,0,480,700)
#定义屏幕刷新率
FRAME_PRE_SEC = 60
class GameSprite(pygame.sprite.Sprite):
"""飞机大战游戏精灵"""
def __init__(self,image_name,speed=1):
#调用父类的初始化方法
super().__init__()
#定义对象属性:
#这里的image_name是图片的存放路径
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=Flase):
#1.调用父类方法实现精灵的创建
super().__init__("./images/background.png")
#2.判断是否是交替图像,如果是,需要设置初始位置
if is_alt:
self.rect.y = self.rect.height
def update(self):
#调用父类的方法实现
super().update()
#判断背景是否溢出屏幕,如果移出屏幕,将图像设置到屏幕的上方
if self.rect.y>=SCREEN_RECT.height:
self.rect.y = -self.rect.height
在plane_main.py中显示背景精灵
1、在__create_sprites
方法中创建精灵和精灵组
2、在__update_sprites
方法中,让精灵组调用update()
和draw()
方法
#plane_main.py模块
class PlaneGame(object):
"""飞机大战主游戏"""
def __create_sprites(self):
bg1 = Background()
bf2 = Background(True)
self.back_group = pygame.sprite.Group(bg1,bg2)
def __update_sprites(self):
self.back_group.update()
self.back_group.draw(self.screen)
04敌机出场
01使用定时器添加敌机
1、游戏启动后,每隔1s会出现一架敌机
2、每架敌机向屏幕下方飞行,飞行速度各不相同
3、每架敌机出现的水平位置也不尽相同
4、当敌机从屏幕下方飞出,不会再飞回到屏幕中
1.1定时器
- 在pygame中可以使用pygame.time.set_time()来添加定时器
- 所谓定时器,就是每隔一段事件,去指定一些动作
set_timer(eventid,milliseconds) --->None
- set_timer可以创建一个事件
- 可以在游戏循环的事件监听方法中捕获到该事件
- 第1个参数事件代号需要基于常量pygame.USEREVENT来指定
USEREVENT是一个整数,再增加的事件可以使用USEREVENT+1指定,一次类推… - 第二个参数是事件触发间隔的毫秒值
定时器事件的监听
- 通过pygame.event.get()可以获取当前时刻所有的事件列表
- 遍历列表并且判断event.type是否等于eventid,如果相等,表示定时器事件发生。
1.2定义并监听创建敌机事件
pygame的定时器使用套路非常固定:
1、定义定时器常量—eventid
2、在初始化方法中,调用set_timer方法设置定时器事件
3、在游戏循环中,监听定时器事件
1)定义事件
在plane_sprites.py的顶部定义事件常量
#敌机出现
CREATE_ENEMY_EVENT = pygame.USEREVENT
2)在初始化方法中,调用set_timer方法设置定时器事件
#plane_main.py文件
class PlaneGame(object):
def __ini__(self):
#前面不再赘述
#4.设置定时器事件 --创建敌机,每1s出现
pygame.time.set_timer(CREATE_ENEMY_EVENT,1000)
3)在游戏循环中,监听定时器事件
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)
02设计Enemy类
- 初始化方法
指定敌机图片
随机敌机的初始位置和初始速度 - 重写update()方法
判断是否飞出屏幕,如果是,从精灵组删除
代码
class Enemy(GameSprite):
"""敌机精灵"""
def __init__(self):
#1.调用父类方法实现精灵的创建,同时指定敌机图片
super().__init__("./images/enemy1.png")
#2.指定敌机的初始随机速度1~3
self.spedd = random.randint(1,3)
#3.指定敌机的初始随机位置
#self.rect.y = -self.rect.height
self.rect.bottom = 0
self.rect.x = random.randint(0,SCREEN_RECT.width-self.rect.width)
def update(self):
#1.调用父类方法,保持垂直方向的飞行
super().update()
#2.判断是否飞出屏幕,如果是,需要从精灵组删除敌机
if self.rect.y >= SCREEN_RECT.height:
self.kill()
def __del__(self):
print("敌机挂了 %s"%self.rect)
创建好敌机类后,在plane_main.py文件中创建精灵并加入到精灵组。创建精灵在监听定时器事件中已完成
加入精灵组
def __create_sprites(self):
self.enemy_group = pygame.sprite.Gruop()