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

Destroying Sprites (Destructors)

Now that we've covered the initialization of sprites (constructors) as well as discussed a few of the "buzz" words that's used in the OOP community (what, you still don't realize that we are slowly moving toward OOP???) let's go over another important function used in classes. These functions can be referred to as de-initializers, but they are better known as destructors.

Before we go over destructors, let's look at how we deallocated sprites using our old struct and functions.

Old way:

// Create a variable from our struct.
struct Sprite GreenBall;

// Initialize the sprite variable.
InitSprite(&GreenBall, 10, 10, 1, 1, 1);

.
.
.
// Code that uses the sprite and does other things
.
.
.

// Clean up time.
// Call the function that destroys sprite.
DestroySprite(&GreenBall);

// Exit program.
exit(0);
    

With the old method, after creating a variable of type struct Sprite we still had to remember to call the functions that actually initialized the elements of the structure. Then before the program could end, we also had to remember to call the function that deallocates the memory allocated for it's pointers. Otherwise, we'd leak memory. Now let's look at what classes have to offer.

Better way:

// Create an instance from our class.
// Upon creating an instance, the constructor is
//   called automatically.  All we need to do is
//   pass the parameters we want.  Since we also
//   made the parameters optional, not all the
//   parameters need to be passed.
Sprite GreenBall(10, 10, 1);

.
.
.
// Code that uses the sprite and does other things
.
.
.

// Clean up time.
// Since GreenBall is a static object (instance of
//   Sprite class) it will fall out of scope (like
//   an int does) upon exiting and be deleted.
// Upon deletion, the destructor is automatically
//   called.
// Exit program.
exit(0);
    

Hmmmm... Three lines down to one! (Well it may not look like it from all the comment lines.) The line Sprite GreenBall(10, 10, 1); declares a sprite object from the sprite class. Unlike the struct example, we do not need to call the initializer function because classes call their initializer functions (constructors) automatically whenever a static object has been declared. (We'll discuss dynamic objects later.) Also, whenever a static object falls out of scope (as this one did when the exit(0); function was encountered.) the destructor function will automatically take care of deleting the object. No need to call a de-initializer function as we did before.


Creating destructors

Okay, so how exactly do we go about creating these magical functions that virtually call themselves whenever an object falls out of scope? Well, let's first look at the old function we used to destroy sprites:

/* DestroySprite
 *
 * Destroys sprites initialized by InitSprite, this function must
 * be called prior to deleting the sprite structure
 */

void DestroySprite(struct Sprite *thisSpr)
{
    int i;

    if (thisSpr->background != NULL)
        farfree(thisSpr->background);

    if (thisSpr->frames != NULL) {
        for (i=0; i<thisSpr->noOfFrames; i++)
            if (thisSpr->imgDynamic && thisSpr->frames[i] != NULL)
                farfree(thisSpr->frames[i]);
        free(thisSpr->frames);
    }
}
    

Translating this function into a C++ class destructor is a piece of cake. Just remove all the thisSpr-> text and change the name of the function. Change it so that it appears like so:


Did you notice that both the constructor and the destructor functions have the same name as the class they are defined in? This is how C++ recognizes these functions. Remember, destructors have a (~) and constructors do not.
Sprite::~Sprite()
{
    int i;

    // Free up our memory before destroying our sprite.

    if (background != NULL)
        farfree(background);

    if (frames != NULL) {
        for (i=0; i<noOfFrames; i++)
            if (imgDynamic && frames[i] != NULL)
                farfree(frames[i]);
        free(frames);
    }
}
    

Now we must make the destructor function a part of the Sprite class, add the prototype for this function into the class declaration like so:

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

.
.
.
// 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 functions
    Sprite(int w=0, int h=0, int numFrames=0, Boolean Dynamic=true,
           Boolean DrtyRect=true);
    ~Sprite();
};
    

Accessing private data

Now, if you look at the class definition you will realize that a lot of the data contained within it is declared as private. Private for very legitimate reasons. Consider, the X and Y location of the sprite. Both of these data members are declared as private (well, technically they are declared as protected:). The main reason is that they're values reflect the actual position of the sprite on the screen and in order to change them, we need to insure that the sprite is relocated to reflect this change. In order to do that properly, we need to make them private. So to change them, and to update the sprite's image to the new location, we write another member function. We can name it anything we want, this is neither a constructor nor a destructor, of course we should give it a name that makes sense so I decided to call the function appropriately move.

void Sprite::Move(int x, int y, int newFrame)
{
    Boolean wasVisible;
    wasVisible = visible;

    Hide();
    lastX = X;
    lastY = Y;
    X = x;
    Y = y;
    if (newFrame >= 0 && noOfFrames > 0) currFrame = newFrame % noOfFrames;
    if (wasVisible) Show();
}
    

This function is rather short and to the point. It declares a variable called wasVisible and assigns it the current status of visible (a flag indicating whether the sprite is visible or not. Hides the sprite, with the Hide member function that we will discuss later. Reassigns the X and Y coordinates with the new values and then reshows the sprite (only if the sprite was visible prior to hiding it). What does newFrame do? This optional third parameter allows you to change the frame image being displayed which allows you to move and animate sprites in a single function call.

Of course, being able to reassign private variables in this way has it's apparent advantages. However, what if you want to get the values of say, the X and Y parameters. Well, since they are private, there is no way that we can directly. Obviously though, there is a way, to do this we create two more member functions, GetX and GetY. All these functions do is return the values of X and Y so the entire function is written within the class declaration like so.

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

.
.
.
// 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 functions
    Sprite(int w=0, int h=0, int numFrames=0, Boolean Dynamic=true,
           Boolean DrtyRect=true);
    ~Sprite();

    // Get and modify sprite's screen location
    void Move(int x, int y, int newFrame=-1);
    int GetX(void) {return X;}
    int GetY(void) {return Y;}
};
    

We can do this for all member data, that is we can write functions that get their values and return them for public use and we can write functions that set their values. Since we can write code that determine how their values can be set or returned we force outside functions to play by the rules of the class.


Send your questions, comments, or ideas here.

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

1