pyglet(1): pyglet基本使用

楔子

pyglet是一个纯python的库,因此安装它直接通过pip install pyglet即可,很方便。当然开发游戏还有其它的库,比如pygame,但是那个相对来说会重一些,我们以后有机会再介绍。另外说一下我这里的环境,我目前使用的是python3.8.1,算是比较新的版本了,pyglet版本是1.5.0,操作系统是Windows,不过pyglet是跨平台的。

import pyglet
import sys

print(pyglet.version)  # 1.5.0
print(sys.version.split(' ', 1)[0])  # 3.8.1

下面我们就来开始pyglet的愉快之旅吧。

创建一个窗口

老规矩,首先写一个游戏,那么肯定要有一个窗口吧,不然怎么显示内容呢?

import pyglet

# 调用pyglet.window.Window方法,传入宽和高,即可创建一个窗口
# 关于这个窗口,如果想象成一个坐标系的话,那么左下角就是(0, 0) 右上角就是(800, 600)
# 之所以说这个,后面会用到
game_window = pyglet.window.Window(800, 600)


if __name__ == '__main__':
    # 调用pyglet.app.run()即可运行
    pyglet.app.run()

"""
另外说一下,我们这里是初学pyglet
所以为了直观的显示调用的函数、类在哪个模块下,我们每一次都会从pyglet包来进行导入

当你熟悉了pyglet模块之后,在自己开发的时候,就可以通过类似于
from pyglet import window
window.Window()
这种from ... import ...的方式导入了,就不用每一次都从pyglet开始导入
"""

此刻我们就创建了一个黑乎乎的窗口,这个窗口就是通过pyglet.window.Window创建的,这是个类,这个类支持的参数如下。

  • width:窗口的宽度,默认是640像素,我们刚才指定的800
  • height:窗口的高度,默认是480像素,我们刚才指定的600
  • caption:窗口的标题,默认是sys.argv[0],我们看到刚才窗口的标题就是我们的文件路径
  • resizable:bool类型,表示窗口是否可以调整大小,就是你把鼠标放在窗口边缘,是否可以进行方向上的拉伸。默认是False,不可以。
  • style:用于指定窗口的边界风格,支持的选择如下:WINDOW_STYLE_DEFAULT(默认选项)、WINDOW_STYLE_DIALOG、WINDOW_STYLE_TOOL、WINDOW_STYLE_BORDERLESS,这几个都在Window这个类里面,可以通过pyglet.window.Window.WINDOW_STYLE_XXX指定
  • fullscreen:是否全屏,默认是False
  • visible:在窗口创建之后是否立刻显示,默认为True。如果你想在窗口显示之前修改某些属性,那么可以设置为False
  • vsync:这个参数比较难理解,如果为True,那么缓冲区翻转将会同步到主屏幕的帧回描上面,从而消除闪烁。默认是为True,我们不需要管它。
  • display:指定使用的显示设备,这个不需要管。
  • 还剩下4个参数,基本上用不到,就不说了。

我们看到支持的参数虽然多,但是真正经常使用的也就是:width、height、caption、resizable这几个参数。

import pyglet

game_window = pyglet.window.Window(
    width=400,
    height=300,
    caption="古明地觉",
    resizable=True
)


if __name__ == '__main__':
    pyglet.app.run()

另外如果把鼠标放在窗口的边缘,那么还可以对窗口进行拉伸,将窗口变大或变小,这就是resizable参数发挥的作用。

给窗口添加点装饰

我们目前的窗口是黑乎乎的,什么也没有,我们是不是该给它们添加一些装饰呢?

添加文字

既然要添加,肯定要先创建一个文字,创建的方式通过pyglet.text.Label,这是一个类,支持的参数如下:

  • text:一个字符串,也就是你要显示的文本内容,默认是''
  • font_name:使用的字体,通过传递字符串指定,如果你传递了一个列表,里面指定了多个字体,那么只会使用第一个字体。默认为None
  • font_size:浮点型,字体的大小
  • bold:是否加粗,默认是False,不加粗
  • italic:是否为斜体,默认是False,不为斜体
  • color:字体的颜色,RGBA格式,三原色加上透明度。默认是(255, 255, 255, 255),即纯白色、不透明
  • x:文本内容的左下角的x坐标
  • y:文本内容的左下角的y坐标
  • width:显示的文本的宽度
  • height:显示的文本的高度
  • anchor_x:x坐标的锚点,参数为字符串:可以选择"left"、"center"、"right"。说说它和参数x的区别吧,anchor_x可以看成是对参数x的一个"弥补"吧,我们说参数x的坐标对应的是文本左下角的坐标,如果anchor_x指定为center,参数x指定的坐标就不再是左下角了,而是左下角所在的水平方向上的中间位置,当然指定为right,就变成右下角了。比如:我们想使文本居中,那么会把x和y的坐标改成窗口宽度和高度的一半,但是由于这两个坐标是针对左下角,所以显示出来会发现文本全部显示在窗口的右侧,这时候就可以通过将anchor_x和anchor_y全部指定为center使其居中。具体可以自己操作感受一下,如果觉得我描述的不好理解的话。
  • anchor_y:y坐标的锚点,参数为字符串:可以选择"bottom"、"baseline"、"center"、"top"。意义同anchor_x
  • align:水平方向的位置,可以为"left"、"center"、"right",只有当width参数指定了,参数align才会发挥作用
  • multiline:是否接受换行符,如果设置为True,那么你必须也要指定width参数,也就是宽度
