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

1 comment:

  1. I had no idea that happened in Sonic games. That's a really simple solution to the problem. Plus it serves as a good example of how the min() function works and how its useful.

    ReplyDelete