19 March 2011

Code of the Ninja: Jump Height Calculator

If you haven't already, read the Code of the Ninja: Introduction

Hello again, Code Ninjas!

Today is something very simple: a formula that allows you to plug in variables for "jump force" and "gravity" and get the maximum height of a jump as the result.

When designing platformers, tweaking the physics until they're just right is very important. Having a formula such as this will be useful for zeroing in on exactly what values constants like gravity should have.

Another use might be this: say you already have established physics, for instance because you're hacking a Sonic game. But you want to add a new object, such as a new kind of bumper or spring, that bounces Sonic exactly 5 times his height. Determining the force at which Sonic should be impelled by the object in order to achieve that height would be a snap using the following formula.

The simplest way to write the formula is this:

g = gravity;
f = jumpForce;
h = 0;
t = 0;
while ( f > 0 )
{
  h += f;
  f -= g;
  t += 1;
}
return h;

g is your gravity, and f is the jump force, which can be set to anything. In Sonic, for example, gravity is 0.21875pps (pixels per step) and the jump force is 6.5pps.

h is the height value we are trying to find out - the number of pixels the character will travel given the jump force f with gravity g. t is time, which is optional - it will be the number of steps it takes to reach the apex of the jump. Both h and t are initialised at 0.

Then we run a loop while f is greater than 0. In the loop, first f is added to h, then g is subtracted from f. This simulates the jump: the force acts upon the character's position, then the gravity acts upon the force. t is then incremented by 1 in order to count the time.

When the loop is finished, h will be the ultimate height of the jump.

Note that this code assumes your physics to handle similarly to Sonic's. In Sonic, because of the particulars of the original code, the jump force is added once to the character's position before gravity acts upon it. If this is not the case in your game, then the loop should be restructured accordingly; g should be subtracted from f before f is added to h.

Earlier I said "the simplest way" to write the formula. The truth is that this, while simple, is a rather brute force method. Depending on the strength of the jump force and gravity, the loop could run dozens of times. Yes, modern computers can handle this without breaking a sweat, and yes, you probably won't even be using the formula in a running game, anyway. But for the sake of mathematical beauty, we can find a better way that doesn't employ a loop.

So let's build this new formula piece by piece. First, we need to find how many steps it will take for gravity to whittle the jump force away to 0 (or less). This will be time t again.

g = gravity;
f = jumpForce;
t = ceil(f/g);

We find t by dividing f by g and rounding up to the nearest 1. Why the rounding up? Well, if the jump force isn't perfectly divisible by gravity, the remainder would still count as upward velocity and the player would still move up by a little bit during that step. Since it counts toward the total, it must be taken into account.

Now that we know how long the jump will take to reach the apex, we can easily discover the distance the character would travel during that time, without gravity, merely by multiplying f by t.

h = f*t;

Of course this seems a little silly, because we're trying to find the height covered with gravity taken into account. But knowing this value is useful; 'cos if we can also determine how much force is deducted by gravity from the jump force over t steps, we can multiply gravity by that number, subtract it from h, and have our correct result.

In the first step (t = 0), the jump force is unaffected. In the next, it is lesser by gravity. In the next, it is lesser by gravity again, i.e. it is equal to the initial jump force value minus gravity times two. Next step, times three, then in the next, times four, and so on, until gravity overcomes the jump force in step t-1.

Visually represented, you might think of the amount of force lost to gravity as a triangular stack like this:

----- (t=0)
g---- (t=1)
gg--- (t=2)
ggg-- (t=3)
gggg- (t=4)
ggggg (t=5)
...

Fortunately it is easy to find the area of a triangle such as this by finding the area of a square the size of t*t-1 and then cutting that value in half. (If, as mentioned above, your physics subtract gravity before the character moves once, the square should have a size of t*t+1 instead).

h -= t*(t-1)*0.5*g;

Et voilĂ , you have the correct height of the jump, identical to the result of the while statement method used above.

Finally, because multiplication is a commutative process, the formula can be recast in a simpler way for our final code:

g = gravity;
f = jumpForce;
t = ceil(f/g);
h = (f-((t-1)*0.5*g))*t;
return h;

You can find a small example .gmk here that lets you play with the variables to get different jump heights. Until next time, happy coding!

If you use my code or scripts in your game or engine, no credit is necessary. But I'd love to hear about your project if you do! Just drop me a comment below, or e-mail me at us.mercurysilver@gmail.com

