Levels in LlamaWorks2D


The LlamaWorks2D game engine contains a class called level. The level class does most of the work of loading level files, figuring out what they contain and allocating the objects they specify.

In your games, you won't use the level class as it is. The LlamaWorks2D level class provides the basic functionality of a level, but you need to derive your own class from the level class. This enables you to add functionality that is specific to your game.

Before we dive into how to use the level class, I want to take a moment to clarify what goes in your custom level class and what you should put into your game class. Until now, all of the game logic for the sample programs has existed in their respective game classes. Once you start using the LlamaWorks2D level class, you'll need to divide the game's work between the level and the game classes. Here's the rule of thumb to use: if information needs to be kept from level to level, it should go in your game class. Otherwise, it goes in your level class.

For instance, the sample game contains the character Captain Chloride. The Captain moves from level to level. Therefore, he should exist in the game class. The player's score, power-ups, and weapons should also be stored in the game class because they continue with the player from level to level.

On the other hand, all of the monsters in a level are specified in the level file. The game logic for handling them should go in the level class. The same is true of the background. Its bitmap should be given in the level file. The logic for handling anything the level contains should be in your level class.

Let's see how all of this works by first examining a simple level file.

Xml Level Files

I mentioned back in chapter 12, "File Input and Output," that games generally store their level files as binary information. They typically save their configuration information in text files containing information in Extensible Markup Language (XML) format. LlamaWorks2D breaks that mold by requiring XML level files. I've found that it's much easier for those new to game programming to fix problems with their levels if the level files are in XML.

XML files always hold text tags. The rules of XML say that the tags usually occur in pairs. In LlamaWorks2D, they always occur in pairs. Pairs of XML tags can contain information, other tags, or both.

The XML schema used by LlamaWorks2D is defined in Table 16.1. The table shows all of the tags that LlamaWorks2D recognizes, what they mean, what tags they must be contained in, and what tags they contain.

Table 16.1. Llamaworks2D XML Tags

Tag Pair

Description

Contains

Cointained By

<AnimatedSprite></AnimatedSprite>

Marks the start and end of a sprite.

<Animation>, <CurrentAnimation>, <XY>, <Velocity>, <BoundingRectangle>

<Level>

<Animation></Animation>

Marks the start and end of an animation.

<Frames>,<TransparentColor>,<CurrentFrame>,<LoopStyle>

<AnimatedSprite>

<Blue></Blue>

Specifies a blue value in a color.

A floating-point blue value.

<Transparent-Color>

<Bottom></Bottom>

Contains the lower boundary of a bounding rectangle.

An integer specifying the bottom of the bounding rectangle.

<Bounding-Rectangle>

<Bounding-Rectangle> </Bounding-Rectangle>

Specifies the limits of a bounding rectangle.

<Top>,<Bottom>,<Left>,<Right>

<Sprite>,<Animated-Sprite>

<CurrentFrame>

Specifies the current frame in an animation.

An integer frame number.

<Animation>

<Frames></Frames>

Defines a set of images used as frames in an animation.

<ImageFileName>

<Animation>

<Green></Green>

Specifies a green value in a color.

A floating-point green value.

<Transparent-Color>

<ImageFileName>,</ImageFileName>

Specifies the name of a bitmap file.

A filename.

<Sprite>,<Animated-Sprite>,<Frames>

<Left></Left>

Contains the left boundary of a bounding rectangle.

An integer specifying the left side of the bounding rectangle.

<Bounding-Rectangle>

<Level></Level>

Marks the start and end of a level.

<Sprite>,<AnimatedSprite>, Custom tags

N/A

<LoopStyle>

Selects the looping style of an animation.

One of the following values:FORWARD, REVERSE, FORWARD_THEN_REVERSE

<Animation>

<Red></Red>

Specifies a red value in a color.

A floating-point red value.

<Transparent-Color>

<Right></Right>

Contains the right boundary of a bounding rectangle.

An integer specifying the right side of the bounding rectangle.

<Bounding-Rectangle>

