code of the Ninja();

// in search of swift, efficient, and invisible code

2010-09-13

The Nitpicker's Guide to Sonic Genesis - Part II

Hello again, Code Ninjas! It has been quite a while since Part I, but never fear - slowly but surely I will give Sonic Genesis the drubbing it deserves. Welcome to The Nitpicker's Guide to Sonic Genesis - Part II.

Code Flaw #002: The Demos Are Totally Nerfed

Original:

GBA:

The demo is completely different in the GBA version, and obviously much worse (Sonic is hurt twice, seems disoriented, and demonstrates less of the level). Why should this be? If we were uncharitable, we might chalk it up to the GBA team recording new demos which betray their underdeveloped skill level. However it is much more likely that the game is using the same demo data, but changes in the physics have thrown it off.

You see, the demo "movies" in the old Sonic games were not actual videos of the action - that would have taken up so much space it would have been prohibitive. Instead, the game itself is running, but with two changes: 1) the game resets when the player hits the start button or when 30 seconds have elapsed, whichever comes first; 2) the Sonic object isn't receiving input directly from the joypad, but from a chunk of data read sequentially from the ROM. This chunk of data was made by logging the button presses while someone played the level (for more information about recording joypad input "movies", see this post).

So, if the physics or level layout changes, the recording won't be appropriate anymore. It's as if your joypad were simultaneously plugged into two Sega Genesises (Geneses?), one of which contained a Sonic 1 cart, and the other, Sonic 2. You might be playing Sonic 1 beautifully, but anyone watching the Sonic 2 game will wonder why Sonic is suddenly acting like a drunkard. (They won't notice a difference in Tails' behaviour, though - wantonly flinging himself into harm's way is business as usual for him. =P )

The team who made Sonic Genesis should have recorded new demos after the physics were in place to avoid this issue. But, one can hardly blame them for not doing so; it would, after all, have involved playing the game, and I wouldn't wish that on anybody!

Fixing The Problem In Your Own Hack

This issue with screwy demos isn't confined to Sonic Genesis. ROM hacks will have the same problem if the physics, controls, or layout are changed, even slightly. I encountered it myself when making Sonic: The One Ring, and needed to find a way to record new demos that would be compatible with the ROM. I've turned what I learnt into a tutorial and utility - go here for the complete story. See, I'm not just complaining about Sonic Genesis just to be mean - I'm using it to frame programming tips to help you guys out! Aren't I nice?

Well, not that nice since I'm now going to throw in a few freebie Bonus Flaws out of spite!

Bonus Flaw #003: Wrong Credits Footage

In addition to having broken demos, the "demos" seen during the game's credits are broken, too. But none so badly as the Labyrinth Zone one: this time, not only does the control movie not sync up because of different physics, it's totally inapposite because the wrong area of the level has been loaded!

The famous underwater section where Sonic is pulled through the tunnel by the current, catching on to the breakable poles and avoiding spikes, has been inexplicably replaced by some other region of the zone.

Bonus Flaw #004: Marble Zone Button Keeps Turning Up Like A Bad Penny

This is pretty hilarious. It's supposed to load different graphics depending on the zone ID, but they can't even get that right.

Bonus Flaw #005: Underwater Palettes Incorrect

Sonic and the Badniks don't look right...

Original:

GBA:

...but objects like doors and blocks fare even worse.

(And why does Scrap Brain Act 3 have such a horrible dark blue background? It's supposed to be a lovely, rich purple.)

Bonus Flaw #006: Missing/Incorrect Background Tiles

This flaw really does take the cake. This isn't a matter of not being able to properly port a complicated game's physics to a new platform, or something relatively forgivable. It's a simple matter of gratuitous incompetence and unconcern for the product.

Well, try to keep your lunch down, Code Ninjas - I know it's not easy after that rogue's gallery. Until next time!

2010-03-19

2D Camera

Welcome back, Code Ninjas!

It's a bit anachronistic to use the word "camera" in reference to 2D games. The concept of the viewable area as the view through a director's camera really only took off with Super Mario 64, whose 3D worlds required the player to be actively mindful of the viewpoint. Four entire buttons on the Nintendo 64 joypad were dedicated to camera control (though they often found other uses), and Super Mario 64 even went so far as to characterise the camera as a Lakitu floating on a cloud, following Mario wherever he went.

However, we live in a post-3D world, and it's justifiable to consider the view in a classic 2D sidescroller to be a "camera". In this Code of the Ninja, we'll be looking at how to implement a natural feeling camera in a sidescrolling game.

Game Maker includes built-in camera functionality. Just about anyone who's used it will be familiar with the "views", and the view_object variable. When view_object is set to the id of an instance, the view will follow that object around automatically. You can adjust some border and speed settings, as well.

For a lot of simple games, this works out just fine. But for anything like Sonic or Mario, which require a bit more flexibility in their camera, it's a better idea to write new camera scripts and ignore Game Maker's built-in object following altogether. (You'll still need to define a view, though, of course. Otherwise the entire room will be shown, scaled to fit the window.) So make sure the view_object is set to none, and let's begin.

Camera Follow

We'll make a new script called "CameraFollow()". If you're going to only ever follow one object in your game, such as the player, you could just call this script in the player object. However, oftentimes we'll want to change which object is followed (perhaps keeping an eye on the boss in a boss fight, for instance). That means it's better to write CameraFollow() to take an argument of which instance to follow, and call it in a persistent control object (you can make a dedicated Camera object, or just call it in whichever existing control object you already have, such as the HUD or an Input handler).

Also, CameraFollow() must be called after the target object has already moved. The best way to make sure of this is to call CameraFollow() in the End Step Event.

script: CameraFollow()

//define centre
cameraCentreX = view_xview + (view_wview/2);
cameraCentreY = view_yview + (view_hview/2);

//determine offset
cameraOffsetX = floor(argument0.x) - cameraCentreX;
cameraOffsetY = floor(argument0.y) - cameraCentreY;

//update view
view_xview += cameraOffsetX;
view_yview += cameraOffsetY;

We'll be adding more features to this script as we go, but I've started with this simple version that simply keeps the target object in the centre of the screen. You can try it now, and it should work.

How's does it work, though?

First, we find the horizontal centre point of the view as it currently stands. That's view_xview (the left edge of the view) plus half of view_wview (the width of the view). We store this value in cameraCentreX. Then we do the same thing to find the vertical centre point, and store it in cameraCentreY.

Note: Some games, such as Sonic the Hedgehog, don't use a perfectly centred view. They bias the camera slightly upward, to show more of what's beneath the player. If you wish to do the same thing, you can replace (view_wview/2) and (view_hview/2) with custom values; or, alternatively, you can add bias values on top of the existing calculation, which may be necessary if your view width or height change during the game (for widescreen toggling purposes, etc).

Next, we find how far away from these desired centre points the target object's (argument0's) x and y positions are, by subtracting the centre point values from the target object's x and y. The difference between them - the offset - we store in cameraOffsetX and cameraOffsetY.

Note: We use floor() on the object's x and y at this point because x and y are often at noninteger (subpixel) values, but the view in Game Maker doesn't render at such positions. Instead, it rounds view_xview and view_yview off. Unfortunately, as rounding sometimes results in rounding up and othertimes rounding down, this can cause jitter. All this is avoided by flooring the object's x and y before using them in any calculations.

Finally, we simply add these offset values to the view x and y position, in effect moving the view by the exact same amount the player moved away from the centre point. (It may seem a roundabout way to have done this, but it's being set up for more complicated functionality later on.)

Staying Inside

Before we add new features to our script, though, there is one problem with it we need to patch up. Unlike Game Maker's built-in object following, this code allows the view to exceed the room boundaries. Depending on how you design your game, this might be a bad thing.

The solution? Create a new script called CameraLimit(). It should be called from CameraFollow(), after everything else.

script: CameraLimit()

if view_xview > room_width-view_wview view_xview = room_width-view_wview;
if view_xview < 0 view_xview = 0;

if view_yview > room_height-view_hview view_yview = room_height-view_hview;
if view_yview < 0 view_yview = 0;

Note: In this version of CameraLimit(), I've used the room dimensions. You can use any custom values you want - there's no strict reason why you can't exceed the room dimensions, even using negative numbers. In fact, since Game Maker doesn't let you resize a room while you're in it, the only way to dynamically change the limits is to use your own variables. Why change the limits? Imagine a boss fight in Sonic - the view is extremely limited, to keep the boss on the screen, but of course the actual room (which contains the whole zone) hasn't really changed size.

Free Zone

Now that our camera is properly chastened and stays within its designated confines, we can add a new feature to CameraFollow(). We're going to add a "free zone" - a region in the centre of the screen (of any size you wish) in which the character can move freely before the camera bothers to try and follow.

Why add such a thing? There are probably many reasons, but the major one is that centring the view so strictly on the player can cause it to move around too much when the player is making a small jump, or merely turning around. It's best to have a little buffer area, so that the camera doesn't seem to jerk so drastically.

How do we add this in? First, you need to decide how large this free zone should be. I'm going to use 8 pixels in either direction horizontally, and 32 in either direction vertically. You can use anything you think is reasonable, and it doesn't even have to be symmetrical.

script: CameraFollow()

//define centre
cameraCentreX = view_xview + (view_wview/2);
cameraCentreY = view_yview + (view_hview/2);

//determine offset
cameraOffsetX = floor(argument0.x) - cameraCentreX;
cameraOffsetY = floor(argument0.y) - cameraCentreY;

//free zone
if cameraOffsetX > 8 cameraOffsetX -= 8; else
if cameraOffsetX < -8 cameraOffsetX += 8; else
cameraOffsetX = 0;

if cameraOffsetY > 32 cameraOffsetY -= 32; else
if cameraOffsetY < -32 cameraOffsetY += 32; else
cameraOffsetY = 0;

//update view
view_xview += cameraOffsetX;
view_yview += cameraOffsetY;

CameraLimit();

That takes care of the free zone. All you have to do is subtract the size of the free zone from the camera offset if the camera offset is larger than the free zone, or set the camera offset to 0 if it's smaller than the free zone (so it won't move at all). There are multiple ways to code this; I chose a simple, if long-winded, method.

Speed Limiting

Next, we need to add speed limiting. Sometimes (but not all the time) you want the camera to only move a maximum number of pixels per step. This can be used for a sense of speed, as the camera lags a little behind the player (as sometimes happens in Sonic 2), but it can also be used to scroll the camera from one target object to another when the targets are quickly switched. If there was no limit on the number of pixels the camera could move per step, the view would immediately switch and the player might not understand what happened.

I've chosen 16px as the speed limit here. Let's add the speed limiting (again, there are several ways to code this):

script: CameraFollow()

//define centre
cameraCentreX = view_xview + (view_wview/2);
cameraCentreY = view_yview + (view_hview/2);

//determine offset
cameraOffsetX = floor(argument0.x) - cameraCentreX;
cameraOffsetY = floor(argument0.y) - cameraCentreY;

//free zone
if cameraOffsetX > 8 cameraOffsetX -= 8; else
if cameraOffsetX < -8 cameraOffsetX += 8; else
cameraOffsetX = 0;

if cameraOffsetY > 32 cameraOffsetY -= 32; else
if cameraOffsetY < -32 cameraOffsetY += 32; else
cameraOffsetY = 0;

//speed limit
if cameraOffsetX > 16 cameraOffsetX = 16; else
if cameraOffsetX < -16 cameraOffsetX = -16;

if cameraOffsetY > 16 cameraOffsetY = 16; else
if cameraOffsetY < -16 cameraOffsetY = -16;

//update view
view_xview += cameraOffsetX;
view_yview += cameraOffsetY;

CameraLimit();

Now we've added the speed limit, we can match Game Maker's built-in object following point for point. Now to add some even more powerful stuff.

Looking Around

In Sonic, Mario, and countless other platformers, you can look up and down, shifting the view slightly to see what's above and below you. In Super Mario World, you can use the L and R buttons to shift the view left and right, as well. Let's add these abilities.

In the player control scripts, when looking up and down, or even left and right, you'll need to add to and subtract from variables which CameraFollow() will use to shift the view. I'll call these cameraShiftX and cameraShiftY.

For instance, pressing Up would subtract 2 from cameraShiftY every step, until it reached the maximum shift you desire. Pressing Down would do the opposite, adding 2 until the maximum shift was reached. In the case of neither button, cameraShiftY would slowly return to 0. (For Super Mario World's L and R shifting, the horizontal shift doesn't drift back to normal upon letting up the button, though. It remains shifted until the player shifts it back.)

Some games actually shift the view horizontally depending on the direction the player is facing. I find this annoying, myself - when turning around and making a jump, the whole screen starts moving, making it harder to line up where to land. But this, too, can be done with the same cameraShiftX variable.

Now, to take the shift into account, all we have to do is change the lines in CameraFollow() that determine the offset. Replace them with these:

script: CameraFollow()

...
//determine offset
cameraOffsetX = floor(argument0.x + cameraShiftX) - cameraCentreX;
cameraOffsetY = floor(argument0.y + cameraShiftY) - cameraCentreY;
...

By adding cameraShiftX and cameraShiftY to the target object's x and y when determining the offset, the camera is technically not following where the player is, but where the player is looking. When the player isn't looking around, cameraShiftX and cameraShiftY return to 0, which is the same as following the player itself.

Re-centring Upon Landing

In Sonic the Hedgehog, the camera behaves differently when Sonic is in the air as opposed to running along the ground. In the air, Sonic has a generous vertical "free zone" before pushing the camera around. But on the ground, the camera keeps him at dead vertical centre, so that when he runs over hilly terrain, the camera follows properly. (The camera behaves the same, horizontally, in either state.)

This is simple enough. You can just add a check in the CameraFollow() for whether he's airborne or not, and exit the vertical free zone calculation if he's on the ground.

Note: Though it works well enough to simply check if Sonic is in his air state, it's a better idea to add another flag in the target object, called GroundCamera, which you set to false when he jumps, springs, or falls, etc, and reset to true when he lands. Why a second flag when his state would do? In the case of Knuckles, when he glides and slides into the ground, even though he's technically landed, the camera doesn't return to normal until he stands up. Thus, it's better to have fine control over the mode the camera is in, independent of the actual state of the character.

If that's all we do, though, we'll be left with a problem. When Sonic lands from a jump, the camera jerks immediately to focus tightly on him. That's no good - it's too much of a jerk to put up with comfortably.

There are two ways to fix this. They both involve reducing the vertical speed limit of the camera to 6 instead of 16 after Sonic lands, so that the camera catches up slowly enough that it doesn't cause violent motion.

You can't simply leave the vertical speed limit at 6 all the time. Sonic often runs downhill, and his vertical speed will well exceed 6. The camera would never catch up if it couldn't go faster than 6 pixels per step! So it's necessary to determine whether Sonic has just landed or not.

The first way is to check his speed. If his vertical speed is less than 6, make the speed limit 6. If it's more than 6, make the speed limit 16. Chances are his vertical speed will be very low after landing on the ground. This method is similar to how the 16-bit Sonic engine does it.

The second way is to set a flag called JustLanded to true when Sonic lands (you also have to set it back to false when he jumps). While it's true, the vertical speed limit should be 6, and while it's not, the vertical speed limit should be 16. The second the camera catches up with Sonic, you can reset JustLanded to false. How can we tell when the camera catches up to Sonic? Check if abs(cameraOffsetY) is less than or equal to 6 (i.e., Sonic isn't more than 6 pixels above or below the vertical centre point). In any step where where that's true, the camera will catch up.

script: CameraFollow()

...
//speed limit
if cameraOffsetX > 16 cameraOffsetX = 16; else
if cameraOffsetX < -16 cameraOffsetX = -16;

var cameraLimitY;

if argument0.JustLanded cameraLimitY = 6; else
cameraLimitY = 16;

if abs(cameraOffsetY) <= 6 argument0.JustLanded = false; else
if cameraOffsetY > cameraLimitY cameraOffsetY = cameraLimitY; else
if cameraOffsetY < -cameraLimitY cameraOffsetY = -cameraLimitY;
...

Jump To A Point

Now that that's all working, there's one last thing to add. Because our camera has a speed limit, when the level starts, you'll have to wait for the camera to scroll to where the player is before you can start playing. This kind of sucks.

The remedy is a script called CameraJumpTo(). You can call it to immediately centre the view around any point you specify. Call it as the game begins to focus on the player.

script: CameraJumpTo()

view_xview = argument0 - (view_wview/2);
view_yview = argument1 - (view_hview/2);

CameraLimit();

The script takes two arguments: the x and y to point at.

Example GMK

For an example GMK, click here.

Well, that's it for custom 2D camera. Until next time, happy coding, fellow Code Ninjas!

2010-02-22

Text Boxes

Welcome back, Code Ninjas!

Last time, I talked about sinusoidal motion, a way to make certain movements and animations look more natural. I mentioned that it could be used to make an opening animation for text boxes. This time we'll be looking at text boxes themselves.

We've all seen text boxes. They're the windows full of dialogue that appear when you talk to people in video games.

It's easy enough to slap a single box of text onto the screen in Game Maker. But most text boxes consist of several pages of dialogue, and the player advances through them by pressing a button.

You could achieve this with an array of strings, like this:

Create Event (TextBox object):

//define the pages of text
//(# is the newline character in GML)
Page[0] = "Hello there, traveller!";
Page[1] = "This is a bomb shop! I stock all#sorts of different explosives.";
Page[2] = "No smoking, please!";
//set the page index
PageIndex = 0;

Draw Event (TextBox object):

//draw the string on the screen
draw_string(TextX,TextY,Page[PageIndex]);

Step Event (TextBox object):

//check for a press of the A button
if JoyButtonPressed(A);
{
  //increase the page index
  PageIndex += 1;
}

This is an okay method, but it has an annoying problem: You have to manually cut the dialogue into pages yourself, as well as place the newline characters.

Imagine you have a game where you want the text box to be resizable. Or, not all text boxes are the same size (as in Final Fantasy VII). Or, you've already written all the dialogue for your RPG, and then decide to change the size of the font or the text box. The method above would suck in these cases - you'd be stuck reworking all your strings every time something changed.

There really needs to be a way to just write the dialogue all in one piece, and let the game take care of the rest: deciding when to break lines, and cutting it into individual pages.

Well, let's see what we can do...

Making Pages

This time we'll give the TextBox object only a single string of dialogue. This will be the source text that the pages are made out of. Also, in order to make the pages contain the right amount of text, the TextBox object will need to know about its size.

Create Event (TextBox object):

//define the source text
//(which can be from the calling object, or loaded from a text file, whatever)
DialogString = "...";
//set dialog offset to 1. This is the position in the source text to start reading from.
DialogOffset = 1;
//set dialog length to the length of the source text. This is the position in the source text to stop at.
DialogLength = string_length(DialogString);

//set position of text box
x = 40; y = 300;
//set size of text box
width = 560; height = 100;
//set size of border (horizontal and vertical)
xborder = 8; yborder = 4;
//determine the size of the text area (text box minus the borders)
textwidth = width-xborder*2;
textheight = height-yborder*2;
//set the height of individual lines
linespacing = 23;

//make the first page of text to show
MakePage();

Step Event (TextBox object):

//check for a press of the A button
if JoyButtonPressed(A);
{
  //make the next page of text to show
  MakePage();
}

We call the MakePage() script every time the player presses the button, to construct the page of text that they'll see next. We also call it once in the create event, so that there's an initial page showing.

MakePage() basically bites off a chunk of the DialogString source text and puts it into a new string, CurrentPageString, which is the string that will be drawn.

script: MakePage()

//set up some temp variables
var numLines,line,letter,word;
line = 0; word = "";
//set the font to the current font so that the font measuring scripts work right
draw_set_font(TextBoxFont);
//empty the CurrentPageString, so we can refill it with text from DialogString
CurrentPageString = "";

//get the number of lines that fit in the box, based on line spacing and height of box
numLines = textheight div linespacing;
//show error message if no lines fit in box
if numLines = 0
{
  show_error("No lines fit in the text box!",1);
}

//main loop
do
{
  //read a letter from the source text
  letter = string_char_at(DialogString,DialogOffset);
  //increase the offset by one since you read one letter
  DialogOffset += 1;
  //is the letter the escape char?
  if letter=="^"
  {
    //change letter to return
    letter = "#";
    //increase the line count to full
    line = numLines;
  }
  //add the letter to word
  word += letter;
  //if the letter was a space, hyphen, or return (or the end of the source text was reached), the word is complete
  if letter==" "||letter=="#"||letter=="-"||DialogOffset>DialogLength
  {
    //check to see if word alone exceeds the textbox width
    if string_width(word)>textwidth
    {
      show_error("Single word is too long for the textbox!",1);
    }
    //check to see if word added to current pages's text is too wide
    if string_width(CurrentPageString+word)>textwidth
    {
      //add a return to go to the next line, and increase the line count
      CurrentPageString += "#";
      line += 1;
      //if this was the last line...
      if line = numLines
      {
        //return the offset to the beginning of the word in order for the next page to start at the right point
        DialogOffset -= string_length(word);
        //blank out the word so it won't be added.
        word = "";
      }
    }
    //only add the word if it hasn't been blanked out
    if word != ""
    {
      //add the word to the current page's text
      CurrentPageString += word;
      //if letter was a return, increase the line count
      if letter="#" line += 1;
      //and reset word to blank
      word = "";
    }
  }
}
until (line >= numLines or DialogOffset > DialogLength)
//stop the loop when reach the last line or the end of the source text

With the comments, MakePage() should be pretty much self-explanatory, but there are two points I want to go into more detail on.

The first is the "escape character", ^. What is it for? Well, it's sort of like a page break. Sometimes you want the sentence of dialogue to end, and not start the next sentence until the player advances to the next page, even if there's enough space to fit the next few words. It all depends on the flow of the dialogue.

I used the caret because it's sufficiently obscure, but of course the escape character can be anything you want to define it as. If your RPG townsfolk are going to use emoticons like ^_^ then you might want to pick something else.

The second point is this: Why is the MakePage() script so complicated? Anyone familiar with GML will know that you can use a function called draw_text_ext(), which will automatically word wrap to any width that you specify. Why do I go through so much trouble to manually run through the string and add newline characters to cause it to wrap?

It becomes clear as we move on to the next aspect of text boxes. They have to type out.

Typing Out

In order to make them type out, we shouldn't draw CurrentPageString in the draw event. Instead, we should make a new string, ResultString, and draw it. ResultString will be built up from CurrentPageString in the step event of the TextBox object.

Draw Event (TextBox object):

draw_set_font(TextBoxFont);
draw_set_halign(fa_left);
draw_set_valign(fa_top);
draw_text_ext(x+xborder,y+yborder,ResultString,linespacing,-1);

Step Event (TextBox object):

//if the text box is typing out the text
if printing
{
  //increase CharIndex
  CharIndex += 1;
  //if CharIndex is the size of the page of text
  if CharIndex >= CurrentPageLength
  {
    //fill the ResultString with the entire current page and stop typing out
    CharIndex = CurrentPageLength;
    ResultString = CurrentPageString;
    printing = false;
  }
  else
  {
    //otherwise, make the ResultString as much of the current page as CharIndex is large
    ResultString = string_copy(CurrentPageString,1,CharIndex);
  }
}

We need the new variables, 'printing' so that we know when it's typing out and when it's done, 'CharIndex' to increase each step so we can keep taking more and more of CurrentPageString, and 'CurrentPageLength' so that we know when we've finished going through CurrentPageString. These three will need to be set up at the end of MakePage() now.

script: MakePage()

...

CurrentPageLength = string_length(CurrentPageString);
CharIndex = 0;
printing = true;

Now it'll print out. It's because of this that MakePage() needs to be so complex. If we relied on draw_text_ext() for word wrap, we'd get ugly results. Because we're actually drawing ResultString to the screen, and ResultString builds up letter by letter, the computer wouldn't know if a word was going to run off the side of the text box until after it had printed fully out. This would result in seeing words print out of bounds, and then skip on to the next line. MakePage() comes to the rescue here, determining where the lines should break before ever being printed, so that the words "know" to be on the next line before they even finish printing out.

Well, now that we've got our dialogue typing out, you'll notice a new problem. When the user presses the button, it'll skip to the next page. We don't want to do that, if the current page hasn't finished printing out. Instead, we want to instantly finish typing out the current page. Only if the user presses the button again should it advance one page.

This will require modifying the step event.

Step Event (TextBox object):

//if the text box is typing out the text
if printing
{
  //increase CharIndex
  CharIndex += 1;
  //if CharIndex is the size of the page of text OR the user presses the button
  if CharIndex >= CurrentPageLength or JoyButtonPressed(A)
  {
    //fill the ResultString with the entire current page and stop typing out
    CharIndex = CurrentPageLength;
    ResultString = CurrentPageString;
    printing = false;
  }
  else
  {
    //otherwise, make the ResultString as much of the current page as CharIndex is large
    ResultString = string_copy(CurrentPageString,1,CharIndex);
  }
}
else
{
  //if it's not typing out, pressing the button should advance one page
  if JoyButtonPressed(A)
  {
    //but if we're on the last page, we should close the text box
    if DialogOffset >= DialogLength
    {
      instance_destroy();
      exit;
    }
    //otherwise, determine the next page of text to type out
    MakePage();
  }
}

What we've done is check for a press of the button while 'printing' is true, and made it do the same thing as reaching the end of the page: ResultString becomes CurrentPageString in total, and 'printing' is set to false. Also, we've made the standard check for the button only happen when 'printing' is not true.

I've also added a check at that point if it's the last page or not. If the player presses the button on the last page, there's no new page to advance to, so the text box should close instead of calling MakePage() again.

Now that it's all working, we should add some visual cue so that the player knows that the button does something different at different times. While the text is typing out, the button skips to the end. While it's not, the button advances one page. On the last page, the button closes the text box.

Most games don't bother with a different icon for each possible state. They just show a triangle or something once the text is done typing out, so that you know there's more. If it's the last page, the triangle simply doesn't appear when the text finishes appearing.

It's easy enough to check for all three states, though, so this is how you can do it - add this to the draw event:

Draw Event (TextBox object):

...

if printing
{
  //draw "skip" icon/message
}
else
{
  if DialogOffset >= DialogLength
  //draw "close" icon/message
  else
  //draw "next" icon/message
}

Variable Text Speed

That's pretty much it for text boxes. But there's some nice finishing touches we can add - variable text speed, for one. In the code blocks above, the text types out at 1 character per step. This speed should be under the player's control, because everybody reads at a different rate.

All that needs to be done is replace the line that says

Code:

CharIndex += 1;

and replace it with

Code:

CharIndex += textspeed;

The text speed can be set to 1 at the game start, and then the player can change it from an option menu. Or - and this is pretty cool - since the left and right buttons usually do nothing while a text box is open, you could let the player alter the text speed any time a box is open.

Just add this to the step event:

Step Event (TextBox object):

if JoyButtonPressed(LEFT)
{
  //decrease the text speed
  textspeed /= 2;
  if textspeed < 0.25 textspeed = 0.25;
}
else
if JoyButtonPressed(RIGHT)
{
  //increase the text speed
  textspeed *= 2;
  if textspeed > 8 textspeed = 8;
}

You can make the upper and lower limits anything that seems reasonable to you. However, the difference between settings isn't enough when they're linear. They need to be logarithmic. So, instead of adding to and subtracting from textspeed, I suggest you multiply and divide it (or bit shift it).

There should be lights or pips or something worked into the design of the text box, so that the player has a visual clue to the setting that the text speed is at.

Canceling

Finally, you can add this to the step event.

Step Event (TextBox object):

if JoyButtonPressed(B)
{
  //close the text box
  instance_destroy();
  exit;
}

This lets the player hit a cancel button to close the text box whether the dialogue is finished or not. It's really annoying to accidentally re-talk to a character and be forced to page through their entire diatribe when you've already read it. You might want to add a check so that the player can only leave like this if they've talked to the person before, but I think the player ought to be able to cancel even if it's new dialogue. There might still be certain important story driven dialogue that they can't cancel, though.

Example GMK

For an example GMK, click here.

Until next time, happy coding, Code Ninjas!

2009-12-08

Sinusoidal Motion

Welcome, Code Ninjas!

This time we'll be looking at a simple script that makes certain motions look more natural.

Imagine you have a platform that you'd like to move back and forth, a common element in platformer games. You might use code something like this:

Create Event:

r = 64;//maximum distance in pixels the platform may travel from its origin before reversing direction

s = 1/32;//speed of the platforms. The divisor is how many steps you wish the platform to take to reach its maximum distance from its origin.

Step Event:

if a //moving forward...
  {
  p += s;
  if p >= 1 { p = 1; a = 0; }
  }
else //...and moving back
  {
  p -= s;
  if p <= 0 { p = 0; a = 1; }
  }

x = xstart + r*p;//update platform's position
xspeed = x-xprevious;//get platform's speed in pixels

(Note: If this code seems a little overcomplicated, it is because it had been purposefully written to be able to make platforms of any speed and range.)

This effect that this code achieves is a platform that moves away from its starting point at the specified speed, reaches its maximum distance, and then immediately reverses direction and trots back to repeat the process indefinitely. This is the sort of platform you'll often see in early 8- and 16-bit games.

The trouble with this kind of motion is the immediate reversal of motion. In one step, the platform can be moving with a speed of +5 pixels, and the in the next, with a speed of -5 pixels. This doesn't look very realistic, because in reality, most often when something reverses direction, it has to slow down to a halt, and then begin to accelerate again.

This jerky motion isn't so bad if the platform's "patrol area" is bounded by walls at its extremes - then it just looks like the platform is bouncing off of the walls, and the motion doesn't look too bad. But if the platform is floating in mid-air, as they often are, there is nothing that appears to plausibly reverse it, and the motion looks unnatural.

And it's worse than just looking unpleasant. It actually makes the game less fair, and less fun. If the player can't tell by some visual cue when the platform is going to decide to turn around, they have a much harder time getting the proper timing on their jump. They will have to use more trial and error, watching the platform make its rounds more than once before they can confidently make their move.

This is almost game-breaking if you want to keep good flow, as in a Sonic game. In the Sonic the Hedgehog games for the Mega Drive (Genesis), almost all platforms not bounded by walls move with a natural motion - decelerating as they reach their extremes, and accelerating toward their point of origin as they turn back around. This allows players to intuit exactly where the platform will be any time when they first come across it, without patient study of its entire cycle. This is one of the subtler points about the Sonic the Hedgehog game, seldom recognised, but it contributes not insignificantly to the sense of speed that made them popular.

Well then, how can we achieve the same effect so that our platforms move with a natural motion, rather than an outdated, unrealistic, and unfair jerky one? Why, with the trusty cosine function, of course!

Imagine, now, a platform that - instead of moving simply up and down, or left and right - moves in a complete circle, as many do in Sonic and Mario games. The platform's speed should be uniform, but if we were to look at just one component of its velocity - say, just the xspeed, or just the yspeed - we would notice acceleration and retardation of its speed. Simply imagine looking at the platform's circular path edge on, instead of face on. It would appear to be moving in a straight line, but slowing down at the edges and speeding up in the middle, just like those Sonic platforms we want to emulate.

So, in effect, what we want to do is make the platform move in a circle - just a really flat circle that might as well be a line. We'd use code something like this:

Create Event:

r = 64;//maximum distance in pixels the platform may travel from its origin before reversing direction (radius of the circle)

s = 180/32;//speed of the platforms. The divisor is how many steps you wish the platform to take to reach its maximum distance from its origin. (This time we use 180, not 1, because we'll be using degrees.)

Step Event:

a += s;
if a >= 360 a -= 360;
//alternatively the preceding two lines could be 'a = (a+s) mod 360;'.

//also, here we don't need to use two states, forward and back, because the circular motion takes care of that for us automatically.

x = xstart + r*cos(degtorad(a));//update platform's position
xspeed = x-xprevious;//get platform's speed in pixels

To make the platform move vertically, we can just replace all the references to x and make them y. Or, to make it actually move in a perceptible circle, we can have both sets of lines. By using a different range value for both x and y, you can squash the circle into any sort of ellipse you want - for example, a circle that is twice as wide as it is tall:

Create Event:

xr = 64;
yr = 32;

Step Event:

//...

x = xstart + xr*cos(degtorad(a)); xspeed = x-xprevious;
y = ystart - yr*sin(degtorad(a)); yspeed = y-yprevious;

Remember to subtract the sine for y, and add the cosine for x, otherwise instead of moving circularly, it'll just move in a smooth diagonal - which is actually another useful effect you might want to achieve.

Well, that about does it for platforms, but the power of sinusoidal motion goes far beyond. There are other applications, and for just such an example, I'll use the purpose for which I first I needed it myself.

In games like Phantasy Star, when you talk to townsfolk or shopkeepers, their dialogue appears on the screen inside of bordered window, or a 'text box'. In most games, the text boxes appear on the screen gradually, either opening up, dropping down, or fading in.

I wanted this animation to appear smoother, so I thought perhaps I could apply sinusoidal motion as the solution. But there was a slight hitch.

Imagine you want to fade in a window, from an alpha of 0 (invisible) to an alpha of 1 (fully opaque). You could simply add 0.1 for ten steps, but that wouldn't look very smooth. How about, instead, we use a sine function.

Code:

step = 18;

for {a=0;a<=180;a+=step}
  {
  alpha = sin(degtorad(a));
  }

Well, clearly this won't work. The value of alpha will go from 0 (the sine of angle 0), accelerate toward 1 (the sine of angle 90) and decelerate back to 0 again (the sine of angle 180). The text box wouldn't fade in, it would fade in and back out again just as quickly! This obviously isn't what we want.

But why not just use 90, instead of 180, so that alpha will stop at 1, thereby fading the text box in how we want it? Well, in that case, the fade would start smoothly, but stop abruptly. I wanted it to both start and stop smoothly.

I needed some way to have the alpha value "move" like a half-circle (slow start and stop), but only "traverse" a quarter-circle (start at 0 and end at 1).

So I made a script, called it 'sinusoidal()', and this is the function I used:

sinusoidal()

//argument0: any value between 0 and 1

return (cos(degtorad(180-(180*argument0)))+1)/2;

Now, sinusoidal motion can be employed anywhere by calling the script. The text box fade code ends up looking something like this:

Code:

step = 0.1;

for {a=0;a<=1;a+=step}
  {
  alpha = sinusoidal(a);
  }

This little script can be very versatile. You can use it to slide logos or menus onto the screen. You could use for flashing lights for smoother look. You could use it to animate a pendulum. You could even use it to make your character push a block (as Link does in the Zelda games) with a less abrupt and better looking motion. And with clever modification, who knows to what ends a Code Ninja might put it to.

For an example GMK illustrating the difference between normal and sinusoidal motion in several types of movement and animation (flashing, shrinking, swinging, sliding), click here.

Next time we'll be looking deeper into text boxes - how to make the text type out, change the text speed, and more. Until then, happy coding, Code Ninjas, and happy holidays, too!

2009-11-13

Watchers

Welcome, Code Ninjas!

Today I have a simple fix for something that bugged me about Game Maker. First, let me tell you about the problem, and then I will move on to the solution.

Many times I would be running my game, and realise that there was some variable I needed to know the value of. Game Maker allows you to list the values of variables that you specify in a readout, but only when running in Debug Mode.

The trouble is, once you are already running your game, there is no way to dynamically switch to Debug Mode. You have to close the game, and run it again. This is kind of an annoyance, because compile time can get quite lengthy.

So, I thought, is there any way to have a list of "watchers" built into the game, that I can invoke and dismiss at will?

At first I used a simple method that worked much like any HUD (heads up display) in a video game. The values I needed to keep an eye on were just printed on the screen. This was fine and all, but there was no good way to add to it while the game was running. Game Maker's built in Debug Mode allows you to add watchers at any time.

Then I got the idea to - instead of printing out the values of a fixed set of variables - read the variables from a ds_list.

This ds_list would contain a series of strings, each of which would contain the name of the variable to watch (mouse_x, image_speed, etc). By right-clicking the list, I would bring up an input box that would let me add a new variable name to the ds_list.

How can you read the value of a variable by its name? Game Maker helpfully includes the functions variable_local_get(), and variable_global_get(), which take as their only argument the variable's name in string format, and return the value of the variable.

However, there are still issues with this method. A) You can only return the contents of variables, not expressions. This means you can print out the value of mouse_x and things like that, but never the value of instance_nearest(PlayerObj), and other such useful functions. B) You can't tell whether a variable is global or local, a constant, or part of an array, so you're pretty much screwed.

But then I had the brainwave. Instead of using variable_local_get() and its ilk, I'd use the execute_string() command!

Using this method, the ds_list could be filled with strings containing variable names, expressions, anything - using the identical syntax as the Game Maker Debug Mode watchers use. In fact, I could even save and load the strings to TXT documents in the same format, making them fully compatible with Game Maker's normal Debug Mode.

When drawing the watchers onto the screen, all I have to do is read the strings from the list. Then, I use the execute_string() command to perform the string as if it were code. By prepending "return" and a space before the string when I do this, execute_string() will return the value, which can be then drawn on the screen.

Code will demonstrate this better:

Code:

//a for loop that steps through the whole list
for (t=0;t<ds_list_size(watchlist);t+=1)
  {
  //the code string in the list to be executed
  r = ds_list_find_value(watchlist,t);
  //align the text to the left
  draw_set_halign(fa_left);
  //print the code - this is how you'll identify the watcher
  draw_text(8,8+16*t,r+":");
  //return the result of the watcher
  r = execute_string("return "+r);
  //align the text to the right
  draw_set_halign(fa_right);
  //print the result of the watcher
  draw_text(192,8+16*t,r);
  }

Now we have a watcher display that's just as nice as the Game Maker one. With a little bit of polish, you can add functions for adding, removing, replacing, and editing watchers from the list, as well as saving and loading lists to TXT documents.

For Code Ninjas who may need a working example to make sense of all this, here is a zip file which contains a GMK example and a sample TXT document of watchers.

Happy coding!

2009-09-25

The Nitpicker's Guide to Sonic Genesis - Part I

Hello again, Code Ninjas, and welcome to the first ever Code of the Ninja special, The Nitpicker's Guide to Sonic Genesis - Part I.

Some Code Ninjas are a disgrace to their title - they fail spectacularly at our subtle art. Perhaps they lack the necessary commitment or training. Or, perhaps they are not entirely to blame, and the reason for their failure is a lack of time, or budget.

Either way, the results of their efforts suffer terrible scars, belying the shoddy and haphazard code underneath. This is unacceptable, for the Code Ninja should be swift, efficient, and invisible.

The outstanding example of such an unsuccessful mission is Sonic the Hedgehog Genesis, for the Nintendo Game Boy Advance. It is supposed to be a port of the 1991 Sonic the Hedgehog for the Sega Genesis (Mega Drive), but you'd barely know it. Whereas the original Sonic the Hedgehog is an exemplar of good programming by a true Goemon of code, this embarrassing port is a shambles, infamous for being the worst Sonic game ever. In fact, it has a strong claim to be the worst programmed video game ever (a distinction a certain Bubsy Bobcat is used to enjoying).

In this special series of Code of the Ninja, I aim to draw attention to each of Sonic Genesis's plenitude of flaws, with special emphasis on their likely causes. It is one thing to notice that Sonic Genesis is bad - it is entirely another to find out why. It is a testament to the degree of the abject failure of the Sonic Genesis programmers that the likely causes of the many glitches in the game are not opaque.

To be sure, I cannot be 100 percent certain of any of the causes I will cite. I do not have access to the programmers' code, nor the inner workings of their brains (and I'm grateful, for they would assuredly be terrifying), but I can make educated guesses. As a Ninja whose current mission plants him squarely in the wilds of his own Sonic engine, I am in a better position than most to make such observations.

