If this page looks like garbage then you need one of these browsers...Please sign my programmer's page guestbook. |
Creating a sprite classUnless you've skipped over the struct and functions sections of creating sprites, you should have learned to write the basic functions and a structure that we use to create sprites in our games. (I hope you did not just skim over them, if you did, shame on you! :) At any rate, now we will learn to create a class that we can use instead, meaning that once we create this class, we will no longer require our struct and functions. WHAT??? All that work I did and now you say that it is no longer needed??? Well, sort of, you see, if you created the structs and functions (or if you just simply saved mine off of this page) you can modify it into a class. Whew, all is not lost for all that labor you did... (Of course, if all you did was copy mine, then you can just simply copy my other source files for the class that I present when I go over them... hee hee hee) Why use a class anyway?Well, definitely there are some advantages, although for this particular application the advantages will not appear to be much, but it will become more practical as we use it in larger programs, such as games. (Don't worry, we will write a game with all of this as soon as we learn the fundamental principles of sprites.) These large programs will make using classes seem more of an ordeal. Of course, I must warn you that unless you are using a C++ compiler (a strictly ANSI C compiler will not work) you cannot use any of these examples. But hey, who are we kidding, at the time of this writing in 1998, C++ compilers are everywhere. I use Turbo C++ myself for all the examples that I present on these pages and can easily be converted to Microsoft's C++, so there's no reason why you can't follow along. The advantages to using a class will become more apparent as we move along and if you are not familiar with writing classes or using them, don't worry, for as long as you know how to use structs and write functions that use them, learning classes will be a breaze. Converting our struct into a classLook at our struct definition again... struct Sprite { int startXoff, startYoff; /* Offset copying position */ int lenXcopy, lenYcopy; /* Actual dimensions to copy */ char far *displayMem; /* Buffer sprite is modifying */ 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 */ int imgDynamic; /* flags if images are static or dynamic */ int visible; /* flags if Sprite is or isn't visible */ int backTransparent; /* opaque or transparent background */ int status; /* Sprite status, varies by game rules */ int animThreshold; /* animation threshold, varies */ int moveThreshold; /* move threshold, varies */ int clock; /* sprite's clock */ void far *otherData; /* Extra data */ }; Well, to convert to a C++ class, just substitute the word "class" for the word "struct" as in the example.
struct Sprite { int startXoff, startYoff; /* Offset copying position */ int lenXcopy, lenYcopy; /* Actual dimensions to copy */ char far *displayMem; /* Buffer sprite is modifying */ . . . Substitute like so...
class Sprite { int startXoff, startYoff; /* Offset copying position */ int lenXcopy, lenYcopy; /* Actual dimensions to copy */ char far *displayMem; /* Buffer sprite is modifying */ . . . |
C++ compilers don't require the words "struct" or "class" when declaring variables from them. |
Now the number one rule we must keep in mind is that we can no longer assign the elements of this class directly anymore. For example, if we used the struct definition above and created a variable like so...
struct Sprite GreenBall; We could assign the elements of the struct like so...
GreenBall.X = 100; But if this were a class, we could not anymore. This example will produce a compiler error on compile.
class Sprite GreenBall; GreenBall.X = 100; // Can't access data members directly Well, naturally there are ways around it but let's understand why this is so first. It has to do with scope, the elements (data members) of a class are of a local scope to that class only. That is, the elements are "private" to the class, whereas in a struct, the data members (elements) are all "public" and can be seen outside the struct by any function that can "see" (is within scope) of the struct. This is equivelent to a local variable defined within a function, and therefore private to the function, as opposed to a "public" (global) variable defined outside functions. Well in both examples, the struct and the class are global definitions. Therefore the public elements (data members or member variables) of these definitions can be "seen" everywhere in the program. Just declare a variable from these definitions and you can assign data to them directly. But classes keep there data members private by default (keywords being "by default"). We can overide this default with another keyword "public:" but before we start making data members public, let's look at how they're being private is so beneficial. Benefits of private dataIf you were paying attention when I went over the structs and the functions that declared an area of memory for holding image data, you may recall that I recomended that you should not modify the frames pointer directly outside the functions written for the sprite. The reason for this is of course the fact that the functions allocate memory and use the frames pointer to point to that area of memory. If we were to directly modify the pointer after this memory allocation occurred, without freeing the memory allocated first, our programs would begin to leak memory. For example, if we were to declare a variable from our struct and the initialize it like so:
struct Sprite GreenBall; InitSprite(&GreenBall, 10, 10, 1, 1, 1); Then later in our program we reassign the frames element directly like so:
GreenBall.frames = NULL; Doing this without freeing the memory allocated to the pointer would cause us to leak memory. A more proper way would have been to call the DestroySprite first. However, using C++ classes, this problem can be avoided altogether by keeping the frames variable private. (As stated earlier, private data members cannot be accessed directly.) Right now you are probably asking how can we actually assign data to private variables. After all, if we can't do it directly like we can with a struct, can we do it at all? The answer is, of course, the way it is done is that only other members of the same class has access to the private data. Non-members of the class do not. So, let's see if this works then. This example changes the InitSprite function so that it accepts classes as a parameter instead of structs. Will this work?
int InitSprite(class 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; } Here's the answer: NO! Why? Because the function InitSprite is not a member of the class called Sprite. Just because it accepts a class as a parameter doesn't make it a member. We must make it a member of the class somehow, here is how you make a function a member of the class. First you must include the function declaration within the class declaration:
class Sprite { int startXoff, startYoff; /* Offset copying position */ int lenXcopy, lenYcopy; /* Actual dimensions to copy */ char far *displayMem; /* Buffer sprite is modifying */ . . . // Snip . . . int status; /* Sprite status, varies by game rules */ int animThreshold; /* animation threshold, varies */ int moveThreshold; /* move threshold, varies */ int clock; /* sprite's clock */ void far *otherData; /* Extra data */ // Function declaration // Member functions are usually public public: int GetX(void); void SetX(int x_parm); }; Then you have to include the name of the class with the actual function definition:
int Sprite::GetX(void) { // Public functions can return private data // these are known as "getters." return X; } void Sprite::SetX(int x_parm) { // Public functions can also change private data // using a function allows you to set guidelines on // what actually gets set in the private data. // These are known as "setters." if (x_parm >= 0) X = x_parm; } We can do this for any kind of functions that we wish to have full access to all member data within the class. By making the functions public and keeping the data private, it helps prevent bugs by not allowing us to "blindly" set data in variables that are only suppose to be set according to certain rules. Of course, this does introduce a needless function call that would have otherwise not be there. Well have no fear because small functions like the ones we just wrote can be made inline. This can be done in two ways. One would be just to use the keyword "inline" and the other way would be to incorporate the entire function definition within the class declaration. Here's an example of using the "inline" keyword: |
"Inline" is a C++ only keyword, it allows you to create inline functions without having to resort to macros as we did in our use with the SetPixel function. They work every bit like a standard function yet translate to inline code when compiled. |
inline int Sprite::GetX(void) { // Public functions can return private data // these are known as "getters." return X; } inline void Sprite::SetX(int x_parm) { // Public functions can also change private data // using a function allows you to set guidelines on // what actually gets set in the private data. // These are known as "setters." if (x_parm >= 0) X = x_parm; } While this works, usually code that is only one or two lines in length are best placed within the class definition itself. Such as the example below: class Sprite { int startXoff, startYoff; /* Offset copying position */ int lenXcopy, lenYcopy; /* Actual dimensions to copy */ char far *displayMem; /* Buffer sprite is modifying */ . . . // Snip . . . int status; /* Sprite status, varies by game rules */ int animThreshold; /* animation threshold, varies */ int moveThreshold; /* move threshold, varies */ int clock; /* sprite's clock */ void far *otherData; /* Extra data */ // Function declaration // Member functions are usually public public: // These functions will be inline by definition int GetX(void) {return X;} void SetX(int x_parm) {if (x_parm >= 0) X = x_parm;} }; Oh! I almost forgot to show how these member functions are called. Well, there are not called like normal functions are, they require that you create a variable from the class definition (better known as an "instance of the class" or an "object") before you can call it's member functions. For example:
// This line is equivalent to: // class Sprite GreenBall; Sprite GreenBall; // Since C++ does not require the keyword class // to create an instance of a class, we will // omit it from now on. GreenBall.SetX(5); // Passes 5 to the SetX function printf("Member \"X\" of the sprite called \"GreenBall\" equals %d", GreenBall.GetX()); In other words, we call member functions the same way we access member data. Cool! Okay, so much for the basics of C++, now let's actually create a sprite using this class. After it is done, using it will be much easier (at least I like to think so) than using a group of functions on a bunch of structs. Recognizing the terminologyBefore we move on let's see if we all understand a little terminology here, because we will be using it from here on out. One of the biggest problems with learning C++ and OOP is all the jargon and it's "misuse." So hopefully, this will help cut through that jargon by introducing the common terms used in OOP.
These are the basic definitions that we need to know for now. Let's get back to the fun stuff and build our sprites! Send your questions, comments, or ideas to: wilkeg@gamewood.net This page hosted by
Get your own Free Home Page |