Saturday, January 31, 2009

Gravitor

When I go to conferences, I find myself with little I can do work-wise, but surrounded by tech stuff. With nothing billable to do, I can't help but direct my free energy towards obsessively working on some pet project.

This time it was Gemini, a game engine in JRuby and powered by Slick and Phys2D that David Koontz and myself were working on. We wanted to make a lot of the done-to-death things we see aspiring game developers rewriting all of the time. Too often, everyone spends time recreating the technology, but not doing anything interesting with it. This even includes 3D/Game engines such as Ogre3D, jMonkeyEngine, CrystalSpace, etc.

While at RubyConf08, my goal was to create a complete game, front to back, with Gemini. Obviously, it wasn't going to be a AAA title. I defined complete game as a game having graphics, sound effects, music, victory conditions, defeat conditions, and app navigation (menu, playing game, game over screen, victory screen, and the ability to move between all of these).
I was able to get it done, and with only 337 lines of code. I did pretty much all of the graphics by hand. The background is a brush template in SeaShore. I got my sound effects from a couple sites that offer free sound effect files. The music was stuff I threw together from GarageBand, but it's mostly looping tracks that were glued together.



The menu button is cleverly disguised as a black hole

The game has a total of 5 classes. A load state, main state, menu state, game over state, and victory state. Everything else is built with Gemini's GameObject/Behavior system.



Pilot your ship with the Gravitor cannon - a device that emits gravity wells


In Gemini, you have states, which is best thought of as a mode for the application. In the MenuState, menu stuff goes on (starting a game, loading, saving, options). In the PlayState, play stuff goes on (the level is shown, the player is able to play, etc). States are pretty simple. Once you're in the state, you think in terms of GameObjects. You can inherit from a GameObject, and slap a bunch of Behaviors on it. You can also just create a GameObject, and slap a bunch of behaviors on it. I didn't repeat myself just now. You can create a subclass for every kind of thing that shows up in your game, or you can create them more dynamically at the object level.

I'm getting ahead of myself. What's this Behavior business I'm talking about? Behaviors are the most powerful thing in the engine. Behaviors can create methods on the GameObject they will be bound to. Behaviors can even depend on other behaviors. For example, a Collidable behavior will depend on the Spatial behavior. In order for collision calculations to work, the Collidable needs to know where it is in relation to other things. The Spatial behavior takes care of that for us already, so Collidable can just declare that it depends on a Spatial, and the engine will ensure that all Collidable game objects are also Spatial.

Behaviors can also create callbacks for the GameObject. Our Collidable Behavior would add an on_collide method, which takes a block. These callbacks are much better way than the old inheritance scheme for handling game objects, as you can register multiple listeners to a particular event. Performance wise, this is slower, but the focus of Gemini is to be a tool that lets you create games (quickly - although I argue this enables you to create them at all, otherwise you suffer Failure of Greater Ambition). Performance is of secondary concern (although it's still a concern). Hey, this is Ruby. If you want fast, go write it in C or Java.

There's a lot more features about Behaviors, but the last major one I want to go into is the runtime component of them. You can add and remove Behaviors from a game object during the runtime. The monsters in Pac-Man might have an AI behavior that causes them to seek out Pac-Man. Once Pac-Man picks up the power pill, simply remove that AI behavior and add on a Flee behavior. Easy stuff!

I might sell the game one day if I can fluff up some of the graphics and audio, and add a bunch of levels. For now, anyone can grab the game and try it out:

If you're using Linux, just download the Windows zip and use this to execute the app:
java -Djava.library.path=lib/java/native_files -XX:+UseConcMarkSweepGC -Djruby.compile.mode=FORCE -Xms256m -Xmx512m -jar Gravitor.jar

2 comments:

jaymcgavren said...

You should talk about any special steps you had to take to package for OSX/Windows.

Logan Barnett said...

Jay,
That's certainly something I can do for another one of my posts. I can tell you now that I had to manually alter the Info.plist because a .app quirk doesn't let you throw all of your JVM args into the jvm-arguments tag (or whatever it is named). Things that set a system property, such as java.library.path, get their own key/value. Rawr will need to support this.

Post a Comment