Click here if you are stuck in someone else's frames.

If this page looks like garbage then you need one of these browsers...

Microsoft Internet Explorer

Netscape Navigator

Please sign my programmer's page guestbook.

Please sign my guestbook

View my guestbook entries

Setting up member data and functions

Okay, now that the basics of C++ programming have been covered we can now begin to apply it to our sprite class. (If you are still fuzzy on C++, don't worry. You will see that creating classes are as easy as creating a struct and a bunch of functions.) So first, let's look at our member data again.

enum Boolean {false, true};

class Sprite {
    int startXoff, startYoff; // Offset copying position
    int lenXcopy, lenYcopy;   // Actual dimensions to copy
    char far *displayMem;     // Buffer sprite is modifying

protected:
    int X, Y;                 // Sprite's current location
    int lastX, lastY;         // Sprite's last location
    int width, height;        // Sprite's dimensions

    int minX, minY;           // Minimum limits
    int maxX, maxY;           // Maximum limits

    int currFrame;            // current frame in animation
    int noOfFrames;           // total number of frames in animation
    char far **frames;        // array of sprite images for animation
    char far *background;     // background stored for dirty rectangle
    Boolean imgDynamic;       // declares if images are static or dynamic

    Boolean visible;          // flags if Sprite is or isn't visible
    int initOk;               // flags if all went well during construction
    Boolean backTransparent;  // opaque or transparent background

public:
    // Public variables
    int status;               // Sprite status, varies by game rules
    int animThreshold;        // animation threshold, varies
    int moveThreshold;        // move threshold, varies
    int clock;                // sprite's clock
};
    

At first glance, it may seem confusing a bit, but if you removed the keywords public: and protected: and replaced class with struct you will see the all familiar struct that we have been using so far.

Okay, time out, I know that classes by default make their data private and that you can use the keyword public: to directly access the data members of a class (like a struct), but what does protected: do? That keyword works like private: with one exception, inheritence. Notice that this class has no variable for otherData like the struct did. That variable was put in the struct for future expansion, if the struct required additional data, it could create another variable (of any type needed) and set the otherData pointer to point to it. That way, we could expand the capabilities of sprites without having to re-write everything.

That type of expansion is not clean, however, we would need to keep up with the pointer to this extra information as well as know exactly what type of variables this pointer points to. C++ has a better method for expansion, inheritance. All we need to do now is create another class that inherits the properties of this class and add more of it's own that it needs. The purpose of the protected: keyword is to allow classes that inherit this class to have direct access to the data without making it available publicly. (The private variables are inaccessible to any class that inherits this one.)


Creating a USEFUL class

Did I forget something here? Ah yes, functions! Without them our class would be totally useless. So let's create some member functions. Since we created a function that would initialize our sprites the first time (with structs and functions) let's do it again. If we are clever, we can write it so that C++ will automatically call this function whenever we create an instance of this class. This takes a little thinking, hmmm, let's start with our original function.

int InitSprite(struct Sprite *thisSpr, int w, int h,
               int numFrames, int dynamic, int drtyRect)
{
  int i, retVal = 1;

  if (w > 0) {
    thisSpr->width = w;
    if (h > 0) thisSpr->height = h;
  }

  thisSpr->imgDynamic = dynamic;
  if (numFrames > 0) {
    thisSpr->frames=(char far **)malloc(numFrames*sizeof(char far *));
    if (thisSpr->frames != NULL) {
      thisSpr->noOfFrames = numFrames;
      for (i=0; i<numFrames; i++)
        if (dynamic && w > 0 && h > 0) {
          thisSpr->frames[i]=(char far *)farmalloc(w*h*sizeof(char));
          if (thisSpr->frames[i] == NULL) retVal = 0;
        } else
          thisSpr->frames[i] = NULL;
    } else
      retVal = 0;
  } else
    thisSpr->frames = NULL;

  if (drtyRect) {
    thisSpr->background = (char far *)farmalloc(w * h * sizeof(char));
    if (thisSpr->background == NULL && w && h) retVal = 0;
    thisSpr->backTransparent = 1;
  } else
    thisSpr->background = NULL;

  thisSpr->maxX = ScreenWidth - 1;
  thisSpr->maxY = ScreenHeight - 1;

  thisSpr->lenXcopy = thisSpr->width;
  thisSpr->lenYcopy = thisSpr->height;

  thisSpr->displayMem = videoMem;

  thisSpr->X = thisSpr->Y = thisSpr->lastX = thisSpr->lastY =
  thisSpr->minX = thisSpr->minY = thisSpr->startXoff = thisSpr->startYoff =
  thisSpr->currFrame = thisSpr->status = thisSpr->visible = thisSpr->clock =
  thisSpr->animThreshold = thisSpr->moveThreshold = 0;

  thisSpr->otherData = NULL;
  return retVal;
}
    

All the text in bold will have to be removed and all the text in italics will have to be changed in order to adapt this function into the class. Rather than go through all the rigormarole of what exactly all this is and why we need to do it, let's go ahead and present the clean code, ready to incorporate into our sprite.

Sprite::Sprite(int w, int h, int numFrames, Boolean Dynamic, Boolean DrtyRect)
{
    int i;

    // width = w, if (w > 0), otherwise, width = 0
    width  = (w > 0) ? w : 0;
    // height = h, if (h>0 && width>0), otherwise, height=0
    height = (width > 0 && h > 0) ? h : 0;

    initOk = true;              // So far so good
    imgDynamic = Dynamic;
    if (numFrames > 0) {
        // Allocate an array of far pointers
        frames = (char far **)malloc(numFrames * sizeof(char far *));
        if (frames == NULL) {
          // Something went wrong
          noOfFrames = 0;
          initOk = false;
        } else {
          noOfFrames = numFrames;
          // For each far pointer, allocate memory to hold an image
          for (i=0; i<noOfFrames; i++)
           if (imgDynamic && width && height) {
             frames[i]=(char far *)farcalloc(width * height, sizeof(char));
             if (frames[i] == NULL) initOk = false;
           } else
             frames[i] = NULL;
        }
    } else {
        frames = NULL;
        noOfFrames = 0;
    }

    // If using dirty rectangles, allocate memory for background image
    if (DrtyRect && width && height) {
        background = (char far *)farcalloc(width * height, sizeof(char));
        if (background == NULL) initOk = false;
        backTransparent = true;
    } else {
        background = NULL;
        backTransparent = false;
    }

    // Initialize the rest of our member variables
    X = Y = lastX = lastY = 0;
    minX = minY = 0;
    maxX = ScreenWidth - 1;
    maxY = ScreenHeight - 1;

    startXoff = startYoff = 0;
    lenXcopy = width;
    lenYcopy = height;

    currFrame = 0;
    visible = false;
    status = animThreshold = moveThreshold = clock = 0;
    displayMem = videoMem;
}
    

This is the function I use, you may recognize quite a few differences beyond what I originally suggested. This function has been tested and retested for bugs hence why the function appears different, but overall, it's the same ordeal.

Of course, we have to declare the function within the class declaration in order to make it part of the class, so let's do so now.


Constructors and destructors should always be declared public. However, not all member functions need to be declared public.
class Sprite {
    int startXoff, startYoff; // Offset copying position
    int lenXcopy, lenYcopy;   // Actual dimensions to copy
    char far *displayMem;     // Buffer sprite is modifying

    .
    .
    .
    // snip
    .
    .
    .

public:
    // Public variables
    int status;               // Sprite status, varies by game rules
    int animThreshold;        // animation threshold, varies
    int moveThreshold;        // move threshold, varies
    int clock;                // sprite's clock

    
    // Member function declarations
    Sprite(int w=0, int h=0, int numFrames=0, Boolean Dynamic=true,
           Boolean DrtyRect=true);
    
};
    

Okay, at a first glance you will find quite a few changes in both the declaration and with the function. Let's begin with the declaration.

First notice that the function has the same name as the class, Sprite. Whenever a function has the same name of the class it is declared in, that functions becomes what is known as a constructor. Constructors are called automatically whenever an instance of (variable from) a class is created, all you need to do is specify the parameters. You may also notice that the function declaration parameters have assignment operators beside them. These specify default values for the parameters if you omit them.

Hmmm, do we need to go through this function and explain each line? We did explain it's "sister" function InitSprite in the structs and functions section. The only main difference between this function and the other is the following:

The initOk variable.
Because constructors cannot return anything (not even void) the results of whether the function was able to properly initialize the data for the sprite is saved in this variable. By contrast, the InitSprite function returned this information to the calling program. This means that after you declare an instance of a sprite, you must check this variable for proper initialization (more on this later).

No struct Sprite *thisSpr parameter.
Ah yes, since the functions are part of the class, this parameter is no longer needed. The class determines which variable (instance) it is using by passing a special hidden pointer called this.

What's with the Boolean.
Boolean is an enumerated variable type that can take on either a true (1) or a false (0). Actually, integers can be used to accomplish the same purpose but the Boolean variables provide more meaningful coding and cost nothing at runtime. Boolean could've also been used in our structs and functions example.

Send your questions, comments, or ideas to: wilkeg@gamewood.net

This page hosted by GeoCities Get your own Free Home Page
 Next Back |