Deep Thinking about Shapes

I've been getting into the real meat of the GameObject class today: drawing shapes and children. Unfortunately, I've run into a little tangle that I need to sort out before I go much further.

A brief explanation GameObject first. GameObject is analogous to Rubygame's Sprite class, in that it's meant to be the base class for space ships, dragons, penguins, and whatever else you can see on the screen. But it has some important differences.

A GameObject itself is not visible, but rather holds Shapes, which are visible and have colors, styles, textures, etc. Shapes are boxes, circles, lines, 2D polygon meshes, and maybe some other things of that nature. In addition to appearance, shapes will be the base for collision and physics, once I integrate Chipmunk.

Also, GameObject is hierarchical, i.e. each object can have child objects (and those can have children, and so on). The children inherit the parent's transformation, and can add their own local transformation. So if the parent is at position <-3, 0> (global space) and the child is at <-1,0> (local space), the child will appear to the user to be at <-4, 0>, because its position is relative to the parent. So, you can make a character out of multiple objects (maybe one for the head, one for the body, and so on), or make a katamari ball that other objects stick to and rotate with, or whatever else you want. Thanks to OpenGL, it's all extremely simple to program.

But, as I said, there's a snag. Shapes all have depths, to decide which shape appears in front of the other. If shape A has a depth of 50, and shape B has a depth of 25, B is closer, so it should be in front. But I hadn't thought about how shapes in separate GameObjects relate to each other. Do GameObjects have depths? If they do, and object O is in front of object P, but O's shapes are deeper than P's shapes, which shapes draw in front of each other? Or what if O and P have multiple shapes, with their depths alternating like two stacks of cards shuffled together?

There are two ways I could go. The lazy way is to just say that Shape's depths only apply within the GameObject, so all shapes of the nearer object will appear on top of all shapes of the deeper object. In this case, it's very simple to do: just render each object in the scene by descending depth, and within each object render its shapes also in descending depth. It'd just be a matter of sorting, really.

The less-lazy way is to say that GameObjects don't have depths, and only the Shapes' depths matter. But in this case, I have to maintain a list of all shapes in the scene, regardless of which object they belong to, sorted by depth, and then draw those in order by descending depth. (For those of you familiar with graphics rendering and wondering why I don't just use the depth buffer: I can't. Shapes can have partially transparent textures, and those wouldn't render correctly if I relied on the depth buffer.)

The first, lazy way has a disadvantage, which is the snag that caught my attention and started this train of thought: how is an object sorted relative to its children? Do the children go in front? Or behind? Well, really, you'd want to choose on a case-by-case basis, but then I have to think about a relative depth system. (Hrm, actually, it wouldn't be too hard — parent depth is 0, children can have negative depths to be in front, positive to be in back.)

Also weighing in is an advantage of the second way: shapes exist in a consistent, cohesive world, where depth is absolute. This feels "cleaner" to me, and it would also allow for neat stuff like having a shadow which is part of the object, but will always appear behind other objects. (To do something like this with the lazy system, you'd need to have the shadow as a separate, deeper object that follows the main object around like, well, a shadow.)

Either way would be fine for most games, so it's just a matter of deciding whether the effort to redesign things and implement the second system is worth the cleanliness.

I'm going to sleep on it. But if I were going to choose right now, I'd go with the first way. It's simpler, and has a certain hierarchical appear.

Rebirth 0.5

I finished up Rebirth 0.5 tonight. There's not much to say about it that I didn't say last time: there's a new Vector class, and Camera and Shape's position, rotation, and scale attributes can be changed. This version is tagged as release-0.5 on Github.

The demo for this one looks the same as for the last version, although I did try a little test where the box could be moved with the arrow keys. Shapes aren't meant to be controlled directly like that, though, so I didn't keep it. I'll add it back properly next version, once I implement the game object class.

Rebirth Progress

The super-charged motivation I've had the past few days is starting to dim, but I think I'll be able to get another one or two versions of Rebirth released before I need to take a mini-vacation from it.

Tonight I was working on the Vector class. It was copied over from my old work on Rubygame, but I took the time to create specs for it, one method at a time, testing along the way. Probably a waste of time since the class was already done and I could have just written all the specs in one go, but oh well.

I've decided to insert one more release before the Gob version. This next version will have the Vector class, and the Camera and Shape classes' position, rotation, and scale will be modifiable, and position will use Vector. The code for Camera and Shape's pos/rot/scale attributes were identical, and Gob's would be too once I wrote it, so I decided to refactor it out into a new mixin module (HasTransform) to keep things DRY.

I also implemented some convenience / utility functions, the most interesting of which is need, a clever wrapper for require that takes relative paths. Credit for the cleverness goes to Drew Olson, who made a library with a similar function. I didn't want to add a dependency just for one simple function, so I wrote my own version (which is probably nearly identical; there aren't too many unique ways to write it):