02 March 2011

Code of the Ninja - Partially Erasing Surfaces in GM

If you haven't already, read the Code of the Ninja: Introduction

Hey there, Code Ninjas!

One of the cool things about Game Maker 8 is the ability to export PNGs with an alpha channel for transparency. There's no separate functions for doing so, though; if you want to be sure the exported image is in PNG format, you have to make sure the extension is explicitly ".png", like so:

Code:

sprite_save("sprite.png");
screen_save_part("screenie.png",320,224);

It's especially cool to create partially transparent surfaces and export them. After creating a surface, you can use the draw_clear_alpha() function to make it completely transparent:

Code:

surface_set_target(surface);

draw_clear_alpha(c_black,0); // clear the entire surface with fully transparent colour

surface_reset_target();

One thing that's annoying, though, is the way that drawing with a partial alpha to a surface works. Instead of blending with the colour underneath, the colour is completely replaced, alpha and all. Effectively this punches "holes" in the surface image.

You'd think this could exploited to create some kind of eraser tool. Draw pixels with an alpha of 0 to the surface to erase pixels that are already there, leaving fully transparent pixels behind.

This doesn't work, though, for some reason. Very low alpha values such as 0 or 0.01 function exactly as you'd normally expect when drawing to the screen, even though higher values such as 0.7 differ when using surfaces.

So much for the ability to erase pixels from a surface using that method... But there is another way.

Set the blend mode to bm_subtract before drawing to the surface and you'll effectively be able to erase from the image:

Code:

surface_set_target(surface);

draw_set_blend_mode(bm_subtract);
draw_set_color(c_white); // color doesn't actually matter
draw_set_alpha(1); // alpha must be 1 to fully erase pixels

// erase an "X" across the surface
draw_line(0,0,surface_get_width(surface),surface_get_height(surface));
draw_line(surface_get_width(surface),0,0,surface_get_height(surface));

draw_set_blend_mode(bm_normal);

surface_reset_target();

This trick may come in handy on occasion, especially when making games where the user is allowed to paint custom textures for things - an erase tool is essential.

Until we meet again, happy coding!

If you use my code or scripts in your game or engine, no credit is necessary. But I'd love to hear about your project if you do! Just drop me a comment below, or e-mail me at us.mercurysilver@gmail.com

01 March 2011

Code of the Ninja: Looking Up and Down

If you haven't already, read the Code of the Ninja: Introduction

Hello again, Code Ninjas!

In most 2D platformers the player has the ability to shift the camera a short way by holding up or down on the D-pad. In Sonic games, it works great but there is minor flaw that's hard to notice and really doesn't cause any problems, but I thought it would nice to show how to fix it anyway.

The problem arises when the player looks up or down near the top or bottom boundaries of the level (or whatever current boundaries the camera is limited to). In order to understand the problem, we need to look at the basic idea behind shifting the camera.

If there were no ability to look up or down, the process would be really simple. The camera would simply follow the player's position directly, with a simple check to make sure it doesn't leave the level boundaries. In order to shift the camera up and down, though, an extra step is needed. An offset is added to the player's Y position before the camera follows it. By increasing or decreasing the offset value when the up or down buttons are pressed, the camera will shift vertically relative to the player's position.

Clearly there need to be maximum and minimum values that the offset can't exceed, otherwise the player could continue to scroll the camera freely until it reached the top or bottom of the level. Normally this behaviour is not desired, so limits are set that will keep the player visible on the screen.

Imagine for a moment, though, that there aren't. Say the player is looking up - the offset decreases and decreases. It will continue to decrease even after the camera stops at the top of the level, because the offset doesn't know when to quit. The camera is limited, sure, and the player never sees beyond the boundary of the level, but the offset value is still decreasing away.

Now what would happen if the player stops pressing up? The offset value will start increasing until it returns to 0 (no shift at all). But depending on how long you've been pressing up, you'll have to wait a few moments before the camera starts to visibly scroll back to the neutral position. After all, the offset has been invisibly counting away for an undetermined amount of time, and that extra time has to be made up for when it tries to return.

This example with no upper/lower limit for the offset illustrates the problem I'm talking about, and that we're going to fix. It's less noticable when limits are present for the offset, because the value can't continue to increase or decrease indefinitely, but it's still there. If you've got a copy of any Genesis Sonic game, try looking up or down near the top or bottom of a zone and see for yourself. Scandalous, isn't it?

