It's a dungeon exploration game. You are a niece of an extraordinarily eccentric mad scientist. You come in your blimp to visit him in his huge underground lab, when you notice the lab is devastated, all the specimen are running lose, the staff has been turned into mutant zombies and the robots try to exterminate everything in sight. Somebody needs to stop this, by getting to the lowest levels of the lab and aborting whatever weird experiment has gone awry. On the way down you collect nifty lab equipment and parts for your portable doomsday machine.
The game is not playable yet, although you can walk around a predefined map and hit some monsters. As it's a hobby on the side, it might take a while to finish.
Not much of a progress: I did some cleanup of the sources, moved the control from player character AI routines to the game's main loop, added several monster and item graphics… The game still misses a random level generator and a menu system (with inventory, skills, stats). And a game over screen, obviously.
Finally the problem with walls is solved! Took me several months of not thinking about it. I just remembered about it today and did it. Took less work than anticipated because of two dirty tricks:
Because of another pause in development (mostly because of another intrusion of real life, but also because of distraction with Dandelion) I posted another release at http://pygame.org, just to wrap it up. Soon after that I got an e-mail with a question about the license of this game, from people wanting to work on it as a school project. Amazing!
Anyways, the code of Junk Lab is now licensed under GPL v2 or later and the artwork is under Creative Commons BY-SA license. Enjoy!
After another period of cleaning up the code I decided to put off making the inventory menu, and work a little on the map instead.
It's going to be an exploration game, so the map is important. I want to make it consisting of rooms, pretty much how it looks so far, in the good old Rogue style. However, there is no exploration if you can see the whole map at once – you have to discover the map by visiting it. It means that initially the screen should be empty, and the rooms appear only ofter you have visited them at least once. To simplify things, once a room appears, it stays visible – the player character is supposed to have hearing and memory good enough for this.
Of course, just drawing the rooms suddenly as you enter them would be jarring in a fully animated game – I decided that I will make them fade in gradually. To do that, I don't draw the tiles on the background layer directly, but instead create "tile sprites", that will gradually fade in and, at the end of their lives, draw themselves on the background layer. This works pretty fine, assuming the rooms are not too big.
There are however several problems caused by the fact, that the floor tiles are larger than the space they represent – they have a bottom part, that is covered by the tiles being closer to the eye. This makes an ugly effect when the tiles are still translucent, and the background can be seen, and also makes some problems when the sprites die and draw themselves on the background – because they will overwrite the tiles that are already there, but closer to the eye. A temporary solution is just drawing a part of the tile if it's supposed to be covered, but I need to think of something better.
Another problem that I need to solve is that right now it's impossible to tell if a room just ends, or if it's just the end of your field of view – I will probably add some kind of an edge to the room tiles.
I might also decide to change the way the tiles are drawn altogether – use walls for "blocked" tiles, instead of the "chasm" I use now. However, the mockups I made so far don't really look right…
Wow, making your source code available to the public early pays off after all. I just got my first bug report for Junk Lab. Turns out that Python had a change of generator function syntax at transition from 2.4 to 2.5, and my code didn't work on Python 2.4 – I didn't even think to check it. A little investigation reveals that generator functions have been changed into simple coroutines in Python 2.5, and allow things like a yield statement (it's an expression now) without a value to "yield". Just adding None to all empty yields made it work better.
What did I use generator functions for in such a simple game? Well, I consider two uses, one of them is already implemented, the other I still think about. The implemented use is for animations: the sprites can have custom animations that will move the sprite around and change its image – for example, when a creature is walking around or an item is pushed. Initially, they were written something like this:
def jump(self): self.anim = self.anim_jump self.step = 0 def update(self): self.anim() def anim_jump(self): if self.step==0: self.image = self.frames[self.facing] elif self.step<=4: self.height += 1 elif self.step>6: self.heigth -= 1 self.step += 1
Of course, self.step has to be zeroed before switching to that animation. The animation function itself is called every frame, just before displaying the sprite. Using generator functions I can write it in a much simpler way:
def jump(self): self.anim = self.anim_jump() def update(self): # We only do one step here, not whole iterator. if self.anim: try: self.anim.next() except StopIteration: self.anim = None def anim_jump(self): self.image = self.frames[self.facing] yield None for step in range(4): self.height += 1 yield None for step in range(2): yield None for step in range(4): self.height -= 1 yield None
You can see that I don't have to keep and reset any internal state anymore, and the animation function actually describes what happens, in linear form, without any need for chained conditional statements and step counting. The update function looks more complicated because it now does what other functions around it used to do. Neat, eh?
Now, the second use is similar, but I fear that the simple coroutines that Python offers might not be enough. I want to use them to program monster behavior. Just like in ZZT, if you know what I mean – just program the steps the creature should perform, in the order it should od them, without implementing a complicated state machine. There are however two problems with this approach: first, I must yield in the generator function directly, not in any function it calls. This means I cannot make behavior templates. Second problem is that I cannot save a "running" generator iterator to disk – they cannot be pickled or anything. So, I can only save the game when the state of all the monsters is known – for example when changing levels. This could be ok with a different game. This particular game was however meant to be interrupted at any moment and then resumed easily. So I cannot use that.
I'm still torn between using a "traditional" behavior routines, for example checking the surroundings every step and deciding on a single next step only, and the cool "coroutine" approach. I consider using greenlets, a micro-threading library for CPython – I can implement a generator function that can yield from within called functions with them, and I've heard they can be pickled. But it's an additional C library, that I would have to either require or bundle with my game.
I think I will stay with the simple behavior functions I have for now.
Yet another burst of activity in an old project – the DungeonWeb mock up. This time I have a lot of work done, looks like pygame is much better thought out than I assumed at first – it's fast and comfortable if you know it well enough and match your style to how it is built.
This is still just a demo, not a playable game, but it has quite a lot things working already. You will need Python and pygame installed. Things I got working so far:
I have dug out the DungeonWeb project and working on it again a little – I'm still distracted pretty much with all the issues of display, animation and user interface – my favorite areas – but I think there will be some progress after all. You can see the new graphics (again ) on the animated screen shot.
I'm still struggling with one design decision – can't seem to get this right. The map is tile-based, meaning that it is divided into small squares. Every square has certain kind of terrain (floor, wall, door, etc.) and can also hold some objects and creatures. This is an approach popular in many computer role-playing games. The problem appears when you add animation – in particular, the player character "walks" from square to square when you press keys, and that walking takes some time, but the discrete nature of the map means that it cannot really be "in between" the squares. What should the game do if the player presses a key when the animation is still in progress and the player character seems to be between squares? There are several possibilities:
Update: I finally decided on a partial solution: I buffer a single, last keystroke and discard any other input during the animation. In addition, keys that are kept pressed continuously are handled specially. This way the animations are all smooth, the interface feels reasonably responsive, yet the user doesn't suffer effects of key presses from 2 minutes ago. Increasing the movement speed (while keeping the actual animation frames the same) further improved the experience. I think I'm done with this part and can move on to the actual game.