Representación de la imagen de Pyglet

Estoy trabajando en una especie de clon de Minecraft 2D para mi primer proyecto en profundidad de Pyglet y me he encontrado con un problema. Cada vez que tengo un número decente de bloques en la pantalla, la velocidad de fotogtwigs cae dramáticamente.

Aquí está mi método de renderizado: uso un diccionario con la clave como una tupla (que representa la coordenada para el bloque) y el elemento como una textura.

Recorro todo el diccionario y renderizo cada bloque:

for key in self.blocks: self.blocks[key].blit(key[0] * 40 + sx,key[1] * 40+ sy) 

PS sx y sy son compensaciones de coordenadas para desplazamiento de pantalla

Me gustaría saber si hay una manera de procesar cada bloque de manera más eficiente.

    Haré todo lo posible para explicar por qué y cómo optimizar su código sin saber realmente cómo se ve su código.

    Asumiré que tiene algo como:

     self.blocks['monster001'] = pyglet.image.load('./roar.png') 

    Todo esto está bien y es excelente, si desea cargar una imagen estática con la que no quiere hacer mucho. Sin embargo, estás haciendo un juego y vas a usar muchos más sprites y objetos que solo un simple archivo de imagen.

    Ahora aquí es donde los objetos compartidos, lotes y sprites son útiles. En primer lugar, ingrese su imagen en un sprite, es un buen comienzo.

     sprite = pyglet.sprite.Sprite(pyglet.image.load('./roar.png')) sprite.draw() # This is instead of blit. Position is done via sprite.x = ... 

    Ahora, dibujar es mucho más rápido que .blit() por muchas razones, pero por ahora nos .blit() por qué y nos .blit() a las .blit() actualizaciones de velocidad .

    Nuevamente, esto es solo un pequeño paso hacia las tablas de cuadros exitosas (aparte de tener un hardware limitado de ofc .. duh ).

    De todos modos, vuelva a cargar su código con las actualizaciones.
    Ahora también desea agregar sprites a un lote para que pueda representar MUCHAS cosas de una sola vez (leer: lote) en lugar de empujar manualmente las cosas a la tarjeta gráfica. El propósito de las tarjetas gráficas fue diseñado para poder manejar gigabits de rendimiento en cálculos de una manera increíblemente rápida en lugar de manejar múltiples E / S pequeñas .

    Para hacer esto, necesitas crear un contenedor por lotes. Y añádele “capas”.
    Es bastante simple, todo lo que necesitas hacer es:

     main_batch = pyglet.graphics.Batch() background = pyglet.graphics.OrderedGroup(0) # stuff_above_background = pyglet.graphics.OrderedGroup(1) # ... 

    Por ahora, seguiremos con uno por lotes, probablemente no necesite más para este propósito de aprendizaje.
    Ok, así que tienes tu lote, ¿ahora qué? Bueno, ahora intentamos con todas nuestras fuerzas ahogar ese infierno vivo de tu tarjeta gráfica y ver si podemos incluso doblarla bajo presión (No se dañó ningún carro gráfico en este proceso, y por favor no te atragantes con las cosas …)

    Oh, una cosa más, ¿recuerdas la nota sobre objetos compartidos? bueno, crearemos un objeto de imagen compartida aquí que empujamos en el sprite, en lugar de cargar una nueva imagen cada … solo … tiempo … monster_image lo llamaremos.

     monster_image = pyglet.image.load('./roar.png') for i in range(100): # We'll create 100 test monsters self.blocks['monster'+str(i)] = pyglet.sprite.Sprite(imgage=monster_image, x=0, y=0, batch=main_batch, group=background) 

    Ahora tienes 100 monstruos creados y agregados al lote main_batch en el background del subgrupo. Sencillo como el pastel.

    Aquí está el kicker, en lugar de llamar self.blocks[key].blit() o .draw() , ahora podemos llamar a main_batch.draw() y disparará a cada monstruo a la tarjeta gráfica y producirá maravillas.

    Ok, ahora has optimizado la velocidad de tu código, pero realmente eso no te ayudará a largo plazo si estás haciendo un juego. O en este caso, un motor gráfico para tu juego. Lo que quieres hacer es subir a la gran liga y usar las clases . Si te sorprendió antes, probablemente perderás las canicas de lo impresionante que se verá tu código una vez que lo hayas hecho.

    Ok, primero, desea crear una clase base para sus objetos en la pantalla, vamos a llamar en baseSprite .
    Ahora hay algunos problemas y cosas que necesitas solucionar con Pyglet, por una parte, cuando heredar objetos Sprite que intentan establecer la image causará todo tipo de fallos y fallas cuando trabajas con cosas, así que estableceremos self.texture directamente, que es básicamente lo mismo pero enganchamos a las variables de las bibliotecas de pyglet en su lugar; D pew pew hehe.

     class baseSprite(pyglet.sprite.Sprite): def __init__(self, texture, x, y, batch, subgroup): self.texture = texture super(baseSprite, self).__init__(self.texture, batch=batch, group=subgroup) self.x = x self.y = y def move(self, x, y): """ This function is just to show you how you could improve your base class even further """ self.x += x self.y += y def _draw(self): """ Normally we call _draw() instead of .draw() on sprites because _draw() will contains so much more than simply drawing the object, it might check for interactions or update inline data (and most likely positioning objects). """ self.draw() 

    Ahora que es tu base, ahora puedes crear monstruos haciendo:

     main_batch = pyglet.graphics.Batch() background = pyglet.graphics.OrderedGroup(0) monster_image = pyglet.image.load('./roar.png') self.blocks['monster001'] = baseSprite(monster_image, 10, 50, main_batch, background) self.blocks['monster002'] = baseSprite(monster_image, 70, 20, main_batch, background) ... main_batch.draw() 

    Probablemente use el @on_window_draw() predeterminado de @on_window_draw() que todos los demás están usando y está bien, pero a la larga lo encuentro lento, feo y no práctico. Quieres hacer progtwigción orientada a objetos … ¿verdad?
    Así se llama, lo llamo código legible que te gusta ver todo el día. RCTYLTWADL para abreviar.

    Para hacer esto, necesitaremos crear una class que imite el comportamiento de Pyglet y llamar a sus funciones subsiguientes en orden y sondear el controlador del evento, de lo contrario, ** se atascará, confía en mí. Hágalo un par de veces. Los cuellos son fáciles de crear.
    Pero ya tengo suficientes errores, aquí hay una clase main básica que puede usar que utiliza el manejo de eventos basados ​​en encuestas y, por lo tanto, limita la velocidad de actualización de su progtwigción en lugar del comportamiento incorporado en Pyglet.

     class main(pyglet.window.Window): def __init__ (self): super(main, self).__init__(800, 800, fullscreen = False) self.x, self.y = 0, 0 self.sprites = {} self.batches = {} self.subgroups = {} self.alive = 1 def on_draw(self): self.render() def on_close(self): self.alive = 0 def render(self): self.clear() for batch_name, batch in self.batches.items(): batch.draw() for sprite_name, sprite in self.sprites.items(): sprite._draw() self.flip() # This updates the screen, very much important. 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. # Basically it flushes the event pool that otherwise # fill up and block the buffers and hangs stuff. event = self.dispatch_events() x = main() x.run() 

    Ahora, nuevamente, esto es solo una clase main básica que no hace nada más que representar un fondo negro y cualquier cosa puesta en self.sprites y self.batches .

    Hacer nota ¿Llamamos a ._draw() en los sprites porque creamos nuestra propia clase de sprite antes? Sí, esa es la asombrosa clase de sprites base que puedes enganchar en tus propias cosas antes de draw() en cada sprite individual.

    De todos modos, todo esto se reduce a un par de cosas.

    1. Usa sprites cuando hagas juegos, tu vida será más fácil.
    2. Usa lotes, tu GPU te amará y los refrescos serán increíbles
    3. Usa clases y esas cosas, tus ojos y tu código mojo te amarán al final.

    Aquí hay un ejemplo completo de todas las piezas confundidas:

     import pyglet from pyglet.gl import * glEnable(GL_BLEND) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) glEnable(GL_LINE_SMOOTH) glHint(GL_LINE_SMOOTH_HINT, GL_DONT_CARE) pyglet.clock.set_fps_limit(60) class baseSprite(pyglet.sprite.Sprite): def __init__(self, texture, x, y, batch, subgroup): self.texture = texture super(baseSprite, self).__init__(self.texture, batch=batch, group=subgroup) self.x = x self.y = y def move(self, x, y): """ This function is just to show you how you could improve your base class even further """ self.x += x self.y += y def _draw(self): """ Normally we call _draw() instead of .draw() on sprites because _draw() will contains so much more than simply drawing the object, it might check for interactions or update inline data (and most likely positioning objects). """ self.draw() class main(pyglet.window.Window): def __init__ (self): super(main, self).__init__(800, 800, fullscreen = False) self.x, self.y = 0, 0 self.sprites = {} self.batches = {} self.subgroups = {} self._handles = {} self.batches['main'] = pyglet.graphics.Batch() self.subgroups['base'] = pyglet.graphics.OrderedGroup(0) monster_image = pyglet.image.load('./roar.png') for i in range(100): self._handles['monster'+str(i)] = baseSprite(monster_image, randint(0, 50), randint(0, 50), self.batches['main'], self.subgroups['base']) # Note: We put the sprites in `_handles` because they will be rendered via # the `self.batches['main']` batch, and placing them in `self.sprites` will render # them twice. But we need to keep the handle so we can use `.move` and stuff # on the items later on in the game making process ;) self.alive = 1 def on_draw(self): self.render() def on_close(self): self.alive = 0 def render(self): self.clear() for batch_name, batch in self.batches.items(): batch.draw() for sprite_name, sprite in self.sprites.items(): sprite._draw() self.flip() # This updates the screen, very much important. 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. # Basically it flushes the event pool that otherwise # fill up and block the buffers and hangs stuff. event = self.dispatch_events() # Fun fact: # If you want to limit your FPS, this is where you do it # For a good example check out this SO link: # http://stackoverflow.com/questions/16548833/pyglet-not-running-properly-on-amd-hd4250/16548990#16548990 x = main() x.run() 

    Algunas cosas adicionales, agregué opciones de GL que usualmente hacen algunas cosas beneficiosas para ti. También agregué un limitador de FPS con el que puedes jugar y jugar.

    Editar:

    Actualizaciones por lotes

    Dado que el objeto sprite se puede usar para hacer representaciones masivas de una sola vez enviándolas todas a la tarjeta gráfica, de manera similar, querría hacer actualizaciones por lotes. Por ejemplo, si desea actualizar la posición de cada objeto, el color o lo que sea.

    Aquí es donde la progtwigción inteligente entra en juego en lugar de pequeñas herramientas ingeniosas.
    Mira, todo lo que sea relevante en la progtwigción ... si quieres que sea.

    Suponga que tiene (en la parte superior de su código) una variable llamada:

     global_settings = {'player position' : (50, 50)} # The player is at X cord 50 and Y cord 50. 

    En tu sprite base simplemente puedes hacer lo siguiente:

     class baseSprite(pyglet.sprite.Sprite): def __init__(self, texture, x, y, batch, subgroup): self.texture = texture super(baseSprite, self).__init__(self.texture, batch=batch, group=subgroup) self.x = x + global_settings['player position'][0]#X self.y = y + global_settings['player position'][1]#Y 

    Tenga en cuenta que tendría que ajustar el draw() (note, no _draw() ya que la representación por _draw llamará al draw y no a _draw ) para cumplir y actualizar las actualizaciones de posición por secuencia de rendering. Eso o podría crear una nueva clase que hereda baseSprite y tenga solo esos tipos de sprite actualizados:

     class monster(baseSprite): def __init__(self, monster_image, main_batch, background): super(monster, self).__init__(imgage=monster_image, x=0, y=0, batch=main_batch, group=background) def update(self): self.x = x + global_settings['player position'][0]#X self.y = y + global_settings['player position'][1]#Y 

    Y por lo tanto, solo llame a .update() en clases de tipo monster / sprites.
    Es un poco difícil conseguirlo de manera óptima y hay maneras de resolverlo y seguir utilizando el renderizado por lotes, pero en algún punto de estas líneas probablemente sea un buen comienzo.

    NOTA IMPORTANTE : acabo de escribir mucho de esto desde la parte superior de mi cabeza (no es la primera vez que escribo una clase de GUI en Pyglet) y por alguna razón, esta instancia de Nix no encuentra mi servidor X ... Así que no puedo probar el código.

    Le haré una prueba en una hora cuando salga del trabajo, pero esto le da una idea general de qué hacer y en qué pensar cuando hace juegos en Pyglet. Recuerda, diviértete mientras lo haces o te rindes antes de que comiences, porque los juegos toman tiempo para hacer ^^

    Pew pew lazors y esas cosas, la mejor de las suertes!