If you don’t know the Open Pandora, then here’s a link:
I just received mine yesterday, and so far it seems to work which makes me quite happy. Now, it’s not a plug and play console, and I have a lot to learn in order to make things work.
However, I could easily install Apocalypse Bunny! The Pandora comes with python 2.6. Installing pygame was a one-liner:
sudo opkg install python-pygame
Then I just downloaded a tar.gz of apocalypse bunny and ran it. It runs out of the box ! Of course, the controls aren’t optimized, and it should be toggled to full screen. But it works.
With one drawback: it starts at 10–6 frames per seconds, and after about 20 seconds in the game (when foxes have appeared), it runs at 1 frame per second. This is way, waaaay below the interactivity threshold.
So… I should forget python when developing for the Pandora I think, or at least the way SDL is implemented/bound to python on Pandora. It’s way too slow. Now, what are the alternatives? Java or C#/mono? Sure they’re going to be faster than python but they still run the code in a virtual machine so I’ll run into the same performance problems later. I really, really, really don’t want to bother with C++. What do you guys think?
Working on Apocalypse Bunny was quite fun. The ambition was low, which makes the goal reachable and the progress quick. Also I got to touch many pieces of the software so there was no time to be bored. I even had to do some pixel art \o/.
Sure there’s no sound, and yes the world map doesn’t make sense, and of course the gameplay is limited. Don’t care.
It’s time to go back to Infiniworld, the big project. I learned some valuable lessons thanks to Apocalypse Bunny.
Let’s split our code in packages and modules as early as we can.
I know the zen of Python: “Flat is better than nested”, and I agree. Python is not java, and theses periods don’t come for free. The thing is that we do not need to have a flat code to have a flat interface. To the user, it does not matter that the constant infiniworld.NATURE_GRASS is actually a shortcut for infiniworld.models.tiles.NATURE_GRASS.
The more files we have, the less code they contain. The less code they contain, the less reasons we have to modify them. The less we modify, the less we break. It also helps us keeping our list of import statements short, which helps keeping track of dependencies.
I started by keeping all my modules in the root package but they grew very big. I got entangled in that mess and had to reorganize all the code in smaller pieces. That’s not a problem at all for Python, and not a problem for the user either if we make our __init__.py files properly, but that’s a problem for git or every version control system: I lost all my history. Git accepts that we move or rename a file, but it cannot handle the fact that one file became five or ten. There’s a big discontinuity in the tree now :(.
So if you know in advance that you will have to split your module, don’t write a module but write a package instead.
We need to make room for the special effects.
Until Apocalypse Bunny, all the EntityViews used to corresponding to an EntityModel. Apocalypse Bunny changed that with what I called “special effects”. The expanding circle drawn when we blast a psy-wave does NOT exist in the model, this is a purely visual artifact created only by the view. Same goes for the blood left on the floor when a creature dies, it’s only visual special effects.
A special effect has a position, a rendering method, a sprite, and listens to events like any other EntityView, but it does not have any corresponding entity in the model. It is so similar to an EntityView that I actually inherited the special effect classes from EntityView. I will reconsider the names here because that’s going to be confusing.
Because of the similarities, we want to process special effects like we process the views corresponding to real entities. So we want to put them in the same list/dictionary/set/group than the ‘real’ ones. But because of the difference, we need a special identifier that no real entity is using. I decided that since the entity models were having positive integers as identifiers, I would use negative integers for the special effects. And thanks to this tiny trick, I got all the special effect code working for free with absolutely no effort at all.
Disappearing entities break everything.
When a fox dies, or when a carrot is picked up, their entity disappears. That crashes the entire game. Imagine: the carrots are picked up when we collide with them. It means that the physics engine is in the middle of its iteration doing collision response when we pick up the carrot and it disappears: RuntimeError: dictionary changed size during iteration.
So you think: “okay, let’s not destroy the carrot entity now, instead let’s schedule it from destruction by posting a DestroyEntityRequest which will be processed when the physics has finished running”. Think again, because you end up with the following possibility: you touch the carrot, and your friend touch the carrot, so you both pick it up and then it’s scheduled for destruction twice. It may not crash (although I did ask the event manager to crash when something is unregistered twice) but it’s buggy: only one of you should get the carrot.
So we need: to remove the dead entity immediately, but also to keep it around, alive. At the same time. Schrödinger would have loved this.
Solution: add a boolean variable on our entity saying “I (don’t) exist”. Since the entity stays in memory the dictionaries don’t splode. And since we can tell that the entity doesn’t exist, we can skip it in the physics step. We must really get rid of the entity at some point though, so after setting this variable to False, we post a DestroyEntityRequest. Neat!
Now, the same is true for appearing entities, except that I do not see any reason to create new entities during a physics iteration. Yet. I’ll think about it tonight.
Not all entities are tangible.
Carrots are supposed to represent small things on the floor. They should not be obstacles, they should not be pushable. Sure they won’t block the bunny since the bunny picks them up when she touches them, but they should not be an obstacle for the foxes either. So we want an entity that detects collisions (so that they detect that a bunny touched them) but does not trigger any collision response code from the physics.
Easy: just add a “I’m (not) solid” boolean variable on the entity body and use it well. Neat, again!
I tried for fun to make the bunny non solid: she crosses the walls and everything, awesome! Now we can have ghosts \o/.
We need to pause the physics.
The physics engine should not run all the time: we may be on the title screen, or paused, or maybe there is not even a world yet. It was not easy to add a pause function to my game loop because of the way I wrote it, so a lot of code has changed in there. Surprisingly, the code got cleaner ô_O.
Partition space for entities.
My collision code for entity vs entity was brute force: test every entity against every other. This O(n2) algorithm is of course very poor, but I didn’t know how poor, so I chose not to optimize it early. Well, after having seen how the performance was crashing once I was reaching 50 entities (I’m talking about 5 FPS here), I HAD to optimize. I did not use any quadtree or cool stuff like that yet, I just cut my map in chunks of 8×8 tiles and query entities by chunks. 8 is a quite random number, I could probably reduce it to 4, 2 or maybe 1, but that creates more chunks containing nothing and therefore more overhead when updating or searching the chunk map. I need to test to find a good value. In any case, as soon as we don’t look at the entire map, we’re fine.
At least all I can think of right now.
The engine source files changed quite a lot ; there is no point in merging the two branches. I will branch out of Apocalypse Bunny and remove the code related to this specific game (it’s all in the bunny package!) to have my new codebase for infiniworld. I’ll be doing some refactoring in the next days. Then, new fun stuff!
You’ve waited for one week; now is your patience rewarded. Behold my first game, Apocalypse Bunny.
Misadventures of a bunny.
You play as a bunny in a post apocalyptic world. The only place where carrots still grow are in cemeteries because of their fertile soil. However, zombies grow in cemeteries too, especially zombie foxes. They are a bit slow and a bit dumb, but they are legion. You hop and run between your foes and try to gather all the carrots before too many zombie foxes hit you. Fortunately, the radioactive carrots give you strange abilities. With each carrot you eat, your super bunny mind can create a telekinetic shock wave that will repel and maybe even kill the zombie foxes.
Let’s see how you long you can survive ! Don’t become a zombie bunny.
How to download.
Here is a direct link to the zip archive: Apocalypse Bunny v1.0.1.
The Git users will find a new branch on the infiniworld repository called “apocalypse_bunny”, ready to be pulled.
How to play.
Deflate the zip archive or pull the git branch.
Run the solo.py script that’s in the src directory.
- [W][A][S][D] keys to move.
- [Space] key to blast a psy-wave.
- [M] key to take a screen shot (it’s close enough to the space key so that you don’t have to look for it too much).
- [Esc] key to quit.
If you’re using a French keyboard, then it’s ZQSD to move and [,] to take a screen shot. So it’s the position of the keys that matter, not what’s written on them.
Hey Taurus my friend, I have not merged your code for configuring the keys as you’ll notice that the code changed a LOT. On the good side, the new code makes configuring the keys easier. Little work is needed to adapt your code to the new codebase. I’ll take care of it :).
Survive as long as you can. Like with real life you cannot win, you can only delay the inevitable horrible end of your cute fluffy bunny. Who wouldn’t want to help her?
To survive, avoid the foxes (they bite you) and kill them with the psychic powers that the uranium-enriched carrots give you. Foxes appear every 3 seconds somewhere on the map (there’s a zombie counter on the screen). Carrots appear every 10 seconds.
Send me your screen shots!
We’ll see who survives the longest :D. Remember: pics or it didn’t happen.
If the game crashes, refuses to start, is horribly slow or jerky, maxes at 20 FPS or whatever, please tell me. I did spend time optimizing performances, and I tried to make sure the code was cross platform (Windows and Linux have totally different ways of measuring time even with python as a common language) but I don’t have that many computers to try the game.
No need to tell me the sound doesn’t work: I have not put sounds yet.
And if it works, tell me that too ! Along with a words telling what kind of operating system and CPU you use.
In the next post…
In the next post I’ll tell you what I learned when writing Apocalypse Bunny. Trying to make a complete working game out of the minimalistic game engine of Infiniworld took quite some effort, and I have a much better insight into what features the engine needs to support. Not much of the engine changed, which is relieving because it means I didn’t get too many things wrong. But many things should be added in. There shall be refactoring soon!
Until next time, folks, until next time.
Python 2.6 didn’t like the version 1.0.0 of the game: it complained about a syntax error. I corrected it and made it the version 1.0.1. The download link above is already updated. Thanks for the bug report, MilanFIN!
I spent quite some time in my previous article writing about the problem of integrating the equations of motions. I looked at three models: Euler, Verlet and RK4. I did not spend much time explaining them because I provided the reader with links to detailed articles about them. After reading this literature, I decided to give RK4 a try.
Copy-pasting the RK4 integrator.
This was much easier and faster than I anticipated. The RK4 integrator itself did not need any work as it is a well known problem and has well known solutions. I just had to copy paste twenty lines of code from the article Fourth order Runge-Kutta numerical integration, by Doswa (not sure it’s the name of the author, could also be the name of the site). That took me a couple of minutes, hop, done!
After that, I needed to feed the integrator with data to integrate. I created a new class named Particle. You can see it in the new module physics.py. A Particle represents a point in space with a mass. It has a position and a velocity in that space. Finally, it contains a set of the forces that are acting on it. A particle does not have a shape or a volume/area; I will introduce these elements later in another class when they will be needed for detecting and responding to collisions.
From the set of forces, and from its mass, the Particle is able to compute its own acceleration at a given moment in time. That’s the famous .
The forces can be anything really. I created two types of forces: a ConstantForce and a KineticFrictionForce. Forces in this framework are actually functions of several parameters. The KineticFrictionForce which simulates friction and slows down Particle has the expression : the faster you go, the stronger the friction. If you give a negative value to the friction coefficient , your particle will slow down. The ConstantForce is simpler, it does not use any of the parameters and always return the same thing when you compute it. That is used for making your entity walk for example.
All that takes 130 lines of code including comments and documentation. Physics engine are easy!
Running the physics.
From the EntityModel I removed the position information and put a Particle instead. The EntityModel declares two forces: the walk force and the friction force. When the player presses or releases the movement keys, the walk force is modified. For now, the friction force is never touched, although it will later when the Entity walks on ice.
The last step is to actually run these physics computations. This is done at the AreaModel level. The AreaModel is a self-contained space with all its landscapes and entities, so it has all the information it needs to calculate trajectories and resolve collisions. Then, I just ask the main game loop to post a RunPhysicsEvent every so often.
That’s it. Very straightforward, it works immediately. You’ve got to like this Model-View-Controller pattern, it really makes our lives easier.
Our implementation is not perfect though. There are no collision detections at all, that’s obvious. But there are other things that can be improved:
- We should introduce a speed threshold: if the speed of a particle is under that threshold then it is considered null. We are not doing that yet, and as a result, many EntityMovedEvent’s are posted even if the entity is moving at the speed of a picometer per century. The kinetic friction force slows particles down but never stops them completely.
- It is time to have a better time loop to: we do not need, and therefore we do not want, to update the physics 60 times per second. We chose RK4 because this algorithm is good with bigger time steps. We should have the physics running at something like 20 Hz. It is commonly said among game developers that in order for the player to feel that its order are instantaneously taken into account, the response to its action should come within one hundred milliseconds. At 20 Hz, we are twice faster than that so we are on the safe side. Running the physics more often is a waste of CPU.
Edited on Monday 15 aug 2011:
Oops, there was a LOL bug in v0.0.4 so I released a patched version: v0.0.4.a. I updated the link at the top of this post. The bug was that units were moving together, all of them following your keypresses. I forgot to tell them to only listen to the orders containing their identification number :p. Fixed now!
Hello hello !
As promised I worked on what I called “Event Management”, the bare bones of my lousy implementation of the Model-View-Controller pattern. It’s uploaded on GitHub :
The important module is evtman.py, “evtman” standing for “Event Management”. It contains three classes:
The classes Event and Listener are abstract, and are meant to be subclassed. The Event class looks a bit complicated, but that’s because it automatizes things in order to make its subclasses extremely easy to define. For example :
>>> class CharacterMovedEvent(Event): ... name = "Character Moved Event" ... attributes = ('character_id', 'position_from', 'position_to') ... >>> event = CharacterMovedEvent('Bunny', (0, 0), (32, 0)) >>> print event.character_id Bunny >>> print event.position_to (32, 0) >>> print repr(event) CharacterMovedEvent(character_id='Bunny', position_from=(0, 0), position_to=(32, 0)) >>> print str(event) Character Moved Event character_id = 'Bunny' position_from = (0, 0) position_to = (32, 0)
See ? Very easy. Now that I think of it, the name attribute should go away because it’s pretty useless; I put it there to have a clean human-readable name but the class name is enough. I made sure that repr would not lead to insanely long lines by capping at 50 characters per attribute. Very useful when events carry a complete dungeon map in a huge list/dictionary/tuple/whatever.
It is also very easy to subclass Listeners:
class CharacterView(Listener): def __init__(self, character_id): self._character_id = character_id self._x = self._y = 0 def onCharacterMovedEvent(self, event): if event.character_id == self._character_id: # 32 pixels per meter, and the display starts at # the bottom while my Y axis goes up. self._x = event.position_to * 32 self._y = 480 - event.position_to * 32
That’s it, you have your View. It should be a pygame Sprite, the zoom level (32) and the window size (480) should not be hardcoded here, but that’s not more complicated than that.
And to use it is very simple:
def example(): class CharacterMovedEvent(Event): attributes = ('character_id', 'position_from', 'position_to') class CharacterView(Listener): def __init__(self, character_id): self._character_id = character_id self._x = self._y = 0 def __str__(self): return "%s: pos = (%i, %i)" % (self._character_id, self._x, self._y) def onCharacterMovedEvent(self, event): if event.character_id == self._character_id: self._x = event.position_to * 32 self._y = 480 - event.position_to * 32 # bunny_view = CharacterView('bunny') hamster_view = CharacterView('hamster') event_manager = EventManager() event_manager.register(bunny_view) event_manager.register(hamster_view) # event = CharacterMovedEvent('bunny', (0, 0), (1, 2)) event_manager.post(event) event_manager.pump() # print bunny_view print hamster_view
And the result is:
bunny: pos = (32, 416) hamster: pos = (0, 0)
There is one thing missing, though: some Listeners may want to post events. For example, the CharacterModel is a Listener which should be able to post the CharacterMovedEvent. This is achieved by giving the event manager to the listener. Add this to the previous code:
class CharacterModel(Listener): def __init__(self, event_manager, character_id): Listener.__init__(self) self._event_manager = event_manager self._character_id = character_id self._x = self._y = 0 def moveTo(self, new_x, new_y): old_x = self._x old_y = self._y self._x = new_x self._y = new_y event = CharacterMovedEvent(self._character_id, (old_x, old_y), (new_x, new_y)) self._event_manager.post(event) hamster_model = CharacterModel(event_manager, 'hamster') event_manager.register(hamster_model) hamster_model.moveTo(3, 3) event_manager.pump() print hamster
hamster: pos = (96, 384)
In this example, the model doesn’t listen to anything, but in a real situation it would.
That’s all there is to it ! It just works.
Just don’t forget to unregister the Listeners you don’t use any longer:
Sure the event_manager keeps only Weak References to the Listeners, so they automatically disappear from its lists. But if they disappear right in the middle of an iteration, that may explode. If you see such an explosion, it means you forgot to properly unregister.
A last word: you can post as many events as you want. They will all be processed, in the order you posted them, when you call the pump method of the event manager. Some event handlers will post events, even register new Listener. That is perfectly okay, the event manager is happy with that, the pump will go on until the event queue is empty.
- I got rid of the ‘name’ variable of events. It was 100 % useless.
- I introduced a SingleListener class, which is for Listeners that are interested in ONE event manager only. And to be fair, that’s the case most of the time.
The time to code is near. I lay before you the tools we shall use to design and build our magnificent game.In short:
- Python : because it’s fast to code with it.
- pygame : because I need to display 2D graphics and handle mouse/keyboard events.
- eclipse : because I kind of got used to it by now even though you probably prefer pico/emacs/vi/notepad/idle.
- PyDev : because I want eclipse to speak Python, not Java.
- Git: for controlling versions.
- GitHub: for sharing the source with you guys and gals.
I wish people would agree on how to capitalize these words… pygame, Pygame, PyGame ? I have to look it up every time. You’d think programmers would become case-sensitive after a while.
Now, there are one gazillion ways of doing it, all the combinations of programming languages and libraries you can come up with. The choice is quickly made though: I’m going to code in Python. I want to code fast and efficiently even if that means sacrificing some performance. Python is open source, cross-platform (mostly), comes with many delicious libraries, and takes care of all the boring stuff to let me focus with the cool things. Yes, python is slow, and I would get so much better results in C ANSI. Except that I’ll spend four months displaying a pixel on a 64 bits Linux laptop, and then I’ll have to redo the same for a 32 bits Vista machine. No thanks. Plus I hate C ; I like Pascal more. Also we’re doing a 2D game, of the kind that ran smoothly on a Nintendo Game Boy, so I think our over-powered machines won’t have too much troubles running a few more layers of software if that makes things easy for me.
Python and its standard library are not enough though. I want something that is able to display sprites and poll the mouse and keyboard events. SDL is going to be enough for me so I’ll go for pygame. pygame is a relatively thin wrapper around SDL and comes with a nifty pair of classes: Rect and Sprite. If I want windows, buttons and all, I’ll have to code them myself.
I really don’t want to use anything more. I don’t want to track bugs in someone else’s code. I don’t want anybody to steal my main loop (Twisted, tk, I’m talking to you). I don’t want to make my game a nightmare to install. I just want to keep control of what I’m doing.
That being said, I must jump on my bike and say hello to my hamster I haven’t seen in two weeks !