<Sprite></Sprite>

Marks the start and end of a sprite.

<ImageFileName>,<TransparentColor>,<XY>,<Velocity>,<BoundingRectangle>

<Level>

<Top></Top>

Contains the upper boundary of a bounding rectangle.

An integer specifying the top of the bounding rectangle.

<Bounding-Rectangle>

<TransparentColor></Transparent-Color>

Specifies the transparent color for a bitmap.

<Red>,<Green>,<Blue>

<Sprite>,<Animation>

<Velocity></Velocity>

Specifies the direction and speed of an object.

<X>,<Y>

<Sprite>,<Animated-Sprite>

<X></X>

Specifies the x component of a point or vector.

An integer x value.

<XY>,<Velocity>

<XY></XY>

Contains the x and y coordinates of an object. Must be integers.

<X>,<Y>

<Sprite>,<Animated-Sprite>


The collection of XML tags in Table 16.1 form the schema that LlamaWorks2D uses to create levels. In addition to these tags, you can make your own tags to have custom objects in your levels. For example, you could create a pair of tags called <Monster> and </Monster> to add a monster to your level. The next section shows how to use the XML and extend it with your own custom tags.

A Level File for Invasion of the Slugwroths

Listing 16.3 shows the level file for Program 16.1, which you'll find on the CD in the folder \Source\Chapter16\Prog_16_01. Because the level is very simple, the level file is quite short.

Listing 16.3. The XML level file for Program 16.1

 1   <Level> 2 3      <CaptainChloride> 4          <XY> 5             <X>320</X> 6             <Y>350</Y> 7           </XY> 8       </CaptainChloride> 9 10      <Background> 11          <Sprite> 12              <ImageFileName>background.bmp</ImageFileName> 13          </Sprite> 14       </Background> 15 16    </Level> 

All level files for LlamaWorks2D must begin with the XML tag <Level> and end with </Level>. If either one of these tags is missing, LlamaWorks2D generates an error. Your levels can contain three kinds of items. First, they can hold sprites. Sprites are defined with the <Sprite> and </Sprite> tags. Second, levels can contain animated sprites, which are defined with the <AnimatedSprite> and </AnimatedSprite> tags. Third, they can contain your custom objects specified with your custom tags.

The level in Listing 16.3 has two custom objects. One represents Captain Chloride himself. The other is the background. Recall that the object in the game that represents Captain Chloride is kept in the program's game class. However, each time a level starts, Captain Chloride can be in a different position in the level. Therefore, his starting position is specified in the level file.

The background object is actually a customized sprite. In fact, when you use LlamaWorks2D, everything on the screen is a sprite, an animated_sprite, or something derived from one of those two classes. In this case, the background is just a sprite object that the program scrolls back and forth.

When it starts up, the program reads the level file in Listing 16.3. It has to separate each individual tag and value in the level file. It must then figure out what they mean and react accordingly. This process is called parsing the tokens. In a LlamaWorks2D level file, the tokens are XML tags. Next, we'll examine how parsing is done.

Parsing Level Files

As your game parses a level file, it adds objects to LlamaWorks2D's collection of visible objects. To parse the level file, a game using LlamaWorks2D needs a custom object derived from the level class. The custom class that the version of Invasion of the Slugwroths uses is shown in Listing 16.4.

Listing 16.4. The chloride_level class

 1    class chloride_level : public level 2    { 3    public: 4        chloride_level(); 5 6        bool Update( 7            invasion *pInvasion); 8 9        bool Render( 10           invasion *pInvasion); 11 12       bool ObjectFactory( 13           FILE *levelFile, 14           std::string tagToParse); 15 16       float ChlorideStartX(); 17       float ChlorideStartY(); 18 19       float MinX(); 20       float MinY(); 21       float MaxX(); 22       float MaxY(); 23 24    private: 25       float chlorideStartX, chlorideStartY; 26 27       float maxWorldX, maxWorldY; 28    }; 

