The use of forces is one of your most powerful tools for customizing your game. Here are a collection of problems to let you try this out. In doing these problems, don't forget that 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.

Exercise 7.1: Changing the relative sizes of the critters and the world

The critters sometimes show forces better if you make them smaller. There are two ways you might make them smaller: (a) change the values of the statics cCritter:: MINRADIUS and cCritter::MAXRADIUS in your game constructor; (b) in your game constructor put a line _border.set(newxsize, newysize); with the newxsize and newysize larger than the default cGame::WORLDWIDTH, cGame::WORLDHEIGHT values defined in game.cpp and used in the cGame constructor. First try making the critters, say, three times as small and then put them back to the same size and try making the world three times as big. The relative sizes of the critter to the world should come out the same either way, that is, the screen sizes should come out the same. But something will be different: the critters will seem to move slower if you make the world bigger. This is because their speeds are set to some specific numerical values of units per second. Is it nicer to have the small critters move slower? Experiment with this a little and decide which way looks more playable.

Exercise 7.2: Making the world larger than the view

Many games are more interesting-seeming if they run across several view screens. You can do this by the following steps that we'll discuss in terms of, say, the cGameStub child class.

Go into your cGameStub constructor and add a line like _border.set(100.0, 100.0) (for a square world) or maybe _border.set(100.0, 8.0) (for a Mario-style side-scroller world).

In the cGameStub constructor after the setPlayer call, you can add a call like pplayer()->moveTo(_border.locorner()). This starts the player out at the corner of the world instead of in the center. This is something you may or may not want to do, depending on the game. In a side-scroller we like to start the player out at the left end of the world, but in an Asteroids-style game we might still want the player to start in the center.

In the cGameStub::initializeViewpoint(cCritterViewer *pviewer) method, replace the code with these lines.

pviewer->setViewpoint(cVector::ZAXIS, pplayer()->position()); 

The exact value of the number you feed into the zoom call will depend on how zoomed-into the world you want to be. The call to setTrackplayer(TRUE) has the pleasant effect of automatically scrolling your screen to keep the player in view as it moves across the edges. The Ballworld game also overloads cgame::worldShape.

Before doing the following exercises, make sure you've made the sizes of your critters smaller relative to the size of the world, otherwise the screen will be too crowded to see rich behavior.

Exercise 7.3: Adding forces

Get fresh (like original) copies of the gamestub.* files in case you changed them during the Space Invaders exercises (3.10.1?3.10.8). Change the cGameStub construcor to set _rivalcount to 0, and _seedcount to 20. Try using some of the different kinds of cForce constructors in the cCritterStubProp initializer. Try cForceDrag(?, cVector(?, ?)) for a wind to the right. Try cForceVortex(?) for a spiraling-in vortex. One caution: if you make the first argument (which is 'friction' parameter) too big in cForceDrag or cForceVortex, the critters will have a bad kind of motion; rather than slowing them to a steady state, a too-large value of friction makes them overshoot and oscillate back and forth. Try cForceDrag(300) to see what we mean. Usually friction shouldn't be much bigger than 1 or 2.

Exercise 7.4: The spring and rod force

In the cGameWorms, make a circular loop of the critters connected by spring and rod forces and push the loop around with the cursor. Adjust the force of the spring upward to make it fairly rigid. Try making a shape like an asterisk, with one critter at the center and four or five separate worms of connected critters coming out of the center. Try making a shape like a person. (You can find an interesting interactive website of rod and spring shapes by searching for 'sodaplay' or 'soda constructor.' The correct address was recently www.sodaplay.com, but this may change.)

Exercise 7.5: Planets

Now implement a cForceObjectGravity that gives a critter a gravitational attraction towards the _pnode of the cForceObject. Give it a Real _gravity field. The force ought to be something like _gravity * pcritter->mass() * _pnode->mass() * pcritter->directionTo(_pnode)/(pcritter->distanceTo( pnode) *pcritter->distanceTo(_pnode)), although you can speed up the computation a bit by prefixing the line with a call to cDistanceAndDirection dnd = pcritter-> distanceAndDirectionTo(_pnode), and then in the next line getting the distance and direction out of dnd instead of doing three separate computations. In the seedCritters go ahead and walk through every possible (i,j) pair and connect every pair of critters with a cForceObjectGravity.

This is not the most computationally efficient way to do it, but first try it and see how it looks. You will need to tweak the gravity force and the speeds and the sizes for a while until you can start to get things like critters going into orbit around each other. Also you want to be doing this for a fairly large worldsize. Also, keep in mind that the mass of the objects depends on their density and size; if they're unresponsive, make them more massive.

Exercise 7.6: Brine shrimp

Try making a tide-pool world in which the critters move like brine shrimp. That is, whenever they slow down to a certain speed, they suddenly propel themselves forward in a slightly different direction.

Do this by having a cForceDrag to slow the critters down, and a new cForceBrineshrimp force to make them periodically dart forwards. The way the cForceBrineshrimp force ought to work is that if a critter's speed drops below a certain level, then the critter's speed is set to its maximum value. You must do a 'sudden impulse' change like this all at once by calling pcritter->setSpeed and not by returning an acceleration value, otherwise the critter will simply speed up a tiny amount to get faster than the trigger speed. When you apply the impulse, also wobble the critter a bit with a call to pcritter->turn( ...small random argument...).

Exercise 7.7: Random linkages

This problem is suggested by a fascinating recent book, Stephen Wolfram, A New Kind of Science (Wolfram Media, 2002). Wolfram makes a case that all the seemingly complex behaviors and patterns we see in the world arise from the interactions of small simple programs. So let's see how well we can do with our simple drag, spring, ball-and-spring, seek and evade forces. Try a world in which each critter gets one or several randomly selected forces linking it to some randomly selected other critter. Arrange your program so that the behavior gets freshly randomized every time you reseed the world, and then press Enter a few times to look at the kinds of overall behaviors you get. Do you see anything that might be useful for making interestingly animated enemies or prey?

Exercise 7.8: Following waypoints

In adventure games and car racing games we often want to make some of the computer operated critters move along certain fixed paths. Thus you might want an enemy guard to patrol a certain route, or you might want a rival race car to drive around and around a track.

A good method to make this work is to set a series of 'waypoints' that you want the critter to follow. Implement a cForceWaypoint which has these fields.

CArray<cVector, CVector> _waypoint 
int _currwpindex 
Real _closeenough 

You might also want to give cForceWaypoint an add(cVector newwaypoint) mutator method for adding points to be _waypoint array. Suppose that the constructor initializes _currwpindex to 0 and _closeenough to some reasonable (relative to your world size) value like perhaps 2.0.

If our waypoints are arranged in a circle, as on a race track, we might define the cForceWaypoint force method like this.

cVector cForceWaypoint ::force(cCritter *pcritter) 
    if (distanceTo (_waypoint[_currwpindex]) <_closeenough) 
        _currwpindex ++; 
        if (currwpindex >= _waypoint.GetSize()) 
            _currwpindex = 0; 
    setTangent(_waypoint[_currwpindex] - position()); /* setTangent 
        will normalize the arg return cVector::ZEROVECTOR; */ 

Get this to work and then make a variation in which the critter moves back and forth along a curving line of waypoints. You can do this either by listing the inner points twice (once in each order), or by using a _currwpinc field that can be either +1 or ?1 to determine the direction in which you traverse the waypoints.

    Part I: Software Engineering and Computer Games
    Part II: Software Engineering and Computer Games Reference