Remember that, when tweaking one particular game mode, it saves time to have the Pop program start up in the game mode that you want to play with. The way to control this is to edit the CPopDoc constructor in popdoc.cpp . Do it now, it'll save you a lot of clicking later on.
Exercise 14.1: Put the sprites into the UML
Go ahead and complicate the Spacewar UML diagram (Figure 14.1) by adding in the sprite classes used by the various critters. This would include the cPolygon , cPolyPolygon , and cSpriteIcon classes. Can you find a way to do it without having any pairs of crossing lines? If not, then show these classes in a separate UML diagram. Are there any other important classes peculiar to the Spacewar game that we're leaving out?
Exercise 14.2: A larger world
You may have already noticed that the 2D Game Stub has a world larger than what you see on the screen. In this example we look at how to change Spacewar to use a larger world as well. You can check the gamestub.cpp file for code examples as well.
You can make the world of the Spacewar game larger by changing the initialization value of the cGameSpacewar::WORLDSIZE to a larger number, say 80.0 instead of 20.0. Alternately, and probably better, you can make the change directly to the arguments of the _border.set(xsize, ysize) call in the cGameSpacewar constructor.
One thing to keep in mind is that whenever you change the size of the _border you need to immediately call setBackgroundBitmap(...) so as to make sure that the game's cSpriteIconBackground bitmap object ' knows ' the shape of the world whose background it is supposed to cover. Another point to mention here is that you will get better results if the visual aspect (that is, the y-size-to-x-size-ratio) of your background bitmap matches the aspect of the _border .
Okay, so now suppose you've changed the _border and alerted the background bitmap. By default the view will show the whole world, which isn't really what you want. The idea is to have a world that extends beyond your current view. To make the view smaller, edit the cGameSpacewar::initializeViewpoint code to include a line like pviewer->zoom(3.0) . The exact value of zoomfactor to use is something you have to experiment with.
What will happen now is that you see your player and part of the world, but if the player goes off one edge it disappears. Either you can require the user to use a Ctrl + Arrow keys to keep looking at the player or, which is more agreeable, you can make the view automatically track the player. To do this, find the cGameSpacewar::initializeView code and add the line pview->pviewpointcritter()->setTrackplayer(TRUE); //Always look at the player .
Exercise 14.3: Change the sprites with the level
Edit the cGameSpacewar::adjustGameParameters() so that it changes the game's _spritetype at certain score levels.
Exercise 14.4: A graphic theme for your game
You can fairly painlessly make an original-appearing game simply by taking the Gamestub game and giving it a uniform graphical theme. Select as a graphic theme some subject such as farming or hunting or ancient Egypt or the jungle or underwater diving or student life, etc. Now do the following steps. (a) Accumulate the bitmaps and get them into usable form. (b) Adjust their sizes and crop them as appropriate. (c) Import them into your project. (d) Set a large theme bitmap as your background. (e) Set some smallish theme bitmaps to be the sprites, the rivals and the props in this game.
Exercise 14.5: Walls
You can always enhance a shooting game by adding walls for the critters to hide behind and bounce off of. Look at the cGameDambuilder constructor code in gamedambuilder.cpp to see how to add walls into a world. We discuss walls some more in Chapter 18: Interesting Worlds.
Exercise 14.6: More levels
Look at Exercise 10.2: A Multi-Level Game that deals with using the game's _level parameter. Give some thought to a comprehensive series of level changes. Things that could change with the levels include the sprite type used by the critters (as mentioned in the last exercise), the background bitmap, the speeds of the critters, the sizes of the parameters in the forces the critters use to chase and avoid things, the radii of the critters, the size of the world, the number of critters, etc.
So as to facilitate tweaking, it's not a bad idea to store these level parameters in arrays, so that you have all the constants side by side for comparison. You'll probably want new members of your game class for these arrays. Also you ought to implement a setLevel method which will plug in all of the constants for you.
Exercise 14.7: A two-and-a-half dimensional game
Go to the cGameSpacewar::initializeView(CPopView *pview) implementation in gamespacewar.cpp and find these lines.
// pview->setGraphicsClass(RUNTIME_CLASS(cGraphicsOpenGL)); //Start in 3D // _plightingmodel->setEnableLighting(FALSE); //Try no light in 3D: ugly, slow.
If you comment in the first line, your game will start out in a 3D view. The default lighting model is to use lights, which is usually faster and better-looking. But you can try commenting in the second line as well to see how the world looks without lights.
2D games that are shown in a 3D view are sometimes called two-and-a-half dimensional games . You can beef up the effect here by making the critters have more thickness in the z -axis. This thickness is controlled by the sprite's _prismdz parameter. The best way to change this is with a call to the cCritter::setPrismDz method inside your critter constructors. Default values for the prismdz are specified by statics of the form ???PRISMDZ in the sprite.cpp file.
Exercise 14.8: Make a better shoot method
In Spacewar, the cCritterBulletSilver that the cCritterUFO shoot at the player never hit the player as long as the player is moving. Give cCritterUFO an override of the virtual void aimAt(cCritter *pcritter) method that's a little smarter . Instead of aiming at where the pcritter is now, have the new method aim the gun at where the pcritter will be when the bullet gets there . Figuring out this algorithm is a non-trivial calculus problem. If you can't solve it, consider at least improving your aimAt with a rough guess. You might, for instance, estimate the approxdt time your bullet will travel to be the current distance to the target divided by the bullet speed, and then aim at where the pcritter will be after approxdt seconds. (Getting the approxdt estimate exactly right is where the calculus would come in!)
Exercise 14.9: Switching between different guns
Games often allow the player to have several different guns, which effectively means shooting different kinds of bullets. You might have a few high-test bullets that have a larger _hitstrength , for instance. Or missile or bomb bullets.
There are two things to do to give the player multiple guns. First we need an array of the allowable kinds of CRuntimeClass *_pbulletclass field, as well as a method for moving through this array. Second we need something to trigger the change to a different gun.
One option is to let the user change guns with the mousewheel or an accelerator key. This would mean changing the OnMouseWheel method of CPopDoc to act differently if the _pgame() is of your cGameSpacewar class. That is, the mousewheel should scroll among gun types instead of among cursor types. Alternately you might add a Bullet menu and then add some accelerator keys for the types of bullet in the menu. If you do this, use some accelerator keys that aren't currently taken, but try and make the keys easy to find and to remember.
So that the user doesn't always just use the best gun, you need some feature to limit the use of the 'good' bullets. Perhaps put a strict limit on how many times they can be used, and make the user pick up an ammo pack to replenish them.
Exercise 14.10: A round world
If you look at our online Java version of the Asteroids game at www.mathcs.sjsu.edu/faculty/rucker/asteroids/asteroids.htm, you'll see that it uses a round game world border, with the objects able to bounce off this edge in a natural fashion (bouncing as if at each point on the circumference the edge were the same as its tangent line). Implement a round world for the Spacewar game. You can find hints in the source code of our Java asteroids online. Implementing the new world shape in a proper object-oriented way will involve some preliminary OOA and OOD. Think about what kind of base class you might use to serve as a container (if you use composition) or parent (if you use inheritance) for both a box world or a round world object.
A round Asteroids-style Java game
Exercise 14.11: The real SpaceWar
Do a little web research on the classic SpaceWar game. This is often played as a two-player game. See if you can implement it within the Pop Framework. You would need to have two player critters. And you'd need to write a new listener similar to the cListenerSpaceship , but tune this second listener so that it is controlled by, say, the D , E , S , X , and B keys in place of Right , Up , Left , Down , and Space .