Creating animations

I have been working on the animations of the game, in order to support something a little bit more complex than what currently can be done. Originally I wasn’t supposed to have any kinds of animations in the game, but then I added tiny damage counters that float away after a character gets hit, poisoned or healed. They looked so nice that I wanted to add more, but that requires a little work under the hood.

There is Model object, that represents everything in the game world. User interface then queries the model when it wants to know what to display on screen. Animations don’t really belong to the model, but luckily I can use Events to trigger them. Every time something happens in the game world, an Event (or multiple events) are raised that describe what just happened. These are relayed to the user interface, who can decide how to show them.

Previously I was inspecting content of those events in the user interface and creating animations for counters when needed. That was one long and unwieldy piece of if – statements. Creating animations sounded like something a single class should handle, so I broke the functionality to a different part and replaced the old if-forest with following:

    def receive_event(self, event):
        """
        Receive event from model
        """
        anim = self.animation_factory.create_animation(event)
        anim.trigger(self)

So, when ever UI gets an event, it will delegate creation of the corresponding animation to a separate factory and then just trigger the resulting animation. Since the AnimationFactory is guaranteed to return an animation object, I don’t even have to check for None here.

Creating animations

Factory itself is pretty simple.

class AnimationFactory():

    def __init__(self):
        """
        Default constructor
        """
        self.animations = {
            'attack hit': AttackHitAnimation,
            'damage triggered': DamageTriggeredAnimation,
            ...
            }

    def create_animation(self, event):
        if event.event_type in self.animations:
            return self.animations[event.event_type](event)
        else:
            return Animation(event)

Initializer creates a dictionary that has a Type for each animation I want to be able to play. create_animation method then just searches the dictionary based on the event_type, creates it and returns it. If there is no match, a generic (empty) animation is created and returned. This simplifies the code in the client side, where there is no need to check for None.

When trigger method of an animation is called, it will create animation using Qt’s animation framework and start it. Qt will then take care of transitions and timings, without Python code having to worry about it at all. There are some house keeping still involved though, like keeping a reference to running animations (otherwise they wouldn’t be running for very long) and removing them after animation has finished playing. This all is complicated by the fact that the animated objects might be removed from the screen at any given moment (for example, by player moving to a different level).

System is now mostly working as intended. I might have to add some way of pausing the game while a long running animation is running. Otherwise the game model and on-screen animations would be out of sync. With short animations it does not matter or the player is not able to notice it, the long running ones are the problematic ones. It is also nice that since the animations are handled completely on the user interface side, I can have different kinds of implementations of them for Qt and Curses interfaces.

Herculeum 0.9 released

Yesterday I released Herculeum 0.9. Release log is short:

  • curses interface
  • bug: Effects with None as duration or frequency cause crash when triggered
  • switch to Python 3

Most of the changes are under the hood. The game is now available at PyPi, which makes installing as easy as:

pip install decorator
pip install hy
pip install herculeum

AI routines of rats and fire beetles have been rewritten. They now choose an area to patrol and walk around there, pausing to wait a bit now and then. Rats prefer to stay close to walls of the room, while fire beetles stay in the middle of the room. A large room with couple of rats and a fire beetle or two is now much more interesting to sneak through.

AI routines have been written with Hy, hence the dependency to it. The plan is to use Hy for all of the AI routines in the future, which means a rewrite for skeleton warriors and the old Crimson Jaw.

Curses interface is up and working (it’s actually the interface I’m mostly using when developing the game nowdays). PyQt interface will continue to be supported though, it is not going anywhere.

Magic didn’t make into this release, although part of it is already working. I’m trying to finish it for the next release, which hopefully won’t take as long as the previous one to make.

Here’s a screenshot of curses interface showing rats and beetles patrolling the room at top right.

herculeum 0.9 - rats and beetles

Curses (the good one)

From the beginning, the goal of Herculeum was to make an easy to play, graphical roguelike. Couple days ago I listened an episode of Roguelike Radio, which talked about designing games for visually impaired. It was really fascinating to listen a blind person play roguelike with a screen reader.

This is not really possible with the current implementation of Qt user interface, so I decided to try my skills with curses interface. There are bindings for Python and they are even shipped with the standard distribution (for *nix only). Windows was easy enough with precompiled distribution.

After couple of evenings, this is what I came up with:

curses interface

Pretty standard looking roguelike, but working. Walking, fighting and picking up items work. Inventory needs to be implemented still.

I learned that writing two user interfaces for a single game is easy way to spot some of the code that does not belong into user interface. Feeling urge to copy/paste code between implementations usually meant that I should move it to somewhere else completely. And every serendipitous moment was moment when I felt that there was something particular nice in the design.