python graphics rendering pyglet

python - Renderización de imagen Pyglet



graphics rendering (1)

Estoy trabajando en una especie de clon de Minecraft 2D para mi primer proyecto de Pyglet en profundidad y me encontré con un problema. Cada vez que tengo un número decente de bloques en la pantalla, la velocidad de fotogramas disminuye drásticamente.

Aquí está mi método de representación: utilizo un diccionario con la clave que es una tupla (que representa la coordenada para el bloque) y el elemento es 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 desplazamientos de coordenadas para el desplazamiento de la pantalla

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


Voy a hacer mi mejor esfuerzo para explicar por qué y cómo optimizar su código sin saber realmente cómo se ve el código.

Asumiré que tienes algo parecido a:

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

Todo esto está muy bien, si quieres cargar una imagen estática con la que no quieras hacer mucho. Sin embargo, estás creando un juego y vas a utilizar infinidad de objetos y más sprites que solo un simple archivo de imagen.

Ahora es aquí donde los objetos compartidos, los lotes y los sprites son útiles. Primero, ingresa tu 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 muchísimo más rápido que .blit() por varias razones, pero .blit() por qué por ahora y solo nos quedaremos con las actualizaciones de velocidad .

Nuevamente, esto es solo un pequeño paso hacia exitosas tasas de fotogramas (aparte de tener hardware limitado de ... duh ).

De todos modos, vuelve a pew pew tu código con actualizaciones.
Ahora también desea agregar sprites a un lote para que pueda procesar MUCHAS cosas de una vez (leer: por lotes) en lugar de cargar cosas manualmente en la tarjeta gráfica. El objetivo de las tarjetas gráficas fue diseñado para ser capaz de manejar gigabits de rendimiento en cálculos en un avance increíblemente rápido en lugar de manejar múltiples E / S pequeñas .

Para hacer esto, necesita crear un contenedor por lotes. Y agrega "capas" a ella.
En realidad, 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) # ...

Seguiremos con uno por ahora, probablemente no necesites más para este propósito de aprendizaje.
Ok, entonces tienes tu lote, ¿y ahora qué? Bueno, ahora hacemos todo lo posible para ahogar ese infierno de tu tarjeta gráfica y ver si podemos incluso abrocharla bajo presión (en este proceso, ningún coche gráfico resultó dañado, y por favor no ahogues las cosas ...)

Oh, una cosa más, ¿recuerdas la nota sobre objetos compartidos? bueno, crearemos un objeto de imagen compartido aquí que empujemos dentro del 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 tiene 100 monstruos creados y agregados al lote main_batch en el background del subgrupo. Simple como un pastel.

Aquí está el truco, en lugar de llamar a self.blocks[key].blit() o .draw() , ahora podemos llamar main_batch.draw() y disparará 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 la larga si estás haciendo un juego. O en este caso, un motor de gráficos para tu juego. Lo que quiere hacer es ingresar a las Grandes Ligas y usar clases . Si se sorprendió antes, probablemente perderá sus mármoles de lo impresionante que se verá su código una vez que haya terminado con él.

Ok, entonces primero, si desea crear una clase base para sus objetos en la pantalla, baseSprite a baseSprite .
Ahora hay algunas torceduras y cosas que necesitas para trabajar con Pyglet, por ejemplo, cuando Sprite objetos de Sprite intentando establecer una image self.texture todo tipo de fallas imprecisas y errores al trabajar con cosas, así que estableceremos self.texture directamente, que es básicamente lo mismo, pero nos conectamos con las variables de las bibliotecas de pyglet; D pew pew jeje.

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 esa 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()

Cómo, probablemente use el @on_window_draw() predeterminado de @on_window_draw() que todos los demás están usando y está bien, pero me parece lento, feo y simplemente no práctico en el largo plazo. Quieres hacer Programación Orientada a Objetos ... ¿Verdad?
Así es como se llama, lo llamo código legible que le 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 de eventos. De lo contrario, sh ** se atascará, créeme. Hazlo un par de veces y embotella los cuellos son fáciles de crear.
Pero bastantes de mis errores, aquí hay una clase main básica que puede usar que utiliza el manejo de eventos basado en encuestas y, por lo tanto, limita la frecuencia de actualización a su programación en lugar de un comportamiento integrado 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, de nuevo, esta es solo una clase main básica que no hace otra cosa que representar un fondo negro y cualquier elemento puesto en self.sprites y self.batches .

¡Nota ! llamamos ._draw() en los sprites porque hemos creado nuestra propia clase de sprites anteriormente? Sí, esa es la increíble clase base de sprites que puedes enganchar en tus propias cosas antes de draw() en cada sprite individual.

Anywho, Todo esto se reduce a un par de cosas.

  1. Usa sprites al hacer 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 código mojo te amarán al final.

Aquí hay un ejemplo completamente funcional de todas las piezas intrigadas:

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://.com/questions/16548833/pyglet-not-running-properly-on-amd-hd4250/16548990#16548990 x = main() x.run()

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

Editar:

Actualizaciones por lotes

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

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

Supongamos 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 debe ajustar un poco el draw() (note, no _draw() ya que la representación por _draw llamará al draw y no _draw ) para cumplir y actualizar las actualizaciones de posición por secuencia de renderizado. Eso o podrías crear una nueva clase que herede baseSprite y solo tengas 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 llama a .update() en clases / sprites de tipo monster .
Es un poco complicado hacerlo óptimo y hay formas de resolverlo y aún usar renderizado por lotes, pero en algún punto a lo largo de estas líneas es probablemente un buen comienzo.



NOTA IMPORTANTE: He escrito mucho de esto desde lo más alto 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 .. Entonces no puedo probar el código.

Lo haré una prueba en una hora cuando salga del trabajo, pero esto te da una idea general de qué hacer y qué pensar al hacer juegos en Pyglet. Recuerda, diviértete mientras lo haces o te rendirás antes de empezar, porque los juegos toman tiempo para hacer ^^

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