More Sprite Transforms: Rotation and Scaling

Rotating a Sprite

XNA performs sprite rotation for us. That, in a nutshell, is the gist of this section on the subject. Digging into the trigonometry reveals that rotation calculations are in relation to the origin of the coordinate system at (0,0). It is possible to rotate a sprite the “hard way” by rotating each pixel of the sprite using the trigonometry functions sine() and cosine() the way they were used in the previous hour to rotate planets around the sun in the Solar System Demo. The same basic principle is used to rotate a sprite, but it is done very quickly in XNA thanks to a very fast SpriteBatch. Draw() method that can handle rotation like a cinch.

Since we have full control over how fast a sprite can rotate or revolve in a scene (such as the planets in the Solar System Demo), it is possible to use rotation with just partial circles to give a sprite complex movements by linking arclike movements together in interesting ways.

Rotation Angles

Not too many years ago, artists on a game project would prerotate all sprites in order to preserve quality. Back in the early days of game development, graphics hardware was very limited—which is why game programming in the “early years” was such a black art, because it required extensive knowledge of the hardware in order to eke out every possible CPU cycle to the fullest. Most games for Nintendo’s original NES fit on a 512Kb ROM cartridge. Compare that with games today, in which a single sprite animation stored as a texture might take up a few megabytes.

Figure 8.1 shows an example of the rotation frames often used in early games. The first direction is usually 0 degrees (north), and each successive frame is 45 degrees clockwise, as listed in Table 8.1.

A sprite pointing in the most common eight directions.
FIGURE 8.1 A sprite pointing in the most common eight directions.

 

Sprite Rotation Frame Directions and Angles

As the number of frames increases, so does the quality of the rotated animation, at which point we might end up with the 32 frames of rotation shown in Figure 8.2. Despite the large number of animation frames here in the form of a sprite sheet, this is not truly animation; it is just a prerendered rotation sequence. Each angle in the 32 frames of rotation represents 11 degrees of rotation. In this situation, compared to the 8-frame rotation, with 45 degrees per angle, you can see that there are four times as many frames for much higher quality.

A 32-frame prerotated sprite sheet.
FIGURE 8.2 A 32-frame prerotated sprite sheet.

Rotation actually takes place around the origin, and in trigonometric terms, we’re still working with the Cartesian coordinate system. Figure 8.3 shows an illustration of a point being rotated around the origin a certain interval.

Rotation takes place around the origin (0,0) in Cartesian coordinates.
FIGURE 8.3 Rotation takes place around the origin (0,0) in Cartesian coordinates.

Sprite Class Changes

We have been slowly adding new features to the Sprite class over the past two hours, and now the class will receive some much-needed rotation know-how. First, to differentiate between the old velocity and what we will need for rotational velocity, I have renamed the old velocity variable to velocityLinear, which is still a Vector2. A new variable has been added to the Sprite class called velocityAngular. A support method has been added, called Rotate(). Assuming that the velocityAngular property variable is set to a value other than zero, the Rotate() method will cause the sprite to rotate automatically (as the Move() method did for linear velocity in the preceding hour). Here is our new Sprite class:

LISTING 8.1 Source code for the even further improved Sprite class!

[code]
public class Sprite
{
private ContentManager p_content;
private SpriteBatch p_spriteBatch;
public Texture2D image;
public Vector2 position;
public Vector2 velocityLinear;
public Color color;
public float rotation;
public float velocityAngular;
public Sprite(ContentManager content, SpriteBatch spriteBatch)
{
p_content = content;
p_spriteBatch = spriteBatch;
image = null;
position = Vector2.Zero;
velocityLinear = Vector2.Zero;
color = Color.White;
rotation = 0.0f;
velocityAngular = 0.0f;
}
public bool Load(string assetName)
{
try
{
image = p_content.Load<Texture2D>(assetName);
}
catch (Exception) { return false; }
return true;
}
public void Draw()
{
p_spriteBatch.Draw(image, position, color);
}
public void Move()
{
position += velocityLinear;
}
public void Rotate()
{
rotation += velocityAngular;
if (rotation > Math.PI * 2)
rotation = 0.0f;
else if (rotation < 0.0f)
rotation = (float)Math.PI * 2;
}
}
[/code]