It may not be the worst problem in the world, but it can be fixed, so let's give it a go.

There are actually two solutions. One would be to reduce the offset by the appropriate amount when the player lets up from pressing up or down, but that's not the solution I'll describe. Why not? Because that method would require the detection of when up or down is released, which - while certainly possible - is harder to slot right into the way the code already works in the original Sonic.

Let's look at some basic code for handling the offset, and then we'll apply the fix to it.

cam_step()

shiftMode = 0; // reset shift mode
if ( joy( UP ) ) shiftMode = -1; // player is looking up
if ( joy( DOWN ) ) shiftMode = 1; // player is looking down

switch ( shiftMode )
{
case 0: // camera is recentring
  if ( shiftOffset < 0) shiftOffset += 2; // scroll down if too high
  if ( shiftOffset > 0) shiftOffset -= 2; // scroll up if too low
  break;
case -1: // camera is shifting up
  if ( shiftOffset > -shiftLimit ) shiftOffset -= 2; // scroll up until reach negative limit
  break;
case 1: // camera is shifting down
  if ( shiftOffset < shiftLimit ) shiftOffset += 2; // scroll down until reach positive limit
  break;
}

The problem occurs in case -1 and case 1: stopping at the shiftLimit isn't good enough, because at the top and bottom of the level, we need to stop increasing or decreasing early.

How close to the top or bottom of the level must one be in order for the undesired behaviour to occur? Close enough that the distance between the top/bottom of the camera and the top/bottom of the level is less than the shiftLimit.

This suggests the solution. Instead of using the shiftLimit alone, we should use whichever happens to lesser - the shiftLimit or the difference between the view boundary and the level boundary.

cam_step()

case -1: // camera is shifting up
  r = min( shiftLimit, view_yview - shiftOffset );
  if ( shiftOffset > -r ) shiftOffset -= 2; // scroll up until reach negative limit
  break;
case 1: // camera is shifting down
  r = min( shiftLimit, levelBottom - ( ( view_yview - shiftOffset ) + view_hview ) );
  if ( shiftOffset < r ) shiftOffset += 2; // scroll down until reach positive limit
  break;

