最近要写 pygame
的小项目了,快复习一下 pygame
!!复习的有点儿急!以后返回来改好,原文大佬是2011年写的但是依然经典,不过是拿python2 写的,我稍微改了改,大BUG应该没有,可能有小问题,我会回来改的相信我!!
写这个玩意儿时还出现点儿小问题,哈哈
摘自这位大佬的博客学习 blog.csdn.net/mingzznet/a…我当年就是现在这里入门的,哈哈,回来,看看师傅,顺便复习!至于图片大家访问大佬的博客吧,哈哈,别打我!一.初识
pygame 有很多模块:
模块名字 | 功能 |
---|---|
pygame.cdrom | 访问光驱 |
pygame.cursors | 加载光标 |
pygame.display | 访问显示设备 |
pygame.draw | 绘制形状、线和点 |
pygame.event | 管理事件 |
pygame.font | 使用字体 |
pygame.image | 加载和存储图片 |
pygame.joystick | 使用游戏手柄或者 类似的东西 |
pygame.key | 读取键盘按键 |
pygame.mixer | 声音 |
pygame.mouse | 鼠标 |
pygame.movie | 播放视频 |
pygame.music | 播放音频 |
pygame.overlay | 访问高级视频叠加 |
pygame | 就是我们在学的这个东西了…… |
pygame.rect | 管理矩形区域 |
pygame.sndarray | 操作声音数据 |
pygame.sprite | 操作移动图像 |
pygame.surface | 管理图像和屏幕 |
pygame.surfarray | 管理点阵图像数据 |
pygame.time | 管理时间和帧信息 |
pygame.transform | 缩放和移动图像 |
有些模块可能在某些平台上不存在,你可以用None来测试一下
>>> pygame.font is None
复制代码
Helloworld.py
# 先指定图像文件名称
background_image_filename = '1.jpeg'
mouse_image_filename = 'fugu.png'
import pygame # 导入pygame库
from pygame.locals import * # 导入常用的常亮
from sys import exit # 向sys模块借一个exit函数用来退出程序
pygame.init() # 初始化pygame,为使用硬件做准备
screen = pygame.display.set_mode((640, 480), 0, 32) # 创建了一个窗口
pygame.display.set_caption("Hello, World!") # 设置窗口标题
#加载并转换图像
background = pygame.image.load(background_image_filename).convert()
mouse_cursor = pygame.image.load(mouse_image_filename).convert_alpha()
#游戏主循环
while True:
for event in pygame.event.get():
if event.type == QUIT:
exit() # 接收到退出事件后退出程序
screen.blit(background, (0,0)) # 将背景图画上去
x, y = pygame.mouse.get_pos() # 获得鼠标位置
x-= mouse_cursor.get_width() / 2
y-= mouse_cursor.get_height() / 2 # 计算光标的左上角位置
screen.blit(mouse_cursor, (x, y)) # 把光标画上去
pygame.display.update() #刷新一下画面
复制代码
注意: 在退出时使用IPython
会报错
runfile('/home/jack/桌面/haozidachaung/Helloworld.py', wdir='/home/jack/桌面/haozidachaung')
An exception has occurred, use %tb to see the full traceback.
SystemExit
复制代码
我也不知道为何但时间比较紧,我发现命令行下运行没有问题,就直接用命令行了,以后在解决。
几个简单的点:
- set_mode会返回一个Surface对象,代表了在桌面上出现的那个窗口,三个参数第一个为元祖,代表分 辨率(必须);第二个是一个标志位,具体意思见下表,如果不用什么特性,就指定0;第三个为色深。
标志位 | 功能 |
---|---|
FULLSCREEN | 创建一个全屏窗口 |
DOUBLEBUF | 创建一个“双缓冲”窗口,建议在HWSURFACE或者OPENGL时使用 |
HWSURFACE | 创建一个硬件加速的窗口,必须和FULLSCREEN同时使用 |
OPENGL | 创建一个OPENGL渲染的窗口 |
RESIZABLE | 创建一个可以改变大小的窗口 |
NOFRAME | 创建一个没有边框的窗口 |
-
convert函数是将图像数据都转化为Surface对象,每次加载完图像以后就应该做这件事件(事实上因为 它太常用了,如果你不写
pygame
也会帮你做);convert_alpha相比convert,保留了Alpha 通道信息(可以简单理解为透明的部分),这样我们的光标才可以是不规则的形状。 -
游戏的主循环是一个无限循环,直到用户跳出。在这个主循环里做的事情就是不停地画背景和更新光标位置,虽然背景是不动的,我们还是需要每次都画它, 否则鼠标覆盖过的位置就不能恢复正常了。
-
blit是个重要函数,第一个参数为一个Surface对象,第二个为左上角位置。画完以后一定记得用update更新一下,否则画面一片漆黑。
二.事件
理解事件
事件是什么,其实从名称来看我们就能想到些什么,而且你所想到的基本就是事件的真正意思了。我们上一个程序,会一直运行下去,直到你关闭窗口而产生了一个QUIT
事件,Pygame
会接受用户的各种操作(比如按键盘,移动鼠标等)产生事件。事件随时可能发生,而且量也可能会很大,Pygame
的做法是把一系列的事件存放一个队列里,逐个的处理。
事件检索
上个程序中,使用了**pygame.event.get()
来处理所有的事件,这好像打开大门让所有的人进入。如果我们使用pygame.event.wait()
,Pygame
就会等到发生一个事件才继续下去,就好像你在门的猫眼上盯着外面一样,来一个放一个……一般游戏中不太实用,因为游戏往往是需要动态运作的;而另外一个方法pygame.event.poll()
**就好一些,一旦调用,它会根据现在的情形返回一个真实的事件,或者一个“什么都没有”。下表是一个常用事件集:
事件 | 产生途径 | 参数 |
---|---|---|
QUIT | 用户按下关闭按钮 | none |
ATIVEEVENT | Pygame被激活或者隐藏 | gain, state |
KEYDOWN | 键盘被按下 | unicode, key, mod |
KEYUP | 键盘被放开 | key, mod |
MOUSEMOTION | 鼠标移动 | pos, rel, buttons |
MOUSEBUTTONDOWN | 鼠标按下 | pos, button |
MOUSEBUTTONUP | 鼠标放开 | pos, button |
JOYAXISMOTION | 游戏手柄(Joystick or pad)移动 | joy, axis, value |
JOYBALLMOTION | 游戏球(Joy ball)?移动 | joy, axis, value |
JOYHATMOTION | 游戏手柄(Joystick)?移动 | joy, axis, value |
JOYBUTTONDOWN | 游戏手柄按下 | joy, button |
JOYBUTTONUP | 游戏手柄放开 | joy, button |
VIDEORESIZE | Pygame窗口缩放 | size, w, h |
VIDEOEXPOSE | Pygame窗口部分公开(expose)? | none |
USEREVENT | 触发了一个用户事件 | code |
如果你想把这个表现在就背下来,当然我不会阻止你,但实在不是个好主意,在实际的使用中,自然而然的就会记住。我们先来写一个可以把所有方法输出的程序,它的结果是这样的。
import pygame
from pygame.locals import *
from sys import exit
pygame.init()
SCREEN_SIZE = (640, 480)
screen = pygame.display.set_mode(SCREEN_SIZE, 0, 32)
font = pygame.font.SysFont("arial", 16);
font_height = font.get_linesize()
event_text = []
while True:
event = pygame.event.wait()
event_text.append(str(event)) # 获得事件的名称
# 这个切片操作保证了列表event_text里面只保留一个屏幕的文字
event_text = event_text[-SCREEN_SIZE[1]//font_height:]
if event.type == QUIT:
exit()
screen.fill((255, 255, 255)) # 全白
#找一个合适的起笔位置,最下面开始但是要留一行的空
y = SCREEN_SIZE[1] - font_height
for text in reversed(event_text):
screen.blit( font.render(text, True, (0, 0, 0)), (0, y) ) #之后会写
y -= font_height # 把笔提一行
复制代码
注:如果你把填充色的(0, 0, 0)改为(0, 255, 0),效果会想黑客帝国的字幕雨一样,我得说,实际试一下并不太像……不过以后你完全可以写一个以假乱真甚至更酷的!
这个程序在你移动鼠标的时候产生了海量的信息,让我们知道了Pygame
是多么的繁忙……我们第一个程序那样是调用pygame.mouse.get_pos()
来得到当前鼠标的位置,而现在利用事件可以直接获得!
处理鼠标事件
MOUSEMOTION
事件会在鼠标动作的时候发生,它有三个参数:
- buttons – 一个含有三个数字的元组,三个值分别代表左键、中键和右键,1就是按下了。
- pos – 就是位置了……
- rel – 代表了现在距离上次产生鼠标事件时的距离
和MOUSEMOTION
类似的,我们还有MOUSEBUTTONDOWN
和MOUSEBUTTONUP
两个事件,看名字就明白是什么意思了。很多时候,你只需要知道鼠标点下就可以了,那就可以不用上面那个比较强大(也比较复杂)的事件了。它们的参数为:
- button – 看清楚少了个s,这个值代表了哪个按键被操作
- pos – 和上面一样
处理键盘事件
键盘和游戏手柄的事件比较类似,为KEYDOWN
和KEYUP
,下面有一个例子来演示使用方向键移动一些东西。
background_image_filename = '1.jpeg'
import pygame
from pygame.locals import *
from sys import exit
pygame.init()
screen = pygame.display.set_mode((640, 480), 0, 32)
background = pygame.image.load(background_image_filename).convert()
x, y = 0, 0
move_x, move_y = 0, 0
while True:
for event in pygame.event.get():
if event.type == QUIT:
exit()
if event.type == KEYDOWN:
# 键盘有按下?
if event.key == K_LEFT:
# 按下的是左方向键的话,把x坐标减一
move_x = -1
elif event.key == K_RIGHT:
# 右方向键则加一
move_x = 1
elif event.key == K_UP:
# 类似了
move_y = -1
elif event.key == K_DOWN:
move_y = 1
elif event.type == KEYUP:
# 如果用户放开了键盘,图就不要动了
move_x = 0
move_y = 0
# 计算出新的坐标
x += move_x
y += move_y
screen.fill((0,0,0))
screen.blit(background, (x,y))
# 在新的位置上画图
pygame.display.update()
复制代码
当我们运行这个程序的时候,按下方向键就可以把背景图移动,但是等等!为什么我只能按一下动一下啊……太不好试了吧?!用脚掌考虑下就应该按着就一直动下去才是啊!?Pygame
这么垃圾么……
哦,真是抱歉上面的代码有点小bug,但是真的很小,你都不需要更改代码本身,只要改一下缩进就可以了,你可以发现么?Python本身是缩进编排来表现层次,有些时候可能会出现一点小麻烦,要我们自己注意才可以。
KEYDOWN
和KEYUP
的参数描述如下:
- key – 按下或者放开的键值,是一个数字,估计地球上很少有人可以记住,所以Pygame中你可以使用K_xxx来表示,比如字母a就是K_a,还有K_SPACE和K_RETURN等。
- mod – 包含了组合键信息,如果mod & KMOD_CTRL是真的话,表示用户同时按下了Ctrl键。类似的还有KMOD_SHIFT,KMOD_ALT。
- unicode – 代表了按下键的Unicode值,这个有点不好理解,真正说清楚又太麻烦,游戏中也不太常用,说明暂时省略,什么时候需要再讲吧。
事件过滤
并不是所有的事件都需要处理的,就好像不是所有登门造访的人都是我们欢迎的一样。比如,俄罗斯方块就无视你的鼠标,而在游戏场景切换的时候,你按什么都是徒劳的。我们应该有一个方法来过滤掉一些我们不感兴趣的事件(当然我们可以不处理这些没兴趣的事件,但最好的方法还是让它们根本不进入我们的事件队列,就好像在门上贴着“XXX免进”一样),我们使用pygame.event.set_blocked(事件名)来完成。如果有好多事件需要过滤,可以传递一个列表,比如pygame.event.set_blocked([KEYDOWN, KEYUP]),如果你设置参数None,那么所有的事件有被打开了。与之相对的,我们使用**pygame.event.set_allowed()**来设定允许的事件。
产生事件
通常玩家做什么,Pygame就产生对应的事件就可以了,不过有的时候我们需要模拟出一些事件来,比如录像回放的时候,我们就要把用户的操作再现一遍。
为了产生事件,必须先造一个出来,然后再传递它:
my_event = pygame.event.Event(KEYDOWN, key=K_SPACE, mod=0, unicode=u' ')
# 你也可以像下面这样写,看起来比较清晰(但字变多了……)
my_event = pygame.event.Event(KEYDOWN, {"key":K_SPACE, "mod":0, "unicode":u' '})
pygame.event.post(my_event)
复制代码
你甚至可以产生一个完全自定义的全新事件,有些高级的话题,暂时不详细说,仅用代码演示一下:
CATONKEYBOARD = USEREVENT + 1
my_event = pygame.event.Event(CATONKEYBOARD, message="Bad cat!")
pgame.event.post(my_event)
#然后获得它
for event in pygame.event.get():
if event.type == CATONKEYBOARD:
print event.message
复制代码
三.显示
全屏显示
我们在第一个程序里使用了如下的语句
screen = pygame.display.set_mode((640, 480), 0, 32)
复制代码
也讲述了各个参数的意思,当我们把第二个参数设置为FULLSCREEN
时,就能得到一个全屏窗口了
screen = pygame.display.set_mode((640, 480), FULLSCREEN, 32)
复制代码
注:如果你的程序有什么问题, 很可能进入了全屏模式就不容易退出来了,最好先调试一下,听哥一句劝。
在全屏模式下,显卡可能就切换了一种模式,你可以用如下代码获得您的机器支持的显示模式:
>>> import pygame
>>> pygame.init()
>>> pygame.display.list_modes()
[(1920, 1080)]
复制代码
看下一个实例:
background_image_filename = '1.jpeg'
import pygame
from pygame.locals import *
from sys import exit
pygame.init()
screen = pygame.display.set_mode((640, 480), 0, 32)
background = pygame.image.load(background_image_filename).convert()
Fullscreen = False # 给全屏赋初值
while True:
for event in pygame.event.get():
if event.type == QUIT:
exit()
if event.type == KEYDOWN:
if event.key == K_f:
Fullscreen = not Fullscreen
if Fullscreen:
screen = pygame.display.set_mode((640, 480), FULLSCREEN, 32)
else:
screen = pygame.display.set_mode((640, 480), 0, 32)
screen.blit(background, (0,0))
pygame.display.update()
复制代码
运行这个程序,默认还是窗口的,按“f ”,显示模式会在窗口和全屏之间切换。程序也没有什么难度,应该都能看明白。
可变尺寸的显示
虽然一般的程序窗口都能拖边框来改变大小,pygame
的默认显示窗口是不行的,而事实上,很多游戏确实也不能改变显示窗口的大小,我们可以使用一个参数来改变这个默认行为。
background_image_filename = '1.jpeg'
import pygame
from pygame.locals import *
from sys import exit
SCREEN_SIZE = (640, 480)
pygame.init()
screen = pygame.display.set_mode(SCREEN_SIZE, RESIZABLE, 32) # 将参数改为RESIZABLE
background = pygame.image.load(background_image_filename).convert()
while True:
event = pygame.event.wait()
if event.type == QUIT:
exit()
if event.type == VIDEORESIZE: # 新事件 VIDEORESIZE
SCREEN_SIZE = event.size
screen = pygame.display.set_mode(SCREEN_SIZE, RESIZABLE, 32)
pygame.display.set_caption("Window resized to "+str(event.size)) # 改变窗口标题
screen_width, screen_height = SCREEN_SIZE
# 这里需要重新填满窗口
for y in range(0, screen_height, background.get_height()):
for x in range(0, screen_width, background.get_width()):
screen.blit(background, (x, y))
pygame.display.update()
复制代码
当你更改大小的时候,后端控制台会显示出新的尺寸,这里我们学习到一个新的事件VIDEORESIZE
,它包含如下内容:
- size — 一个二维元组,值为更改后的窗口尺寸,size[0]为宽,size[1]为高
- w — 宽
- h — 一目了然,高;之所以多出这两个,无非是为了方便
原作者注:注意:在我的Windows 7 64bit上运行的时候,一改变窗口大小就非法退出;在Linux机器上很正常,应该是系统的兼容性问题(Pygame还只支持32位),不过想来平时都不会更改游戏窗口大小,问题不大。
其他、复合模式
我们还有一些其他的显示模式,但未必所有的操作系统都支持(放心windows、各种比较流行的Linux发行版都是没问题的),一般来说窗口就用0全屏就用FULLSCREEN
,这两个总是OK的。
如果你想创建一个硬件显示(surface会存放在显存里,从而有着更高的速度),你必须和全屏一起使用:
screen = pygame.display.set_mode(SCREEN_SIZE, HWSURFACE | FULLSCREEN, 32)
复制代码
当然你完全可以把双缓冲(更快)DOUBLEBUF
也加上,这就是一个很棒的游戏显示了,不过记得你要使用pygame.display.flip()
来刷新显示。pygame.display.update()
是将数据画到前面显示,而这个是交替显示的意思。
稍微说一下双缓冲的意思,可以做一个比喻:我的任务就是画黑板报,如果只有一块黑板,那我得不停的写,全部写完了稍微Show一下就要擦掉重写,这样一来别人看的基本都是我在写黑板报的过程,看到的都是不完整的黑板报;如果我有两块黑板,那么可以挂一块给别人看,我自己在底下写另一块,写好了把原来的换下来换上新的,这样一来别人基本总是看到完整的内容了。双缓冲就是这样维护两个显示区域,快速的往屏幕上换内容,而不是每次都慢慢地重画。
还有OPENGL
模式,这是一个得到广泛应用的3D加速显示模式。不过一旦使用了这个模式,pygame中的2D图像函数就不能用了,我们会在以后讲详细的内容。
四.字体模块
使用字体模块
就像上一次说的,一个游戏,再怎么寒碜也得有文字,俄罗斯方块还有个记分数的呢;印象中没有文字的电子游戏只有电脑刚刚诞生的那种打乒乓的了。Pygame可以直接调用系统字体,或者也可以使用TTF字体,稍有点电脑知识的都知道这是什么。为了使用字体,你得先创建一个Font对象,对于系统自带的字体:
my_font = pygame.font.SysFont("arial", 16)
复制代码
第一个参数是字体名,第二个自然就是大小,一般来说“Arial”字体在很多系统都是存在的,如果找不到的话,就会使用一个默认的字体,这个默认的字体和每个操作系统相关,你也可以使用pygame.font.get_fonts()
来获得当前系统所有可用字体。还有一个更好的方法的,使用TTF的方法:
my_font = pygame.font.Font("my_font.ttf", 16)
复制代码
这个语句使用了一个叫做“my_font.ttf”,这个方法之所以好是因为你可以把字体文件随游戏一起分发,避免用户机器上没有需要的字体。。一旦你创建了一个font对象,你就可以使用render
方法来写字了,然后就能blit
()到屏幕上:
text_surface = my_font.render("Pygame is cool!", True, (0,0,0), (255, 255, 255))
复制代码
第一个参数是写的文字;第二个参数是个布尔值,是否开启抗锯齿,就是说True
的话字体会比较平滑,不过相应的速度有一点点影响;第三个参数是字体的颜色;第四个是背景色,如果你想没有背景色(也就是透明),那么可以不加这第四个参数。
下面是一个小例子演示下文字的使用,不过并不是显示在屏幕上,而是存成一个图片文件。
my_name = "Will McGugan"
import pygame
pygame.init()
my_font = pygame.font.SysFont("arial", 64)
name_surface = my_font.render(my_name, True, (0, 0, 0), (255, 255, 255))
pygame.image.save(name_surface, "name.png")
复制代码
追加说明一下如何显示中文, 简单来说,首先你得用一个可以使用中文的字体,宋体、黑体什么的,或者你直接用中文TTF文件,然后文字使用unicode,即u”中文的文字”这种,最后不要忘了源文件里加上一句关于文件编码的“魔法注释”,具体的可以查一下Python的编码方面的文章。
注:如果我没记错的话,在python3
中已经取代了unicode
字符串直接用str
来代替,也就是说Python3的话可以不必加‘u’
举一个这样的例子:
# -*- coding: utf-8 -*-
# 记住上面这行是必须的,而且保存文件的编码要一致!
import pygame
from pygame.locals import *
from sys import exit
pygame.init()
screen = pygame.display.set_mode((640, 480), 0, 32)
#font = pygame.font.SysFont("宋体", 40)
#上句在Linux可行,在我的Windows 7 64bit上不行,XP不知道行不行
#font = pygame.font.SysFont("simsunnsimsun", 40)
#用get_fonts()查看后看到了这个字体名,在我的机器上可以正常显示了
font = pygame.font.Font("方正瘦金体.ttf", 40)
#这句话总是可以的,所以还是TTF文件保险啊
text_surface = font.render("你好", True, (0, 0, 255))
x = 0
y = (480 - text_surface.get_height())/2
background = pygame.image.load("1.jpeg").convert()
while True:
for event in pygame.event.get():
if event.type == QUIT:
exit()
screen.blit(background, (0, 0))
x -= 2 # 文字滚动太快的话,改改这个数字
if x < -text_surface.get_width(): # 注意这是文本的宽度
x = 640 - text_surface.get_width() # 思考下是啥意思
screen.blit(text_surface, (x, y))
pygame.display.update()
复制代码
pygame的错误处理
程序总会出错的,比如当内存用尽的时候Pygame就无法再加载图片,或者文件根本就不存在。再比如下例:
>>> import pygame
>>> screen = pygame.display.set_mode((640, -1))
---------------------------------
Traceback (most recent call last):
File "<interactive input>", line 1, in ?
pygame.error: Cannot set 0 sized display mode
----------------------------------
复制代码
对付这种错误一个比较好的方法:
try:
screen = pygame.display.set_mode(SCREEN_SIZE)
except pygame.error, e:
print "Can't create the display :-("
print e
exit()
复制代码
其实就是Python的标准的错误捕捉方法就是了,实际的游戏(或者程序)中,错误捕捉实在太重要了,如果你写过比较大的应用,应该不用我来说明这一点,Pygame
中也是一样的。
Pygame
的基础就到这里,后面我们会进行一些高级的介绍,下一次的话,就开始讲画东西了~
五.游戏中的视觉——像素
像素的威力
凑近显示器,你能看到图像是由一个一个点构成,这就是像素。至于屏幕分辨率的意义,也就不用多说了吧,一个1280×1024的显示器,有着1310720个像素,一般的32为RGB系统,每个像素可以显示16.7百万种颜色,我们可以写一个小程序来显示这么多的颜色~
import pygame
pygame.init()
screen = pygame.display.set_mode((640, 480))
all_colors = pygame.Surface((4096,4096), depth=24)
for r in range(256):
print(r+1, "out of 256")
x = (r&15)*256
y = (r>>4)*256
for g in range(256):
for b in range(256):
all_colors.set_at((x+g, y+b), (r, g, b))
pygame.image.save(all_colors, "allcolors.bmp")
复制代码
色彩的威力
色彩是一个很有趣的话题,比如把蓝色和黄色混合产生绿色,事实上你可以用红黄蓝混合出所有的颜色(光学三原色),电脑屏幕上的三原色是红绿蓝(RGB),要想更深刻的理解这个东西,你得学习一下(就看看李涛的PhotoShop讲座吧,VeryCD上有下的,讲的还是很清楚的)~
稍有点经验的图像设计者应该看到RGB的数值就能想象出大概的颜色,我们来用一个Python脚本加强这个认识。
import pygame
from pygame.locals import *
from sys import exit
pygame.init()
screen = pygame.display.set_mode((640, 480), 0, 32)
def create_scales(height):
red_scale_surface = pygame.surface.Surface((640, height))
green_scale_surface = pygame.surface.Surface((640, height))
blue_scale_surface = pygame.surface.Surface((640, height))
for x in range(640):
c = int((x/640.)*255.)
red = (c, 0, 0)
green = (0, c, 0)
blue = (0, 0, c)
line_rect = Rect(x, 0, 1, height)
pygame.draw.rect(red_scale_surface, red, line_rect)
pygame.draw.rect(green_scale_surface, green, line_rect)
pygame.draw.rect(blue_scale_surface, blue, line_rect)
return red_scale_surface, green_scale_surface, blue_scale_surface
red_scale, green_scale, blue_scale = create_scales(80) # 返回三个”长条“
color = [127, 127, 127]
while True:
for event in pygame.event.get():
if event.type == QUIT:
exit()
screen.fill((0, 0, 0))
screen.blit(red_scale, (0, 00))
screen.blit(green_scale, (0, 80))
screen.blit(blue_scale, (0, 160))
x, y = pygame.mouse.get_pos()
if pygame.mouse.get_pressed()[0]:
for component in range(3):
if y > component*80 and y < (component+1)*80:
color[component] = int((x/639.)*255.)
pygame.display.set_caption("PyGame Color Test - "+str(tuple(color)))
for component in range(3):
pos = ( int((color[component]/255.)*639), component*80+40 )
pygame.draw.circle(screen, (255, 255, 255), pos, 20)
pygame.draw.rect(screen, tuple(color), (0, 240, 640, 240))
pygame.display.update()
复制代码
这个程序稍稍有点难度了,而且用到了一些没讲到的知识(pygame.draw),我们以后会介绍,现在无所谓。在这个例子里,你可以用鼠标移动三个白点,代表了三原色的量,下面就是不同混合得到的结果,在标题上你可以看到RGB三个数值。
当我们有了一个颜色,比如说一颗流星划过天际,那么那个时候它是个“火球般的橘黄色”,不过一旦它着地了,它就会灭掉,慢慢变暗,如何能找到比这个“火球般的橘黄色”更暗的颜色?
颜色的缩放
“缩放颜色”并不是一种合适的说法,它的准确意义就是上面所说的把颜色变亮或者变暗。一般来说,把颜色的RGB每一个数值乘以一个小于1的正小数,颜色看起来就会变暗了(记住RGB都是整数所以可能需要取整一下)。我们很容易可以写一个缩放颜色的函数出来,我就不赘述了。
很自然的可以想到,如果乘以一个大于1的数,颜色就会变亮,不过同样要记住每个数值最多255,所以一旦超过,你得把它归为255!使用Python的内置函数min,你可以方便的做到这事情,也不多说了。如果你乘的数字偏大,颜色很容易就为变成纯白色,就失去了原来的色调。而且RGB也不可能是负数,所以谨慎选择你的缩放系数!
颜色的混合
很多时候我们还需要混合颜色,比如一个僵尸在路过一个火山熔岩坑的时候,它会由绿色变成橙红色,再变为正常的绿色,这个过程必须表现的很平滑,这时候我们就需要混合颜色。
我们用一种叫做“线性插值(linear interpolation)”的方法来做这件事情。为了找到两种颜色的中间色,我们将这第二种颜色与第一种颜色的差乘以一个0~1之间的小数,然后再加上第一种颜色就行了。如果这个数为0,结果就完全是第一种颜色;是1,结果就只剩下第二种颜色;中间的小数则会皆有两者的特色。
import pygame
from pygame.locals import *
from sys import exit
pygame.init()
screen = pygame.display.set_mode((640, 480), 0, 32)
color1 = (221, 99, 20)
color2 = (96, 130, 51)
factor = 0.
def blend_color(color1, color2, blend_factor):
r1, g1, b1 = color1
r2, g2, b2 = color2
r = r1 + (r2 - r1) * blend_factor
g = g1 + (g2 - g1) * blend_factor
b = b1 + (b2 - b1) * blend_factor
return int(r), int(g), int(b)
while True:
for event in pygame.event.get():
if event.type == QUIT:
exit()
screen.fill((255,255,255))
tri = [ (0, 120), (639, 100), (639, 140) ]
pygame.draw.polygon(screen, (0, 255, 0), tri)
pygame.draw.circle(screen, (0, 0, 0), (int(factor * 639.0), 120), 10)
x, y = pygame.mouse.get_pos()
if pygame.mouse.get_pressed()[0]:
factor = x / 639.0
pygame.display.set_caption("Pygame Color Blend Test - %.3f" % factor)
color = blend_color(color1, color2 , factor)
pygame.draw.rect(screen, color, (0, 240, 640, 240))
pygame.display.update()
复制代码
在这里例子里,移动小球你能看到下方的颜色在“火球橙”和“僵尸绿”之间渐变,更改代码里的color1和color2,你能看到任意两种颜色渐变的过程!
六.游戏中的视觉——图像
简述
掌握了小小的像素,我们可以使用更加复杂一点的东西了,对,就是图像,无数的像素的集合~还记得上次我们为了生成的一张图片,花了无数时间,还好一般游戏不会在游戏的过程中动态生成图像,都是将画好的作为资源封装到游戏中。对2D游戏,图像可能就是一些背景、角色等,而3D游戏则往往是大量的贴图。
虽然是基础,这里还是要罗嗦一下,之前说的RBG图像,在游戏中我们往往使用RGBA图像,这个A是alpha,也就是表示透明度的部分,值也是0~255,0代表完全透明,255是完全不透明,而像100这样的数字,代表部分透明。你可以使用多种软件创建含有Alpha通道的图片,具体的网上查查吧……
这个世界上有很多存储图像的方式(也就是有很多图片格式),比如JPEG、PNG等,Pygame都能很好的支持,具体支持的格式如下:
- JPEG(Join Photograhpic Exper Group),极为常用,一般后缀名为.jpg或者.jpeg。数码相机、网上的图片基本都是这种格式。这是一种有损压缩方式,尽管对图片质量有些损坏,但对于减小文件尺寸非常棒。优点很多只是不支持透明。
- PNG(Portable Network Graphics)将会大行其道的一种格式,支持透明,无损压缩。对于网页设计,软件界面设计等等都是非常棒的选择!
- GIF 网上使用的很多,支持透明和动画,只是只能有256种颜色,软件和游戏中使用很少
- BMP Windows上的标准图像格式,无压缩,质量很高但尺寸很大,一般不使用
- PCX
- TGA
- TIF
- LBM, PBM
- XPM
使用Surface对象
对于Pygame而已,加载图片就是pygame.image.load
,给它一个文件名然后就还给你一个surface对象。尽管读入的图像格式各不相同,surface对象隐藏了这些不同。你可以对一个Surface对象进行涂画、变形、复制等各种操作。事实上,屏幕也只是一个surface,pygame.display.set_mode
就返回了一个屏幕surface对象。
创建Surfaces对象
一种方法就是刚刚说的pygame.image.load,这个surface有着和图像相同的尺寸和颜色;另外一种方法是指定尺寸创建一个空的surface,下面的语句创建一个256×256像素的surface:
bland_surface = pygame.Surface((256, 256))
复制代码
如果不指定尺寸,那么就创建一个和屏幕一样大小的。
你还有两个参数可选,第一个是flags
:
- HWSURFACE – 类似于前面讲的,更快!不过最好不设定,Pygame可以自己优化。
- SRCALPHA – 有Alpha通道的surface,如果你需要透明,就要这个选项。这个选项的使用需要第二个参数为32~
第二个参数是depth,和pygame.display.set_mode
中的一样,你可以不设定,Pygame会自动设的和display一致。不过如果你使用了SRCALPHA
,还是设为32吧:
bland_alpha_surface = pygame.Surface((256, 256), flags=SRCALPHA, depth=32)
复制代码
转换Surfaces
通常你不用在意surface里的具体内容,不过也许需要把这些surface转换一下以获得更高的性能,还记得一开始的程序中的两句话吗:
background = pygame.image.load(background_image_filename).convert()
mouse_cursor = pygame.image.load(mouse_image_filename).convert_alpha()
复制代码
第一句是普通的转换,相同于display;第二句是带alpha通道的转换。如果你给convert或者conver_alpha一个surface对象作为参数,那么这个会被作为目标来转换。
矩形对象(Rectangle Objects)
一般来说在制定一个区域的时候,矩形是必须的,比如在屏幕的一部分画东西。在pygame中矩形对象极为常用,它的指定方法可以用一个四元素的元组,或者两个二元素的元组,前两个数为左上坐标,后两位为右下坐标。
Pygame中有一个Rect类,用来存储和处理矩形对象(包含在pygame.locals中,所以如果你写了from pygame.locals import *就可以直接用这个对象了),比如:
my_rect1 = (100, 100, 200, 150)
my_rect2 = ((100, 100), (200, 150))
#上两种为基础方法,表示的矩形也是一样的
my_rect3 = Rect(100, 100, 200, 150)
my_rect4 = Rect((100, 100), (200, 150))
复制代码
一旦有了Rect对象,我们就可以对其做很多操作,比如调整位置和大小,判断一个点是否在其中等等。以后会慢慢接触到,求知欲旺盛的可以在www.pygame.org/docs/ref/re…中找到Rect的详细信息。
剪裁(Clipping)
通常游戏的时候你只需要绘制屏幕的一部分。比如魔兽上面是菜单,下面是操作面板,中间的小兵和英雄打的不可开交时候,上下的部分也是保持相对不动的。为了实现这一点,surface就有了一种叫*裁剪区域(clipping area)*的东西,也是一个矩形,定义了哪部分会被绘制,也就是说一旦定义了这个区域,那么只有这个区域内的像素会被修改,其他的位置保持不变,默认情况下,这个区域是所有地方。我们可以使用set_clip来设定,使用get_clip来获得这个区域。
下面几句话演示了如何使用这个技术来绘制不同的区域:
screen.set_clip(0, 400, 200, 600)
draw_map()
#在左下角画地图
screen.set_clip(0, 0, 800, 60)
draw_panel()
#在上方画菜单面板
复制代码
子表面(Subsurfaces)
Subsurface就是在一个Surface中再提取一个Surface,记住当你往Subsurface上画东西的时候,同时也向父表面上操作。这可以用来绘制图形文字,尽管pygame.font可以用来写很不错的字,但只是单色,游戏可能需要更丰富的表现,这时候你可以把每个字母(中文的话有些吃力了)各自做成一个图片,不过更好的方法是在一张图片上画满所有的字母。把整张图读入,然后再用Subsurface把字母一个一个“抠”出来,就像下面这样:
my_font_image = Pygame.load("font.png")
letters = []
letters["a"] = my_font_image.subsurface((0,0), (80,80))
letters["b"] = my_font_image.subsurface((80,0), (80,80))
复制代码
填充Surface
填充有时候可以作为一种清屏的操作,把整个surface填上一种颜色:
screen.fill((0, 0, 0))
复制代码
同样可以提供一个矩形来制定填充哪个部分(这也可以作为一种画矩形的方法)。
设置Surface的像素
我们能对Surface做的最基本的操作就是设置一个像素的色彩了,虽然我们基本不会这么做,但还是要了解。set_at方法可以做到这一点,它的参数是坐标和颜色,下面的小脚本会随机的在屏幕上画点:
import pygame
from pygame.locals import *
from sys import exit
from random import randint # 用来生成随机数
pygame.init()
screen = pygame.display.set_mode((640, 480), 0, 32)
while True:
for event in pygame.event.get():
if event.type == QUIT:
exit()
rand_col = (randint(0, 255), randint(0, 255), randint(0, 255))
#screen.lock() #很快你就会知道这两句lock和unlock的意思了
for _ in range(100):
rand_pos = (randint(0, 639), randint(0, 479))
screen.set_at(rand_pos, rand_col)
#screen.unlock()
pygame.display.update()
复制代码
获得Surface上的像素
set_at
的兄弟get_at
可以帮我们做这件事,它接受一个坐标返回指定坐标点上的颜色。不过记住get_at
在对hardware surface操作的时候很慢,而全屏的时候总是hardware的,所以慎用这个方法!
锁定Surface
当Pygame往surface上画东西的时候,首先会把surface锁住,以保证不会有其它的进程来干扰,画完之后再解锁。锁和解锁时自动发生的,所以有时候可能不那么有效率,比如上面的例子,每次画100个点,那么就得锁解锁100次,现在我们把两句注释去掉,再执行看看是不是更快了(好吧,其实我没感觉出来,因为现在的机器性能都不错,这么点的差异还不太感觉的出来。不过请相信我~复杂的情况下会影响效率的)?
当你手动加锁的时候,一定不要忘记解锁,否则pygame有可能会失去响应。虽然上面的例子可能没问题,但是隐含的bug是我们一定要避免的事情。
Blitting
blit的的中文翻译给人摸不着头脑的感觉,可以译为位块传送(bit block transfer),其意义是将一个平面的一部分或全部图象整块从这个平面复制到另一个平面,下面还是直接使用英文。
blit是对表面做的最多的操作,我们在前面的程序中已经多次用到,不多说了;blit的还有一种用法,往往用在对动画的表现上,比如下例通过对frame_no的值的改变,我们可以把不同的帧(同一副图的不同位置)画到屏幕上:
screen.blit(ogre, (300, 200), (100 * frame_no, 0, 100, 100))
复制代码
七.图形绘制
pygame.draw
中函数的第一个参数总是一个surface
,然后是颜色,再后会是一系列的坐标等。稍有些计算机绘图经验的人就会知道,计算机里的坐标,(0,0)代表左上角。而返回值是一个Rect
对象,包含了绘制的领域,这样你就可以很方便的更新那个部分了。
函数 | 作用 |
---|---|
rect | 绘制矩形 |
polygon | 绘制多边形(三个及三个以上的边) |
circle | 绘制圆 |
ellipse | 绘制椭圆 |
arc | 绘制圆弧 |
line | 绘制线 |
lines | 绘制一系列的线 |
aaline | 绘制一根平滑的线 |
aalines | 绘制一系列平滑的线 |
我们下面一个一个详细说明。
pygame.draw.rect
用法:pygame.draw.rect(Surface, color, Rect, width=0)
pygame.draw.rect在surface上画一个矩形,除了surface和color,rect接受一个矩形的坐标和线宽参数,如果线宽是0或省略,则填充。我们有一个另外的方法来画矩形——fill方法,如果你还记得的话。事实上fill可能还会快一点点,因为fill由显卡来完成。
pygame.draw.polygon
用法:pygame.draw.polygon(Surface, color, pointlist, width=0)
polygon就是多边形,用法类似rect,第一、第二、第四的参数都是相同的,只不过polygon会接受一系列坐标的列表,代表了各个顶点。
pygame.draw.circle
用法:pygame.draw.circle(Surface, color, pos, radius, width=0)
很简单,画一个圆。与其他不同的是,它接收一个圆心坐标和半径参数。
pygame.draw.ellipse
用法:pygame.draw.ellipse(Surface, color, Rect, width=0)
你可以把一个ellipse想象成一个被压扁的圆,事实上,它是可以被一个矩形装起来的。pygame.draw.ellipse的第三个参数就是这个椭圆的外接矩形。
pygame.draw.arc
用法:pygame.draw.arc(Surface, color, Rect, start_angle, stop_angle, width=1)
arc是椭圆的一部分,所以它的参数也就比椭圆多一点。但它是不封闭的,因此没有fill方法。start_angle和stop_angle为开始和结束的角度。
pygame.draw.line
用法:pygame.draw.line(Surface, color, start_pos, end_pos, width=1)
我相信所有的人都能看明白。
pygame.draw.lines
用法:pygame.draw.lines(Surface, color, closed, pointlist, width=1)
closed是一个布尔变量,指明是否需要多画一条线来使这些线条闭合(感觉就和polygone一样了),pointlist是一个点的数组。
上面的表中我们还有aaline和aalines,玩游戏的都知道开出“抗锯齿(antialiasing)”效果会让画面更好看一些,模型的边就不会是锯齿形的了,这两个方法就是在画线的时候做这事情的,参数和上面一样,暂时不写。
import pygame
from pygame.locals import *
from sys import exit
from random import *
from math import pi
pygame.init()
screen = pygame.display.set_mode((640, 480), 0, 32)
points = []
while True:
for event in pygame.event.get():
if event.type == QUIT:
exit()
if event.type == KEYDOWN:
# 按任意键可以清屏并把点回复到原始状态
points = []
screen.fill((255,255,255))
if event.type == MOUSEBUTTONDOWN:
screen.fill((255,255,255))
# 画随机矩形
rc = (randint(0,255), randint(0,255), randint(0,255))
rp = (randint(0,639), randint(0,479))
rs = (639-randint(rp[0], 639), 479-randint(rp[1], 479))
pygame.draw.rect(screen, rc, Rect(rp, rs))
# 画随机圆形
rc = (randint(0,255), randint(0,255), randint(0,255))
rp = (randint(0,639), randint(0,479))
rr = randint(1, 200)
pygame.draw.circle(screen, rc, rp, rr)
# 获得当前鼠标点击位置
x, y = pygame.mouse.get_pos()
points.append((x, y))
# 根据点击位置画弧线
angle = (x/639.)*pi*2.
pygame.draw.arc(screen, (0,0,0), (0,0,639,479), 0, angle, 3)
# 根据点击位置画椭圆
pygame.draw.ellipse(screen, (0, 255, 0), (0, 0, x, y))
# 从左上和右下画两根线连接到点击位置
pygame.draw.line(screen, (0, 0, 255), (0, 0), (x, y))
pygame.draw.line(screen, (255, 0, 0), (640, 480), (x, y))
# 画点击轨迹图
if len(points) > 1:
pygame.draw.lines(screen, (155, 155, 0), False, points, 2)
# 和轨迹图基本一样,只不过是闭合的,因为会覆盖,所以这里注释了
#if len(points) >= 3:
# pygame.draw.polygon(screen, (0, 155, 155), points, 2)
# 把每个点画明显一点
for p in points:
pygame.draw.circle(screen, (155, 155, 155), p, 3)
pygame.display.update()
复制代码
到此为止,最简单的部分都看完了,我返回来会仔细更改这篇的,毕竟是原博主11年写的