Drawing with Rotation

Besides the rotation code, some huge changes have had to be made to the Draw() method to accommodate rotation. We’re using the seventh and final overload of the SpriteBatch.Draw() method, which looks like this:

[code]
public void Draw( Texture2D texture,
Vector2 position,
Rectangle? sourceRectangle,
Color color,
float rotation,
Vector2 origin,
float scale,
SpriteEffects effects,
float layerDepth );
[/code]

There are nine parameters in this version of Draw(), which is capable of servicing all of our sprite needs for the next few chapters. But first things first: the rotation factor. The fifth parameter requires the rotation value as a float, so this is where we will pass the new rotation property (added to the preceding class).

For rotation to work correctly, we need to pass the right value for the origin parameter. The origin defines the point at which rotation will occur. If we pass Vector2.Zero, as in the sample call

[code]
Vector2 origin = new Vector2(0, 0);
float scale = 1.0f;
p_spriteBatch.Draw(image, position, null, color, rotation, origin,
scale, SpriteEffects.None, 0.0f);
[/code]

then the origin is set to the upper-left corner of the sprite (0,0), causing the result to look as shown in Figure 8.4, with the image rotation around the upper left.

The origin parameter defines the point at which rotation takes place.
FIGURE 8.4 The origin parameter defines the point at which rotation takes place.

We need to calculate the center of the image so that it will rotate correctly from the center of the image rather than the upper-left corner. This can be calculated easily enough using the width and height of the image:

[code]
Vector2 origin = new Vector2(image.Width/2,image.Height/2);
float scale = 1.0f;
p_spriteBatch.Draw(image, position, null, color, rotation,
origin, scale, SpriteEffects.None, 0.0f);
[/code]

This is the new and final code for Draw() at this point. We will also make minor changes to it again in the next section on scaling. The new version is shown in Figure 8.5.

Changing the origin to the center of the sprite’s image affects its position as well as rotation center—the origin becomes the focus for the position, even if the sprite is not rotated (with rotation set to 0.0f).

The sprite now rotates correctly, with the origin set to the center of the image.
FIGURE 8.5 The sprite now rotates correctly, with the origin set to the center of the image.

Sprite Rotation Demo

For this rotation demo, we will use user input to rotate the sprite either right or left, depending on where the screen is touched, on the right side or left side of the screen. The second screenshot of this demo is shown in Figure 8.5, referenced earlier. Now the sprite is centered on the screen and rotating from the center. Listing 8.2 contains the source code for the test program.

LISTING 8.2 Test program for the new rotation capability of our Sprite class.

[code]
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Viewport screen;
SpriteFont font;
Sprite ship;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = “Content”;
TargetElapsedTime = TimeSpan.FromTicks(333333);
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
screen = GraphicsDevice.Viewport;
//create the font
font = Content.Load<SpriteFont>(“WascoSans”);
//create the ship sprite
ship = new Sprite(Content, spriteBatch);
ship.Load(“ship”);
ship.position = new Vector2(screen.Width/2, screen.Height/2);
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
ButtonState.Pressed)
this.Exit();
//get state of touch inputs
TouchCollection touchInput = TouchPanel.GetState();
//look at all touch points (usually 1)
foreach (TouchLocation touch in touchInput)
{
if (touch.Position.X > screen.Width / 2)
ship.velocityAngular = 0.05f;
else if (touch.Position.X < screen.Width / 2)
ship.velocityAngular = -0.05f;
}
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
ship.Rotate();
ship.Draw();
string text = “Rotation: “ + ship.rotation.ToString(“N4”);
Vector2 size = font.MeasureString(text);
float x = (screen.Width – size.X) / 2;
spriteBatch.DrawString(font, text, new Vector2(x, 440), Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
}
[/code]

Scaling a Sprite

Sprite scaling is also handled by SpriteBatch.Draw(), but it doesn’t hurt to learn a little bit about how scaling happens. Like rotation, scaling occurs in relation to the origin of the Cartesian coordinate system, used for many trigonometry functions, although the most common we use in game programming are sine() and cosine(). Figure 8.6 shows a before-and-after result of an object (represented with just two points, which might be a line). The points are scaled by a factor of 0.5 (50%), resulting in the new positions shown.