def need( &block )
  require File.expand_path(,
                            File.dirname( eval( "__FILE__",
                                                block.binding ) ))

It's used like this:

need { "vector" }    # requires ./vector.rb

The cleverness is that it uses a block (note the curly braces) to capture the current state at the time the block was created, in the file that called need. This allows you to write the need function in one file, but have it access the __FILE__ variable from the calling file. Crazy!

Anyway, it's 4AM, and I'm rambling. I expect to finish up Rebirth 0.5 tomorrow (er, today).

Rebirth 0.4

I had a burst of inspiration this weekend, so I hammered out Rebirth 0.4, which adds the new Camera class.

Much of the code was borrowed from my earlier forays into OpenGL and higher-level game frameworking. One major different is that, since I'm not attempting SDL support (as I was going to when these features were slated for Rubygame 3.0), I can take advantage of OpenGL's transformation code, rather than implementing my own Transform and Matrix3 classes to calculate how objects look from the camera's viewpoint. I might have to borrow them later, to transform mouse clicks into world and object-local coordinates, but gluUnProject seems to do that already, so I'll have to investigate how to use it in ruby-opengl.

One amusing thing I noticed is that I haven't added any support for modifying the position, rotation, or scale of shapes or cameras — there are readers, but no writers. Obviously, that's no good in the long run! I decided to leave it that way for 0.4 to keep the release small, but I'll be adding that in for 0.5, which will also have the game object class (which I think I'll call Gob). Game object is analogous to Rubygame's Sprite class, except that instead of having an image and a rect, it's made up of Shapes; it will also correspond to Chipmunk's Body class.

