Bumping into a Solid Door


In LlamaWorks2D, everything that moves is a sprite or an animated sprite. That includes doors, which open and close. Putting a solid object, such as a door, into Captain Chloride's path is simply a matter of creating the doors as a sprite.

Tip

Because walls, trees, rocks, and similar objects don't move, you should probably not define them as sprites. Instead, you can create custom objects that just contain an x, y location and a bounding rectangle. Any object that has a bounding rectangle can be tested for collisions.


The game must somehow detect when Captain Chloride bumps into sprites. As you may remember from the Ping program in previous chapters, detecting collisions is exactly what bounding rectangles are for. Invasion of the Slugwroths assigns a bounding rectangle to all sprites and animated sprites that Captain Chloride can have physical contact with. When the Captain's bounding rectangle overlaps the bounding rectangle of another sprite (or animated sprite), the game must react in some way. In the case of a door, the game reacts by playing a bumping sound and preventing the Captain from moving forward.

Collision Management

Using bounding rectangles to detect collisions is actually just the first step in dealing with colliding onscreen objects. Reacting to collisions requires some sort of collision management system. Game programmers tend to be very particular about how this system is implemented, so I did not make it part of LlamaWorks2D. However, I'll demonstrate a simple collision management system by creating a class called world_object. The world_object class implements collision handling for all objects in Captain Chloride's world. Listing 17.1 provides the definition for the world_object class.

Note

Virtually all 2D games use bounding rectangles to detect collisions between sprites, animated sprites, and nonmoving objects on the screen. This technique can be easily extended into 3D, so most 3D games use it as well.


Listing 17.1. A simple collision management class

 1   class world_object 2   { 3   public: 4      world_object(); 5 6      virtual bool Intersects(world_object &other); 7 8      virtual void Hit(world_object &stationary); 9 10     virtual bool Collidable(); 11 12     virtual bool Visible(); 13 14     void X( float x); 15     float X(); 16 17     void Y( float y); 18     float Y(); 19 20     void CollisionWidth(float w); 21     float CollisionWidth(); 22 23     void CollisionHeight(float h); 24     float CollisionHeight(); 25 26     virtual std::string Type() = 0; 27 28   protected: 29     vectorf worldPos; 30 31   private: 32     float collisionWidth; 33     float collisionHeight; 34     bool collidable; 35     bool visible; 36 37   public: 38     world_object *next; 39   }; 

Note

You'll find the code for the world_object class in the files WorldObject.h and WorldObject.cpp. They are in the folder Source\Chapter17\Prog_17_01.


Warning

The next member of the world_object class is public. I made this compromise to keep the list management code very simple. However, it's seldom a good idea to have public data members in professional programs. Public data members can be used in incorrect ways and cause objects to contain invalid data.


For each object that the game checks for collisions, it creates a world_object. It then adds each world_object to a list that it keeps. The list, which is called a linked list, is stored in the game class with a pointer to objects of type world_object. The pointer points to the first item in the list. When the game moves to the next item in the list, it uses a pointer in the world_object class called next. The next pointer points to the next item in the list. If the game has reached the end of the list, the next pointer contains NULL.

Note

In the world_object class, the Hit() function doesn't do anything. The derived class implements the Hit() function to provide the reaction.


The world_object class is intended to be used as a base object for derived classes. It has a member function called Intersects() that tests to see whether two objects with bounding rectangles are currently intersecting. Its Hit() function, which should be overridden in derived classes, reacts to collisions.

The other member data and functions of the world_object class keep track of whether the object associated with the world_object can collide with other objects. They also tell whether the object is currently visible on the screen.

A Basic Door

In this version of the game, the door simply blocks Captain Chloride's way. As you'll see in chapter 18, "That's a Wrap," the door also plays a role in killing the evil Slugworths. For now, I'll demonstrate how to tell the game whether the door is open or closed. I'll also show how to detect collisions between Captain Chloride and the door.

Note

In the section called "Making the Door Open and Close," which appears later in this chapter, I'll add more functionality to the door class.