Scaling also occurs in relation to the origin (0,0).
FIGURE 8.6 Scaling also occurs in relation to the origin (0,0).

In our previous example on rotation, the scale factor was hard-coded in the Sprite.Draw() method like so:

[code]
public void Draw()
{
Vector2 origin = new Vector2(image.Width / 2, image.Height / 2);
float scale = 1.0f;
p_spriteBatch.Draw(image, position, null, color, rotation,
origin, scale, SpriteEffects.None, 0.0f);
}
[/code]

We need to delete this out of Draw() and add a new variable to the class’s public declaration, with a new initializer in the constructor.

[code]
private ContentManager p_content;
private SpriteBatch p_spriteBatch;
public Texture2D image;
public Vector2 position;
public Vector2 velocityLinear;
public Color color;
public float rotation;
public float velocityAngular;
public Vector2 scale;
public Sprite(ContentManager content, SpriteBatch spriteBatch)
{
p_content = content;
p_spriteBatch = spriteBatch;
image = null;
position = Vector2.Zero;
velocityLinear = Vector2.Zero;
color = Color.White;
rotation = 0.0f;
velocityAngular = 0.0f;
scale = new Vector2(1.0f);
}
[/code]

The scale variable is no longer just a mere float, but has been upgraded to a Vector2. This will allow us to scale the sprite horizontally and vertically at different rates, if desired. This can be annoying, however. Every time you need to change the scale value, a Vector2 has to be set. I would much rather just set the scale as a float by default, and have the option of setting the scale individually for the horizontal or vertical axes. This is not because setting a Vector2 is time-consuming, but because the scale will most often be uniform on both axes. We want the most often-used properties and methods to reflect the most common need, not unusual needs.

We need to rename the scale variable to scaleV, and add a new property called scale. After making the modification, scaleV is initialized in the constructor, which sets both the X and the Y values. Anytime Sprite.scale is changed, both the X and the Y properties are changed.

[code]
scaleV = new Vector2(1.0f);
[/code]

The new scale property looks like this:

[code]
public float scale
{
get { return scaleV.X; }
set {
scaleV.X = value;
scaleV.Y = value;
}
}
[/code]

In the Sprite Scaling Demo coming up shortly, we can scale the sprite very large or very small by simply modifying the Sprite.Draw() method to use Sprite.scaleV (which is a Vector2). It is also now possible to scale the width and height of a sprite. See Figure 8.7.

We’re not going to add a scale velocity because that is so rarely needed that if the need does arise, it can be done with a global variable outside of the Sprite class. Or you can go ahead and add that capability to your version of the Sprite class if you want! In fact, let’s see the complete new version of the Sprite class (in Listing 8.3), just to be thorough, since that was a lot of new information to sort out.

Scaling a sprite up to 5× the normal size.
FIGURE 8.7 Scaling a sprite up to 5× the normal size.

LISTING 8.3 Source code for the updated Sprite class with new scaling abilities.

[code]
public class Sprite
{
private ContentManager p_content;
private SpriteBatch p_spriteBatch;
public Texture2D image;
public Vector2 position;
public Vector2 velocityLinear;
public Color color;
public float rotation;
public float velocityAngular;
public Vector2 scaleV;
public Sprite(ContentManager content, SpriteBatch spriteBatch)
{
p_content = content;
p_spriteBatch = spriteBatch;
image = null;
position = Vector2.Zero;
velocityLinear = Vector2.Zero;
color = Color.White;
rotation = 0.0f;
velocityAngular = 0.0f;
scaleV = new Vector2(1.0f);
}
public float scale
{
get { return scaleV.X; }
set {
scaleV.X = value;
scaleV.Y = value;
}
}
public bool Load(string assetName)
{
try
{
image = p_content.Load<Texture2D>(assetName);
}
catch (Exception) { return false; }
return true;
}
public void Draw()
{
Vector2 origin = new Vector2(image.Width / 2, image.Height / 2);
p_spriteBatch.Draw(image, position, null, color, rotation,
origin, scaleV, SpriteEffects.None, 0.0f);
}
public void Move()
{
position += velocityLinear;
}
public void Rotate()
{
rotation += velocityAngular;
if (rotation > Math.PI * 2)
rotation = 0.0f;
else if (rotation < 0.0f)
rotation = (float)Math.PI * 2;
}
}
[/code]

