Make Your Own Sideways Scroller: Part 4 - Moving and Rendering Steve


(Simon) #1

The code described in these lessons can be found at https://github.com/filmote/Steve


Moving and Rendering Steve

When Steve is not standing around waiting for the game to begin he may be running, ducking or simply be dead. These various states or stances are shown below.

0016 0018 0019

0020 0021 0022

These stances are enumerated in an enum called Stance. As with the ground types detailed above, a matching array of dinosaur images has been declared with the images arranged in the same order as the Stance enumeration. A second array of masks has also been declared – again with the masks in the same order as the Stance enumeration – to allow us to use the Stance values as indexes.

enum Stance {
  Standing,
  Running1,
  Running2,
  Ducking1,
  Ducking2,
  Dead1,
  Dead2,
};

const byte *steve_images[] = { dinosaur_still, dinosaur_running_1, dinosaur_running_2, dinosaur_ducking_1, dinosaur_ducking_2, dinosaur_dead_1, dinosaur_dead_2 };
const byte *steve_masks[] = { dinosaur_still_mask, dinosaur_running_1_mask, dinosaur_running_2_mask, dinosaur_ducking_1_mask, dinosaur_ducking_2_mask, dinosaur_dead_2_mask, dinosaur_dead_2_mask };

All of the details relating to Steve’s position and current stance are stored in a single structure. Note that in structures, we can include fields using the common data types (integers, Booleans and so forth) and ones that refer to the enumerations we have already defined.

struct Steve {
  int x;
  int y;
  Stance stance;
  bool jumping;
  byte jumpIndex;
  const byte *image;
  const byte *mask;
};

Once the structure has been declared, we can create an instance of it and initialise all of the elements in a single line as shown below. We now have a container to track changes in Steve’s position.

Steve steve = {0, STEVE_GROUND_LEVEL, Stance::Standing, false, false, dinosaur_still, dinosaur_still_mask };

Our Steve structure has two fields that point to the image and mask that represent his current stance. Later we will use these to determine whether Steve has crashed into a cactus or pterodactyl. For now, we can populate these by looking up looking up the image and mask from the array of images using Steve’s current stance as an index.

Steve can then be rendered using the Sprites::drawExternalMask() function with Steve’s current position and the two image references. As Steve can be standing or ducking, I have assumed that his coordinates represent the lower left position. By default, images are rendered from the top left position and therefore I have subtracted the height of the image from Steve’s Y position to accommodate this.

void drawSteve() {

  steve.image = steve_images[steve.stance];
  steve.mask = steve_masks[steve.stance];
  Sprites::drawExternalMask(steve.x, steve.y - getImageHeight(steve.image), steve.image, steve.mask, frame, frame);

}

Now that we can draw Steve on the screen, we need to be able to move him around. Our structure includes an X and Y coordinates plus fields that represent his current stance and whether or not he is jumping.

Handling user input is generally handled in the main loop of the program. The code below tests first whether Steve is jumping and, if not, allows the player to control him. The Arduboy library provides four commands for detecting the pressing and releasing of buttons - justPressed(), justReleased(), pressed() and notPressed(). The first two commands are used in conjunction with the command pollButtons().

A call to pollButtons() at the start of the main loop captures the current state of the Arduboy’s buttons. The justPressed() and justReleased() commands will return true if the user has pressed a button since the last time pollButtons() was called.

The pressed() command returns true if the nominated button is pressed or remains pressed over a period of time. You can see that I am using the justPressed() command to detect a jump as it is a one off event whereas I am allowing the user to hold the left and right buttons to move continuously by using the pressed() command .

arduboy.pollButtons();

…

if (!steve.jumping) {

  if (arduboy.justPressed(A_BUTTON)) {
    steve.jumping = true;
    steve.jumpIndex = 0;
  }

  if (arduboy.justPressed(B_BUTTON)) {
    if (steve.stance != Stance::Ducking2) {
      steve.stance = Stance::Ducking1;
    };
  }
  if (arduboy.pressed(LEFT_BUTTON) && steve.x > 0) {
    steve.x--;
  }
  if (arduboy.pressed(RIGHT_BUTTON) && steve.x < 100) {
    steve.x++;
  }

  if (arduboy.notPressed(B_BUTTON) && 
     (steve.stance == Stance::Ducking1 || steve.stance == Stance::Ducking2)) {
    steve.stance = Stance::Running1;
  }

}

