manualmente - python y pygame: ¿qué sucedió durante la ejecución de este envoltorio?
instalar numpy en python (2)
Voy a suponer que usa pygame.sprite.Group()
para agrupar sprites y luego renderizarlos en la pantalla. Corrígeme si me equivoco en eso. Luego creas una colisión que crea usando los atributos del sprite, pero luego ''destruye'' el sprite, lo que significa que cualquier grupo que hayas creado usando pygame.sprite.Group
que contenga ese sprite ya no contendrá ese sprite. Ahora que considera que ha insertado un enlace en su pregunta, supongo que ya ha leído los documentos.
Así que aquí está el código que publicaste con comentarios de lo que hace cada uno.
#so here is your wrapper which will call make explosion before it kills the sprite.
def make_explosion(obj, func):
#you call kill and so here it goes.
def inner(*args, **kwargs):
#you add the explosion to your queue
allqueue.add(Explosion(obj.x, obj.y)) #create an instance of explosion inside.
# return to the outside function `make_explosion`
return func(*args, **kwargs)
#you call inner second and then go to the inner.
return inner
#after it returns inner kill is called
Entonces esto significa que llama a make_explosion, y luego llama a kill en el sprite que lo elimina de cualquier grupo. Dígame si necesita alguna aclaración sobre esto o si necesita una respuesta mejor o más profunda. O dime si tienes otras cosas que necesitas.
Espero que la recompensa atraiga a un individuo que sabe una cosa o dos sobre el funcionamiento interno de PyGame (traté de mirar el código fuente ... hay muchos) y me puede decir si esto realmente está relacionado con la presencia. de threading.py, o si hay algo de práctica que puedo evitar en general para evitar que esto suceda en otros proyectos de PyGame.
obj.kill()
un contenedor para objetos que explotan cuando se llama a obj.kill()
.
def make_explosion(obj, func):
def inner(*args, **kwargs):
newX, newY = obj.x, obj.y
newBoom = Explosion(newX, newY)
allqueue.add(newBoom) #an instance of pygame.sprite.Group
return func(*args, **kwargs)
return inner
Obtiene las coordenadas xey del objeto, luego crea una nueva instancia de Explosion
en esas coordenadas, lo agrega a la allqueue
que uso para asegurarse de que todo esté update
d durante el juego, y finalmente devuelve la función que está envolviendo - en este caso, obj.kill
. obj.kill()
es un método pygame.sprite.Sprite
que elimina el objeto de todas las instancias de sprite.Group
que pertenece.
Simplemente, envolvería el método de la siguiente manera, durante la creación de una instancia de Enemy
.
newEnemy = Enemy()
##other code to add new AI, point values…eventually, the following happens:
newEnemy.kill = make_explosion(newEnemy, newEnemy.kill)
Cuando ejecuté el juego, las explosiones aparecieron en lugares aleatorios, no cerca de las coordenadas reales xey del objeto. Anecdóticamente, ni siquiera parecía que estaba ocurriendo en el origen del objeto (x, y) o incluso en o cerca de los caminos que recorrieron durante su breve existencia en la pantalla (soy bastante bueno en mi propio juego, no para presumir) , así que sentí que tenía que descartar que se asignaran xey en el momento en que se envolvió el método.
Un poco sin rumbo fijo, me volví y cambié el código a esto:
def make_explosion(obj, func):
def inner(*args, **kwargs):
allqueue.add(Explosion(obj.x, obj.y))
return func(*args, **kwargs)
return inner
Este cambio hizo que funcionara "según lo previsto": cuando se llama al método self.kill()
del objeto, la explosión aparece en las coordenadas correctas.
Lo que no entiendo es por qué esto funciona! Especialmente teniendo en cuenta la documentación de PyGame, que establece que kill()
no elimina necesariamente el objeto; simplemente lo elimina de todos los grupos a los que pertenece.
de https://www.pygame.org/docs/ref/sprite.html#pygame.sprite.Sprite.kill -
matar()
eliminar el Sprite de todos los grupos kill () -> None
El Sprite se elimina de todos los Grupos que lo contienen. Esto no cambiará nada sobre el estado del Sprite. Es posible continuar usando el Sprite después de que se haya llamado a este método, incluido agregarlo a Grupos.
Entonces, aunque accidentalmente la solución a mi problema, en realidad no lo entiendo en absoluto. ¿Por qué se comporta de esta manera?
EDITAR: basado en algunos comentarios iniciales, he tratado de reproducir una condición similar sin el uso de la biblioteca PyGame, y no puedo hacerlo.
Por ejemplo:
>>> class A(object):
... def __init__(self, x, y):
... self.x = x
... self.y = y
... def go(self):
... self.x += 1
... self.y += 2
... print "-go-"
... def stop(self):
... print "-stop-"
... print "(%d, %d)" % (self.x, self.y)
... def update(self):
... self.go()
... if self.x + self.y > 200:
... self.stop()
>>> Stick = A(15, 15)
>>> Stick.go()
-go-
>>> Stick.update()
-go-
>>> Stick.x
17
>>> Stick.y
19
>>> def whereat(x, y):
... print "I find myself at (%d, %d)." % (x, y)
...
>>> def wrap(obj, func):
... def inner(*args, **kwargs):
... newX, newY = obj.x, obj.y
... whereat(newX, newY)
... return func(*args, **kwargs)
... return inner
...
>>> Stick.update = wrap(Stick, Stick.update)
>>> Stick.update()
I find myself at (17, 19).
-go-
>>> Stick.update()
I find myself at (18, 21).
-go-
>>> Stick.update()
I find myself at (19, 23).
-go-
Entonces, de inmediato noto que whereat()
se está llamando antes del cambio en las coordenadas bajo Stick.go()
así que está usando y
justo antes del incremento, pero está bien; Podría cambiar fácilmente el contenedor para esperar hasta que se llame a go()
. El problema no está presente aquí como lo estaba en mi proyecto PyGame; las explosiones ni siquiera eran "enemigas adyacentes", aparecían en todo tipo de lugares aleatorios, no en las coordenadas previas (x, y) del sprite (si lo hubiera hecho, ¡es posible que ni siquiera haya notado el problema!).
ACTUALIZACIÓN: Después de que un comentario del usuario me hizo preguntarme, busqué un poco de Internet, ejecuté cProfile
en el proyecto en cuestión, y he aquí que PyGame definitivamente usa threading.py
. Desafortunadamente, la misma Internet no fue lo suficientemente buena como para decirme exactamente cómo PyGame usa threading.py
, así que aparentemente necesito leer cómo funcionan los hilos. Oi. :)
Después de leer un poco el código fuente de PyGame (bleh) encontré un módulo que parece arbitrar cuándo y por qué PyGame engendra un hilo. Por lo poco que sé sobre los hilos, esto puede causar algo así como una condición de carrera; x no está definido ''en el tiempo'' para que se ejecute el wrapper, por lo que (el objeto Explosion
) termina en la ubicación incorrecta. He estado viendo otros errores apareciendo en un proyecto diferente que es un poco más pesado en las matemáticas; estos son más parecidos a los vertederos de núcleos, pero normalmente implican que el pulse
no puede tener un evento, o que se agota el tiempo de espera de un evento, o algo similar (tengo una v. larga pila de fichas tiradas en caso de que alguien tenga problemas dormido).
Al final del día, sospecho que esto es algo que puedo evitar apegándome a ciertas prácticas, pero no sé cuáles serían esas prácticas en realidad, y no sé cómo evitar que PyGame arbitre qué procesos obtienen su propios hilos y cuando eso es peor que NO hacerlo. Si hay una teoría unificada para todo esto, me encantaría saber al respecto.
Desafortunadamente, tengo que decir que para este problema en particular, el error fue mío.
En la clase Explosion
, cuyo código no se incluyó aquí, no estaba actualizando correctamente sus valores self.x
y self.y
durante su creación o su método self.update()
. Cambiar ese código, junto con el acortamiento del código que he mostrado aquí, hizo que ese problema y otros desaparecieran.
Sé que es un poco negligente aceptar tu propia respuesta y cerrar la pregunta sumariamente, especialmente después de que el problema estuviera realmente en una parte diferente del código, especialmente después de poner una recompensa. Mis disculpas.