I do need to implement a proper 2D vector class (I'm just using [x,y] Arrays right now). I'll probably clean up Ftor for it, then later switch it to be based on Chipmunk's vectors, once I add physics.

I also need to add proper color support (I'm using [r,g,b,a] Arrays now). I'll probably extend the Rubygame color classes to have a "to_opengl" method that converts them to the format used by OpenGL. I'll need a general conversion method too, so that I can run any symbol, string, array, or color through the converter and get a valid color back (or else raise an error). I might extend the OpenGL functions that take a color to do that conversion automatically, if I'm feeling particularly peppy. If I do, I'd use a mixin module so that the new methods can just use super. (Isn't ruby great?)

The demo for 0.4 looks pretty much the same as it did for 0.3, but the camera viewport deliberately doesn't fill the entire image, to demonstrate that you can make cameras that only draw on part of the screen. This would be useful for having two screens side by side, for example with a 2-player racing game.

Rebirth 0.4 screenshot showing a white rectangle on a purple background

This version is tagged as release-0.4 on Github.

Next version is Gob (game object), then Circle, then Color/Style, then Line, then World — after that, nothing's planned, so I'll be thinking about what's after that. Probably the Game class, which manages the flow of the entire game, keeps track of objects in the scene, cameras, the view, global event manager, etc. I'm not sure when Chipmunk will come in.

Rebirth 0.3

Finished up Rebirth 0.3 today. This version is quite a bit heavier than the previous two, but still just light enough to be called a micro-release. It adds two new simple classes:

  • Shape, which is the base class for all shapes in Rebirth. It implements @pos, @rot, @scale, and @visible attributes, and handles the OpenGL matrix stuff for transforming the shape.
  • Box, which inherits from Shape. It draws as a square or rectangle, depending on whether @scale is the same or different on x and y. It overrides #_draw to provide the OpenGL calls for rendering the polygons in local space.

I also made a mixin, HasEventHandler, which extends Rubygame's mixin of the same name to add a new method, #make_magic_hooks_for. It's just like HasEventHandler#make_magic_hooks, but uses the specified object as the hook owner. I'm planning on adding that method for the next release of Rubygame.

The demo for this version just shows a white rectangle. The background is purple (instead of the promised black) so that I could tell the screen was being cleared. Here's a commemorative screenshot; it's not much to look at, but you can click for the full image.

Screenshot showing a tilted white rectangle on a purple background

Like the other micro-releases, there's no download for this, but it's tagged as release-0.3 on github.

The next version was slated to be GameObject (i.e. Sprite) — not Circle, as I had thought — but while working on this version, I realized I needed a Camera class to see anything on the screen. I implemented a basic camera class in the demo, but I'm going to make the next release Camera, then GameObject, then Circle.

Rebirth 0.2

In addition to Rubygame 2.4.1, I finished Rebirth 0.2 yesterday.

I'm continuing with the "micro-releases" concept for Rebirth. This version adds just one feature, and it only took a couple hours to write: the EventManager class. It's basically an EventHandler with a built-in EventQueue. Nothing too special, just another milestone (or inchstone, maybe) towards a complete game framework.

There's also an exciting new demo for this version: a black screen that quits when you press Q. The demo for the previous version was a black screen that quit after 10 seconds. The next version will add a white rectangle. Excitement ensues!

There's no source drop for this release, and there's nothing interesting enough to warrant downloading it, but it's tagged for posterity as release-0.2 on Github.

An aside: I'm really glad I had done so much planning back in June, when I was just starting Rebirth. It has been so long since then that I would have had no idea what to do today. Having the plans and roadmap set out already really helped me remember the direction I wanted to go, and let me focus on what to do next. It also lowered the mental barrier to re-entry, since I didn't have to dread the unpleasant task of trying to remember what was going on back then.

So: Planning. I wholeheartedly endorse it.

Rubygame 2.4.1 released

I've made a quick patch-level release for Rubygame. Version 2.4.1 fixes a couple bugs, most importantly module scope problems in EventHandler and the event triggers (i.e. they wouldn't work unless you did include Rubygame in your code). It also makes Rubygame compatible with Ruby 1.9.1 (in addition to 1.8). Yay!

There are no API changes in this version.

New Forums for Rubygame

I've set up forums for Rubygame, since the unofficial ones seem to be completely dead.

The new forums are at Easy to remember, yeah? Go break them in with some new posts!

If anyone has any recommendations for a better forum theme, I'd love to hear them. The forums are running phpBB.

(Yeah, I know, it's heresy for a Ruby programmer to use an application written in any other language. Well, guess what, I'm planning to switch these blogs to Wordpress as soon as I can work out how to convert the databases, too. Ease of use and maintenance trumps blind language devotion.)

What's going on with Rubygame?

The answer to that question is: Not much for a long time, but some things soon.

I rather abruptly dropped off the face of the planet immediately after the 2.4 release back in October, at least as far as Rubygame was concerned. I didn't even stick around to arrange for someone to take over. I was gone. Poof.

Well, not really. I was still around, still reading my email (but rarely responding), and still following a handful of Ruby blogs. I even did a little bit of Rubygame development: Ruby 1.9.1-preview 1 came out less than a week after Rubygame 2.4 (coincidence?! ... yes :P), and I made some tweaks to Rubygame to try to make it compatible. I never pushed those revisions, and I can't remember whether or not I got it completely working. If I recall correctly, I ran into some issues with having 1.8 and 1.9 installed side by side, and things like Rake and Gems not wanting to cooperate.

