Showing posts with label Code of the Ninja. Show all posts
Showing posts with label Code of the Ninja. Show all posts

24 August 2011

Code of the Ninja Moves House

Just a heads up: all future "Code of the Ninja" posts can be found at its new dedicated blog, Code of the Ninja. Please update your following procedures accordingly. And, er... go there now, 'cos there's a new post. =)

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

13 September 2010

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!

19 March 2010

Code of the Ninja: 2D Camera

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

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!

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

22 February 2010

Code of the Ninja: Text Boxes

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

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!

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

08 December 2009

Code of the Ninja: Sinusoidal Motion

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

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!

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

13 November 2009

Code of the Ninja: Watchers

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

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!

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

25 September 2009

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!

05 September 2009

Code of the Ninja: Smart Triggers

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

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!

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