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!