PyWeek 4 post-mortem

2007-04-10 14:24:16

Another year, another PyWeek. In case you missed it, my entry was delta-v. Here's my thoughts on how it went, what I learned, what I'll do next time. Sorry for the longevity. For interest, my previous two PyWeek entries were Brains in 2004 [with another programmer and an artist] and Nelly's Rooftop Garden in 2006 [post-mortem ]. **Game Design** Like last year, I started with no design, or even ideas (for the theme that was voted in). I had committed myself to these goals though: * Making a 3D game instead of the usual 2D fare. Incidentally, this removed most of the advantage of using pyglet (since getting a GL context in pygame is not particularly different). * Creating a look that isn't cartoony, and gameplay that isn't "silly". These are commonly seen on pyweek entries (both of my previous ones), and in indie games in general. I think this look is a bit of a cop-out, because it's relatively easy to draw cartoon characters, have them move in silly ways and choose colours that don't go together. Of course, not being an artist I was basically dooming myself to disappointment here. * Making the game *hard*. Because I'm not much of a gamer, I find this tricky to judge. My previous efforts turned out to be far too easy for anyone who plays games even semi-regularly. Going 3D but lacking any character animation tools or library meant it had to be first-person. And because I lack imagination, that meant it had to be an FPS. I didn't figure out the game design until day 2: An orbital city has fallen into the clouds. It's your job to boost it back into orbit by activating various systems around the city. After each boost, the city is further from Earth, and so has less gravity, allowing you to reach more areas (this is both the theme element and primary novelty of the game). There are some extradimensional creatures which seek to hamper your efforts. The "city" very quickly was revised to be an orbiting space station or satellite, after thinking about how good a modeller I am not. I had some very specific influences in mind during the week: * ICO and Shadow of the Collossus. Both games have largely untold back-stories (nowhere in delta-v is the story revealed). The baddies are based on the small creatures from ICO: small, shadowy, difficult to focus on -- I think they're really scary, and tried to make my ones similar. The lack of a HUD (besides the crosshair, without which shooting with any accuracy is impossible): the use of motion-blur in place of a health meter was very deliberate, as was the placement of the weapon charge indicator on the weapon itself. * Unreal Tournament 2004 -- the game of choice in the graphics lab at uni. I've only recently started playing, and am quite the amateur compared to the guys that play regularly. I tried to recreate the feel and dynamics, especially of the low-gravity maps. The weapon is also a based on the "instagib" friendly-fire. **Implementation** You can read sketchy in-progress diary entries on the PyWeek site. The main point was that I tried to get gameplay in early. In both *Brains* and *Nelly* this was added only in the final hours. This didn't leave time for tweaking or experimenting, or even deciding whether or not the idea would work. I have a friend whose day job is writing mobile phone games, and he explained to me sometime last year how gameplay is implemented even before graphics: an action-oriented boxing game began life as basically a text adventure. While I didn't take things to that extreme (among other reasons, I hadn't completely figured out the gameplay when I started), I did have the majority of the triggers for winning/losing implemented by day three. Getting the complete game together though needed the whole level (station) to be designed and modelled first. It was finished on day 6, which is a good 24 hours better than last year, so I'm pleased with that, though that's an obivous thing to tackle more next time. I did unfortunately get bogged down rewriting existing, working code (the OBJ loader). This is a habit I'm breaking very slowly; I knew I was doing bad while I was doing it, but that was nearly balanced by some pretty good justifications (the existing loader had no support for named objects within the file, for drawing meshes efficiently outside of a display list, or for accessing the polygons and materials programmatically). [Incidentally, I was reading the latest on COLLADA last week and I'm excited: we need to base pyglet's 3D object model around it.] **How it works** For lack of a real level editor, I used Wings 3D to model the entire station in one file. The indoor sections are separate objects, allowing me to toggle the walls into locked/wireframe for easier editing. The player's starting position, baddie spawn points, particle emitter sources are all just cubes placed in the appropriate place. The triggers that the player walks over are two-sided faces co-planar with the floor but raised slightly above it. When the level is loaded, objects ("meshes" in delta-v source) are retrieved by name from the loaded OBJ file. They are compiled into display lists explicitly (objects that change material obviously cannot be compiled). When not compiled, meshes are drawn one material at a time with interleaved vertex arrays. This is not noticeably slower (by FPS counter) than using a display list (because Python is so freakin' slow compared to the graphics bus). euclid is used for vector math (I tried some matrix math too, but it didn't work... maybe a euclid bug, maybe not). I discovered some bugs in euclid that can be chalked up to it never being used before. More importantly, I discovered that its geometry classes are really useless. Who needs to find the closest distance between two abstract geometries? Why is there no method to determine the contact point(s) of a collision? Why can't transforms be applied to the geometries? I'm planning a big rethink of all this, including making it easier to add more geometries (delta-v includes rough Ellipsoid and ConvexPolygon classes, neither of which can be easily integrated into euclid without hacking the library). If euclid had included the functionality it should have, I probably would've saved two days of development (I'm not too quick with the maths). The player and baddies are represented by an ellipsoid for finding body collisions, and a line segment where you'd normally find your shin (for finding floor collisions). The environment is a set of ConvexPolygons, which can be approximately tested for collision with the ellipsoids (Google for the "bevelling" problem of why it's not exact). Player/baddie collisions are modelled as ellipsoid/ellipsoid. There's some bug here which you can see if you turn on collision visualisation (in, but it's not noticeable in game. Originally every ellipsoid was checked against every polygon every frame. This worked fine until I added more than two or three baddies, which killed performance. My quick-n-hacky solution was to bin the polygons into a sparse uniform grid (a dict with (x,y,z) coordinate keys). I think this is a really good Pythonic solution: dicts are fast and easy. In C, dicts would be very tricky, and I'd probably use a BSP tree or octree, both of which would be tricky and slow (or complicated to avoid the function recursion) in Python. The game has around 22,000 grid cells populated with an average of 3 polygons each. Grid cells are more than twice the radius of the player or baddie, so checks against more than one cell are relatively uncommon. (Note that the grid is static with the geometry: the player or baddies are not inserted into it). **Final thoughts** I'm waiting on feedback from gamers, to see if I got the FPS dynamics right, and how hard the game is. The game is easy to mod: all the gameplay is within "" and the OBJ files it specifies. Could delta-v become the Python equivalent of the Unreal engine? Probably not.. but hey, look! An FPS in Python! [Richard just pinged me: "dude, your game is teh cool, but haaard"] :-)
Your comment:
Name: Email: No spam: (what number comes after 2?)
[description](http://url) for links; lines are wrapped to 80 columns after link replacement; no other markup recognised.