Game Development Community

dev|Pro Game Development Curriculum

When you hit a wall, you must flow around it -- like water.

by Jon Frisby · 05/29/2006 (11:29 pm) · 1 comments

Spoiler alert: Downloadable goodies for TGBers at the end of this post.

So I had the first 18 hour coding marathon I've had in quite some time yesterday/today. I decided I was going to "rebuild" my game for TGB 1.1Beta4. So much has changed in TGB since I started work on this game, and every time I bump up to a new build of the engine, I have to hack off parts of the engine (*cough*level builder*cough*) in order to make things work. On top of that, the code for the game itself has gotten rather... crufty.

I've experimented a lot. Tweaked and fiddled and adjusted and I've slowly hacked in one feature after another. I started with a single hard-coded skin. I managed to build some menus around that. Then I added a second skin. And a third. And a sort of jerky transition between them. Then I added music. Then I made the music stream so the game didn't take 5+ minutes to start. Then I added a single-skin mode. And scoring. Then I managed to get Ogg Theora backgrounds working for skins, except that the video didn't loop. Then I got the God of Sarcasm (*all hail his Tomness!*) to code me up a way to asynchronously prefetch the music for the next skin, and loop theora video so that skin transitions happen more smoothly and theora backgrounds are *almost* practical.

But now I find it is getting harder and harder to add that last little bit of polish. Nailing that last bug that hits when you happen to drop a block *precisely* when the skins change. Fading in and out of skins, rather than jumping between them. The little stuff that may mean the difference between yet another mediocre title that nobody will remember in a week, and a title that recoups its development costs.

So I went on a binge. And hit a brick wall. Hard. A key behavior of TorqueScript that I relied heavily upon no longer works, and there just isn't a good workaround. As Tom pointed out, I wound up fighting the system and that's rarely productive. Time to think about things in a new way.

So I've decided to do a clean-slate with the code. The code itself is *not that complex*. I will lift individual bits of code (the C++ class GameBoard is reasonably clean and elegant, and will likely make the cut), reconsider others (my FadeableStaticSprite class got redone in C++ -- more below), and completely rethink others.

The two things that are going to be completely rethought are how I manage the notion of time, and the lifetime and scope of responsibility of skins.

So how I manage time right now is a bit of a mess. The main game loop tracks the amount of physical time a song has been playing (subtracting out pauses), and makes sure the sweeper is constantly positioned precisely where it needs to be. This works perfectly, but the way it works makes it a bit difficult for me to gracefully transition to a different tempo. Additionally I've got a mess of scheduled jobs that do things like move debris down, adnimate things fading in and out, force the current brick downward, ad infinitum. So I'm thinking that a more centralized "clock" functionality (maybe ITickable based, maybe not) that fires callbacks at the right time, and properly manages things like pause (don't even ASK how I'm managing pause with respect to scheduled jobs!). And then generous use of ITickable for fading things in and out, and the like.

The skins themselves right now are responsible for playing and stopping music, synchronously or asynchronously prefetching music and unloading it when done, creating all needed sprites, controlling animations, displaying the game board, score, and so forth. They are not directly responsible for displaying the active or upcoming blocks, and they're constructed in a way that makes having more than one in existence at a time be somewhat... awkward.

I need to answer one question before I proceed with the way I'm thinking of proceeding: If I have a bunch of t2dSceneObjects (or subclasses of course) that are set to be disabled (setEnabled(false)), do they impact framerate? How do they impact memory usage? I want to move to a more "static" model where all skins "exist" at once, and I simply enable (and fade in) new ones, and fade out (the disable) old ones. Additionally, each skin would be responsible for displaying active and upcoming blocks but not actually be responsible for maintain the game state related to them. For a variety of reasons, a number of things become much less awkward in such an approach.

I am not going to go through and simply translate what I've got into C++ -- that would be awkward, time-consuming and would just lead to crufty C++. Instead I am building up a toolkit of things I will definitely need and use, then will step by step start implementing the game again, in C++. The game really is not that complicated, so I don't anticipate that this will take me a very long time -- most of the time will be spent dusting off a few remaining C-related cobwebs and pestering Tom "The Second Coming" Bampton for newbie C++/Torque info.

So here's what I've got so far:

First up, a patch that combines asynchronous audio pre-fetching, Theora looping, Justin DuJardin's particle effects fix, Tom Bampton's simple hash table, and audio and video pause/unpause support. This code has a few dragons lurking in it (the theora looping stuff has a tendency to hold onto the thread longer than it needs to, which can make switching videos produce a crash pretty reliably, I haven't tested the pause-related functionality in beta4, and if you try to prefetch an audio file twice without unloading in between, I'm told bad things will happen -- but I've never personally tried). For this reason -- and absolutely NOT because I'm really lazy and tired -- I'm leaving this as a unified diff so only people with the leet unix skillz to apply it will blow their feet off when they try to use it. ;) www.mrjoy.com/audioVideoParticlesAndHashesForBeta4.patch.zip (16KB)

Second up, and of more interested to a wider variety of people, is a class that uses ITickable to smoothly change from one alpha value to another over a fixed amount of time. Simply call beginFade(A, T) where A is the target alpha value (from 0 to 1), and T is the amount of time (in seconds, as a floating point number -- 0.5 is about 500ms) over which the transition should occur. Once done, it will fire a script callback -- onFadeEnd(). Other than that, it's just like t2dStaticSprite in every way, shape, and form.

Here's an example of how to ue it to make something fade in and out continuously:
// Somewhere in your code:
	$x = new FadeableStaticSprite() { 
		scenegraph = sceneWindow2D.getSceneGraph(); 
		size = "100 75";
		position = "0 0";
		layer = "1";
		imageMap = asteroidsImageMap;
		frame = 0;
		
		state = 0;
		interval = 2.5;
	};
	$x.setEnabled(true);
	$x.setVisible(true);
	$x.beginFade($x.state, $x.interval);

// Then later on:
function FadeableStaticSprite::onFadeEnd(%this)
{
	if (%this.state == 0)
		%this.state = 1;
	else if (%this.state == 1)
		%this.state = 0;
	else
		return;
		
	%this.beginFade(%this.state, %this.interval);
}
// Set $x.state = 2 in order to stop it.
Fading that is as smooth as silk, and no script overhead just to fade something in or out. The only gotcha right now is that it doesn't yet obey the sceneGraph's pause status. I blame ITickable. :p Actually, there's an easy enough way to do this, I just haven't done it yet. I'll do it later, and that version will get posted as a resource.
www.mrjoy.com/FadeableStaticSprite_20060529.zip

Anyway, I'm tired. Time to go to bed.

#1
05/29/2006 (11:41 pm)
Addendum: To get the FadeableStaticSprite to obey scene pause, go to line ~88 or so in the .cc file, where it says:
if (mFadeInterval <= 0.0f)
		return;

And insert this code immediately afterwards:
if (getSceneGraph()->getScenePause())
		return;

Voila.

-JF