zoukankan      html  css  js  c++  java
  • 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()
    
  • 相关阅读:
    Eclipse使用xdoclet1.2.3 生成hibernate配置文件和映射文件
    Eclipse安装SVN插件
    SourceTree安装和使用
    myeclipse通过数据表生成jpa或hibernate实体
    Delphi 快速读取TXT 指定行的数据
    delphi中如何将一整个文件读入内存
    Delphi TextFile读取文本文件
    Delphi读取和写入utf-8编码格式的文件
    Delphi 判断特定字符是为单字节还是双字节
    delphi按字节长度分割字符串函数(转)
  • 原文地址:https://www.cnblogs.com/traditional/p/12392621.html
Copyright © 2011-2022 走看看