Я надеюсь, что награда привлечет человека, который знает кое-что о внутренней работе PyGame (я пытался посмотреть исходный код... там много всего) и может сказать мне, действительно ли это связано с присутствием threading.py, или, если есть какая-то практика, я могу избежать вообще, чтобы предотвратить это в других проектах PyGame.
Я создал оболочку для объектов, которые взрываются при вызове 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
Он получает координаты x и y объекта, затем создает новый экземпляр Explosion
в этих координатах, добавляет его к allqueue
, который я использую, чтобы убедиться, что во время игры все update
d, и, наконец, возвращает функцию, которую он обертывает - в этом случае , obj.kill
. obj.kill()
— это метод pygame.sprite.Sprite
, который удаляет объект из всех экземпляров sprite.Group
, которым он принадлежит.
Затем я бы просто обернул метод следующим образом во время создания экземпляра Enemy
.
newEnemy = Enemy()
##other code to add new AI, point values…eventually, the following happens:
newEnemy.kill = make_explosion(newEnemy, newEnemy.kill)
Когда я запускал игру, взрывы появлялись в случайных местах, а не рядом с реальными координатами x и y объекта. Как ни странно, это даже не казалось, что это происходило в месте происхождения объекта (x, y) или даже на путях, по которым они шли во время своего краткого существования на экране, или рядом с ними (я довольно хорошо играю в свою собственную игру, чтобы не хвастаться) , поэтому я чувствовал, что должен исключить это из того, что x и y были назначены во время переноса метода.
Немного бесцельно я повозился и изменил код на этот:
def make_explosion(obj, func):
def inner(*args, **kwargs):
allqueue.add(Explosion(obj.x, obj.y))
return func(*args, **kwargs)
return inner
Это изменение заставило его работать «как задумано» — когда вызывается метод объекта self.kill()
, взрыв появляется в правильных координатах.
Чего я не понимаю, так это почему это работает! Особенно с учетом документации PyGame, в которой говорится, что kill()
не обязательно удаляет объект; он просто удаляет его из всех групп, к которым он принадлежит.
из https://www.pygame.org/docs/ref/sprite.html#pygame.sprite.Sprite.kill --
убийство()
удалить спрайт из всех групп kill() -> None
Спрайт удаляется из всех содержащих его групп. Это ничего не изменит в состоянии Sprite. Можно продолжать использовать спрайт после вызова этого метода, в том числе добавлять его в группы.
Так что, хотя я случайно нашел решение своей проблемы, я на самом деле вообще этого не понимаю. Почему оно так себя ведет?
EDIT: Основываясь на некоторых ранних комментариях, я пытался воспроизвести подобное состояние без использования библиотеки PyGame, и мне это не удалось.
Пример номинала:
>>> 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-
Итак, я сразу заметил, что whereat()
вызывается перед изменением координат под Stick.go()
, поэтому он использует x
и y
непосредственно перед приращением, но это нормально; Я мог бы легко изменить оболочку, чтобы дождаться вызова go()
. Здесь этой проблемы нет, как в моем проекте PyGame; взрывы даже не были "смежными врагами", они появлялись бы во всевозможных случайных местах, а не в предыдущих координатах (x, y) спрайта (если бы они были, я мог бы даже не заметить проблему!).
ОБНОВЛЕНИЕ: после того, как комментарий пользователя заставил меня задуматься, я немного покопался в Интернете, запустил cProfile
в рассматриваемом проекте и, о чудо, PyGame определенно использует threading.py
. К сожалению, тот же Интернет не был достаточно хорош, чтобы точно сказать мне, как PyGame использует threading.py
, поэтому, видимо, мне нужно прочитать, как вообще работают потоки. Ой. :)
Немного прочитав исходный код PyGame (bleh), я нашел один модуль, который, кажется, решает, когда и почему PyGame порождает поток. Из того немногого, что я знаю о потоках, это может вызвать что-то вроде состояния гонки; x не определен «вовремя» для выполнения оболочки, поэтому он (объект Explosion
) оказывается в неправильном месте. Я видел другие ошибки, появляющиеся в другом проекте, который немного сложнее в математике; это больше похоже на дампы ядра, но обычно они связаны с pulse
невозможностью получить событие, или тайм-аутом ожидания события, или чем-то подобным (у меня есть очень длинная трассировка стека на случай, если у кого-то возникнут проблемы спать).
В конце концов, я подозреваю, что этого можно избежать, придерживаясь определенных методов, но я не знаю, что эти методы на самом деле будут быть, и я не знаю, как предотвратить PyGame от арбитража того, какие процессы получают свои собственные потоки и когда это хуже, чем НЕ делать этого. Если есть единая теория для всего этого, я бы очень хотел об этом узнать.