import pyglet

game_window = pyglet.window.Window(
    width=400,
    height=300,
    caption="古明地觉",
    resizable=True
)

# 创建Label对象
label = pyglet.text.Label('Hello, world',
                          font_size=25,  # 字体不指定,使用默认的,大小为25
                          x=game_window.width//2,
                          y=game_window.height//2,
                          anchor_x='center', anchor_y='center'
                          )


# 下面问题来了,我们要如何将字体显示在上面呢?
# 首先显示文本内容,可以通过label.draw()方法
# 但是我们直接写label.draw()是不行的,因为这样无法显示在窗口上面,显示不到窗口上面是无意义的
# 这里我们说一下,当pyglet创建窗口的时候,会调用窗口的on_draw方法,也就是Window这个类的on_draw方法
# 我们只有将label.draw()写到这个on_draw方法里面,才可以实现。
# 一种办法是继承Window这个类,然后重写里面的on_draw方法,用继承Window的类创建窗口,但是这个显然不科学
# 另一种就是通过反射的方式

def show_label():
    # 将初始的窗口内容删除
    game_window.clear()
    # 添加文本,重新绘制窗口
    label.draw()


# 重写on_draw方法,以后就会执行我们在show_label里面指定的代码
setattr(game_window, "on_draw", show_label)


if __name__ == '__main__':
    pyglet.app.run()

这样文字就显示在上面了,但是我们是通过反射的方式,其实pyglet还提供了一种方法,通过装饰器的方式。

import pyglet

game_window = pyglet.window.Window(
    width=400,
    height=300,
    caption="古明地觉",
    resizable=True
)

label = pyglet.text.Label('Hello, world',
                          font_size=25,
                          x=game_window.width//2,
                          y=game_window.height//2,
                          anchor_x='center', anchor_y='center'
                          )


# 通过game_window.event进行装饰即可,但是函数名必须也要叫on_draw,我们后面都会使用这种方式
@game_window.event
def on_draw():
    game_window.clear()
    label.draw()


if __name__ == '__main__':
    # 最后再来说一下这个run方法,我们之前简单提了一下。
    """
    通过pyglet.app.run()将会进入pyglet的默认事件循环,并让pyglet响应所有的事件,比如:鼠标、键盘
    将会在需要的时候被调用你的event handler
    """
    pyglet.app.run()

结果是一样的,不再截图。

添加图片

我们如何添加一张图片呢?通过pyglet.resource.image,pyglet.resource会返回一个Loader对象,内部不同的方法用来加载不同的资源,pyglet.resource.image则是加载图片的,支持的参数如下:

  • name:文件路径
  • flip_x:是否水平翻转,默认为False
  • flip_y:是否垂直翻转,默认为False
  • rotate:按照逆时针的旋转角度,应该是90的整倍数
import pyglet

game_window = pyglet.window.Window(
    width=800,
    height=600,
    caption="古明地觉",
    resizable=True
)

image = pyglet.resource.image("images/高木同学.png")
# 但是注意:图片的大小和窗口大小如果不一致,就会存在冲突
# 因为图片显示的方式是:假设窗口的宽为x、高为y。那么会从图片的左下角向右截取x个像素的部分、向上截取y个像素的部分,贴在窗口上面
# 如果图片比窗口小,那么肯定无法全部覆盖,窗口的右侧或者上方会存在之前的、没有覆盖到的黑色区域。
# 如果图片比窗口大,那么图片无法全部展示,只会展示图片的一部分,图片的右侧或者上方会有一部分细节展示不到
# 比如我们的窗口是800 600,但是当前图片的的大小是2400 1679