Aside from that, though, I pretty much avoided anything having to do with Rubygame. I was tired of it, and I had a big, exciting new hobby project to start swimming in. That project is unrelated to Ruby or games, and I won't be writing about it either here or on my personal blog. It's still ongoing (and probably will be for years), but it's getting mature enough that I don't have to nurture it every day. Plus, I have a project partner to help out and provide support, a luxury I never had with Rubygame. (I did receive patches from time to time, and I did and do appreciate them, but it's not the same thing.)

But as exciting as the other project is, my calling is to interactive media design ("video games"), and Ruby is still my programming language of choice. (Sadly, I don't get to use it on the other project, because we've inherited a code base written it C++). So I find my thoughts these days drifting back to the desire to write games in Ruby.

The sad irony of Rubygame is that I never got around to actually using it much; my energy was entirely on making it. Well, that's going to change. I'm retired from being Rubygame's developer, so I'm going to start being one of its users.

I have decided, though, that I will stay on as Rubygame maintainer. And I really mean strictly maintenance: sweeping away the dust, applying patches, maybe fixing a bug once in a while. I'll probably finish up the Ruby 1.9 compatibility fixes (since 1.9.1 was released recently). But I won't be developing new features. (I'd still like to find a developer with the enthusiasm and energy to push Rubygame into new territory, as I wrote back in October.)

Mostly, I'll be working on two things: a game, and Rebirth (which will probably get a new name). I'll be developing Rebirth as a support library for my game, so they will grow with each other. When my game needs some new feature, it will either be added to Rebirth as a general library, or else it will start in my game and then, after the feature is done, be extracted and put into Rebirth.

In case you've forgotten (or never knew), Rebirth will use OpenGL for graphics, with Rubygame underneath for stuff like event handling and image loading. Eventually, it will have physics support using Chipmunk. I'm toying with the idea of OpenAL audio, but Rubygame's basic audio support will be fine for starters, and I don't want to get occupied developing a Ruby OpenAL library, too.

On rare occasion, some of the work on my game or Rebirth might make its way back into Rubygame. For example, I intend to brush off the improved Clock class I developed as part of the mythical Rubygame 3.0 so many moons ago. Once that's done, I might submit it as a patch to be applied to Rubygame (as crazy as submitting a patch to my own project seems).

For those who are curious about the game I'm planning: it's going to be an ambient music creation toy, inspired by (but not a clone of) Toshio Iwai's Electroplankton. I've had ideas about this floating in my head for many years, and it's time to bring it to life. Let's hope my pessimistic prediction in that old blog post doesn't come true.

Assuming I actually go through with this, I'll be blogging about the game over at my personal blog. Rebirth news might be over there too, since it's not really Rubygame. Although, this blog would get rather lonely. We'll see which way it goes.

Rubygame 2.4 released; Project seeking new maintainer

I have two major announcements related to Rubygame:

  1. Rubygame 2.4 is now available
  2. I'm stepping down from my position as project maintainer

First, the happier news. Rubygame 2.4 is, finally, done. This release contains the much-anticipated event handler system, which allows you to define how your objects respond to events (e.g. keyboard presses, mouse clicks, and even your own custom, gameplay-related events) in a dynamic, object-oriented way. I'm very proud of this system, because it is flexible, extensible, and powerful, yet at the same time easy to use and made of very simple parts.

Rubygame 2.4 also includes a new suite of event classes for keyboard, joystick, and mouse input, and all the other things. They are a full replacement of the older events, and I strongly encourage you to upgrade your games to use them (use EventQueue#enable_new_style_events to do that).

This release also includes a patch to enable key repeat, contributed by Roger Ostrander (atiaxi), and several bug fixes. See the NEWS file for the full details. Downloads for Rubygame 2.4 are available at Rubyforge. Precompiled gems for Windows and Mac are not available as I'm writing this, but I'll post them at that same spot as they are sent to me. (Hint, hint.)

Now then, the more serious news.

After 4 years of off-and-on work on Rubygame, I have finally decided to step down as project maintainer altogether. From my vantage point, this is long overdue. Anyone reading this blog over the past 8 months can see the reason why I'm stepping down: I just don't have the time, energy, or motivation to be a reliable maintainer anymore.

So, Rubygame 2.4 will be the last release of Rubygame I make, but I hope not the last release of Rubygame ever. There are plenty of ideas on the ROADMAP, and the Git repository has a lot of code in the dev-3.0.0 and old-3.0.0 branches that could be cleaned up and released.

But the project needs a new maintainer with the time, enthusiasm, and experience to keep it moving forward.

If you're interested in being that person, please send me an email: jacius at gmail.