As in the infancy of the discipline of taxonomy, before the advent of the field of genetics, one simply looked at the external features of a lifeform when classifying it. The underlying coded information, the recipe for those external features, was invisible to taxonomists at the time, just as Sonic Genesis's code is unavailable to me.

They made mistakes, certainly, especially because of the wonderful yet maddening effects of convergent evolution, but plenty of good work was done, enough to cement the endeavor as respectable.

It is in this spirit that I undertake nitpicking Sonic Genesis. Whether all of my evaluations turn out to be true or false, I hope many of them will be incising insights, which will arm inchoate Code Ninjas and help them avoid the same traps and pitfalls (some of which the Sonic Genesis programmers' feet are still sticking out of, accompanied by contented digestive noises).

As a bonus, I will be pointing out some extra flaws each time which were not the result of programming.

Code Flaw #001: Sonic is not synched to moving platforms

Original:

GBA:

Programming moving platforms in a video game is actually relatively easy. When the character object detects a platform, it remembers the ID of the platform. From then on (until the character falls or jumps off the platform), the platform's motion is simply added to the character's.

Sounds easy enough. But a lot of beginners (including me, back in the day) are surprised to discover upon running their game, that the character's movement is not perfectly synchronised with that of the platform.

It turns out that it all relies on the order in which the code is performed. Every frame of the game (and there are usually 60 per second), the objects perform their code. But they can't do this at the same time - they queue up and do it one after another.

If the platform moves first, then Sonic follows suit. Then the screen is refreshed, and the player sees Sonic stuck fast to the platform. All is well.

But what if the platform comes later in the queue than Sonic? Then, Sonic moves based on the speed or position that the platform had in the last step. Then the platform moves to its new position. Then the screen is refreshed. The player sees Sonic juttering about the general vicinity of the platform, but not firmly atop it. Sonic is lagging behind, basing his position on variables that are one frame out of date!

Unless all moving solids complete their code before the character object's routine is run, this will be a problem. In Game Maker, this would amount to putting the platform routines in the "Begin Step" event.

Apparently the "programmers" of Sonic Genesis were too rushed or lazy to bother with this simple fact, and so they fail to achieve decent moving platform physics - something that early NES games can do in their sleep. It's pretty pathetic, when you think about it.

Bonus Flaw #001: The background in the title screen isn't animated

Not only is there no paralax, and the clouds don't blow by on the breeze, but the waterfalls and sparkles on the surface of the lake are totally frozen! The GBA can palette cycle, so there seems to be no explanation for this besides sheer sloppiness.

Bonus Flaw #002: There is no shrapnel when crushing through walls

Yes, folks - the segments of rock (or metal, in Starlight Zone) simply disappear, accompanied by a lame "poit" sound effect that is nothing like the original. I'm guessing that the 6 month delay still wasn't enough time to implement a few bits of shrapnel flying away.

Well, that's it for now. The normal Code of the Ninja will not be interrupted by the Nitpicker's Guide, so I'll see you next time.

Happy coding!

2009-09-05

Smart Triggers

Welcome to another lesson, Code Ninjas! This time I'll be demonstrating a game design concept, and not actual code. It's pretty simple, actually, but it requires a bit of backstory.

In The Legend of Zelda: The Windwaker for the Gamecube, you can target enemies with the L trigger. They call this L-Targeting. In the options menu of the game, you can change the behaviour of the L-Targeting. The two settings are Switch and Hold. The difference between them is thus: In Switch Mode, you begin targeting by pressing and releasing the L trigger once. You then stop targeting by pressing and releasing the L trigger again. In Hold Mode, you begin targeting by pressing and holding the L trigger down, and you stop targetting by releasing the L trigger.

Now, this option is an important one. I use Hold Mode, myself, and play miserably in Switch Mode. Some of my friends, however, use Switch Mode, and perform admirably. One mode isn't really better than the other. It all depends on the type of player.

However, when using either mode, sometimes things still don't work out so well. For instance, in Hold Mode, during long battles against one enemy, your finger can get tired out squeezing the trigger the whole time. In Switch Mode, in battles with many enemies, pressing the L trigger again sometimes cycles to the next enemy instead of ceasing to target altogether. This makes terminating a confrontation and retreating a confusing process. Continually switching between modes through the option menu would be tedious, though, so a player tends to pick one mode and stick with it, warts and all.

While thinking about these issues, I thought of a simple third setting, which I called Smart Mode. Perhaps it does not solve all of the problems, and I can't quite test it out in Windwaker, but here is how it would work.

Basically, when a press of the L trigger is detected, a timer begins. In Game Maker, you would use an alarm event, or increase a variable every step. Anyway, then when a release of the L trigger is detected, one of two things would happen:

1 - If the timer was below a certain time (say two-thirds of a second, about), you wouldn't stop targeting. It's rare that a player would want to target something for so short a time. At this point, it would require another press of L to cease targeting.

2 - If the timer was above that time, then you would cease targeting. In this way, a natural quick press and release of the trigger would enter Switch Mode, and pressing and holding down the trigger would enter Hold Mode. In a way, both modes would be available to you at any time, without having to change anything in the options menu. The computer could detect which you wanted it to be based on how you pressed the button. It is for this reason that I call it Smart Mode. It's like the computer knows what you're thinking.

This "smart toggling" system could be used for anything. It doesn't have to be targeting in a 3D adventure game. It could be used for opening and dismissing a Seiken Densetsu style menu ring, or activating a protective shield, or even for changing between two different weapon types in an action sidescroller.

And I'm sure that you Code Ninjas could think of many more applications that I couldn't. So, think about how you might add "smart" triggers or toggles to your game. It might make it a little more user-friendly.

Happy coding!