SGADE Tutorial – Boulder Bombers Advance

by Mark T. Price
Chief Scientist, Sudden Presence

 
 
 
 Previous Chapter Index Next Chapter 

Chapter 4 – Displaying “Character” Sprites

In this chapter, we will extend our program to display two planes that fly left and right over the canyon playfield. The planes will be implemented as a pair of animated sprites.

What you need for this chapter
1. The project created in Chapter 3
2. The bitmap containing the plane images

To make the planes fit with the canyon from the last chapter, we'll be using more left-over “Programmer-Art” from the PC version of Boulder Bombers.

Again we start by converting the images into code that can be included in the project. Unfortunately if you use the SoConverter to process the vertical strip image to the right, you will get a sprite animation that contains just one frame. There are two ways around this. The first is to use an outside tool to create an eight frame FLI animation file out of the eight plane images and then use SoConverter to convert that. The second is to cheat. Since cheating doesn't require any additional tools, that's what we'll be doing.

Put the planes.bmp file in the data subdirectory of the BBAdvance project.

From a command prompt in the BBAdvance/data directory run the SoConverter.exe to convert the bitmap to a sprite animation and then again to convert it to a palette.

  % SoConverter.exe -file planes.bmp -converter SoSpriteAnimation
  Invalid value for the -converter argument
  Please wait while converting....

  Done

  Press almost any key to continue...

  % SoConverter.exe -file planes.bmp -converter SoPalette
  Invalid value for the -converter argument
  Please wait while converting....

  Done

  Press almost any key to continue...

Now we've got to fix up the planesSpriteAnimation.dat file to correct the size and number of frames.

Open the planesSpriteAnimation.dat file using your editor and page down to the end of the file to the planesSpriteAnimation variable declaration. Change it from a single frame 16x128 pixel animation to an eight frame 16x16 pixel animation* and save the result.

  const SoSpriteAnimation planesSpriteAnimation = { false, 16, 16, 8,
    (u8*) planesSpriteAnimationData };

Next we turn our attention to the code needed to make use of the plane data. Our plane sprites will need some persistent data to keep track of where they are and where they're going. We'll add this to the top of the file along with some #defines to make the code more readable.

First, add #include statements to reference the data we generated for the planes. These should be placed next to the #includes we previously added for the tile data.

  #include "planesPalette.dat"
  #include "planesSpriteAnimation.dat"

Now, add the following lines following the #include “Socrates.h” statement. The PLAYER_SPRITE_* #defines are indexes into our frame animation. The player offset refers to the number of frames for an individual plane type (orange or purple in this case) and the direction offset refers to the number of frames for the plane in a single direction (prop up and prop down). The Player position #defines give the limit positions for the sprites going in each direction. They are slightly outside of the screen coordinates to allow for a short delay when the plane leaves the playfield.

  // =======================================================================
  // Configuration #defines
  // =======================================================================
 
  #define MAX_PLAYERS 2
 
  #define DIRECTION_RIGHT 0
  #define DIRECTION_LEFT  1
 
  #define PLAYER_SPRITE_PLAYER_OFFSET    4
  #define PLAYER_SPRITE_DIRECTION_OFFSET 2
  
  #define PLAYER_POSITION_MIN -(16+8)
  #define PLAYER_POSITION_MAX (SO_SCREEN_WIDTH+8)

It's generally helpful to declare a structure to contain all of the data specific to a particular player. This typically includes all of the variables that together make up the player state. For us, this is currently just a reference to the player sprite, its position and direction. Add a playerInfo structure to contain this data. It should be placed after the 'Configuration #defines' section

  // =======================================================================
  // Data Structures
  // =======================================================================
  
  struct playerInfo {
      SoSprite *sprite;
      int       iType;
      int       iDirection;
      SoVector2 vPosition;
  };

To make the main code simpler, we'll use a couple of global constants to specify the starting/ending positions and directions of all players. Add this code to the end of the 'Constant data' section following the canyonData declaration.

  // starting values ... indexed by player number
  const u32 startDirection[] = { DIRECTION_RIGHT, DIRECTION_LEFT };
		
  // starting values ... indexed by DIRECTION_*
  const SoVector2 startPosition[] = {
      { PLAYER_POSITION_MIN, 16 },
      { PLAYER_POSITION_MAX, 32 }
  };

Finally, we need a couple of global variables to hold the persistent data about our planes.

  // =======================================================================
  // Globals
  // =======================================================================
  
  struct playerInfo g_player[MAX_PLAYERS];
  u32 g_iPlayerBaseSpr;

Now that all of our data is in place, we add the code to use it.

Add a new subroutine to initialize the sprites. It should be located after our setupPlayfield subroutine. You must call a minimum of nine functions to set up a rotating/scaling sprite. These calls are:

