是否有理由使用 pyglet 批处理来迭代和绘制字典或数组?

Is there a reason to use a pyglet batch over iterating and drawing through a dictionary or array?

我更愿意只使用字典或数组,这样我就可以循环遍历并按特定顺序绘制(或更新)事物。但是我在这个模块上看到的所有指南和帖子都使用了批处理。如果有差异,是否足以让我特意使用我不太喜欢的方法?

下面是一个使用批次抽取多个项目的例子...

mainBatch = pyglet.graphics.Batch()

background = pyglet.sprite.Sprite(pyglet.image.load(image), x=xCor, y=yCor, batch=batch)
player = pyglet.sprite.Sprite(pyglet.image.load(image), x=xCor, y=yCor, batch=batch)
enemy = pyglet.sprite.Sprite(pyglet.image.load(image), x=xCor, y=yCor, batch=batch)

class gameWindow(pyglet.window.Window):
    def on_draw(self):
        self.clear()
        mainBatch.draw()

根据我在网上看到的,先画后画没有特定的顺序,根据我的经验,背景画在播放器上方。

这是一个使用字典并循环遍历每个字典以按顺序抽出它们的示例...

gameObjects = {
    'background': pyglet.sprite.Sprite(pyglet.image.load(image), x=xCor, y=yCor),
    'player': pyglet.sprite.Sprite(pyglet.image.load(image), x=xCor, y=yCor),
    'enemy': pyglet.sprite.Sprite(pyglet.image.load(image), x=xCor, y=yCor),
}

class gameWindow(pyglet.window.Window):
    def on_draw(self):
        self.clear()
        for i in gameObjects:
            gameObjects[i].draw()

我更喜欢这种方式,但如果它们有明显的优势,我完全赞成改变。

Batch() 对象被渲染 “一次性”,其中一个接一个地绘制精灵将调用多个绘制 (和其他元函数),这反过来会导致触发大量开销事件(如更新屏幕等)。因此,最好使用批处理并调用一个绘制函数和一个更新屏幕而不是多个。定位也是如此,而不是单独执行 sprite.x = ...,你最好使用 sprite.position,它不会在两次调用时重新计算。

使用批渲染与手动渲染的性能提升大致 <num of elements> * 3。如果您对“层”感兴趣或按顺序渲染事物,您可以使用 OrderedGroup 作为分层选项,一个更简单的平台示例是:

from pyglet import *
from pyglet.gl import *

key = pyglet.window.key

class collision():
    def rectangle(x, y, target_x, target_y, width=32, height=32, target_width=32, target_height=32):
        # Assuming width/height is *dangerous* since this library might give false-positives.
        if (x >= target_x and x < (target_x + target_width)) or ((x + width) >= target_x and (x + width) <= (target_x + target_width)):
            if (y >= target_y and y < (target_y + target_height)) or ((y + height) >= target_y and (y + height) <= (target_y + target_height)):
                return True
        return False

class GenericSprite(pyglet.sprite.Sprite):
    def __init__(self, x, y, width, height, color=(255,255,255), batch=None, group=None):
        self.texture = pyglet.image.SolidColorImagePattern((*color, 255)).create_image(width, height)
        super(GenericSprite, self).__init__(self.texture, batch=batch, group=group)
        self.x = x
        self.y = y

    def change_color(self, r, g, b):
        self.texture = pyglet.image.SolidColorImagePattern((r, g, b, 255)).create_image(self.width, self.height)

class Block(GenericSprite):
    def __init__(self, x, y, batch, group):
        super(Block, self).__init__(x, y, 30, 30, color=(255, 255, 255), batch=batch, group=group)

class Player(GenericSprite):
    def __init__(self, x, y, batch, group):
        super(Player, self).__init__(x, y, 30, 30, color=(55, 255, 55), batch=batch, group=group)

class main(pyglet.window.Window):
    def __init__ (self, width=800, height=600, fps=False, *args, **kwargs):
        super(main, self).__init__(width, height, *args, **kwargs)
        self.keys = {}
        self.status_labels = {}

        self.batch = pyglet.graphics.Batch()
        self.background = pyglet.graphics.OrderedGroup(0)
        self.foreground = pyglet.graphics.OrderedGroup(1)

        self.player_obj = Player(40, 40, self.batch, self.foreground)
        self.status_labels['player_position'] = pyglet.text.Label(f'Player position: x={self.player_obj.x}, y={self.player_obj.y}, x+w={self.player_obj.x+self.player_obj.width}, y+h={self.player_obj.y+self.player_obj.height}', x=10, y=self.height-30, batch=self.batch, group=self.background)
        self.blocks = {}
        for index, i in enumerate(range(10, 120, 30)):
            self.blocks[i] = Block(i, 10, self.batch, self.background)
            self.status_labels[f'block{i+1}_position'] = pyglet.text.Label(f'Block #{index+1}: left={self.blocks[i].x}, bottom={self.blocks[i].y}, top={self.blocks[i].y+self.blocks[i].height}, right={self.blocks[i].x+self.blocks[i].width}', x=10, y=self.height-(50+(index*16)), batch=self.batch, group=self.background)

        self.alive = 1

    def on_draw(self):
        self.render()

    def on_close(self):
        self.alive = 0

    def on_key_release(self, symbol, modifiers):
        try:
            del self.keys[symbol]
        except:
            pass

    def on_key_press(self, symbol, modifiers):
        if symbol == key.ESCAPE: # [ESC]
            self.alive = 0

        self.keys[symbol] = True

    def render(self):
        self.clear()

        for key_down in self.keys:
            if key_down == key.D:
                self.player_obj.x += 1
            elif key_down == key.A:
                self.player_obj.x -= 1
            elif key_down == key.W:
                self.player_obj.y += 1
            elif key_down == key.S:
                self.player_obj.y -= 1

        self.status_labels['player_position'].text = f'Player position: x={self.player_obj.x}, y={self.player_obj.y}, x+w={self.player_obj.x+self.player_obj.width}, y+h={self.player_obj.y+self.player_obj.height}'
        for index, i in enumerate(range(10, 120, 30)):
            if collision.rectangle(self.player_obj.x, self.player_obj.y, self.blocks[i].x, self.blocks[i].y, width=30, height=30, target_width=30, target_height=30):
                # self.blocks[i].change_color(255,55,55)
                self.status_labels[f'block{i+1}_position'].color = (255,55,55,255)
            else:
                self.status_labels[f'block{i+1}_position'].color = (55,255,55,255)

        self.batch.draw()

        self.flip()

    def run(self):
        while self.alive == 1:
            self.render()

            # -----------> This is key <----------
            # This is what replaces pyglet.app.run()
            # but is required for the GUI to not freeze
            #
            event = self.dispatch_events()

