Wednesday, April 7, 2010

How I Learned Python

Being primarily a C++ programmer, I have lived a hard life: All the rules, all the syntax, all the mean spirited compiler errors from the STL...

While I have dabbled in scripting languages in the past, I never really put too much time into learning one. I decided it was time to learn a scripting language in more detail, and I chose Python. The best way to learn a new language is to create a relatively complex program in it, so to accomplish this task, I set out to make a game in no more than 7 days using Python and PyGame (SDL wrapper). Being familiar with using SDL from C++, I felt confident this project would be a success, and it was. 4 days later, I had a new game, "Cloud Cover", and I knew a decent amount of Python. Here's how it happened.

Game Design

I had been toying with an idea for a game that involved the rain theme for quite some time. Originally, I wanted to develop the game for the iPhone and take advantage of the tilt-control. The idea at that point was to tilt the iPhone left and right to tilt platforms in order to fill up buckets with water in order to progress in the level. However, not particularly liking Objective-C or the hoops needed to jump through in order to develop for the iPhone, I nixed that idea.

After hearing the instrumental "2 Die 4" by 'John 5' and then going to sleep, I awoke with the game design in my head. It literally came to me in my sleep. In fact, I even feature the song in my game because it fits so well (and it should since it inspired the game!).

Essentially, the game design is as follows:
  • You play the part of a bowling ball who must fill up beakers with rain drops.
  • On each level, you will have either 1 or 2 beakers to fill, and they may be positioned with at most 1 beaker on each side of the player 
  • The game will only have 2 controls: roll left and roll right. This adds to the challenge of the puzzles because you can only move each beaker in 1 direction.
  • Rain is created by a dynamic rain system from 1 or 2 clouds per-level.
  • The clouds can be configured to behave in many different ways, such as: being stationary, moving left/right (single or multiple passes), being restricted to certain x-value bounds, having endless rain drops, or having a limited supply of rain drops.
  • Tweaking the dynamic rain system along with the placement and movement of the cloud(s) and beaker(s) can be used to create challenging puzzles
Implementation

Dynamic Rain System

Implementing the dynamic rain system was a lot of fun, and a lot easier than you may think. Basically, it boils down to this:
  • Assign each cloud a life span (number of drops it can produce)
  • Assign each cloud an intensity value (number of drops it produces per-frame)
  • Using the dimensions of the cloud, generate a random x,y coordinate at which to generate a new rain drop within the bounds of the cloud.
  • Generate the rain drops in a loop ranging from 1 to the intensity value
  • For each drop created, subtract 1 from the life span of the cloud
  • As long as the life span is > 0 (or the cloud has an infinite life span), continue to generate rain
Pretty simple, eh?

Beakers

There were a few things I had to deal with when implementing the beakers. First, I wanted to have some sort of physics effect so that the character would be slowed down while pushing them. I decided to fake the physics first and see how well it worked before implementing the real deal. I got lucky on my first attempt at this. I gave each beaker a fractional weight value (settled on .5). Then, on collision, I would multiply the player's speed by the weight of the beaker. Since it is a fractional value, it would decrease the player's speed by a factor of the beaker's weight. The result is a very believable friction effect. Sorry, Newton, I don't need you today...

The next issue with the beakers was getting them to "fill up" as they caught rain drops. This one took a little bit more time to nail down, but I'm pretty happy with the solution I came up with. Basically, each beaker keeps track of how many drops it can hold, and how many it's already caught. Using these values, combined with the height and width of the beaker, I do some simple division to create a fill percentage and draw a filled rectangle inside the beaker. This gets updated on every game update, so as you collect more drops, the box grows taller and taller, thus the beaker appears to be filling up. Until the beaker is full, the box is the same color as the rain drops, and once full, it turns red so the player knows to stop trying to fill it. Some normalization had to be done for when the beaker does fill up, as well as to account for leaving some space on the sides, but the code for this is pretty straightforward:



I did have to address one issue with the beakers in that they were detecting "caught" drain drops anytime they collided with a rain drop. Clearly, you could collide with low-falling drops by pushing the beaker into them, but that's hardly catching them, so it had to be fixed.

See this post for details on how I went about that.

Player

The player is a fairly simple piece of code, although there is one neat trick I employed. I was faced with the question of, "How do I make the bowling ball roll around?". Sure, I could do some involved math to do it, but I'm just using a static image for the player character. So, what to do? Rotate the image!



Conclusion 

All in all, the game took around 4 days, but probably only about 15-20 hours of time. It was essentially the first non-trivial program I had ever written in Python, and it turned out really great if I do say so myself.

You can find the game here

                         
 


No comments: