Curriculum Simulation and Animation

From Real Software Documentation

Jump to: navigation, search

Aim
We’re now going to do a reasonably significant exercise in simulation and animation. We’ll produce a SpriteSurface with a collection of the chevrons from our earlier lesson, and we&rsquoo;ll have them chase the mouse pointer. Then we’ll see a little of the advantages of object orientation when we see how easy it is to animate the sprites. Note that once again there is a little trigonometry involved in this project.

Contents

Prereading

You should read about the SpriteSurface and the Sprite classes before continuing.

A Physics Simulation

It is common in a game to design a physics simulation. These will usually be less than completely realistic, but will somewhat resemble how objects behave in the real world. In this case, we are going to have a group of objects with the same behavior. In particular, these objects will all have:

  • Momentum, meaning they have a direction and speed with which they are moving, and it will require the application of a force to change that direction and speed; and
  • Thrust, which is a force directed in the direction they are facing.

The combination of these produces objects that behave like they are driven by rockets but are sliding about on ice. Another way to say this is that we are simulating a friction-free, zero-gravity situation. We will have the objects always face toward, and thus thrust toward, the mouse pointer. And we will display a group of these objects, starting from somewhat random locations. Can you imagine what this is going to look like?

The Math

We need to track the locations and the momentum of the objects, and to apply the thrust to them. Most of the math involved in doing this makes use of vectors. A vector is, roughly speaking, an arrow: it is a direction and a distance[note 1], or (equivalently) an x and y distance. We will implement this as an object with multiple get and set operations: you can get either the x and y distances, or the direction and magnitude. For this program, we will only provide set operations for x and y. We also provide three other operations on vectors (we’ll see how these are useful as we explore the project): MultiplyBy We provide an operation to multiply a vector by a Double. This multiplies the magnitude by the Double. Note that since we happen to be storing the vector as an x and y quantity, we have to scale both of them, using trigonometry very similar to the calculations we used in our Graphics I lesson. Normalized This function returns a new vector with the same direction as the vector it is applied to, but with a Magnitude of 1. Add and Operator_Add To understand adding two vectors together, imagine them as arrows, and put the sec- ond arrow at the end of the first[note 2]:

Figure65.c = a + b. To compute the sum of two vectors, we can just add the horizontal components of the two vectors to get the horizontal component of the result, and the vertical components of the vectors to get the vertical component of the result: Figure66.To sum vectors, sum the horizontal and vertical components separately.

The First Project

  1. Open the first included project, SpriteFlock.

  2. Examine the code in the Vector class; make sure you can see how it implements the operations described above. Notice that most of the operations change the content of the vector itself, but that Add is a function that returns a new vector.

  3. Examine the code in the MomentumSprite class. This uses simple animation principles, moving the sprite according to its current speed between frames. We don’t try to fix the speed, so the speed of the sprites will vary depending on how fast the computer is that the program is running on. Notice that although MomentumSprite is a sprite, and therefore it has X and Y integer properties, we need to keep the coordinates of the sprite as Doubles, to avoid rounding errors (which would cause the sprite to “jump” at low speeds, for example). We perform all our calculations about location using the doubles, then round that to the nearest integer for the actual X and Y properties for each frame.This also means that we create a MoveTo operation, so when we move the sprite somewhere, we remember to set both the X and Y and the ExactX and ExactY to the new location. This is a great example of why directly setting properties is less flexible than using get and set methods. Next, notice that the momentum of the sprite is kept as a vector, and that we have an Apply operation, to add acceleration to the current momentum. Each frame, we will work out the direction to the mouse pointer, and apply a vector in that direction to the vector representing the momentum. The final method is Increment, which works out the new location of the sprite. Once again, we are effectively adding two vectors (the current location is just like a vector).

We don’t use a vector for this because we need the separate X and Y components at this point. So the location is a vector, the speed is a vector and the acceleration is a vector. We add the acceleration to the speed, and the speed to the location1.

  1. Take a quick whip through the rest of the program.