@game_window.event
def on_draw():
    game_window.clear()
    # 这行代码后面说
    image.blit(0, 0)


if __name__ == '__main__':
    pyglet.app.run()

这里图片的细节没有全部展示出来,这里是从图片左下角向右截取800像素、向上截取600像素的结果。那么如何解决这一问题呢?

import pyglet

game_window = pyglet.window.Window(
    width=800,
    height=600,
    caption="古明地觉",
    resizable=True
)

image = pyglet.resource.image("images/高木同学.png")
# 是的image也有anchor_x和anchor_y,尽管参数里面没有,但是我们可以通过返回的image进行设置
image.anchor_x = 1000
image.anchor_y = 800
# 这两行代码表示什么含义呢?我们说图片是从左下角开始向右、向上截取的,直到宽为窗口的宽度、高为窗口的高度。
# 如果图片提前结束了,比如窗口高度是500,但是图片高度只有300,那么上方剩余的200就是默认的黑色区域
# 但是通过指定anchor_x=1000和anchor_y=800,那么将不再从图片的左下角进行截取了
# 而是会从向右1000个像素、向上800个像素的地方开始,再向右、向上进行截取


@game_window.event
def on_draw():
    game_window.clear()
    # 这行代码后面说
    image.blit(0, 0)


if __name__ == '__main__':
    pyglet.app.run()

但是说实话,这么做虽然可以让图片的关键部分展示出来,但这仍然不是我们期望的结果,我们还是希望图片完整的显示出来,那么最好的办法就是让图片的大小和窗口大小保持一致。

import pyglet

game_window = pyglet.window.Window(
    width=800,
    height=600,
    caption="古明地觉",
    resizable=True
)

image = pyglet.resource.image("images/高木同学.png")
# 没错,image还有width和height,我们依旧可以通过返回的image进行设置
print(image.width, image.height)  # 2400 1679
image.width = 800
image.height = 600
# 此时图片的大小和窗口的大小是一致的,那么从图片的左下角开始向右、向上所截取的部分,正好是图片的全部
# 因此就不需要anchor_x和anchor_y了
# 通过image.width=800和image.height=600,其实是对图片在水平和垂直方向上进行了缩放,但是原来的图片的比例显然不是800 / 600
# 但如果差别不大也无影响,但是如果窗口是800 100,那么图片肯定会变形。
# 这时候你可能要改变窗口大小了,或者换一张宽高比和窗口的宽高比更接近的图片


@game_window.event
def on_draw():
    game_window.clear()
    # 这个就跟label.draw()一样,肯定是要绘制在窗口上面的,blit是绘制图像
    # 里面参数(0, 0)表示从图片的左下角开始绘制,是的,我们之前没有说,是因为我们这里指定了(0, 0),表示从左下角开始绘制
    # 我们看到blit里面的参数其实还可以充当anchor_x和anchor_y的作用,但是一般我们都在blit里面传入0, 0
    image.blit(0, 0)


if __name__ == '__main__':
    pyglet.app.run()

同时添加文字和图片

import pyglet

game_window = pyglet.window.Window(
    width=800,
    height=600,
    caption="古明地觉",
    resizable=True
)

label = pyglet.text.Label("高木同学",
                          font_size=25,
                          x=game_window.width//2,
                          y=game_window.height//2,
                          anchor_x='center', anchor_y='center',
                          color=(155, 255, 0, 255)
                          )

image = pyglet.resource.image("images/高木同学.png")
image.width = 800
image.height = 600

@game_window.event
def on_draw():
    game_window.clear()
    # 这是一个值得注意的点,绘制顺序是从上往下。
    # 我们要先绘制图片,再绘制文字,让文字显示在图片上方。如果顺序反了,那么图片会把文字给挡住
    image.blit(0, 0)
    label.draw()


if __name__ == '__main__':
    pyglet.app.run()

监控键盘和鼠标

监控键盘

监控键盘,无非是当某个键按下的时候触发相应事件,当某个键松开的时候触发某个事件。

import pyglet

game_window = pyglet.window.Window(
    width=800,
    height=600,
    caption="古明地觉",
    resizable=True
)

# 这里我们只写一个字,当我们按下上下左右的时候,让这个字进行移动
label = pyglet.text.Label("木",
                          font_size=25,
                          x=game_window.width//2,
                          y=game_window.height//2,
                          anchor_x='center', anchor_y='center',
                          color=(155, 255, 0, 255)
                          )

image = pyglet.resource.image("images/高木同学.png")
image.width = 800
image.height = 600

@game_window.event
def on_draw():
    game_window.clear()
    image.blit(0, 0)
    label.draw()


