When you get into 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.† Simply comment in exactly the one setGameClass line corresponding to the game you want to play.† If you make a new game class, add a line for it.† For the following exercises, have your startup game be cGameStub.
/*† Choose the type of game you want at startup by commenting in ONE setGameClass line.
The setGameClass sets brandnewgameflag to TRUE. */
If you do this exercise and the game still starts up in the original Spacewar Game mode, itís very likely that you edited the wrong copy of popdoc.cpp.† One of the gotchas of Visual Studio is that when you use the File | Open command, the file selection dialog doesnít make it clear which directory you are in.† Visual Studio has a certain persistence of state, and if you open a file in DirectoryA, the next time you open a file the dialog is likely to search in DirectoryA again, even if you are now working on a project in DirectoryB.† One often has multiple copies of the Pop Framework code on oneís disk, and it is easy to be editing a file in the wrong directory.
A sure sign that youíre editing the wrong files is if (a) your program always compiles and runs with no warnings or error messages and (b) the appearance of the executable looks the same after each ďbuild.Ē
How to avoid this gotcha?† If you see signs of (a) and (b), close all your files, close your project, reopen your project in your desired directory, and then do a File | Open, only this time use the dialog to back a step or two up the directory tree to find out what directory youíre realy in, and then go back down into the correct directory.
We edit some of the cGameStub methods in the gamestub.cpp file to change the appearance of the game world.
(a)† Our goal here is to make a simple Space Invaders game.††† Letís not use the cCritterStubRival at all, letís just have dumb non-shooting cCritterSpaceInvadersProp falling down on us.† We can do this by changing the two static critter count numbers that are used in the cGameStub constructor to initialize _rivalcount and _seedcount.† The statics are defined right before the cGameStub::cGameStub() constructor.† Change the lines to read:
int cGameStub::DEFAULTSEEDCOUNT = 8;
int cGameStub::DEFAULTRIVALCOUNT = 0;
(b) Letís make our world tall and thin.† We can do this by changing a line in the cGamestub::cGamestub constructor.† Take the line _border.set(60.0, 40.0), and change it to _border.set(20.0, 40.0).
(c) We donít want to start out zoomed in on the world.† Find the code for the void cGameStub::initializeViewpoint(cCritterViewer *pviewer) method and comment out two lines.
(d) We donít want the view to move with the player, so find the void cGameStub::initializeView(CPopView *pview) code and comment out a line.
(a) Before changing the constructor, change the value of a static variable used in the constructor.† At the start of the gamestub.cpp file, change the PLAYERHEALTH line to this.
int cGameStub::PLAYERHEALTH = 3;
Now weíre going to add some code to the end of the cCritterStubPlayer::cCritterStubPlayer(cGame *pownergame) code in gamestub.cpp.
(b) We want to use the arrow keys to move our player.† Add this line to the end of the constructor.† Alternately you could replace the existing setListener line with this line.
††††††† setListener(new cListenerArrow());
(c) To make the arrow key motion a little peppier, give the player a higher maximum speed (which is the speed the arrow moves it at).† Add this line.
††††††† setMaxspeed(30.0); // Careful not to write setMaxSpeed
(c) Limit the player to moving back and forth along the bottom of the screen.† This means we want to change the playerís cRealBox _movebox field.
We do this in the playerís constructor.† Our framework is set up so that in this code block you can assume that the playerís _movebox has already been set to match the gameís _border box.† We now want to use setMoveBox to change the _movebox.
The setMoveBox call takes a cRealBox as argument.† The cRealBox constructor we use here takes cVector specifying two opposite corners as arguments, the lower left front corner and the upper right back corner.† Add this block of code to the end of the constructor code.† What weíre doing here is to move the ďhigh cornerĒ down almost to the bottom of the _border box.
/* At this point the player's _movebox matches the _border it got from pownergame.
†††††††††††††† Now we want to make the _movebox just be the bottom edge of the world. */
†††††††††††††† _movebox.hicorner() - //Move high corner almost to the bottom of world.
†††††††††††††††††††††† (_movebox.ysize()-2*radius())* cVector::YAXIS
(d) Another aspect of a Space Invaders game is that the playerís gun always points straight up.† Weíll make this change in the cCritterStubPlayer constructor.† Change the old cCritterStubPlayer constructor by adding these lines to the bottom of it.
†††††††††††††† /* Note that for this line to have its proper effect, you need
†††††††††††††† to edit the method void cListenerArrow::listen(Real dt, cCritter *pcritter)
†††††††††††††† inside the listener.cpp file.† What you have to do is to change the last
†††††††††††††† line of that method to have an if condition, so the line reads as follows:
†††††††††††††††††††††† if (pcritter->attitudetomotionlock())† // Need this condition to allow
††††††††††††††††††††††††††††† //a "space invaders" type shooter that always points up
††††††† setAttitudeTangent(cVector::YAXIS); //Call this AFTER turning off the lock
(e) In order for (d) to have the desired effect, you need to make a correction to the listener.cpp file that you have.† In this file you have to edit the method void cListenerArrow::listen(Real dt, cCritter *pcritter). What you have to do is to change the last line of that method to have an if condition. Before your change, the line reads
After your change, the line should read as follows:
Now we make some changes to the bottom of the cCritterStubProp::cCritterStubProp(cGame *pownergame) code in gamestub.cpp.
(a) Letís have the props automatically be positioned up near the top of the world.† Since the prop constructor uses a cGame argument, its baseclass constructor will have set its _movebox to match the gameís _border.† To move the critters up to the top of the world, add these lines.
†††††††††††††† _movebox.locorner() +
†††††††††††††††††††††† (_movebox.ysize() - 2*radius()) * cVector::YAXIS,
(b) Letís put a force of gravity on the Prop critters.† Usually when you have gravity, itís a good idea to put in some ďair frictionĒ as well.† Add these lines to the end of the constructor code.
††††††† addForce(new cForceGravity());
††††††† addForce(new cForceDrag());
(c) Letís soup up the game by allowing the critters to fall a bit faster.† Add this line.† You might find the value 8.0 to be a shade too low or high.
††††††† setMaxspeed(8.0);† //Careful not to write setMaxSpeed
(d) Now letís try having the Props run away from the bullets.† Try adding a line like this.† You may not like the effect of this, so its optional.
††††††† addForce(new cForceClassEvade(4.0, 1.0, RUNTIME_CLASS(cCritterStubPlayerBullet)));
Each critter has an int _outcode field that is an OR combination of bit flags telling you which, if any, edge of its cRealBox _movebox the critter touched during its last move.† The bit flags, which are defined in the realbox.h file, have simple names like BOX_LOY.† We will use the _outcode to take action when a prop hits the bottom or the top of the screen.
When one of the prop critters hits the bottom of the screen, we want to kill off the critter and reduce the playerís health by calling its damage method.
If you have called setWrapflag(cCritter::WRAP), then the props might get to the bottom by going around the top.† When our props run away from bullets they might sometimes do this.† It would unfairly punish the player if let the props get away with that, as then they would be in a position to cross back and game might think they landed on the bottom.† Therefore if a prop hits the top of the world we kill it off without charging the player a damage point.
We do all this by changing the cCritterStubProp update method to look like this.
void cCritterStubProp::update(CPopView *pactiveview)
††††††† cCritter::update(pactiveview); //Always call this first
††††††† if (_outcode & BOX_LOY) //Landing damages me
††††††† if (_outcode & BOX_HIY) //So they don't sneak around over the top.
Letís eliminate the feature of cGameStub which rewards the player for bumping into a Prop critter.† This means you should comment out this line from within the lines from the cCritterStubPlayer::collide(cCritter *pcritter) code
††††††† //†††††††††††† setHealth(health() + 1);
(a) In the gamestub.cpp file, try giving yourself prop-seeking missiles for your bullets.† Change the code of the cCritterStubPlayer::shoot() as follows.
That is, give your bullets a cForceObjectSeek †so they turn into smart missiles that hunt down whichever critter was closest to the line you aimed along.† If you think it makes the game too easy or too hard, leave it out or perhaps use a smaller value for the argument 50.0 passed to the cForceObjectSeek constructor.
††††††† cCritterBullet *pbullet = cCritterArmedPlayer::shoot();
††††††† cCritter* paimtarget = pgame()->pbiota()->pickClosest(cLine(position(), aimvector()), this);
†††††††††††††† /* Find the critter closest to your aiming line.† Including this as the second argument
†††††††††††††† means to exclude yourself from consideration as the closest critter. */
††††††† pbullet->addForce(new cForceObjectSeek(paimtarget, 50.0));
††††††† return pbullet;
(b)You may now find the game is now hard to play because your bullets die at the screen edges and sometimes do this before hitting a prop.† Fix this by adding this line to the cCritterStubPlayerBullet::cCritterStubPlayerBullet() constructor.
††††††† _dieatedges = FALSE;
(c) A downside of (b) is that youíll now notice that some silly bullets bounce off the top and get confused and bumble around on the bottom of the world.† To fix this, you have to overload the void cCritterStubPlayerBullet::update(CPopView *pactiveview) as follows.†
Add this line to the prototype in gamestub.h
††††††† virtual void update(CPopView *pactiveview);
Add this code to gamestub.cpp.
void cCritterStubPlayerBullet::update(CPopView *pactiveview)
††††††† cCritterBullet::update(pactiveview); //Always call base update first
††††††† if (_outcode & BOX_LOY || _outcode & BOX_HIY) //Landing damages me
(a) To make this more of a game, itís better to have the action be non-stop.† As it presently stands, you can kill off all the attackers.† We fix it so each time you kill an attacker, some new ones come in.† We do this by adding this code to the bottom of the cGameStub::adjustGameParameters code.
††††††† int propcrittercount = pbiota()->count(RUNTIME_CLASS(cCritterStubProp));
††††††† if (propcrittercount < _seedcount)
†††††††††††††† new cCritterStubProp(this); //The constructor automatically adds the critter to the game.
Note that if youíve killed, say, three props all at once, it will take the game three steps of calling adjustGameParameters to restore the full prop count.† This is fine, as visually itís just as well not to change the game too rapidly.
(b) What about making the game get harder as you play it?† This step is optional.
You could keep track of the cGame::score(), and each time this gets larger than some increment size, make the game harder in some way, perhaps by increasing the size of _seedcount.
Or you could add a cCritterStubRival whenever the score passes a certain size, similar to how the Spacewar game adds cCritterUFO.† To make this effective you might need to tweak the cCritterStubRival methods a bit.
(20% for Mechanics) Hand in the following: (1) A sheet of paper describing the controls of your game and listing any Special Features you added.† (2) Two floppy disks: one disk with a Release Build of the executable in the root directory, and another disk with clean, minimal-sized, buildable source code.† Label the disks with your name and with a word to indicate if this is the EXE or the SOURCE disk.† You will probably need to WinZip your source to fit it onto a floppy.† If the *.zip is larger than 1 Meg you probably havenít cleaned your source directory properly (or youíve included a lot of extra big sounds and bitmap files).† If your cleaned and zipped source wonít fit on a floppy, check with the professor about alternate disk formats.† Emailing your homework to the professor as a gigundo attachment is forbidden!† (3) Put disks and paper in a two-pocket folder with your name on it. (4) Put your name on the program caption bar. To change the caption bar, see section 23.9 of this book.
(40% for Basics) Carry out the steps outlined in the series of Space Invaders exercises just above.† You donít necessarily need to use all the exact same parameter values suggested.† Get the program working so that the critters are neither too hard nor too easy to hit, the game itself should be neither too hard or too easy. You may need to tweak some params to get it right.
(20% for Improvements) Possibilities: Change the background, use different kinds of sprites, add code to make the game get progressively harder, change the code so that the enemies jiggle back and forth like in the traditional Space Invaders rather than running away from bullets.† Add sound effects.† Have the enemies change appearance when you hit them before disappearing, maybe have them shatter or show a cSpriteIcon or cSpriteLoop explosion bitmap for a few seconds.† Looking at the gamespacewar.cpp or the gamedefender3d.cpp file may provide inspiration.