More Comments

  • We’ve separated the two important constants (the number of sprites, and the amount of their thrust) into constants so we can easily change their values or convert them to global variables. It is almost always sloppy coding to put a constant right in the code.

This doesn’t mean it isn’t sometimes OK, but it means you should always think about it before putting a constant value (number or string) into your code. An example of a value that was OK to put in amongst the code was PI — it is very unlikely to change…;

  • A quirk of the order of calling events on objects in the window meant we couldn’t just initialize everything in the window’s Open event: at this point, the window isn’t maximized, so we don’t know where to put the sprites. There are many ways to handle this — we’ve used a simple but practical method of kicking everything off one second after the application is started, from a timer. Hence InitSpriteSurface; and
  • Note that we’ve somewhat separated out the display of the sprite from its simulation (we didn’t aggressively separate them as we’ve talked about before, because we wanted to keep the code simpler). This will be important in the next project, where we see how easy it is to animate the sprites as a result.
  1. Run the project. Pretty! Stop and consider how this simulation could be easily adapted to do a whole range of interesting things: a solar system simulator, for example.

1. If you know some basic calculus, these are the position, the first derivative of the position (the speed), and the second derivative of the position (the acceleration). Note how the sprites start out quite close together, but that over time, they get further and further apart. AnimatedSpriteFlock 1 Open the project AnimatedSpriteFlock. Run it. Note the difference: the sprites are animated (they change color). We do this by creating a class that can replace the graphics source in the previous code. This new class (AnimatedPicture) contains one or more DirectedPicture objects. Based on the amount of time that has passed, it will pass requests for pictures in a cycle to each of its contained objects. Thus, this container class provides simple time-based cycling of pictures. 2 Examine the code that does this. Note how simple it is! Apart from some code to initialize this class, and an extra constructor and a method in the DirectedChevron class (so we could get the color from one instance to set the color of its opposite), the changes to add this new capability were very straightforward. Can you see why a good object-oriented design made this so? The answer involves polymorphism and a simplified model-view-controller paradigm. Can you see how this is a kind of simplified MVC design?

Fixing the Speed

Note that while the animation cycles based on time, the speed at which the simulation runs is dependent on the number of sprites and the speed of the computer (this determines the framerate of the SpriteSurface). There are a few ways to fix this: either explicitly set the framerate of the SpriteSurface; separate the model out entirely and update it based on a timer; or have the model keep track of the time between frames, and run the necessary number of simulation steps for the amount of time that has elapsed. Think about the advantages and disadvantages of each of these.

Further Exercises

This project can be extended in a great many ways. Some starting ideas:

  • Bring in some more physics (you might do some searching on the Internet or in a high school physics text on the details). Reasonably easy is adding friction. Harder is having the sprites bounce off each other (easiest: assume each is actually a circle). This is a good point to think about realistic physics, versus having something that looks reasonable.
  • Make a solar system simulator. Either one based on the real solar system, or have a central sun, to which all objects are attracted, and place a bunch of objects with random masses, positions and velocities, and see which ones fall into the sun, fly off

into space, or enter a stable orbit. It’s fairly simple doing just the sun as a source of gravity; it’s interesting to do a project where each of the objects has gravity. Calculating that properly involves a number of operations proportional to N2 for N objects, so it won’t take many objects to start really slowing such a simulation down.

  • Make a game or toy, such as an ice-skater simulation.

You will have to research some of the other abilities of the SpriteSurface, such as collision detection or keyboard scanning, but research is a good thing…

References

Notes
  1. It is customary to say “magnitude” rather than “distance” because a vector can represent not just direction and distance but other things that have direction and “amount” — say, direction and amount of thrust.
  2. The Operator_Add function lets us define our own + operator for this class. In other words, it lets us find out the sum of two vectors v1 and v2 by writing v1 + v2. Note that we don’t actually wind up using this in any of the projects.
Personal tools