# 以上代码不变,显然我们需要把键盘注册成事件
# 函数是on_key_press,当我们按下键盘的某个键是会触发这个事件
@game_window.event
def on_key_press(symbol, modifiers):
    # symbol参数指的是你按下的键,modifiers后面说

    # 通过key可以模拟键盘的键,当然这个import我们应该放在上面的
    from pyglet.window import key
    """
    key.LEFT: ←
    key.RIGHT: →
    key.UP: ↑
    key.DOWN: ↓
    当然还有其它的键,比如回车:key.ENTER等等
    """
    # 按下左键,"木"字左移10个像素,右键,右移10个像素,同理还有上键、下键等等
    # 如果按下其它的键,那么退出。game_window.close()表示让窗口退出
    if symbol == key.LEFT:
        label.x -= 10
    elif symbol == key.RIGHT:
        label.x += 10
    elif symbol == key.UP:
        label.y += 10
    elif symbol == key.DOWN:
        label.y -= 10
    else:
        game_window.close()


if __name__ == '__main__':
    pyglet.app.run()

此时我们通过上下左右键,就可以是"木"这个字进行移动,很简单。关于物体移动,并不是直接把物体移动了,而是先计算出移动之后的位置,然后重新渲染,并将物体放在新的位置上。

# 所以这段代码很关键,这个on_draw你可以理解为只有事件发生就会调用这个方法
@game_window.event
def on_draw():
    # 当我们将"木"的坐标改变之后
    # 将窗口清空
    game_window.clear()
    # 绘制图像
    image.blit(0, 0)
    # 绘制文字,此时再绘制就是我们移动后的位置了
    # 所以移动本质上只是计算出了新的坐标,然后将"木"字展示在新的位置上
    # 所以看起来就仿佛,"木"这个字在移动一样。
    label.draw()

同理,on_key_press对应按下某个键,那么on_key_release则是松开某个键。

监控鼠标

监控鼠标和监控键盘类似,也有两个函数:on_mouse_press和on_mouse_release分别对应鼠标的按下和松开事件。

import pyglet

game_window = pyglet.window.Window(
    width=800,
    height=600,
    caption="古明地觉",
    resizable=True
)

label = pyglet.text.Label("木",
                          font_size=25,
                          x=game_window.width//2,
                          y=game_window.height//2,
                          anchor_x='center', anchor_y='center',
                          color=(155, 255, 0, 255)
                          )

image = pyglet.resource.image("images/高木同学.png")
image.width = 800
image.height = 600

@game_window.event
def on_draw():
    game_window.clear()
    image.blit(0, 0)
    label.draw()


@game_window.event
def on_mouse_press(x, y, symbol, modifiers):
    # 参数x和y是你鼠标点击的位置的坐标
    from pyglet.window import mouse
    # mouse.LEFT左键,mouse.MIDDLE中间键,mouse.RIGHT右键

    if symbol == mouse.LEFT:
        label.x, label.y = x, y
    else:
        game_window.close()


if __name__ == '__main__':
    pyglet.app.run()

此时我的鼠标点击在什么位置,"木"这个字就会移动到什么位置。

写个小游戏吧

对了,我想到了一个游戏。大致过程就是上面写着"你爱我吗?",然后下面写着两个选项:"是"和"否",当你点击"是",正常退出,点击"否",那么这个"否"字就跑到其它位置上。

import random
import pyglet
from pyglet.window import mouse

game_window = pyglet.window.Window(
    width=800,
    height=600,
    caption="古明地觉",
    resizable=True
)

# 这里我们只写一个字,当我们按下上下左右的时候,让这个字进行移动
label = pyglet.text.Label("do you love me?",
                          font_size=25,
                          x=game_window.width // 2,
                          y=game_window.height // 1.2,  # 我们要是文本靠近上方,那么y要适当增大
                          anchor_x='center', anchor_y='center',
                          color=(155, 255, 0, 255)
                          )

# 这里我们就不计算了,直接写上坐标
yes_label = pyglet.text.Label("yes", font_size=30, x=200, y=400,
                              anchor_x='center', anchor_y='center',
                              color=(155, 255, 0, 255))

no_label = pyglet.text.Label("no", font_size=30, x=600, y=400,
                             anchor_x='center', anchor_y='center',
                             color=(155, 255, 0, 255))

image = pyglet.resource.image("images/高木同学.png")
image.width = 800
image.height = 600


@game_window.event
def on_draw():
    game_window.clear()
    image.blit(0, 0)
    # 绘制文字
    label.draw()
    yes_label.draw()
    no_label.draw()


