文章目录
一、pygame简介
1.历史
Pygame最初是由Pete Shinner编写的一个利用SDL(Simple DirectMedia Layer)写成的游戏库,SDL是一个用于控制多媒体的夸平台C库,Pete最初创造pygame的目的是为了让Loki更有效的工作。Loki是一家公司,致力于向Linux上移植Windows的游戏的一家公司,不过已经倒闭了,不过Pete Shinner写的pygame库却留了下来,并且不断发展更新直到现在(ps:唉~ 人才进行工作,而天才进行创造啊~~)。截止这篇博客编写日期为止,pygame最新的版本为1.9.6
Pygame是跨平台Python模块,专为电子游戏设计,包含图像、声音。建立在SDL基础上,允许实时电子游戏研发而无需被低级语言(如机器语言汇编语言和)束缚。
2.pygame常用模块
用pygame写一个2D游戏那是绰绰有余,但是如果你想写一个大型3D游戏,我还是劝你醒一醒。比如飞机大战、植物大战僵尸等等。
关于pygame的安装我就不在赘述了,网上有很多教程,一般有三种安装方式,官网下载安装,命令行安装,以及在PyCharm中的Project Interpreter中搜索pygame安装,这里推荐第一种和第三种安装方式,因为有时候在PyCharm中使用pygame的时候,会出现智能提示不全的情况,我认为是在安装的过程中,pygame加载没完成引起的(命令行使用pip install pygame容易产生这个问题),PyCharm一直有这个缺陷,不过已经越来越好了,相信以后这个问题能够得到解决。
模块名 | 说明 |
---|---|
pygame.init() | 开发游戏过程中的第一行代码,作用是加载pygame中导入到模块,如果查看源码你会看到 两行注释 "init() -> (numpass, numfail) initialize all imported pygame modules"括号里的第一个参数意思是加载通过的模块数,第二参数意思是失败的模块数 |
pygame.image.* | *:load()、save()、get_extended()、tostring()、fromstring()、frombuffer(),这些方法一般最常用的是load函数,当然还有另外几个函数,他们都可以再pygame的官方文档上找到介绍,这里就不在赘述 |
pygame.display | 控制窗口和屏幕的显示,具体方法参照pygame的官方文档 |
pygame.draw | 绘制图形用到的模块包括线和点最常用的是pygame.draw.rect() |
pygame.event | 管理事件,比如点击时间,键盘按压事件等 |
pygame.font | 加载和呈现字体 |
pygame.key | 读取键盘按键 |
pygame.locals | 这个模块包含了 Pygame 定义的各种常量。使用 from pygame.locals import * 导入,比如HWSURFACE、KEYDOWN、KEYUP 等 |
pygame.mixer | 用于加载和播放声音 |
pygame.mouse | 鼠标点击事件 |
pygame.Rect | 管理矩形区域 |
pygame.Surface | Pygame 中用于表示图像的对象。 |
pygame.time | 用于监控时间 |
pygame.music | 控制流音频 |
pygame.Color | 用于颜色表示 |
pygame.* | pygame本身包含的函数方法:init、quit、get_init、error、get_error、set_error、get_sdl_version、get_sdl_byteorder等等 |
这些都是最常用的模块,除此之外,pygame还包含cursors、Overlay、math等模块以及camera、cdrom(磁盘存储)等等。如果英文过了四级的可以去看看pygame的官方文档 pygame官方文档 关于不同模块包含的方法,在这个文档中也是可以找到的。
Pygame会接受用户的各种操作(比如按键盘,移动鼠标等)产生事件。事件随时可能发生,而且量也可能会很大,Pygame的做法是把一系列的事件存放一个队列里,逐个的处理。
其实用pygame做游戏,最本质的就是这段代码,一个死循环等待监听事件并作出相应的处理。
# **死循环**中监听鼠标和键盘的事件
while True:
# 获取窗口中的时间监听->列表
even_list = pygame.event.get()
# 遍历所有事件
for event in even_list:
# 如果是鼠标点击关闭事件
if event.type == pygame.QUIT:
# 停止游戏引擎
pygame.quit()
# 关闭窗口,退出游戏,
sys.exit()
二、一个窗口小游戏要做的事情
一般小游戏需要用到的常用的基础操作如下:
- 导入与初始化
- 窗口相关操作
- 图像相关操作
- 事件相关操作
- 音效相关操作
- 文字显示操作
"""
飞机大战游戏
"""
import pygame
import sys
# 实例化pygame
pygame.init()
# 创建游戏窗口[宽:高]->窗口对象
window = pygame.display.set_mode([512, 768])
# 设置窗口的title
pygame.display.set_caption("飞机大战")
# 设置窗口的图标icon,加载一个图片对象
logo_image = pygame.image.load("res/images/app.ico")
pygame.display.set_icon(logo_image)
# 游戏背景图片添加到窗口上,blit(需要添加的对象,(x坐标,y坐标))
bg_image = pygame.image.load('res/images/img_bg_level_1.jpg')
window.blit(bg_image, (0, 0))
# 刷新窗口显示添加的元素
pygame.display.update()
# **死循环**中监听鼠标和键盘的事件
while True:
# 获取窗口中的时间监听->列表
even_list = pygame.event.get()
# 遍历所有事件
for event in even_list:
# 如果是鼠标点击关闭事件
if event.type == pygame.QUIT:
# 停止游戏引擎
pygame.quit()
# 关闭窗口,退出游戏,
sys.exit()
if event.type == pygame.KEYDOWN:
# 是否按下esc
if event.key == pygame.K_ESCAPE:
# 停止游戏引擎
pygame.quit()
sys.exit()
# 监听键盘长按事件 -> 元组(0没按下,1长按了) 字母对应阿斯克码
pressed_keys = pygame.key.get_pressed()
# 判断向上是否被长按(1)
if pressed_keys[pygame.K_UP]:
print("向上")
# 判断向下是否被长按(1)
if pressed_keys[pygame.K_DOWN]:
print("向下")
# 判断向左是否被长按(1)
if pressed_keys[pygame.K_LEFT]:
print("向左")
# 判断向右是否被长按(1)
if pressed_keys[pygame.K_RIGHT]:
print("向右")
最基本的操作就是上面代码中呈现的那样,除此之外,还有硬盘存取、音效播放等等操作。加载图片之后启动,然后就能看到如下的运行效果
然后在此基础上,让地图动起来,然后再添加玩家飞机,敌机 子弹和声音
三、飞机大战游戏
开始用pygame开发小游戏之前,必须清楚python中的坐标系,在Python中坐标系的原点在屏幕的左上角,向右和向下分别是x轴和y轴的正方向。游戏飞机的移动其实就是飞机图片在这个坐标系中坐标的变换而已。
1.准备工作
飞机大战中涉及的对象:
用面向对象的思想进行开发,先来锊清楚游戏中有几个对象:游戏窗口、玩家飞机、敌机、子弹、背景图、文字、声音
飞机大战中事件的处理
- 玩家飞机上下左右运动
- 空格发射子弹
- 退出
2. 模块设计
- 游戏窗口模块
- 飞机模块
- 子弹模块
- 分数模块
- 地图模块
游戏窗口模块
首先是游戏运行的主类,窗口类(GameWindow),窗口类完成游戏吃初始化以及事件监听、事件处理以及游戏运行等工作,主要框架代码如下(篇幅限制,具体方法实现请参考目录五)
导入模块
import pygame
# 定义常量 记录窗口的宽和高
WINDOW_WIDTH = 512
WINDOW_HEIGHT = 768
# 自定义游戏窗口类
class GameWindow(object):
# 构造函数
def __init__(self):
# 实例化窗口对象
self.window = pygame.display.set_mode([WINDOW_WIDTH, WINDOW_HEIGHT])
# 加载资源图片
self.icon_img = pygame.image.load("res/app.ico")
# 设置窗口icon
pygame.display.set_icon(self.icon_img)
# 设置窗口标题
pygame.display.set_caption("飞机大战")
# 1.处理各种矩形坐标移动
def action(self):
pass
# 2.根据矩形坐标,重新对元素进行绘制
def draw(self):
pass
# 3.处理窗口中的监听事件
def event(self):
pass
# 4.刷新窗口
def update(self):
pass
# 开始执行程序
def run(self):
# 死循环
while True:
# 处理矩形移动
self.action()
# 绘制元素
self.draw()
# 处理窗口的事件监听
self.event()
# 刷新窗口
self.update()
# 程序的入口
def main():
# 实例化game
game = GameWindow()
# 执行程序
game.run()
if __name__ == '__main__':
main()
子弹模块
飞机模块中包含两个类,玩家飞机类和游戏中的敌机类,玩家飞机类在初始化的时候要先实实例化子弹类。然后在类中定义响应的方法
import pygame
class Bullet(object):
def __init__(self):
# 加载子弹图片
self.bullet_img = pygame.image.load("res/images/bullet_10.png")
# 图片矩形
self.bullet_img_rect = self.bullet_img.get_rect()
# 子弹状态
self.is_shot = False
# 子弹移动速度
self.speed = 5
def move_up(self):
"""
子弹向上移动(玩家飞机)
:return:
"""
self.bullet_img_rect.move_ip(0, -self.speed)
if self.bullet_img_rect[1] <= -self.bullet_img_rect[3]:
self.is_shot = False
def move_down(self):
"""
子弹向下移动(敌方飞机)
:return:
"""
pass
飞机模块
飞机模块中包含两个类,玩家飞机类和敌机类,其中玩家飞机类要实例化子弹类。
import pygame
from game import bullet
import random
WINDOW_WIDTH = 512
WINDOW_HEIGHT = 768
class HeroPlane:
def __init__(self):
# 飞机图片
self.img = pygame.image.load('res/images/hero2.png')
# 英雄飞机的外框矩形 -> (x,y, 宽像素,高像素)
self.img_rect = self.img.get_rect()
# 飞机的初始位置
self.img_rect.move_ip((WINDOW_WIDTH - self.img_rect[2]) / 2, (WINDOW_HEIGHT - self.img_rect[3]) - 50)
# 飞机的移动速度
self.speed = 3
# 子弹弹夹
self.bullet_list = [bullet.Bullet() for _ in range(6)]
def move_up(self):
# 边界检查
if self.img_rect[1] >= 0:
self.img_rect.move_ip(0, -self.speed) # move_ip(x.y)
def move_down(self):
# 边界检查
if self.img_rect[1] <= WINDOW_HEIGHT - self.img_rect[3]:
self.img_rect.move_ip(0, self.speed) # move_ip(x.y)
def move_left(self):
# 边界检查
if self.img_rect[0] >= 0:
self.img_rect.move_ip(-self.speed, 0) # move_ip(x.y)
def move_right(self):
# 边界检查
if self.img_rect[0] <= WINDOW_WIDTH - self.img_rect[2]:
self.img_rect.move_ip(self.speed, 0) # move_ip(x.y)
def shoot(self):
# 遍历所有子弹
for bul in self.bullet_list:
if not bul.is_shot:
# 设置子弹发射位置
bul.bullet_img_rect[0] = self.img_rect[0] + (self.img_rect[2] - bul.bullet_img_rect[2]) / 2
bul.bullet_img_rect[1] = self.img_rect[1] - (bul.bullet_img_rect[3] - 10)
# 更新子弹状态
bul.is_shot = True
# 一次只能发射一颗子弹
break
class EnemyPlane:
def __init__(self):
# 随机加载敌机图片
self.num = str(random.randint(1, 7))
self.img = pygame.image.load('res/images/img-plane_' + self.num + '.png')
# 敌机的外框矩形
self.img_rect = self.img.get_rect()
# 敌机的初始位置
self.img_rect.move_ip((WINDOW_WIDTH - self.img_rect[2]) / 2, 0)
# 敌机的移动速度
self.speed = 3
# 随机初始化位置
self.reset()
def move_down(self):
"""
敌机向下移动
:return:
"""
self.img_rect.move_ip(0, self.speed)
# 敌机位置回收
if self.img_rect[1] >= WINDOW_HEIGHT:
self.reset()
def reset(self):
"""
敌机初始化位置
:return:
"""
self.img_rect[0] = random.randint(0, WINDOW_WIDTH - self.img_rect[2])
self.img_rect[1] = self.img_rect[3]
self.speed = random.randint(3, 5)
地图模块
import random
import pygame
WINDOW_WIDTH = 512
WINDOW_HEIGHT = 768
class GameMap(object):
def __init__(self):
self.num = str(random.randint(1, 5))
# 图片1,2无缝连接,上下滚动
self.img_1 = pygame.image.load("./res/images/img_bg_level_" + self.num + ".jpg")
self.img_2 = pygame.image.load("./res/images/img_bg_level_" + self.num + ".jpg")
# 记录背景图片的y轴坐标
self.img1_y = -WINDOW_HEIGHT
self.img2_y = 0
# 背景移动速度
self.speed = 2
def move_down(self):
"""
背景图片向下移动
:return:
"""
# 地图的y轴重置
if self.img1_y >= 0:
self.img1_y = -WINDOW_HEIGHT
self.img2_y = 0
self.img1_y += self.speed
self.img2_y += self.speed
分数模块
import pygame
import random
class GameScore:
__cls_score = 0
def __init__(self, font_size):
self.font = pygame.font.SysFont("SimHei", font_size)
self.text_obj = self.font.render("分数:0", 1, (255, 255, 255))
def set_text(self):
self.__cls_score += 1
# 随机颜色
r = random.randint(0, 255)
g = random.randint(0, 255)
b = random.randint(0, 255)
self.text_obj = self.font.render("分数:%d" % self.__cls_score, 1, (r, g, b))
下面这几个模块都需要在GameWindow中进行实例化并加载到窗口中,然后在运行游戏中检测键盘和鼠标等事件的发生,做出相应的处理。游戏其实就是一个大循环,动画特效其实就是图片切换以及在坐标系中的坐标变换。(详细的游戏图片、音效素材以及代码在 目录五 中查看)
然后可以另外创建一个启动文件start.py 用来启动游戏
from game_main import GameWindow
if __name__ == '__main__':
window = GameWindow()
window.run()
游戏运行效果
除此之外还可以将游戏打包中exe可执行文件,参照 目录四
四、游戏打包成exe可执行文件
1、准备
使用的打包工具是pyinstaller ,所以需要使用命令行在pycharm的Terminal终端运行pip install pyinstaller 进行安装不过要首先安装pywin32
pip install pywin32
pip install pyinstaller
2、打包
打包方式很很多种,比如将工程文件复制到其他文件夹,然后在工程的入口文件(比如:main.py)同级的目录打开命令行(shift+鼠标右键)然后运行打包命令,不过这种方式需要你将pyinstaller安装到电脑,而如果是用pycharm中安装的,一般都是安装到虚拟环境中。
推荐方式:
第一步: pycharm中打开Terminal终端进入虚拟环境,运行命令(命令参数参照下表)
pyinstaller -D -w -i app.ico start.py
第二步:把res文件夹复制到 …\dist\start 下,就是生成exe文件的目录下,res是游戏需要的图片和音频资源,然后运行exe文件就可以开始游戏了
因为使用的是生成依赖文件(-D)的打包方式,所以打包之后的文件要比只生成exe文件的打包方式(-F)文件要大一点,但是相对的运行速度要快一点
打包成功之后的问价结构如图1,删除无用的文件如图2
图1 | 图2 |
可执行文件start.exe就在dist文件下,找到之后就可以运行游戏了。
可选参数 | 功能说明 |
---|---|
-F | 只在dist中生产一个demo.exe文件。 |
-D | 默认选项,除了demo.exe外,还会在在dist中生成很多依赖文件,推荐使用。 |
-c | 默认选项,只对windows有效,使用控制台,就像编译运行C程序后的黑色弹窗。 |
-w | 只对windows有效,不使用控制台。 |
-p | 设置导入路径,一般用不到。 |
-i | 将file.icon设置为exe文件的图标,推荐一个icon网站:icon |
3、注意事项
在打包过程中会遇到的常见问题:打包成功,但是运行之后会报错,比如 Failed to execute script…
产生的原因有很多,参考以下两种因素
1.打包文件中含有系统无法识别的文字
2.工程需要的资源没有放到上文所列的目录
五、完整代码以及游戏素材下载地址
GitHub 资源地址 (ps:码字不容易,赏个“Star”吧!!)
这个小游戏只实现了基本的功能,如果你感兴趣还可以进行迭代开发,比如增加飞机碰撞、子弹击中效果、飞机切换子弹、飞机变身等等功能。
附录1 pygame中表示键盘按键的常用常量列表
pygame Constant | ASCII | Description |
---|---|---|
K_BACKSPACE | \b | backspace |
K_TAB | \t | tab |
K_CLEAR | clear | |
K_RETURN | \r | return |
K_PAUSE | pause | |
K_ESCAPE | ^[ | escape |
K_SPACE | space | |
K_EXCLAIM | ! | exclaim |
K_QUOTEDBL | " | quotedbl |
K_HASH | # | hash |
K_DOLLAR | $ | dollar |
K_AMPERSAND | & | ampersand |
K_QUOTE | quote | |
K_LEFTPAREN | ( | left parenthesis |
K_RIGHTPAREN | ) | right parenthesis |
K_ASTERISK | * | asterisk |
K_PLUS | + | plus sign |
K_COMMA | , | comma |
K_MINUS | - | minus sign |
K_PERIOD | . | period |
K_SLASH | / | forward slash |
K_0 | 0 | 数字键((K_数字)) |
K_a | a | 字母键(K_小写字母) |
K_DELETE | delete |
详细按键常量查询 pygame官方文档
参考博客:
https://blog.csdn.net/qq_38526635/article/details/82688786
https://blog.csdn.net/Hubz131/article/details/86718684
https://www.jianshu.com/p/48f6dea265eb