As you probably expect by now, you create a door in a game by creating a door class. The door class needs an enumeration to show what state it's in. The door can be open, in the process of opening, closed, or in the process of closing. Listing 17.2 gives the code for the door class.

Listing 17.2. An initial version of the door class

 1   class door : public world_object 2   { 3   public: 4       // The door will go from one state to another 5       // depending on user input and other events. 6       enum state 7       { 8          STATE_CLOSED, 9          STATE_OPENING, 10         STATE_OPEN, 11         STATE_CLOSING 12      }; 13 14      door(); 15 16      std::string Type(); 17 18      bool Update( 19          invasion *theGame); 20 21      bool Render( 22          invasion *theGame); 23 24      void Movement( 25          vectorf new_velocity); 26 27   private: 28      vectorf velocity; 29      vectorf worldPos; 30      door::state currState; 31   }; 

The enumeration on lines 612 of Listing 17.2 define all of the states that the door can be in. The Type() function returns a string telling what data type this object is. In this version of the class, the Update(), Render(), and Movement() functions do nothing because the image of the door is part of the background. Notice that the door class inherits from the world_object class. That's where all of the work for the door object occurs.

Note

The code for the door class is in the files Door.h and Door.cpp in the folder Source\Chapter17\Prog_17_01.


The Very Collidable Captain Chloride

Enabling Captain Chloride to collide with objects in his world requires very little modification of the chloride class. In fact, the chloride class only needs one new function, named Hit(), and a modification to its Update() function. Listing 17.3 gives the code for both.

Note

The code for this version of the chloride class is in files Chloride.h and Chloride.cpp in the folder Source\Chapter17\Prog_17_01.


Listing 17.3. The Update() and Hit() functions for the chloride class

 1   bool 2   chloride::Update( 3       invasion *theGame) 4   { 5       // update the world position. 6       worldPos += velocity; 7 8      // correct the world position if necessary. 9      theGame->CollisionCheck(*this); 10 11     // update the "camera". 12     // this is the world position that we would like 13     // to be in the center of the screen if the world 14     // had no boundaries. 15     float cameraWorldX = 16         X() + 0.5 * CollisionWidth(); 17     theGame->SetCamera(cameraWorldX); 18 19      return (true); 20   } 21 22 23   void 24   chloride::Hit( 25      world_object &stationary) 26   { 27      if (stationary.Type() == "door") 28      { 29          door *theDoor = (door*)&stationary; 30 31          // We hit a door. Go back to previous position 32          // and play a sound. 33          worldPos -= velocity; 34 35         // Need a way to know if this is still playing 36         // and to not play it again until the current 37         // instance has finished. 38         bonkSound.Play(); 39      } 40   } 

The version of the chloride::Update() function in Listing 17.3 is almost the same as the version from chapter 16, "The World of Captain Chloride." However, there is one key difference. Notice that on line 9, the Update() function invokes a function in the game class called CollisionCheck(). The CollisionCheck() function takes an object derived from the world_object class as its only parameter. In this case, the object being passed in is Captain Chloride himself. The statement *this, which appears in the parameter list, refers to the current object. Because Update() is a member of the chloride class, and because there's only one object of that type in the entire game, the call on line 9 always passes in the object that represents Captain Chloride.

When the chloride::Update() function calls CollisionCheck(), the CollisionCheck() function compares the object that represents Captain Chloride to every other object on the screen. If there's an overlap between their bounding rectangles, a collision has occurred. In that event, the chloride::Hit() function is called automatically. The Hit() function begins on line 23 of Listing 17.3.

When the Hit() function is called, it receives the object that the Captain collided with in its parameter list. The first thing that the Hit() function needs to do is find out what kind of object the Captain banged into. It calls the Type() function, which every class derived from world_object has. The Type() function simply returns a string containing the name of the class for that kind of object. If the string is "door" it means that Captain Chloride hit an object of type door. If that's the case, the Hit() function points a pointer at the door object on line 29. It uses a type cast to clearly state that the object is a door. It then positions Captain Chloride so that he can't move any farther. This prevents him from walking through the closed door. The Hit() function then plays a bonk sound.

Captain Chloride Collides with Reality

The last thing needed to implement collision handling in the program is to write the game class's CollisionCheck() function. To check for collisions, CollisionCheck() tests to see whether the object being checked has gone off the screen. It then scans a list of all of the objects in the world to see if there is overlap between them and the object being tested. Listing 17.4 provides the code for the CollisionCheck() function.

Note

The code for the CollisionCheck() function is in the file Invasion.cpp in the folder Source\Chapter17\Prog_17_01.


Listing 17.4. The CollisionCheck() function for the invasion class

 1   void invasion::CollisionCheck( 2      world_object &movingObject) 3   { 4     // First test against the world limits 5     double width = movingObject.CollisionWidth(); 6     double height = movingObject.CollisionHeight(); 7     if (movingObject.X() < currentLevel.MinX()) 8     { 9         movingObject.X(currentLevel.MinX()); 10    } 11    else if (movingObject.X() > currentLevel.MaxX()  width) 12    { 13        movingObject.X(currentLevel.MaxX()  width); 14    } 15 16    if (movingObject.Y() < currentLevel.MinY()) 17    { 18        movingObject.Y( currentLevel.MinY()); 19    } 20    else if (movingObject.Y() > currentLevel.MaxY()  height) 21    { 22        movingObject.Y(currentLevel.MaxY()  height); 23    } 24 25    // Now test against all the objects 26    world_object *testObject = worldObjects; 27    while(testObject) 28    { 29        if (testObject != &movingObject) 30        { 31           if (testObject>Intersects(movingObject)) 32           { 33               movingObject.Hit(*testObject); 34           } 35        } 36        testObject = testObject>next; 37     } 38   } 

Because of the way it's designed, the game can use the CollisionCheck() function to check for collisions between virtually any two moving objects on the screen. It begins by obtaining the height and width of the object being tested. Next, it tests to see whether the x coordinate of the object is off the left edge of the screen. If it is, the function sets the object's x coordinate to the screen's left edge on line 9 of Listing 17.4.

If the moving object has not gone off the left edge of the screen, the CollisionCheck() function tests to see whether it went off the right edge on line 11. If so, it adjusts the object's position so that it stays on the screen.

On lines 1623, the CollisionCheck() function checks whether the moving object went off the top or bottom of the screen. If so, it sets the y position to keep the object on the screen.

It's at this point that the CollisionCheck() function uses the linked list presented earlier in this chapter. The start of the list is a pointer to objects of type world_object. The CollisionCheck() function enters a loop on line 27. In this loop, it uses the Intersect() function to see if there's any overlap between the moving object and the current object in the linked list. If there is, CollisionCheck() calls the Hit() function for the moving object. It passes the current object in the list as the parameter for the Hit() function.

Note

Pointers of type world_object can point at any object derived from the world_object class. Therefore, every object in the list must be derived from world_object.


If there is no overlap between the bounding rectangles of the moving object and the current object in the list, the loop executes the statement on line 36. This statement sets the pointer variable testObject to point at the next object in the list. The CollisionCheck() function then performs the loop test on line 27. This test checks to see whether the variable testObject points at anything. Another way to write this test is as follows:

 while (testObject != NULL) 


This statement and the statement on line 27 say exactly the same thing. They are both true while testObject does not contain the value NULL. When this loop gets to the last object in the linked list, the next pointer on line 36 is NULL. As a result, the variable testObject receives the value NULL. This causes the loop test on line 27 to be false, which makes the loop end.

Invasion of the Slugwroths now has a system that can handle collisions between any two moving objects on the screen. The code in Program 17.1 on the CD demonstrates how to bump Captain Chloride into solid objects such as doors. However, the game can use pretty much the same code to enable him to pick objects as well. That's the subject of the next section.



Creating Games in C++(c) A Step-by-Step Guide
Creating Games in C++: A Step-by-Step Guide
ISBN: 0735714347
EAN: 2147483647
Year: N/A
Pages: 148

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net