The GMBehavior Virtual Object
One of the capabilities of the GMSprite object is to use a "GMBehavior" object to control its movement. It may surprise you that the "GMBehavior" object doesn't exist and will never be used. There is no pre-defined behavior. So how can the GMSprite be programmed to use a non-existent GMBehavior to control the sprite?
In C++ terminology, this GMBehavior is a "virtual object", or a "virtual base class". For the most part it is simply a placeholder for something that you can define later. The important part of this GMBehavior placeholder is it says there will be two useful functions: Initialize and Move. The GMSprite says "Okay. I can call the Initialize function to initialize the behavior, and then call the Move function when I want the behavior to tell me how to move next." Of course, these functions don't exist until you write a real behavior object that provides the instructions.
The real behavior object "inherits" all that is defined in the GMBehavior base class, and adds its specific definition. Your "derived" object IS a GMBehavior, but with your definition added to it.
In real life, we could talk about a "transport object" as being something that people can get into, and this object moves people to new locations. We can talk about needing stations where people can wait for these "transport" objects to arrive, and where the "transport" object delivers people, and we can define a lot of things about this "station" just based on the knowledge that the "transport" object exists. (Just like we can define GMSprites to use a behavior, even though we don't know exactly what the behavior is.) Later, we can build trains and buses and airplanes and taxicabs. Each of these IS a "transport object", yet each has its own definition. Similarly, each new behavior you write will BE a GMBehavior, but with its own definition.
This crucial part of object-oriented programming is "inheritance".
Sample Programs
Program a simple GMBehavior |
Make it a little smarter
Generalize the behavior in a separate source file
Program to use the generalized behavior
(uses critters.zip)
Sample Behavior Collection
Sample Behaviors from MP and Contributors:
Behavior Test Program |
BehaviorBob
BehaviorHover |
Behavior3DBounce |
BehaviorBounceWithSound
Behavior Test Program using BounceWithSound
(uses planets.zip)
![]()
The following is the definition of the GMBehavior base class from the file GraphicsMagicianCore.h:
// Behavior Object, base class
class GMBehavior
{
protected:
virtual void Initialize() {return;}
virtual int Move(){return 0;}
GMSprite* pSprite;
GMMachine* pMachine;
int SpriteWidth, SpriteHeight;
int ScreenWidth, ScreenHeight;
private:
void Init(GMSprite* pSprite);
// Init is used by GMSprite, does standard init,
// then calls Initialize for the rest
friend class GMSprite;
};
The important parts are in the section labeled protected. protected means these are for the most part private functions and properties, with the exception that they CAN be used by any object inheriting the GMBehavior base class. Your behaviors can use these things.
There are two virtual functions. virtual means they don't exist here, and that these are just "placeholders". When you inherit the GMBehavior object, you MUST provide Initialize and Move functions.
Also protected are five properties: pSprite, SpriteWidth, SpriteHeight, ScreenWidth, and ScreenHeight. pSprite is a pointer to the GMSprite to which this behavior belongs, so you can call any of the sprite's functions, such as SetPosition. pMachine is a pointer to the GMMachine object, so you can easily call machine methods such as PlayWave. The other four integers tell you about the size of the sprite and the screen. These are automatically initialized for you in the private Init function. The Init function then calls your Initialize function so you can set up the starting values of any variables you need for your behavior.
![]()
Here is the definition of the sample behavior, BehaviorBounce, which already included for you in GraphicsMagician.h. Notice especially the first line, which shows the syntax for inheriting GMBehavior:
// Example of a behavior
class BehaviorBounce: public GMBehavior
{
private:
void Initialize();
int Move();
void Launch();
int IsStopped() const;
int x, y;
int xvelocity, yvelocity; // speed of the ball
int slowdowncounter; // used to slow a rolling ball gradually
};
The Initialize and Move functions are required. Everything else was put in to make the "bounce" behavior work. You can add whatever functions and properties you need for your behaviors.
![]()
Here is the actual code definition for BehaviorBounce. Note that for this behavior the Initialize and Move functions simply do something to compute an x,y position for the sprite and then call the sprite's SetPosition function. There's a bunch of gibberish in the code below about choosing random numbers, computing when the object bounces off the side or top or bottom of the screen, and changing the velocities. But the essential part is that some x,y value is computed, and then SetPosition is called for the sprite!
Note also the various return values for the behavior's Move function. The return values are defined by you, and they can be made very useful in your programs. We'll use them in one or two of our sample programs.
// Constructor randomly creates x,y coordinate and x/y velocities.
void BehaviorBounce::Initialize()
{
// pick a random number from 0 to "screen width minus sprite width"
x = rand()%(ScreenWidth-SpriteWidth);
// and a random number for y in the same manner, using height
y = rand()%(ScreenHeight-SpriteHeight);
// and set our sprite at this random x,y location
pSprite->SetPosition(x,y);
// use the launch function to get the ball rolling!
Launch();
}
// Give the ball some velocity!
void BehaviorBounce::Launch()
{
// pick random x and y velocities from -30 to 30
xvelocity = rand()%61 - 30;
yvelocity = rand()%61 - 30;
slowdowncounter = 0;
}
// Return true if ball has stopped at the bottom.
int BehaviorBounce::IsStopped() const
{
if (xvelocity == 0 && yvelocity == 0 && y >= ScreenHeight-SpriteHeight)
return 1;
else
return 0;
}
// Use the current velocity settings to move the ball, and
// adjust the velocity settings for gravity and "friction".
// Return values: 1 left bounce, 2 right, 3 top, 4 bottom,
// 5 rolling on bottom, 6 stopped and new launch ready!
// 0 somewhere in middle, just doing its gravity thing....
int BehaviorBounce::Move()
{
int r=0; // return value
x += xvelocity;
// check if at left wall
if (x < 0)
{
x = 0;
xvelocity = -xvelocity - 1;
r=1;
}
// check right wall
else if (x > ScreenWidth-SpriteWidth-1)
{
x = ScreenWidth-SpriteWidth-1;
xvelocity = -xvelocity + 1;
r=2;
}
// if ball is on the ground, friction will slow it
if (yvelocity == 0 && y >= ScreenHeight-SpriteHeight)
{
// (but decrementing every time slows it too much.
// need extra counter)
slowdowncounter++;
if (slowdowncounter % 10 == 0)
{
if (xvelocity > 0) xvelocity --;
else if (xvelocity < 0) xvelocity ++;
}
}
y += yvelocity;
// if bounce on top
if (y <= 0)
{
y = 0;
yvelocity = -yvelocity;
r=3;
}
// bounce off bottom
else if (y >= ScreenHeight-SpriteHeight)
{
y = ScreenHeight-SpriteHeight;
if (yvelocity) yvelocity = -yvelocity + 1; // +1 subtracts some bounce
if (yvelocity) r = 4;
else r = 5;
}
else
yvelocity ++;
pSprite->SetPosition(x,y);
if (IsStopped()) {Launch(); r=6;}
return r;
}
![]()