The chloride_level class contains functions to update and render the level. These functions are overrides of the same functions in the llamaworks2d::level class. If you don't provide these functions, the versions in the level class are called. The level::Update() function doesn't actually do anything. It just returns true. The level::Render() function automatically renders every object in LlamaWorks2D's collection of visible objects. To get everything displayed on the screen properly, your custom level class should call the level::Render() function.

One of the most important functions in the chloride_level class is ObjectFactory(). If your file contains any custom XML tags, your level class must have a function called ObjectFactory(). It's in this function that your level class parses your custom XML. Listing 16.5 gives the ObjectFactory() function for the chloride_level class.

Listing 16.5. The ObjectFactory() function

 1    bool chloride_level::ObjectFactory( 2        FILE *levelFile, 3        std::string tagToParse) 4    { 5        bool parseOK = true; 6        std::string temp; 7 8        if (IsTag(tagToParse,"CaptainChloride")) 9        { 10           EliminateWhiteSpace(levelFile); 11           temp = ReadTagOrValue(levelFile); 12           int startX, startY; 13           parseOK = ParseXY(levelFile, 14               startX, startY); 15           if (parseOK) 16           { 17               chlorideStartX = (float)startX; 18               chlorideStartY = (float)startY; 19 20               EliminateWhiteSpace(levelFile); 21               temp = ReadTagOrValue(levelFile); 22 23               if (!IsTag(temp,"/CaptainChloride")) 24               { 25                   parseOK = false; 26               } 27           } 28       } 29       else if (IsTag(tagToParse,"Background")) 30       { 31           sprite *theBackground = new sprite; 32           if (theBackground != NULL) 33           { 34               EliminateWhiteSpace(levelFile); 35               temp = ReadTagOrValue(levelFile); 36               if (IsTag(temp,"Sprite")) 37               { 38                   parseOK = ParseSprite(levelFile, theBackground); 39                   if (parseOK) 40                   { 41                       allObjects.push_back(theBackground); 42                   } 43                } 44 45                EliminateWhiteSpace(levelFile); 46                temp = ReadTagOrValue(levelFile); 47 48                if (!IsTag(temp,"/Background")) 49                { 50                    parseOK = false; 51                } 52                if (parseOK) 53                { 54                   maxWorldX = (float)theBackground- >BitmapWidth(); 55                   maxWorldY = (float)theBackground- >BitmapHeight(); 56                 } 57             } 58             else 59             { 60                 parseOK = false; 61                 theApp.AppError(LWES_OUT_OF_MEMORY); 62             } 63       } 64 65       return (parseOK); 66    } 

The chloride_level::ObjectFactory() function starts by declaring the variables it needs. It then examines its tagToParse parameter. When LlamaWorks2D calls the ObjectFactory() function, it passes the XML tag it doesn't recognize in the tagToParse parameter.

Note

When your ObjectFac-tory() function looks for XML tags, such as <CaptainChloride> or </CaptainChloride>, it does not need to include the < and > characters. LlamaWorks2D removes them.


For example, line 8 of Listing 16.5 tests to see if the tag was <CaptainChloride>. To do so, it calls the level class's IsTag() function. This is one of several parsing functions that the level class provides to your custom level classes. All of the parsing functions in the level class are protected, which means they are available to classes derived from the level class. You can find the prototypes for these functions in the LlamaWorks2D file called LW2DLevel.h.

If the tag is <CaptainChloride>, the ObjectFactory() function calls the level::EliminateWhiteSpace() function. This is another parsing function in the level class. It throws away white space such as tabs, spaces, endlines, and so forth.

At this point, the ObjectFactory() function reads the next tag or value to parse by calling level::ReadTagOrValue(). The next thing the ObjectFactory() function should encounter in the XML file is the <XY> tag. If it doesn't, the XML file is wrong.

