大家好,未来的开发者们请上座
随着人工智能的发展,强化学习基本会再次来到人们眼前,遂想制作一下相关的教程。强化学习第一步基本离不开虚拟环境的搭建,下面用大家耳熟能详的贪吃蛇游戏为基础,制作一个Agent,完成对这个游戏的绝杀。
万里长城第一步:用python开发贪吃蛇游戏
用Python进行游戏开发的首选模块就是PyGame。
pygame 是一个流行的跨平台Python模块,专为电子游戏设计,包含图像、声音等,创建在SDL(Simple DirectMedia Layer)基础上,允许实时电子游戏研发而不会被低级语言,如C语言或是更低级的汇编语言束缚。基于这样一个设想,所有需要的游戏功能和理念(主要是图像方面)都完全简化为游戏逻辑本身,所有的资源结构都可以由高级语言(如Python)提供。它提供了一套丰富的功能,使得游戏开发者可以轻松地创建图形、动画、音效和游戏逻辑。
以下是 pygame 的一些关键特性:
注:不需要特地去记,一开始只需要知道pygame有哪些关键特性,哪些功能他可以实现即可。
pip install pygame
一旦安装了 pygame,你就可以开始创建游戏窗口、加载图像、处理用户输入和创建游戏逻辑了。pygame 的社区活跃,有大量的教程和文档可供学习,这使得即使是编程新手也能够开始他们的游戏开发之旅。
Pygame做游戏开发的优势在于不需要过多地考虑底层相关的内容,可以把工作中心放在游戏逻辑上。例如,PyGame中集成了很多和底层相关的模块,如访问显示设备、管理事件、使用字体等。
Pygame常用模块如下:
模块名 | 功能 |
---|---|
pygame.display | 访问显示设备。 |
pygame.draw | 绘制形状、线和点。 |
pygame.event | 管理事件。 |
pygame.font | 使用字体。 |
pygame.image | 加载和存储图片。 |
pygame.key | 读取键盘按键。 |
pygame.mixer | 声音。 |
pygame.mouse | 鼠标。 |
pygame.movie | 播放视频。 |
pygame.music | 播放音乐。 |
pygame.rect | 管理矩形区域。 |
pygame.sndarray | 操作声音数据。 |
pygame.sprite | 操作移动图像。 |
pygame.surface | 管理图像和屏幕。 |
pygame.surfarray | 管理点阵图像数据。 |
pygame.time | 管理时间和帧信息。 |
pygame.transform | 缩放和移动图像。 |
使用Pygame的display模块和event模块创建一个PyGame窗口,代码结构主要分三部分:
初始化涉及到的pygame与sys函数:
模块名 | 功能 |
---|---|
pygame.init() | 初始化所有的pygame模块,使用其他模块之前基本一定要调用init方法 |
pygame.display.set_mode() | 初始化窗口的宽高,并返回游戏屏幕(后续对象都需要画到游戏屏幕上) |
pygame.display.set_caption() | 初始化窗口的名字 |
pygame.event.get() | 键盘、鼠标、手柄 事件响应 |
pygame.QUIT | 点击窗口右上角红X的事件 |
sys.exit() | 程序退出 |
pygame.quit() | 程序终止前调用,卸载所有pygame模块 |
# -*- coding: utf-8 -*-
# 一、模块导入
import sys # 导入sys模块
import pygame # 导入pygame模块
# 二、pygame窗口初始化
pygame.init() # 初始化pygame
size = width, height = 320, 240 # 设置窗口尺寸
screen = pygame.display.set_mode(size) # 显示窗口
# 设置标题
pygame.display.set_caption('窗口初始化测试')
# 三、游戏主体
# 执行死循环,确保窗口一直显示,同时监听关闭按钮事件
while True:
# 检查事件
for event in pygame.event.get(): # 遍历所有事件
if event.type == pygame.QUIT: # 如果单击关闭按钮
pygame.quit() # 卸载所有pygame模块
sys.exit() # 终止当前程序
运行结果如下:
这样子你就创建了一个窗口,点击关闭按钮,程序捕捉到pygame.QUIT事件,pygame.quit()卸载完pygame模块后,运行 sys.exit() 退出程序。
这样子就开始了基于pygame制作游戏的第一步。窗口初始化
涉及到游戏基本就离不开控制角色移动,遂就将其当做第二课
运行结果如下:
该课程涉及到的pygame函数(不重复上一课学习到的了):
模块名 | 功能 |
---|---|
pygame.time.Clock() | 创建时钟对象,用于控制屏幕绘制速度 – 刷新频率 |
clock.tick(60) | 时钟对象(一定要放在整个游戏循环最后,不然会起不到稳定控制游戏刷新速度的效果),设置频率为60。如果你的游戏逻辑和渲染非常快,tick 方法将会使程序暂停,以保持 60 FPS 的速度,如果速度慢于60则不会等待 |
pygame.key.get_pressed() | 获取当前全部键盘key的状态,没按下的按钮值为False,按下的按钮值为True |
screen.fill() | 快捷填充背景色 |
pygame.draw.rect() | 绘制矩阵(游戏屏幕,颜色,矩阵(x,y,width,height),width(矩阵粗细,没有则填充整个矩阵)) |
pygame.display.update() | 更新屏幕画面 |
注:额外知识点补充游戏中的坐标系:
具体的逻辑代码如下
使用Pygame的创建绿色方块并键盘移动,代码结构主要分三部分:
# -*- coding: utf-8 -*-
# 一、模块导入
import sys # 导入sys模块
import pygame # 导入pygame模块
# 二、pygame窗口初始化
pygame.init() # 初始化pygame
size = width, height = 320, 240 # 设置窗口尺寸
screen = pygame.display.set_mode(size) # 显示窗口
# 设置标题
pygame.display.set_caption('创建绿色方块并键盘移动')
# 三、游戏主体
# 执行死循环,确保窗口一直显示,同时监听关闭按钮事件
## 3.1 游戏内对象初始化
# 设置矩阵的属性
rect_color = (0, 255, 0) # 绿色
rect_len = 20 # 矩阵的边长
rect_pos = [width // 2, height // 2] # 矩阵的初始位置
rect_speed = [2, 2] # 矩阵的速度
# 定义时钟对象
clock = pygame.time.Clock()
while True:
# 3.2 监听按键、鼠标事件并设置事件内容,修改方块的值
# 检查事件
for event in pygame.event.get(): # 遍历所有事件
if event.type == pygame.QUIT: # 如果单击关闭按钮
pygame.quit() # 卸载所有pygame模块
sys.exit() # 终止当前程序
# 检查按键,并对上下左右每一个按键,绑定事件
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
rect_pos[0] -= rect_speed[0]
if keys[pygame.K_RIGHT]:
rect_pos[0] += rect_speed[0]
if keys[pygame.K_UP]:
rect_pos[1] -= rect_speed[1]
if keys[pygame.K_DOWN]:
rect_pos[1] += rect_speed[1]
# 3.3 依据方块的位置宽高,重新渲染画面和方块
#确保方块不会移出屏幕
rect_pos[0]=max(0,min(width-rect_len,rect_pos[0]))
rect_pos[1]=max(0,min(height-rect_len,rect_pos[1]))
# 填充背景色(每次用黑色的背景覆盖掉旧有的画面)
screen.fill((0,0,0))
#画矩阵
pygame.draw.rect(screen,rect_color,(*rect_pos,rect_len,rect_len))
# 更新屏幕
pygame.display.update()
# 控制游戏刷新速度
clock.tick(60)
上述代码中,添加了键盘事件检测。pygame.key.get_pressed()能够获取键盘状态,查询目标按钮的状态并添加相应的事件,如:
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
rect_pos[0] -= rect_speed[0]
if keys[pygame.K_RIGHT]:
rect_pos[0] += rect_speed[0]
if keys[pygame.K_UP]:
rect_pos[1] -= rect_speed[1]
if keys[pygame.K_DOWN]:
rect_pos[1] += rect_speed[1]
如果按下left,则rect的x值减少,按下right,则rect的x值增加。如果按下up,则y值减少,按下down,y值增加。具体对应关系可参考上面的pygame中的游戏坐标系进行理解。
游戏基本一定会涉及到游戏内物体的交互,一个游戏的玩法核心就是交互,在设计游戏玩法的时候,各位帅哥美女们可以参考下面,设计一下交互规则。
创建交互规则如下:
具体的逻辑代码如下
初始化随机生成一个果子的位置,并渲染出来
当二者左上角的位置距离<8 px的时候,果子刷新位置
该课程涉及到的pygame函数(不重复上一课学习到的了):
模块名 | 功能 |
---|---|
random.randint(0, 30) | 随机从0到30这31个数抽一个数 |
np.linalg.norm(3,4)= 5 | 求一个向量的长度,与sqrt(pow(x, 2) + pow(y, 2))等价 |
*迭代对象(比如程序中的:*foodPosition) | 列表解包,比如:将foodPosition拆成零散的变量,而不是之前的元组 |
具体代码如下:
# -*- coding: utf-8 -*-
# 一、模块导入
import sys # 导入sys模块
import pygame # 导入pygame模块
import random # 导入random模块
import numpy as np
# 二、pygame窗口初始化
pygame.init() # 初始化pygame
size = width, height = 320, 240 # 设置窗口尺寸
screen = pygame.display.set_mode(size) # 显示窗口
# 设置标题
pygame.display.set_caption('3、控制绿色方块吃掉红色果子')
# 三、游戏主体
# 执行死循环,确保窗口一直显示,同时监听关闭按钮事件
## 3.1 游戏内对象初始化
# 设置矩阵的属性
rect_color = (0, 255, 0) # 绿色
rect_len = 20 # 矩阵的边长
rect_pos = [width // 2, height // 2] # 矩阵的初始位置
rect_speed = [2, 2] # 矩阵的速度
# 设置果子的初始属性
food_color = (255,0,0 ) # 果子颜色:红色
food_len = 15 # 果子大小:15像素
foodPosition = (random.randint(0, width - 30) , random.randint(0, height - 30) ) # 果子的初始位置
# 定义时钟对象
clock = pygame.time.Clock()
while True:
# 3.2 监听按键、鼠标事件并设置事件内容,修改方块的值
# 检查事件
for event in pygame.event.get(): # 遍历所有事件
if event.type == pygame.QUIT: # 如果单击关闭按钮
pygame.quit() # 卸载所有pygame模块
sys.exit() # 终止当前程序
# 检查按键,并对上下左右每一个按键,绑定事件
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
rect_pos[0] -= rect_speed[0]
if keys[pygame.K_RIGHT]:
rect_pos[0] += rect_speed[0]
if keys[pygame.K_UP]:
rect_pos[1] -= rect_speed[1]
if keys[pygame.K_DOWN]:
rect_pos[1] += rect_speed[1]
if np.linalg.norm((rect_pos[0]-foodPosition[0],rect_pos[1]-foodPosition[1]))<8:
foodPosition = (random.randint(0, width - 30) , random.randint(0, height - 30) ) # 果子的初始位置
# 3.3 依据方块的位置宽高,重新渲染画面和方块
# 确保方块不会移出屏幕
rect_pos[0] = max(0, min(width - rect_len, rect_pos[0]))
rect_pos[1] = max(0, min(height - rect_len, rect_pos[1]))
# 填充背景色(每次用黑色的背景覆盖掉旧有的画面)
screen.fill((0, 0, 0))
# 画果子
pygame.draw.rect(screen, food_color, (*foodPosition, food_len, food_len))
# 画矩阵
pygame.draw.rect(screen, rect_color, (*rect_pos, rect_len, rect_len))
# 更新屏幕
pygame.display.update()
# 控制游戏刷新速度
clock.tick(60)
定义游戏名字:贪吃蛇
创建交互规则如下:
翻译成代码逻辑:
初始化随机生成一个果子的位置,并渲染出来
蛇吃果子代码逻辑:当二者重叠超过百分之七十时候,果子刷新位置
蛇移动代码原理:每次在监听键盘,如果有移动操作导致头部发生改变,那么在新的位置绘制一个矩形,并插入蛇的身体列表开头做为新的头部。
如果这一步没有吃到果子则弹出尾巴,有就不弹出。
该课程涉及到的pygame函数(不重复上一课学习到的了):
模块名 | 功能 |
---|---|
pygame.Rect(x,y,width,height) | 生成pygame中的Rect对象(后面碰撞检测会用到) |
rect1.clip(rect2) | 求两个Rect对象的重叠矩阵 |
代码逻辑如下:
# -*- coding: utf-8 -*-
# 一、模块导入
import sys # 导入sys模块
import pygame # 导入pygame模块
import random # 导入random模块
import numpy as np
# 二、pygame窗口初始化
pygame.init() # 初始化pygame
size = width, height = 320, 240 # 设置窗口尺寸
screen = pygame.display.set_mode(size) # 显示窗口
# 设置标题
pygame.display.set_caption('3、控制绿色方块吃掉红色果子')
# 三、游戏主体
# 执行死循环,确保窗口一直显示,同时监听关闭按钮事件
## 3.1 游戏内对象初始化
# 设置矩阵的属性
rect_color = (0, 255, 0) # 绿色
rect_len = 20 # 矩阵的边长
snake_len = 3
body0 = pygame.Rect(width // 2, height // 2, rect_len, rect_len) # 矩形
rect_snake_body = [body0] # 矩阵的初始位置
rect_speed = [2, 2] # 矩阵的速度
# 设置果子的初始属性
food_color = (255, 0, 0) # 果子颜色:红色
food_len = 15 # 果子大小:15像素
foodPosition = (random.randint(0, width - 30), random.randint(0, height - 30)) # 果子的初始位置
food_body = pygame.Rect(*foodPosition, food_len, food_len)
# 定义时钟对象
clock = pygame.time.Clock()
while True:
# 3.2 监听按键、鼠标事件并设置事件内容,修改方块的值
# 检查事件
for event in pygame.event.get(): # 遍历所有事件
if event.type == pygame.QUIT: # 如果单击关闭按钮
pygame.quit() # 卸载所有pygame模块
sys.exit() # 终止当前程序
rect_pos = [rect_snake_body[0].x, rect_snake_body[0].y]
# 检查按键,并对上下左右每一个按键,绑定事件
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
rect_pos[0] -= rect_speed[0]
if keys[pygame.K_RIGHT]:
rect_pos[0] += rect_speed[0]
if keys[pygame.K_UP]:
rect_pos[1] -= rect_speed[1]
if keys[pygame.K_DOWN]:
rect_pos[1] += rect_speed[1]
# 3.3 根据上一步的数值变化进行,二次处理处理功能:1、不会移出屏幕。2、吃掉果子。3、蛇身移动
# 1、确保蛇不会移出屏幕
rect_pos[0] = max(0, min(width - rect_len, rect_pos[0]))
rect_pos[1] = max(0, min(height - rect_len, rect_pos[1]))
# 2、吃掉果子逻辑
overRect = food_body.clip(rect_snake_body[0])
if overRect.width * overRect.height >= food_body.width * food_body.height * 0.7:
foodPosition = food_body.x, food_body.y = random.randint(0, width - 30), random.randint(0,height - 30) # 果子的随机位置
snake_len += 1 # 蛇的身长加1
# 3、移动
if rect_pos[0] != rect_snake_body[0].x or rect_pos[1] != rect_snake_body[0].y:
newHead = pygame.Rect(*rect_pos, rect_len, rect_len)
rect_snake_body.insert(0, newHead)
if len(rect_snake_body) >= snake_len:
rect_snake_body.pop()
# 3.4 渲染
# 填充背景色(每次用黑色的背景覆盖掉旧有的画面)
screen.fill((0, 0, 0))
# 画果子
pygame.draw.rect(screen, food_color, food_body)
# 倒序画蛇身
for i in range(len(rect_snake_body)):
# 画矩阵
p=rect_snake_body[len(rect_snake_body)-i-1]
pygame.draw.rect(screen, rect_color, p)
pygame.draw.rect(screen, (200,200,200), p,1)
# 更新屏幕
pygame.display.update()
# 控制游戏刷新速度
clock.tick(60)
经过前面的学习,想必各位帅哥美女对怎么用pygame写交互逻辑有了一些初步的认知。现在让我们学习一下:1、图片导入。2、文本。3、游戏结束和重新开始。4、空格游戏暂停
初级的博客在个人博客里可以搜到,后续看反响,如果还行整理成视频在b站发布。
中级的话就是工程开发,将以上的全部,按照工程标准分多个文件开发。后面也会提到