The complete Sprite Scaling Demo is found in Listing 8.4. A view of the program with the scale set very small is shown in Figure 8.8.

Scaling a sprite down to 10% of normal size.
FIGURE 8.8 Scaling a sprite down to 10% of normal size.

LISTING 8.4 Source code for the updated Sprite class with new scaling abilities.

[code]
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Viewport screen;
SpriteFont font;
Sprite ship;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = “Content”;
TargetElapsedTime = TimeSpan.FromTicks(333333);
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
screen = GraphicsDevice.Viewport;
//create the font
font = Content.Load<SpriteFont>(“WascoSans”);
//create the ship sprite
ship = new Sprite(Content, spriteBatch);
ship.Load(“ship”);
ship.position = new Vector2(screen.Width / 2, screen.Height / 2);
//rotate the sprite to the right
ship.rotation = MathHelper.ToRadians(90.0f);
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
ButtonState.Pressed)
this.Exit();
//get state of touch inputs
TouchCollection touchInput = TouchPanel.GetState();
foreach (TouchLocation touch in touchInput)
{
if (touch.Position.X > screen.Width / 2)
ship.scale += 0.05f;
else if (touch.Position.X < screen.Width / 2)
ship.scale -= 0.05f;
}
//keep the scaling within limits
if (ship.scale < 0.10f)
ship.scale = 0.10f;
else if (ship.scale > 5.0f)
ship.scale = 5.0f;
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
ship.Rotate();
ship.Draw();
string text = “Scale: “ + ship.scale.ToString(“N4”);
Vector2 size = font.MeasureString(text);
float x = (screen.Width – size.X) / 2;
spriteBatch.DrawString(font, text, new Vector2(x, 440),
Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
}
[/code]

That concludes our study of the three basic transforms: translation, rotation, and scaling. These techniques, along with SpriteBatch and some good math formulas, can produce any type of special effect or animated movement that we need for a game, on small game sprites, or even whole backgrounds (give that a try!).

Transforming Sprites

Translating (Moving) a Sprite

Sprite translation, or movement, can be done directly or indirectly. The direct approach involves writing code to move a sprite specifically to one location or another. The indirect approach involves setting up properties and variables that cause the sprite to move on its own based on those properties—we only write code that updates and draws the sprite, but the properties determine the movement. Although it is possible to cause a sprite to move in an arc or to follow a curve, that is not the type of movement we will be discussing in this hour. Instead, we’ll see how to move a sprite a short distance per frame (and remember, although an XNA game normally draws at 60 fps, a WP7 game runs at only 30). The “curves” our sprites will follow are actually complete circles, as you’ll see in the Solar System Demo coming up.

The math calculations we will perform in this hour to translate (move) a sprite are based on the 2D Cartesian coordinate system, represented in Figure 7.1. There are two axes, X and Y, which is why we refer to it as a “2D” system. Both the X and the Y axes start at the center, which is called the origin, residing at position (0,0). The X axis goes left (-) and right (+) from the origin. The Y axis goes down (-) and up (+) from the origin.

2D Cartesian coordinates are composed of two axes, X and Y.
FIGURE 7.1 2D Cartesian coordinates are composed of two axes, X and Y.

When making trigonometric calculations to transform a sprite’s position, the Y value must be reversed. If you look at the figure, you can see why! On a computer screen, as well as the WP7 screen, the origin (0,0) is located at the upper left, with X increasing right and Y increasing down. Since the Cartesian system represents Y positive going up, any Y coordinates we calculate must be reversed, or negated. This can be done by multiplying every Y result by -1.

Moving the position of a sprite needn’t be overly complicated. This is the easiest of the three transforms that we can calculate, because the calculation can just be skipped and the point can be moved to the desired target position (at this early stage). See Figure 7.2.

Translation (movement) from one point to another.
FIGURE 7.2 Translation (movement) from one point to another.

Using Velocity as Movement Over Time