Because the <XY> tag is already part of the schema that LlamaWorks2D uses, the ObjectFactory() function can call the level::ParseXY() function to parse it. To do so, it must pass the variables to receive the x and y values as the second and third parameters in the ParseXY() function's parameter list. If the ParseXY() function is successful, it returns TRue and the variables contain the x and y values it parsed. The ObjectFactory() function stores the x and y values for Captain Chloride's starting location in some private data members of the chloride_level class. When the level starts, the game can query these data members to place Captain Chloride properly in the level.

Warning

I did not put error checking into the ObjectFactory() function in Listing 16.5 to detect when the <XY> tag is missing. I did this to keep the code brief. However, your parsing code should not do this as it will cause your game to crash when the XML file is wrong.


The next XML tag the ObjectFactory() function should encounter is </CaptainChloride>. To parse it, ObjectFactory() tHRows away any white space and then reads the next tag. If it is not </CaptainChloride>, ObjectFactory() sets a status variable to return the error.

Note

The level class in LlamaWorks2D has a protected data member called allObjects. Because this member is protected, it is available to classes derived from level. The allObjects member is a fancy type of array provided by the C++ Standard Template Library. Its push_back() member function adds items to the array. It also has a size() function that tells how many items are in the array.


Beginning on line 29 of Listing 16.5, ObjectFactory() parses the <Background> tag. The background is actually a sprite object, so line 31 allocates a sprite. It calls the level::ParseSprite() function to parse the sprite information. If it does that successfully, it stores the background in the level class's collection of screen objects. It does this by passing the background's sprite to the push_back() function.

The ObjectFactory() function looks for the </Background> tag on lines 4551. If it isn't next in the XML file, ObjectFactory() sets an error status variable. When ObjectFactory() successfully parses the information between the <Background> and </Background> tags, it sets the height and width of the world coordinate system to the height and width of the background bitmap.

With an XML level file and an ObjectFactory() function, the program has what it needs to load a level.

Loading a Level

When the Invasion of the Slugwroths programs starts, LlamaWorks2D calls the invasion::InitGame() function. At that time, it initializes the chloride object that represents Captain Chloride. It also calls a function to load a level. The code for these two functions appears in Listing 16.6.

Listing 16.6. The game class's functions for loading a level

 1    bool invasion::InitGame() 2    { 3       bool initOK = true; 4 5       initOK = theCaptain.LoadResources(); 6 7       if (initOK==true) 8       { 9           initOK = InitLevel(); 10      } 11 12      return (initOK); 13   } 14 15 16   bool invasion::InitLevel() 17   { 18      bool initOK = true; 19 20      initOK = 21          currentLevel.Load( 22              "Invasion.xml", 23              level_file_base::LWLFF_XML); 24 25      if (initOK==true) 26      { 27         theCaptain.X( 28             currentLevel.ChlorideStartX()); 29         theCaptain.Y( 30             currentLevel.ChlorideStartY()); 31      } 32 33      return (initOK); 34   } 

Listing 16.6 shows that the invasion class's InitGame() function invokes the chloride class's LoadResources() function to load all of the bitmaps for Captain Chloride's animations. It then calls the invasion::InitLevel() function, which starts on line 16. InitLevel() uses the level class's Load() function and passes it the name of the XML level file as the first parameter. The second parameter is a constant telling the Load() function that the level file is XML.

The level::Load() function reads the XML level file and parses it. As mentioned earlier in this chapter, level::Load() is able to parse any sprites or animated sprites in the XML level file. If Load() encounters any tags it does not recognize, such as the <CaptainChloride> and <Background> tags, it automatically calls the chloride_level::ObjectFactory() function that was given in Listing 16.5. The ObjectFactory() function parses those tags. When the level has been loaded, InitLevel() calls a pair of functions from the chloride_level class to obtain Captain Chloride's starting position in the world.

Updating a Level

Earlier in this chapter, in the section called "The New Captain Chloride," I showed how the chloride object is updated during each frame. Please refer back to Listing 16.2 if you need a refresher on how that works. The program updates the Captain's position, status, and animations from the invasion class's UpdateFrame() function, as shown in Listing 16.7.

