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.
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?
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.
Edit the cGameSpacewar::adjustGameParameters() so that it changes the game's _spritetype at certain score levels.
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.
Get a theme and scout around for bitmaps. If you can't find bitmaps on a theme, change the theme. If your bitmaps are in a format other than *.bmp, such as *.gif or *.jpg, you will need to convert them to *.bmp. You can do this by loading the bitmap into some image-manipulation software like PhotoShop and then saving it as *.bmp. If they're already a *.bmp, you can manipulate it with the Windows Accessory | Paint program. If it doesn't degrade the color too much, try saving your bitmaps as 256-color bitmaps, as this makes the files considerably smaller. Save your bitmaps as *.bmp files in the res subdirectory of your project code directory.
Looking ahead towards steps (d) and (e), adjust the sizes of the bitmaps. The size of the background bitmap you will use as your cSpriteIconBackground doesn't need to be as large as the screen; the Pop Framework code will stretch (or shrink) the bitmap to fit. It is important, however, that the proportions, or y-to-x-aspect, of the bitmap should approximately match the proportions of the world you plan to use it in.
The sizes of your critter icon bitmaps that you will use as cSpriteIcon for the critters should be about the same size in pixels as the critter images you expect to see. The critter images will adjust their shape to match the size of these bitmaps, so you don't really need to worry about these aspects. Do note these points: (a) crop the image so that there is not much extra blank space around it, (b) make sure the pixel in the upper left hand corner is the color that you wish to treat as transparent when you show this image on the screen, and (c) use powers of two.
Now import the new *.bmp files into your code as bitmap resource. Use Resource... | Insert... and then navigate to find your *.bmp file. The Resource Editor will open your imported bitmap. [With Version 6.0, import a bitmap by using Insert | Resource | Bitmap | Import... In 6.0, some bitmaps will not be viewable within the Resource Editor, but this doesn't mean they can't be added to the project.]
It's helpful to give your bitmap an easy-to-remember resource ID name like IDB_HAPPYDOG instead of the machine-generated ID (like IDB_BITMAP1) it will have received. To call up the Bitmap Properties dialog you can Alt+Enter while the bitmap is open in the editor.
Change your background by putting a line like setBackgroundBitmap(IDB_UNDER-SEALEVEL1) after the _border.set(..) call in your constructor.
You can use specific character bitmaps by having lines like setSprite(new cSpriteIcon(IDB_BLUEFISH)) in your critter constructors.
Alternately you can arrange things so that a critter constructor call to setSprite(pgame()->randomSprite(cGame::ST_BITMAPS)) will automatically pick a random bitmap from some interesting new set of theme bitmaps you want to use. To do this, change the list of default bitmap IDs that your game keeps in the array _bitmapIDarray. You could do this by putting code like this into your game's constructor.
bitmapIDarray.RemoveAll(); bitmapIDarray.Add(IDB_BLUEFISH); bitmapIDarray.Add(IDB_STINGRAY); bitmapIDarray.Add(IDB_CUTTLEFISH); bitmapIDarray.Add(IDB_HUMUHUMUNUKUNUKUAPUAA); bitmapIDarray.Add(IDB_MAGURO);
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.
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.
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.
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!)
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.
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
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.