PyQt and animating QGraphicsItem objects

Qt has really nice system for creating animations by using QPropertyAnimation objects. Essentially, you define start and end values for a property, duration and fire of the animation. Qt takes care of the rest. After animation completes, a signal is emitted. You can have multiple animations running at any given time and are free to your own stuff in the meantime.

Slight problem with this system is that QPropertyAnimation only works with objects that inherit QObject. QGraphicsItem does not do that and therefore is incompatible with it. Common solution to this problem in Qt side is to create own class and inherit both QObject and QGraphicsItem. PyQt does not seem to support this though, so I had to come up with something else.

class DamageCounter(QGraphicsSimpleTextItem):
    """
    Counter for showing damage

    .. versionadded:: 0.6
    """
    def __init__(self, damage, parent):
        """
        Default constructor
        """
        super(DamageCounter, self).__init__()

        self.setText(str(damage))
        self.setBrush(QColor('white'))

        self.adapter = DamageCounterAdapter(self, self)

class DamageCounterAdapter(QObject):
    """
    Adapter for damage counter

    .. versionadded:: 0.6
    """
    def __init__(self, parent, object_to_animate):
        """
        Default constructor
        """
        super(DamageCounterAdapter, self).__init__()
        self.object_to_animate = object_to_animate

    def __get_y_location(self):
        return self.object_to_animate.y()

    def __set_y_location(self, y):
        self.object_to_animate.setY(y)

    y_location = pyqtProperty(int, __get_y_location, __set_y_location)

DamageCounter inherits QGraphicsSimpleTextItem and can be placed on QGraphicsScene. It has DamageCounterAdapter attached to it, that is inherited from QObject. I defined only one property y_location in the adapter, mainly because I only need that currently. This property reflects state of the DamageCounter’s y-coordinate, it can be read and written.

Because Adapter inherits QObject, I can use QPropertyAnimation to modify it. Modifications are reflected in DamageCounter, thus animating it.

Creating a little damage counter that moves towards top of the screen when a creature gets hit is now pretty simple:

damage_counter = DamageCounter(damage = damage,
                               parent = self)
self.view.scene().addItem(damage_counter)
damage_counter.setPos(target.location[0] * 32 + 16,
                      target.location[1] * 32)

animation = QPropertyAnimation(damage_counter.adapter,
                               'y_location')
animation.setDuration(1500)
animation.setStartValue(target.location[1] * 32)
animation.setEndValue(target.location[1] * 32 - 32)
animation.finished.connect(self.remove_finished_animation)

self.animations.append(animation)

animation.start()

Here’s a screen shot of rat hitting the player.

Maybe not the most elegant solution, but it works. I’ll clean and wrap that into a function somewhere next.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s