是否有理由使用 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]。
我更愿意只使用字典或数组,这样我就可以循环遍历并按特定顺序绘制(或更新)事物。但是我在这个模块上看到的所有指南和帖子都使用了批处理。如果有差异,是否足以让我特意使用我不太喜欢的方法?
下面是一个使用批次抽取多个项目的例子...
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]。