SoSpriteManagerInitialize -- called just once
SoSpriteMemManagerInitialize -- called just once
SoSpriteMemManagerLoad -- called once per sprite type (e.g. our plane)
SoSpriteManagerRequestSprite -- called once per sprite
SoSpriteCopyPropertiesFromAnimation - called once per sprite
SoSpriteManagerSetRotationAndScale -- called once per unique rotation/scaling configuration
SoSpriteSetRotationAndScaleIndex - called once per sprite
SoSpriteSetRotationAndScaleEnable -- called once per sprite
SoSpriteManagerEnableSprites -- called just once
  // Setup Players (Sprites)
  void setupSprites(void)
  {
      int ii;

      SoPaletteSetPalette(SO_SPRITE_PALETTE, planesPalette, true);

      SoSpriteManagerInitialize();
      SoSpriteMemManagerInitialize();

      // load sprite data
      g_iPlayerBaseSpr = SoSpriteMemManagerLoad(&planesSpriteAnimation);

      for(ii = 0; ii < MAX_PLAYERS; ++ii)
      {
            g_player[ii].iDirection = startDirection[ii];
            g_player[ii].vPosition  = startPosition[ii];
            g_player[ii].iType      = ii;

            g_player[ii].sprite = SoSpriteManagerRequestSprite();
            SoSpriteCopyPropertiesFromAnimation(g_player[ii].sprite,
              &planesSpriteAnimation);

            SoSpriteManagerSetRotationAndScale(ii, 0,
              SO_FIXED_FROM_WHOLE(1), SO_FIXED_FROM_WHOLE(1));
            SoSpriteSetRotationAndScaleIndex(g_player[ii].sprite, ii);
            SoSpriteSetRotationAndScaleEnable(g_player[ii].sprite, true);
      }

      SoSpriteManagerEnableSprites();
  }

Add a new subroutine to update the sprites each time a new frame is to be drawn. We'll need to call three SGADE routines to update each sprite. They are:

SoSpriteSetTranslate -- called to update/set the sprite's new position
SoSpriteSetAnimationIndex -- called to update/set the sprite's image
SoSpriteManagerUpdate -- applies all updates to actual sprites

  // ***********************************************************************
  // ***  CONTROL
  // ***********************************************************************
  
  void update(void)
  {
      int ii;
      static int cntFrame = 0;
      static int animFrame = 0;
 
      cntFrame += 1;
      if(!(cntFrame&1))
      animFrame += 1;

      // update player positions
      for(ii = 0; ii < MAX_PLAYERS; ++ii)
      {
          if(g_player[ii].iDirection == PLAYER_DIRECTION_RIGHT)
          {
              g_player[ii].vPosition.m_X += 1;
          }
          else
          {
              g_player[ii].vPosition.m_X -= 1;
          }

          // check if player went off screen
          if((g_player[ii].vPosition.m_X < PLAYER_POSITION_MIN) ||
            (g_player[ii].vPosition.m_X > PLAYER_POSITION_MAX))
          {
              if(g_player[ii].iDirection == PLAYER_DIRECTION_RIGHT)
              {
                  g_player[ii].iDirection = PLAYER_DIRECTION_LEFT;
              }
              else
              {
                  g_player[ii].iDirection = PLAYER_DIRECTION_RIGHT;
              }
              g_player[ii].vPosition =
                startPosition[g_player[ii].iDirection];
          }

          // move plane
          SoSpriteSetTranslate(g_player[ii].sprite,
            g_player[ii].vPosition.m_X, g_player[ii].vPosition.m_Y);
          SoSpriteSetAnimationIndex(g_player[ii].sprite, 
            g_iPlayerBaseSpr + (animFrame%PLAYER_SPRITE_DIRECTION_OFFSET +
            (g_player[ii].iType*PLAYER_SPRITE_PLAYER_OFFSET) +
            (g_player[ii].iDirection*PLAYER_SPRITE_DIRECTION_OFFSET)) *
            SoSpriteAnimationGetNumIndicesPerFrame(&planesSpriteAnimation));
      }

      SoSpriteManagerUpdate();
  }

Finally, we modify our AgbMain function to make use of the new sprite functions.

First, add a call to setupSprites() immediately following the call to setupPlayfield(). Then, replace the infinite for(;;) loop with this block of code:

  for(;;)
  {
      // Wait for VBlank;
      SoDisplayWaitForVBlankEnd();
      SoDisplayWaitForVBlankStart();

      update();
  }

The SoDisplayWaitForVBlankEnd() and SoDisplayWaitForVBlankStart() calls ensure that the update is done just once during each vertical blank -- about 60 times a second. Since the planes move one pixel per update, this means that it will take about four seconds to go all the way across the screen.

Compile your updated project by running ‘make’ from a command shell in the BBAdvance directory. Once it’s ready, try it out. You will get a screen like this one (it's much more interesting when it's moving).

click here to download the solution to this chapter

Footnotes

* Note that the cheat to create a multiple frame SoSpriteAnimation using SoConverter.exe on a single bitmap only works when the frames are consecutively arranged in the bitmap in a vertical stack. Each frame must also be aligned on an 8-pixel boundary.

 Previous Chapter Index Next Chapter