Velocity can be calculated as distance over time. Mathematically, the formula is V = D / T. This calculation is more useful after a certain distance has been traveled in a certain amount of time, to figure out the average speed. But it is not as useful when it comes to moving sprites across the screen. For that purpose, we need to set a certain velocity ahead of time, while the distance and time variables are not as important. Let’s add a new velocity property to our ever-evolving Sprite class. Since velocity is a public property, we can use it outside of the class. Just for fun, let’s add a new method to the class to move the sprite based on velocity, as shown in Listing 7.1.

LISTING 7.1 Adding a new property to our Sprite class.

[code]
public class Sprite
{
private ContentManager p_content;
private SpriteBatch p_spriteBatch;
public Texture2D image;
public Vector2 position;
public Vector2 velocity;
public Color color;
public Sprite(ContentManager content, SpriteBatch spriteBatch)
{
p_content = content;
p_spriteBatch = spriteBatch;
image = null;
position = Vector2.Zero;
color = Color.White;
}
public bool Load(string assetName)
{
try
{
image = p_content.Load<Texture2D>(assetName);
}
catch (Exception) { return false; }
return true;
}
public void Draw()
{
p_spriteBatch.Draw(image, position, color);
}
public void Move()
{
position += velocity;
}
}
[/code]

The Move() method does something very simple—adds the velocity to the sprite’s position. The position will be rounded to the nearest X,Y pixel coordinate on the screen, but the velocity definitely will not be represented with whole numbers. Considering a game running at 60 fps, the velocity will be a fraction of a pixel, such as 0.01 (1/100th or 1% of a pixel). At this rate, assuming that the game is running at 60 fps, the sprite will move one pixel in 6/10ths of a second. This is equal to roughly two pixels per second, because 0.01 times 60 is 0.60. See Figure 7.3.

Sprite velocity in relation to frame rate.
FIGURE 7.3 Sprite velocity in relation to frame rate.

An XNA WP7 program runs at only 30 fps, so expect a difference in performance compared to Windows and Xbox 360 projects (which usually run at 60 fps).

We can put this simple addition to the Sprite class to the test. Included with this hour is an example called Sprite Movement. Open the project and take it for a spin. You’ll see that a funny-looking spaceship is moving over a background image, as shown in Figure 7.4, wrapping from right to left as it reaches the edge of the screen. Two asset files are required to run this project: craters800x480.tga and fatship256.tga, also included with the project. The source code is found in Listing 7.2.

Testing velocity with sprite motion.
FIGURE 7.4 Testing velocity with sprite motion.

LISTING 7.2 Test project for the new and improved Sprite class with movement capability.

[code]
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Sprite background;
Sprite ship;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = “Content”;
TargetElapsedTime = TimeSpan.FromTicks(333333);
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
background = new Sprite(Content, spriteBatch);
background.Load(“craters800x480”);
ship = new Sprite(Content, spriteBatch);
ship.Load(“fatship256”);
ship.position = new Vector2(0, 240 – ship.image.Height / 2);
ship.velocity = new Vector2(8.0f, 4.0f);
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
ButtonState.Pressed)
this.Exit();
//move the sprite
ship.Move();
//rebound from top and bottom
if (ship.position.Y < -70 | ship.position.Y > 480 – 185)
{
ship.velocity.Y *= -1;
}
//wrap around right to left
if (ship.position.X > 800)
ship.position.X = -256;
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
spriteBatch.Begin();
//draw the background
background.Draw();
//draw the sprite
ship.Draw();
spriteBatch.End();
base.Draw(gameTime);
}
}
[/code]

Moving Sprites in a Circle

We’re going to use the sun and planet artwork introduced in the preceding hour to make a simple solar system simulation. The calculations will not be meaningful to an astronomy fan; we just want the planet sprites to rotate around the sun, with no relation to our actual solar system. For this project, we need several planet bitmaps with alpha channel transparency, but because the background will just be black, if the planets have a black background that will suffice.

The sun needs to be positioned at the center of the screen, with the planets moving around it in concentric circular orbits. To make a sprite move in a circle, we need to use basic trigonometry—sine and cosine functions.