if __name__ == '__main__':
    x = main()
    x.run()

创建此示例是为了帮助某些人进行碰撞检测,但我认为它可以在这里工作,因为它使用 OrderedGroup 进行两层和批量渲染。

您可以查看其他一些有趣的优化和示例,例如 particle system,它已得到增强,可以使用一些很酷的 GL 功能在屏幕上渲染和更新超过 100 万个对象:

from array import array
import random
import pyglet
import arcade
from arcade import gl

window = pyglet.window.Window(720, 720)
ctx = gl.Context(window)
print("OpenGL version:", ctx.gl_version)

size = window.width // 4, window.height // 4

def gen_initial_data(width, height):
    dx, dy = window.height / width, window.width / height
    for y in range(height):
        for x in range(width):
            # current pos
            # yield window.width // 2
            # yield window.height // 2
            yield x * dx + dx / 2
            yield y * dy + dy / 2
            # desired pos
            yield x * dx + dx / 2
            yield y * dy + dy / 2


def gen_colors(width, height):
    for _ in range(width * height):
        yield random.uniform(0, 1) 
        yield random.uniform(0, 1) 
        yield random.uniform(0, 1) 

buffer1 = ctx.buffer(data=array('f', gen_initial_data(*size)))
buffer2 = ctx.buffer(reserve=buffer1.size)
colors = ctx.buffer(data=array('f', gen_colors(*size)))

geometry1 = ctx.geometry([
    gl.BufferDescription(buffer1, '2f 2x4', ['in_pos']),
    gl.BufferDescription(colors, '3f', ['in_color']),
])
geometry2 = ctx.geometry([
    gl.BufferDescription(buffer2, '2f 2x4', ['in_pos']),
    gl.BufferDescription(colors, '3f', ['in_color']),
])

transform1 = ctx.geometry([gl.BufferDescription(buffer1, '2f 2f', ['in_pos', 'in_dest'])])
transform2 = ctx.geometry([gl.BufferDescription(buffer2, '2f 2f', ['in_pos', 'in_dest'])])

# Is there a way to make ortho projection in pyglet?
projection = arcade.create_orthogonal_projection(0, window.width, 0, window.height, -100, 100).flatten()

points_program = ctx.program(
    vertex_shader="""
    #version 330

    uniform mat4 projection;
    in vec2 in_pos;
    in vec3 in_color;
    out vec3 color;

    void main() {
        gl_Position = projection * vec4(in_pos, 0.0, 1.0);
        color = in_color;
    }
    """,
    fragment_shader="""
    #version 330

    in vec3 color;
    out vec4 fragColor;

    void main() {
        fragColor = vec4(color, 1.0);
    }
    """,
)
points_program['projection'] = projection

transform_program = ctx.program(
    vertex_shader="""
    #version 330

    uniform float dt;
    uniform vec2 mouse_pos;

    in vec2 in_pos;
    in vec2 in_dest;

    out vec2 out_pos;
    out vec2 out_dest;

    void main() {
        out_dest = in_dest;
        // Slowly move the point towards the desired location
        vec2 dir = in_dest - in_pos;
        vec2 pos = in_pos + dir * dt;
        // Move the point away from the mouse position
        float dist = length(pos - mouse_pos);
        if (dist < 60.0) {
            pos += (pos - mouse_pos) * dt * 10;
        }
        out_pos = pos;
    }
    """,
)
frame_time = 0
mouse_pos = -100, -100


@window.event
def on_draw():
    global buffer1, buffer2, geometry1, geometry2, transform1, transform2
    window.clear()
    ctx.point_size = 2

    geometry1.render(points_program, mode=gl.POINTS)
    transform_program['dt'] = frame_time
    transform_program['mouse_pos'] = mouse_pos
    transform1.transform(transform_program, buffer2)

    buffer1, buffer2 = buffer2, buffer1
    geometry1, geometry2 = geometry2, geometry1
    transform1, transform2 = transform2, transform1


def update(dt):
    global frame_time
    frame_time = dt


@window.event
def on_mouse_motion(x, y, dx, dy):
    global mouse_pos
    mouse_pos = x, y


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

它使用 arcade 库。

如果您尝试在不进行批处理和一些 GL 优化的情况下执行这些操作,您的 PC 就会崩溃。最后一个示例的所有功劳都归功于官方不和谐服务器中的 einarf 和 Rafale25 [FR]。