(The reason why shiftOffset has to be subtracted from view_yview is so the difference won't change once the screen starts scrolling and the offset starts to change.)

And that's all that's needed. It may not be much, but obsessive compulsives will enjoy the game more!

If you enjoyed this and the previous Code of the Ninja, be sure to come back tomorrow for one more before I slip back into the shadows. =P

If you use my code or scripts in your game or engine, no credit is necessary. But I'd love to hear about your project if you do! Just drop me a comment below, or e-mail me at us.mercurysilver@gmail.com

28 February 2011

Code of the Ninja: Checking Multiple Joypad Buttons

If you haven't already, read the Code of the Ninja: Introduction

Welcome back, Code Ninjas!

I apologise for not posting in such a long time (I know ninjas are supposed to be silent, but not that silent), but I've been working on a couple of projects that I hope will soon suprise and delight.

Right now, though, I want to talk about an improvement to my earlier Joypad code. I've been interacting a lot recently with disassemblies of Sonic the Hedgehog, and it's a great learning experience. Regardless of what might have happened to the Sonic series over the years, Yuji Naka's programming remains an inspiration to me. Studying his code has taught me plenty of little tricks, not least because the Genesis is very limited by today's standards and it took a lot of skill to squeeze great results out of it.

Anyway, last time I had described a system that updates a variable called JoyCurrent each step with the current state of the joypad, with each bit representing one button. There was also a second variable called JoyPrevious in which JoyCurrent is stored right before JoyCurrent gets updated. And finally, a third variable called JoyPast, only there to smooth out problems with cheap joypads that occasionally glitch up.

There were three scripts: joy() for checking if a button is down; joy_pressed() for checking if a button is down now but not one step ago; and joy_released() for checking if a button is up now but not one step ago (or even two steps ago, in the case of the aforementioned glitchy controllers).

But I've since discovered a serious deficiency with the joy_pressed() and joy_released() scripts. They can't check for more than one button at a time without causing problems. Let me explain.

Since JoyCurrent and its related variables contain bits that are either on or off to represent the state of buttons on the joypad, checking the state of a button is as easy as testing any given bit with code like this:

Code:

return ( JoyCurrent & argument0 );

where argument0 is a value such as 1 (for testing the first bit), 2 (for testing the second bit), 4 (for testing the third bit), and so on. It's best to define these values as constants so that they can be sensibly named after the buttons, like A, B, LEFT, or START.

Anyone paying enough attention can see that you can test for more than one button simultaneously just by passing a value as argument0 that has more than 1 bit on. For instance, you could pass a value like 65535 to test if any button was down, or you could use OR to test any combination such as LEFT|RIGHT.

Now, the way the script was written it will return true if any bit of argument0 matches up with one in JoyCurrent. If you want to be sure all the bits match, then you'd have to write something like this:

Code:

if (joy(LEFT|RIGHT)==LEFT|RIGHT) then ...

This is all very well and good, but it falls apart when we get to joy_pressed(). This is how it was written:

joy_pressed()

return ( JoyCurrent & argument0 ) and !( JoyPrevious & argument0 );

Now suddenly, because of that boolean "and", the value being returned is degraded - it's now only useful as true or false and doesn't give us as much information. Worse, the following happens:

Imagine you want to check whether A, B, or C are pressed, like in the old Sonic games where any of the three buttons makes him jump or Spin Dash. You don't care if one of the buttons is already down when another is pressed - you still want to detect the new press. The way my code was written, this is impossible with only one call to joy_pressed() because if any of the bits is on in JoyPrevious, the new press won't be detected. The only solution would be to make multiple calls something like this:

Code:

if (joy_pressed(A) or joy_pressed(B) or joy_pressed(C)) then ...

which is just tacky and consumes more processor time. It would be far better to be able to type:

Code:

if (joy_pressed(A|B|C) then...

and have it be done with. (Of course, A|B|C could be a constant called JUMPBUTTON or something, too, to make it even nicer.)

Well, then, how can we change the code so that this is possible? I'm glad you asked that.

At the end of the script joy_step() (the one that updates JoyCurrent and JoyPrevious), we need to update two new variables, JoyPressed and JoyReleased (not to be confused with the scripts that have similar names!) These should be global variables, declared in joy_init().

These variables are destined to behave just like JoyCurrent, only for pressed and released. Just like how you can test to see if buttons are down by checking JoyCurrent as joy() does:

joy()

return ( JoyCurrent & argument0 );

you'll be able to check which buttons are newly down or up in one simple comparison by rewriting joy_pressed like so:

joy_pressed()

return ( JoyPressed & argument0 );

and joy_released() like so:

joy_released()

return ( JoyReleased & argument0 );

(At this point these scripts are all so simple you might not even want to make them scripts at all, but merely type JoyPressed&BUTTON anywhere you would have typed joy_pressed(BUTTON), but it's up to you.)

This sounds great, and it will solve all of the problems I mentioned above, but I haven't told you yet how to update JoyPressed and JoyReleased at the end of joy_step(). It requires a little bit of explanation, though, so we can understand the underlying principles. Otherwise, it would get confusing and complex if you ever need to expand upon it.

First, let's look at a visual representation of our variables. I'm assuming only 8 buttons for convenience. Here's a state with no buttons down:

JoyPrevious: - - - - - - - -
JoyCurrent:  - - - - - - - -

Let's press the first button (we'll call it A).

JoyPrevious: - - - - - - - -
JoyCurrent:  - - - - - - - A

Now let's, without advancing a step yet, add a third variable to this visual guide, temp. It's contents will be JoyPrevious AND JoyCurrent (i.e. "temp = JoyPrevious&JoyCurrent;").

JoyPrevious: - - - - - - - -
JoyCurrent:  - - - - - - - A
temp:        - - - - - - - -

As far as temp is concerned, nothing has happened! But what happens when we do advance one step, without letting go of A?

JoyPrevious: - - - - - - - A
JoyCurrent:  - - - - - - - A
temp:        - - - - - - - A

JoyPrevious becomes JoyCurrent, JoyCurrent remains the same, and temp finally notices what's going on. Clearly, temp is no good for checking buttons that are newly down, because for one temp has only detected the new press one step late, and for two if we continue to hold A temp will not revert to 0. Merely using bitwise AND (&) isn't enough. We need to do one more calculation, XOR (^). Let's go back to our previous step:

JoyPrevious: - - - - - - - -
JoyCurrent:  - - - - - - - A
temp:        - - - - - - - -

and add a fourth variable, called JoyPressed. It's contents will be temp ^ JoyCurrent.

JoyPrevious: - - - - - - - -
JoyCurrent:  - - - - - - - A
temp:        - - - - - - - -
JoyPressed:  - - - - - - - A

By XORing temp and JoyCurrent, JoyPressed contains only bits that are different between them. In the next step, the magic happens:

JoyPrevious: - - - - - - - A
JoyCurrent:  - - - - - - - A
temp:        - - - - - - - A
JoyPressed:  - - - - - - - -

Now JoyPressed has reverted to 0, meaning it accurately represents buttons pressed - bits will only trigger for one frame when their corresponding button is pressed. The same thing will happen even if A is released instead of held down:

JoyPrevious: - - - - - - - A
JoyCurrent:  - - - - - - - -
temp:        - - - - - - - -
JoyPressed:  - - - - - - - -

And, if a new button is pressed while another is held down, it will still be detected as a new press:

JoyPrevious: - - - - - - - A
JoyCurrent:  - - - - - - B A
temp:        - - - - - - - A
JoyPressed:  - - - - - - B -

Fantastic! Let's add another variable, JoyReleased, that is temp ^ JoyPrevious (instead of JoyCurrent) and advance one step while releasing B (but not A).

JoyPrevious: - - - - - - B A
JoyCurrent:  - - - - - - - A
temp:        - - - - - - - A
JoyPressed:  - - - - - - - -
JoyReleased: - - - - - - B -

The same principle operates as with JoyPressed. We just solved the problem. Hooray! The actual code at the end of joy_step() would look something like this:

JoyPressed = ( JoyPrevious & JoyCurrent ) ^ JoyCurrent;
JoyReleased = ( JoyPrevious & JoyCurrent ) ^ JoyPrevious;

Really the only thing to be done now is make sure that cheap joypads don't cause false press and release events simply because the signal is interrupted for a step once in a while. This is easily done by ORing JoyPrevious and JoyPast together to create a sort of "buffered" previous state when checking for presses, and ORing JoyCurrent and JoyPrevious together for a buffered current state (and using JoyPast in place of JoyPrevious where it used to appear in the line) when checking for releases. For example:

joy_step()

JoyPressed = ( ( JoyPrevious | JoyPast ) & JoyCurrent ) ^ JoyCurrent;
JoyReleased = ( ( JoyCurrent | JoyPrevious ) & JoyPast ) ^ JoyPast;

Conceivably you could also, instead of doing everything in 2 lines, store more information like so:

joy_step()

JoyCurrentBuffered = JoyCurrent | JoyPrevious;
JoyPreviousBuffered = JoyPrevious | JoyPast;

JoyDown = JoyPreviousBuffered & JoyCurrent;
JoyUp = JoyCurrentBuffered & JoyPast;

JoyPressed = JoyDown ^ JoyCurrent;
JoyReleased = JoyUp ^ JoyPast;

This way you could check JoyDown or JoyUp to see whether a button is down but not pressed, or up but not released, which might be useful. Hey, you never know.

That takes care of today's subject. I'll be posting again soon. Until then, happy coding!

If you use my code or scripts in your game or engine, no credit is necessary. But I'd love to hear about your project if you do! Just drop me a comment below, or e-mail me at us.mercurysilver@gmail.com

05 January 2011

The Most Famous Fastest Hedgehog Alive In The World

UPDATE: This article has been featured at Saturday Morning Sonic, so you could zoom over and read it there instead.

Sonic is described as "the Most Famous Hedgehog in the World" on the original Mega Drive boxart, and as "the Fastest Thing Alive" in the theme song of the Saturday morning cartoon.

When I was young, I watched both Sonic cartoons - Sonic the Hedgehog (SatAM) and Adventures of Sonic the Hedgehog (AoStH) - at the same time as I played the Sonic games on the Sega (well, not the same time; I'm not that good at multi-tasking!)

I could tell there were marked differences between them all, but I never felt that they were in conflict. They all felt equally "Sonic" to me, and to this day I still like them all equally. (In the interest of full disclosure I should admit that I did go through a brief "purist" phase in the early 2000's. It was then that I was first discovering (through Sonic Adventure, the OVA, and information on the Internet) the original Japanese Sonic "canon". Being a big anime nut I temporarily considered it superior - mostly because Sonic was drawn so much cooler in the early Japanese art. This was as I say only a "phase" because after the dust settled I realised that I now had twice as much Sonic than before. There was no need for a false dichotomy that would only reduce it back to half.)

Sure, there were things in one version of Sonic that weren't in the others (most notably, the characters that never crossed over) but the thing that was important to me - Sonic's personality - was consistent across them all. I had no trouble imagining "the Fastest Thing Alive" Sonic in Sonic 2; when he turned toward me, impatient, tapping his foot and looking at his watch ('I'm waaaaaiting....'); as he blazed across hilly landscapes with Tails whirling after ('Up, over, and gone!'). In fact I had no reason at all not to imagine him that way. Likewise, the Sonic in the cartoons was still "the Most Famous Hedgehog in the World"; when he spun into a spiky ball and destroyed robotic enemies; when he used the power of Rings; when he fought to rescue his friends from being turned into robots by the mad Dr Robotnik. He was "the Most Famous Fastest Hedgehog Alive in the World." =P

I respect the opinion of those who personally dislike the cartoons but love the games (there are many of them) or don't care much for the games but love the cartoons (there are a few). I even sympathise on many points: I'm p.o.'ed that Knuckles never showed up in SatAM, I think Sonic's spines are depicted too much like fins in the American art, I wish my favourite Freedom Fighters had appeared in the games, and so on. Everyone's opinions are different, depending on what they grew up with or what happens to strike their fancy. There's no reason - or need - for me to oppose that.

But there's some things I just won't have said: that SatAM "isn't Sonic"; that it could have been about "any other furry" and been just the same; that the games (and their universe, and by extension Sonic Team's current characters and world) are objectively superior for some reason; or that SatAM just "doesn't count" because it throws out a lot of the games' concepts (or completely reworks them). Sometimes the haters will go so far as to to marginalise the American Sonic - such as the laughably and demonstrably false claim that the name "Mobius" is some sort of mistake - in an attempt to make Sega of America look like careless buffoons.

I don't know why there is so much resistance (in rare cases bordering on bigotry against the fans) to the side of Sonic that I grew up with. It would be one thing if I was talking about some obscure Icelandic version of Sonic that appeared only once on a gimmicky push-button picture book, but this is the portrayal of Sonic that was used in America - you know, that place where Sonic was the most successful? (And was more successful before Sega "internationalised" the franchise. Tcheh, what a filthy euphemism - it's not like they borrowed any Western elements besides the fleeting use of the name "Robotnik".)

Is there hate because a large swath of SatAM fans are furries? I mean, as open as I am to alternative sexualities, BunnieXAntoine porn is just too much... but that's really no excuse to dislike SatAM fans specifically. I mean, there's just as much inflatable Rouge smut in the other camp.

Is there hate because of the Archie comic? It's a katamari ball of turtles, mammoths, and other crap, and it's gone off the rails more times than I can count. There are plenty of good reasons to criticise its many examples of poor art and storytelling, but that shouldn't reflect on the SatAM universe - the Archie comic is a melting pot of all versions of Sonic. They've done just as much damage to Shadow, Amy Rose, and their own original characters as they have to any of the Freedom Fighters. Archie's suckage is equal opportunity suckage.

The only reason that makes any sense to me whatsoever (though it's not a good reason by any stretch) is the one I have personal experience with: the whole "purity" thing. The feeling that maybe this isn't what Sonic is "supposed to be".

If this feeling can colonise and spread in a mind like mine, which was pumped full of Knothole, Robotropolis, Swatbots, and Uncle Chuck at a tender young age, then even the most die-hard American Sonic fan's loyalties can start to drift to a new Sonic iteration - especially when that new iteration is seen to be more "real" or "correct".

But this pernicious perception is only an illusion, and it doesn't do the victim any favours (I'm certainly not proud of the era I spent as a snobbish purist). I didn't quite alienate my friends who also liked SatAM, but I must have afflicted them with a fair amount of confusion and frustration. It was as if I wasn't doing my own thinking. Like some mind virus that makes one superstitiously and obsessively eat only white foods, I was restricting my Sonic "diet" arbitrarily.

What's that actually mean in down-to-earth language? It means I was not watching the show anymore, I was getting rid of old merchandise, and I wasn't drawing those characters. Also - since I've been designing my own Sonic fan games since I first had the physical strength to pick up a pen - it meant not including what used to be some of my favourite characters and concepts in my own creations.

On the other side of this, with the sobered hindsight of one who is no longer an angry teenager trying to fit life into neat partitions, I wish that I had seen the myth of purity for what it was. This is just about Sonic, but human nature is a continuum - I recognise this as the same "in-group, out-group" mentality that fuels racism and inspires atrocities. I'm very glad to be wiser.

All of the arguments I had used to assert SatAM's inferiority to the "real" universe of the games, as well as all the ones I hear bandied about online, fall apart on closer scrutiny - that is to say, even a brief second glance.

The first - and perhaps most important - thing to keep in mind is that the classic games don't even have a universe in any strong sense. There is a handful of events, less than a dozen characters, and no dialogue. Of course, the classic games are awesome, and Sonic Team did an amazing job of making Sonic compelling and cool with what they had. Sonic's personality oozed from his sprites, something we take for granted in today's world of fluid cartoon graphics and motion-captured acting. I'm certainly not going to argue in favour of SatAM by turning around and shitting on the games' world.

But there are some clear reasons why Sonic's world is far less developed in the games as opposed to the cartoons. Video games are not necessarily a more limited medium than cartoons, but the particular type of game that Sonic was - action platformer - is certainly more limited in terms of telling story. In fact, the whole reason why there's a SatAM universe in the first place is that very fact! If the creators of Sonic's first game had filled in all the blanks, the cartoons would have been a 1:1 copy and paste job.

Adapting an action video game to a story-based medium is fraught with difficulties, for the nature of early video games makes creating a compelling serial adventure saga out of one nearly impossible without great changes and total shifts in focus. Unless you want to have a show with a lot of jumping and gathering floating objects with the occasional interjected bonus round, something drastic need be done.

Take Pacman, for example. How does one tell an engaging story about running around and chomping Power Pellets? How about a new story each week? Hanna-Barbera actually took on this challenge, producing a Pacman cartoon in the early 80's. When I first learned of it (by watching re-runs on Boomerang a few years ago; the original show was a bit before my time) I was incredulous that such a thing was attempted because the Pacman "universe" was just so narrow in scope. (I really shouldn't have been surprised - television managed to scrape together Carmen Sandiego and Rubik's Cube cartoons as well, so I guess they'll try anything.) But I found the Pacman cartoon to be boring and silly precisely because it retained too many of the game's abstract gameplay elements.

The problem is the mentality that the show has to be about the actual video game. This is what leads to the Pacman problem that sees your show full of things that frankly don't make any sense to a television audience (unless they're the Scott Pilgrim consuming, canny audience of today). But let's shake the mentality by flipping it around. If one were to adapt a TV show to a video game, they'd never make it one long digital cinema scene! They'd work to add the elements that make a video game a video game, and worth playing instead of watching. Sonic doesn't gather Rings or jump on floating platforms in the games because it's something Sonic as a character must do to remain unmistakably himself - they're things he does because they're fun to do in a game. But they aren't much worth watching. In the same way, in a Home Improvement video game, Tim Allen fights dinosaurs - because (as we all know), that's what video games are for. Fighting dinosaurs.

So the trick is to picture the Sonic games not as the be-all, end-all perfect template that all other Sonic iterations must slavishly adhere to, but as video games adapted from some hypothetical story - the Platonic Sonic, if you will. =P

Seen like this, it's obvious that things like SatAM can't be denigrated for not being just like the games, any more than the games can be denigrated for not being just like SatAM. Which came first is irrelevant - they are both windows to a grander whole, in the style of their own particular medium. Clung-to ideas like "Sonic should be a silent character" because "he never talked in the games" are revealed as silliness - it would be like arguing that Final Fantasy Advent Children should have exclusively employed blue text boxes to impart its story (though, in fairness it might have made more sense that way! =P)

One might fairly say that this is no excuse for Sega (or a fan like me) to justify making a Sonic game full of tedious story, exposition, or a preponderance of SatAM characters to the exclusion of what makes the games great. But no one would want story to get in the way of their game in the first place, no matter what universe it took place in. It's fine to not want a heavy-handed Sonic game (none of the good ones have been, after all), but why should it really offend if the side characters are from SatAM, new folks like Chip and Professor Pickle, or even a mix suited to the tastes of the developer? I know some would scream foul if Sega ever had the nerve to give a nod to what's fast becoming the "lost" Sonic of the west, but would it really hurt? Wouldn't it finally patch the gaping divide that every Sonic fan has to deal with? They've taken the first step with chili dogs in Sonic Unleashed. I for one would be happy if they went even further, perhaps even half-and-half.

"Modern" Sonic is now well-established, and over a decade old. It's still as maddeningly tight-lipped as ever when it comes to acknowledging a significant slice of Sonic's legacy. For many fans, maybe it's starting to slowly erase how they used to picture Sonic. With time (and repeated beatings) comes acceptance, and learned helplessness. But there's no good reason to take it sitting down - you can stand up for yourself and your childhood and ask "why?" Why be so quick to accept that Sonic has moved on? (It's like the infuriating people who like to say "Classic Sonic is never coming back, so deal with it!", as if they can predict the future or something. PROTIP: They can't.) Most of the latest Sonic games have been crappy, by fan consensus and critical review alike. It's not like Sega has proven they can do just fine and dandy without the American universe, thanks. So let's - amidst the endless barage of those who would love to shout us down with cries of "it's not even really Sonic" - remember what's actually what here.

Am I reacting too strongly? Is there really that much resistance to the SatAM elements in the larger Sonic community? Well, to me it sometimes feels like there's a SatAM mitigation brigade, just waiting for you to mention it at which point they are moved to post, all but telling us we're fools for continuing to consider the show to be Sonic. This happened just recently at Sonic Retro when Richard Kuta (the force behind the nascent Animated Sonic Fan Film) said this:

However, Sonic is a rare case where Americans made the franchise better! If I was introduced to Sonic during the whole Sonic Adventure/Sonic X era, I would have no interest cause it comes off as a generic, paper-thin anime stereotype. Excluding Sonic Colors, everything beforehand [in the modern Sonic era] was garbage. Sonic had little to no personality and his ballooned cast of friends had no character development. Even though Adventures of Sonic and Sonic Underground were cheesy and campy, Sonic actually had a likable personality.

(For context, the discussion was basically about how Sega of Japan appears to want to have nothing to do with Sega of America's Sonic anymore, even going so far as to attempt to kill Kuta's film.)

But then David the Lurker replied:

But I guess what I'm saying here is that SatAM was not the be-all, end-all of Sonic the Hedgehog. The main writer of the show, Ben Hurst, took one look at the games and threw out EVERYTHING that made it Sonic. I'll admit that when I was a kid, I loved the show.

Which doesn't refute Kuta's point one bit! He was saying how Sega of America bolstered the Sonic franchise with valuable elements, but he gets this thrown in face as if the improvements don't count because it's not exactly like the games. (Also, to nitpick a little, Ben was not actually "main writer" of the show, just particularly prominent, especially in Series 2. And to lay all the blame for SatAM's format on him is misleading, he and the story team would have been working with Sega of America's production bible concepts that were evolving then. Pressed for time, the writers never were given a grand tour of the Sonic franchise, apprised of each detail by Yuji Naka, and then proceeded to chuck it all out in favour of pet ideas. That's a fantasy.)

It may seem like I'm doing a petty thing here and complaining about the behaviour of an individual from the safety of my blog rather than responding critically in the original thread. This is not my intention; I've got nothing against David the Lurker. I am merely using this (mild) incident from a public forum as an example of what I'm talking about, because it's bad form to complain about a phenomenon without citing a single example of it.

It illustrates exactly the type of thing I mean about folks, not just disliking Americanised Sonic, but actively trying to dissuade others from considering it part of the Sonic whole. And this quote is from someone who professes to have liked the show when younger! Why, then, drink Sega's koolaid and allow your concept of Sonic's universe to be replaced piece by piece until it's nothing like it was when you first fell in love with it?

The lack of Item Monitors, Lamp Posts, and cyclical Robotnik boss confrontations make SatAM no less Sonic than the lack of the "SCORE-TIME-RINGS" counter. On the other hand, the use of samey Swatbots, or the underuse of the Tornado biplane... these are legitimate complaints and I do wish these things had appeared in the show (no more or less, though, than I wish that elements of the show had been in the games, so this in no way reflects badly on SatAM). The fact remains that Sonic the Hedgehog - the real Sonic the Hedgehog - fought for the freedom of his friends and his planet from the robotic tyranny of Robotnik. And isn't that what really counts?

30 December 2010

First Attempt

I thought it would be funny to show off my very earliest attempt at making a Sonic engine in Game Maker, so here's the link.

Now you can laugh at all the glitches and mistakes I made 5 years ago! I know, it's a poor way to tide folks over until the new shiny version is released.

23 December 2010

!SCIENCE #4

As promised...

This, my friends, is why I should concentrate on programming. =P