Listing 16.7. Functions for updating and checking for collisions

 1    bool invasion::UpdateFrame() 2    { 3       GetKeyboardInput(); 4       bool updateOK = theCaptain.Update(this); 5       if (updateOK) 6       { 7           updateOK = currentLevel.Update(this); 8       } 9 10      return (updateOK); 11    } 12 13 14    void invasion::CollisionCheck( 15        vectorf &worldPos, double fWidth, double fHeight) 16    { 17        if (worldPos.X() < currentLevel.MinX()) 18        { 19            worldPos.X(currentLevel.MinX()); 20        } 21        else if (worldPos.X() > currentLevel.MaxX() - fWidth) 22        { 23            worldPos.X( currentLevel.MaxX() - fWidth); 24        } 25 26        if (worldPos.Y() < currentLevel.MinY()) 27        { 28            worldPos.Y( currentLevel.MinY()); 29        } 30        else if (worldPos.Y() > currentLevel.MaxY() - fHeight) 31        { 32             worldPos.Y(currentLevel.MaxY() - fHeight); 33        } 34   } 

The invasion class has an object of type chloride called theCaptain. During each frame, the UpdateFrame() function calls the chloride::Update() function, as shown on line 4 of Listing 16.7. When chloride::Update() executes, it checks for collisions between the Captain and objects in the level. CollisionCheck(), the function it calls to perform the check, is in the invasion class. You can find its code beginning on line 14. The current version of CollisionCheck() is extremely simple. It just uses if-else statements to determine whether the Captain's bitmap has moved off the screen. However, when we add more objects to Captain Chloride's world, this function will grow to check for more collisions.

Rendering a Level

When Invasion of the Slugwroths renders a frame, it must render both the level and Captain Chloride, as shown in Listing 16.8 Most of the complex work of rendering is done in the chloride::Render() function, which you saw back in Listing 16.2.

Listing 16.8. The invasion::RenderFrame() function

 1   bool invasion::RenderFrame() 2   { 3       bool renderOK = currentLevel.Render(this); 4 5       if (renderOK) 6       { 7           renderOK = theCaptain.Render(this); 8       } 9 10      return (renderOK); 11   } 

Listing 16.9 demonstrates that rendering the level involves setting the position of the background on the screen and then rendering it.

Listing 16.9. The chloride_level::Render() function

 1   bool 2   chloride_level::Render( 3       invasion *theGame) 4   { 5       bool renderOK = true; 6       vector screenPos; 7       vectorf worldPos; 8 9       // The background is always at 0,0 in world coordinates. 10      sprite *theBackground = (sprite*)allObjects[BACKGROUND]; 11      worldPos.X( 0.0); 12      worldPos.Y( 0.0); 13      theGame->WorldToScreen( worldPos, screenPos); 14      theBackground->X( screenPos.X()); 15      theBackground->Y( screenPos.Y()); 16 17      renderOK = level::Render(); 18 19      return (renderOK); 20   } 

Note

If your game stores custom objects in a level, the custom objects must inherit from the LlamaWorks2D screen_ object class.


The chloride_level class's Render() function inherits from the llamaworks2d::level class. As a result, it has direct access to the level class's collection of screen objects. The function uses this collection, which is called allObjects, on line 10. As I mentioned previously, this collection is a fancy array provided by the C++ Standard Template Library. Line 10 demonstrates that when the chloride_level::Render() function access this collection, it uses the collection just like any other array. The collection stores generic pointers to screen objects such as sprites and animated sprites. Everything in the collection must inherit from a class in LlamaWorks2D called screen_object. Both the sprite and animated_sprite classes inherit from screen_object.

The chloride_level::Render() function gets a pointer to the background on line 10. It type casts the pointer variable to be a pointer to sprite objects and saves it in the variable theBackground. The functions in your level class should use the same technique when they need access to an object in the collection.

On lines 1115, the chloride_level::Render() function sets the position of the bitmap on the screen. In world coordinates, the position of the background is always (0,0). However, because the game updates its imaginary camera, it "knows" how to convert (0,0) in world coordinates to screen coordinates.



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