To make the code in our Solar System Demo a little cleaner, I’ve created a quick subclass of Sprite called Planet that adds three properties: radius, angle, and velocity. Notice that the Planet constructor, Planet(), calls the Sprite constructor using the base() call, passing ContentManager and SpriteBatch as the two required parameters to Sprite. The syntax for inheritance from a base class is shown in the class definition that follows. This is very helpful as we can count on Sprite to initialize itself.

[code]
public class Planet : Sprite
{
public float radius;
public float angle;
public float velocity;
public Planet(ContentManager content, SpriteBatch spriteBatch)
: base(content, spriteBatch)
{
radius = 0.0f;
angle = 0.0f;
velocity = 0.0f;
}
}
[/code]

The rest of the source code for the Solar System Demo is up next. There are just four planets, but it is still efficient to use a List container to store them rather than using global variables. We’re slowly seeing a little more code added to our WP7 “programmer’s toolbox” with each new hour and each new example, which is a good thing. There’s a lot more code in the initialization part of ContentLoad() than in either Update() or Draw(), but that is to be expected in a program with so few sprites on the screen. Figure 7.5 shows the program running, and the source code is found in Listing 7.3.

Solar System demo.
FIGURE 7.5 Solar System demo.

LISTING 7.3 The Solar System Demo project code.

[code]
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Viewport screen;
Sprite sun;
List<Planet> planets;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = “Content”;
TargetElapsedTime = TimeSpan.FromTicks(333333);
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
screen = GraphicsDevice.Viewport;
//create sun sprite
sun = new Sprite(Content, spriteBatch);
sun.Load(“sun”);
sun.position = new Vector2((screen.Width – sun.image.Width)/2,
(screen.Height – sun.image.Height)/2);
//create planet sprites
planets = new List<Planet>();
//add 1st planet
Planet planet = new Planet(Content, spriteBatch);
planet.Load(“planet1”);
planet.velocity = 0.2f;
planet.radius = 100;
planet.angle = MathHelper.ToRadians(30);
planets.Add(planet);
//add 2nd planet
planet = new Planet(Content, spriteBatch);
planet.Load(“planet2”);
planet.velocity = 0.16f;
planet.radius = 140;
planet.angle = MathHelper.ToRadians(60);
planets.Add(planet);
//add 3rd planet
planet = new Planet(Content, spriteBatch);
planet.Load(“planet3”);
planet.velocity = 0.06f;
planet.radius = 195;
planet.angle = MathHelper.ToRadians(90);
planets.Add(planet);
//add 4th planet
planet = new Planet(Content, spriteBatch);
planet.Load(“planet4”);
planet.velocity = 0.01f;
planet.radius = 260;
planet.angle = MathHelper.ToRadians(120);
planets.Add(planet);
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
ButtonState.Pressed)
this.Exit();
//update planet orbits
foreach (Planet planet in planets)
{
//update planet’s orbit position
planet.angle += planet.velocity;
//calculate position around sun with sine and cosine
float orbitx = (float)(Math.Cos(planet.angle) * planet.radius);
float orbity = (float)(Math.Sin(planet.angle) * planet.radius);
//center the planet so it orbits around the sun
float x = (screen.Width – planet.image.Width) / 2 + orbitx;
float y = (screen.Height – planet.image.Height) / 2 + orbity;
//save new position
planet.position = new Vector2(x, y);
}
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
spriteBatch.Begin();
//draw the sun
sun.Draw();
//draw the planets
foreach (Planet planet in planets)
{
planet.Draw();
}
spriteBatch.End();
base.Draw(gameTime);
}
}
[/code]

Did you find the code in LoadContent() a bit disconcerting, given that the same planet variable was used over and over again for each planet sprite? The important thing is that each one was added to the planets list, which became a “group” of sprites. You should do this whenever possible with related sprites, because it greatly simplifies everything about your code!

We can also use these sine() and cosine() functions to fire projectiles (such as a missile) from a launcher toward any target at any position on the screen.

The Sprite Movement Demo showed how a sprite can be moved automatically based on its velocity property, although we still have to step in and manage the event when the sprite reaches a screen edge. Although rotation is a subject of the next hour, we did explore moving a sprite around in a circle. This is different from sprite rotation, which involves actually changing the image. In our Solar System Demo, the sprite images were not rotated; they remained in a fixed rotation but followed a circular path around the sun using trigonometry.