The last clause of the code above detects whether the player has let go of the B button (the duck button) and returns Steve to a running stance if he was already ducking. The second part of the condition is important as the notPressed() command - as the name suggests - will return true when the user is not pressing the button and we do not want Steve’s stance to be changed unless he was actually ducking.

At the end of each loop, we need to update Steve’s position if he is jumping or change the image displayed to give the illusion of his feet moving.

Jumping

Previously, we detected if the user had pressed the A button and set the jumping flag to true. If it is true we want Steve to jump an over oncoming obstacles in an arc that somewhat simulates the effects of gravity – fast acceleration at the start of the jump, deceleration as he reaches full height followed by acceleration as he falls to earth,

The array below describes the Y positions of Steve as his jump progresses. It starts and ends at Y = 55 (or ground level) and reaches a height of 19 in the middle.

unsigned char jumpCoords[] = {55, 52, 47, 43, 40, 38, 36, 34, 33, 31, 30, 29, 28, 27, 26, 25, 24, 24, 23, 23, 22, 22, 21, 21, 20, 20, 20, 20, 19, 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 26, 27, 28, 29, 30, 31, 33, 34, 36, 38, 40, 43, 47, 51, 55 };

The code below updates Steve’s position when jumping. In addition to the jumping flag, the data structure that maintains Steve’s details also includes an array index, named jumpIndex, which is used to keep track of the current position in the array. On each pass of the main loop, adjust Steve’s height to the value within the array that the index points to. We then increment the array index up until the last value of the array is reached at which point Steve has returned to the ground and the jump is over. Steve’s jumping property is set to false and the array index is set to zero in anticipation of his next jump.

void updateSteve() {


  if (steve.jumping) {

    steve.y = jumpCoords[steve.jumpIndex];
    steve.jumpIndex++;

    if (steve.jumpIndex == sizeof(jumpCoords)) {

      steve.jumping = false;
      steve.jumpIndex = 0;
      steve.y = STEVE_GROUND_LEVEL;

    }

  }
  else {

    ...

  }

}

Running or Ducking

The illusion of running is achieved by alternating the images used when rendering Steve. If we alternate the images every frame (or every time we pass through the main loop), Steve’s legs will appear as a blur as this is too fast. To slow this down, we can use an Arduboy library function called everyXFrames(n) which returns true on every nth frame.

The code below alternates Steve’s stance every 3 frames. If his current stance is Stance::Running1 then it becomes Stance::Running2 – if it was already Stance::Running2 then it reverts back to the original value. This is true of the ducking images but not true of the dead images – once you are dead, you remain dead.

void updateSteve() {

  if (steve.jumping) {

    ...

  }
  else {

    if (arduboy.everyXFrames(3)) {

      switch (steve.stance) {

        case Stance::Running1:
          steve.stance = Stance::Running2;
          break;

        case Stance::Running2:
          steve.stance = Stance::Running1;
          break;

        case Stance::Ducking1:
          steve.stance = Stance::Ducking2;
          break;

        case Stance::Ducking2:
          steve.stance = Stance::Ducking1;
          break;

        case Stance::Dead1:
          steve.stance = Stance::Dead2;
          break;

        default:
          break;

      }

    }

  }

}

Now that we have Steve running, jumping and ducking we can move on to the obstacles he will need to avoid.


The code described in these lessons can be found at https://github.com/filmote/Steve

Prev Article > Make Your Own Sideways Scroller: Part 3 - Moving the Ground

Next Article > Make Your Own Sideways Scroller: Part 5 - Launching and Moving Obstacles


Make Your Own Sideways Scroller: Part 5 - Launching and Moving Obstacles
(Berit) #2

Thank you for all the effort youve put in so far!
You guys keep this community alive.

Waiting patiently for the rest :smile:


(Simon) #3

You can find the rest in the latest Arduboy Mag! I was planning on waiting a week or so before publishing the rest.


(Simon) #4

The remainder of the article has been published now …