@game_window.event
def on_mouse_press(x, y, symbol, modifiers):
    if symbol == mouse.LEFT:
        """
        我们来计算鼠标是否点击在了文本上,首先对于Label对象来说,它还有一个content_width和content_height,表示这个文本整体所占的宽度和高度
        而Label对象的x和y则表示这个文本的水平方向和竖直方向上的中间位置
        那么我们就想到了:
            对于yes_label,如果|x - yes_label.x| <= yes_label.content_width / 2并且|y - yes_label.y| <= yes_label.content_height / 2
            也就是x和yes_label.x之差的绝对值小于等于文本整体宽度的一半、y和yes_label.y之差的绝对值小于等于文本整体高度的一半,那么我们就认为点击了yes
            
        同理,对于no_label也是一样的,如果点击了,这个时候应该让no_label移动到别的位置上
        但是注意不能和别的文字进行重叠
        """
        if abs(x - yes_label.x) <= yes_label.content_width // 2 and abs(
                y - yes_label.y) <= yes_label.content_height // 2:
            game_window.close()
        elif abs(x - no_label.x) <= no_label.content_width // 2 and abs(y - no_label.y) <= no_label.content_height // 2:
            # 显然应该让no_label移动到其它的位置,但是文字不要越界
            while True:
                (pos_x, pos_y) = (
                    random.randint(no_label.content_width // 2, game_window.width - no_label.content_width // 2),
                    random.randint(no_label.content_height // 2, game_window.height - no_label.content_height // 2))

                # 并且pos_x和pos_y不能和已有的文字重叠,由于都是中心位置,那么它们的距离肯定要大于各自文本的一半之和
                # 为了更明显,我们再额外加上10像素
                if (
                        abs(pos_x - yes_label.x) > yes_label.content_width // 2 + no_label.content_width // 2 + 10 and
                        abs(pos_y - yes_label.y) > yes_label.content_height / 2 + no_label.content_height // 2 + 10 and
                        abs(pos_x - label.x) > label.content_width // 2 + no_label.content_width // 2 + 10 and
                        abs(pos_y - label.y) > label.content_height // 2 + no_label.content_height // 2 + 10
                ):
                    # 分配成功结束循环,否则重新生成位置
                    no_label.x, no_label.y = pos_x, pos_y
                    break


if __name__ == '__main__':
    pyglet.app.run()

启动之后界面如下,当我们点击no的时候,会发现no跑到了其它位置上了。

播放音乐

我们说加载资源使用pyglet.resource,加载图像:pyglet.resource.image,加载音乐:pyglet.resource.media,这里面接收两个参数:

  • name:文件路径
  • streaming:布尔类型,如果为True,那么会从磁盘一边加载一边播放,直到全部加载完。如果为False,那么会等到全部都加载到内存里面之后,再进行播放,默认是True。如果采用流式,那么对于比较长的曲目很有效,但如果是短小的声音、比如枪声、爆炸声,就不应该是用流式。应该在全部加载到内存里,并减少CPU性能损失。
import pyglet

game_window = pyglet.window.Window(
    width=800,
    height=600,
    caption="古明地觉",
    resizable=True
)

image = pyglet.resource.image("images/高木同学.png")
image.width = 800
image.height = 600

# 加载音乐,但是注意:如果你本地没有ffmpeg的话,那么音乐必须是wav格式的
# 关于如何将音频格式进行转化,可以看我的这篇博客:https://www.cnblogs.com/traditional/p/12391872.html
sound = pyglet.resource.media("sounds/Hanser,YUKIri - 遥控器(リモコン)(Cover 镜音).wav",
                              streaming=False)
# 播放音乐,就不需要放在on_draw里面了
# 我们需要调用pyglet.media.Player()实例化一个Player对象
play = pyglet.media.Player()
# 通过play.queue(sound)将要播放的音乐加入到队列当中
# 如果有多个音乐,那么组合成一个列表添加进去,play.queue([sound1, sound2, ...])
play.queue(sound)
# 依次播放队列里面的音乐
play.play()

@game_window.event
def on_draw():
    game_window.clear()
    image.blit(0, 0)


if __name__ == '__main__':
    pyglet.app.run()

如果你发现音乐播放一遍之后停止了,那么你可以这么做:

sound = pyglet.resource.media(r"sounds/Hanser,YUKIri - 遥控器(リモコン)(Cover 镜音).wav")
play = pyglet.media.Player()

def repeat():
    while True:
        yield sound
play.queue(repeat())
# 这样就可以无限播放了
play.play()
原文地址:https://www.cnblogs.com/traditional/p/12392621.html