python и pygame — что произошло во время выполнения этой оболочки?

Я надеюсь, что награда привлечет человека, который знает кое-что о внутренней работе 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, который я использую, чтобы убедиться, что во время игры все updated, и, наконец, возвращает функцию, которую он обертывает - в этом случае , 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 от арбитража того, какие процессы получают свои собственные потоки и когда это хуже, чем НЕ делать этого. Если есть единая теория для всего этого, я бы очень хотел об этом узнать.


person Community    schedule 04.03.2014    source источник
comment
Вы ведь не используете многопоточность/многопроцессорность в своей игре?   -  person Silas Ray    schedule 04.03.2014
comment
Конечно не намеренно. Я думаю, что PyGame выполняет некоторую многопоточность по собственному желанию, без прямого вызова пользователем какой-либо из них, но я не уверен.   -  person    schedule 04.03.2014
comment
Я так не думаю, потому что это затруднит создание игр с использованием pygame на raspberry pi, что, как многие знают, вполне возможно.   -  person KodyVanRy    schedule 08.03.2014
comment
Не могли бы вы создать небольшой рабочий пример, показывающий такое поведение? Опыт подсказывает мне, что кажущиеся случайными вещи, происходящие во фрагменте, часто воспроизводимо вызваны тем, что находится за пределами фрагмента. Для начала вы можете отслеживать значения x и y для вашего объекта и взрыва и посмотреть, имеют ли они смысл в их контексте.   -  person ddelemeny    schedule 08.03.2014


Ответы (2)


Я собираюсь предположить, что вы используете pygame.sprite.Group() для группировки спрайтов, а затем отображаете их на экране. Поправьте меня, если я ошибаюсь в этом. Затем вы создаете коллизию, которая создается с использованием атрибутов спрайта, но затем «уничтожает» спрайт, а это означает, что любые группы, которые вы создали с помощью pygame.sprite.Group, которые затем содержат этот спрайт, больше не будут содержать этот спрайт. Теперь, учитывая, что вы поместили ссылку в свой вопрос, я предполагаю, что вы уже прочитали документы.

Итак, вот код, который вы разместили с комментариями о том, что каждый из них делает.

#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

Это означает, что он вызывает make_explosion, а затем вызывает kill для спрайта, который удаляет его из любой группы. Пожалуйста, скажите мне, если вам нужны какие-либо разъяснения по этому поводу или вам нужен лучший или более глубокий ответ. Или скажите мне, если у вас есть какие-либо другие вещи, в которых вы нуждаетесь.

person KodyVanRy    schedule 07.03.2014
comment
Это не говорит мне, почему присвоение значений в разных строках приведет к тому, что оно будет работать иначе, чем в однострочном. Я совершенно уверен, что понимаю декоратор, разница между моим первым примером и моим вторым примером озадачивает. - person ; 08.03.2014
comment
Ну, это то, что я понял из вопроса, но я постараюсь понять это лучше, когда вернусь к своему компьютеру. - person KodyVanRy; 08.03.2014

К сожалению, я должен сказать, что в этом конкретном случае ошибка была моей.

В классе Explosion, код которого здесь не указан, я неправильно обновлял его значения self.x и self.y во время его создания или его метода self.update(). Изменение этого кода в сочетании с сокращением кода, который я сделал здесь, привело к исчезновению этой и других проблем.

Я знаю, что это немного неуклюже принимать свой собственный ответ и сразу закрывать вопрос, особенно после того, как проблема была на самом деле в другой части кода, особенно после объявления вознаграждения. Мои извинения.

person Community    schedule 23.03.2014