XNA Game Making

Resources for making games with XNA

Chapter 8: Scrolling Backgrounds

 To get the free pdf or to buy a printed version go to the book page at  lulu.com.

One of the most popular things to do with 2D games is to have backgrounds that have several layers that move at different speeds. You've probably seen this effect in 2D scrolling games where a character is walking across the screen and the immediate background behind the character is moving at one speed and a background farther in the distance (like a sky far away) is moving at a different speed a little bit slower. This creates a 3D like effect even though the whole game is still 2D. The technical term for these backgrounds with different layers is parallax backgrounds, but here we'll just call them multi-backgrounds or scrolling backgrounds. We'll also talk a bit about including third party code mixed in with your own. Specifically we will:

 

l  Look at a scrolling background class.

l  Put the scrolling background into our ship game.

 

Scrolling Background Example Project

Download and build the Scrolling Background from xnagamemaking.com. If you run this program you'll see a scrolling background with two layers:

 


 

If you press the S key it will stop the moving, the M key will restart it. The left and right arrows let you manually move the backgrounds, and the 'U' and 'R' keys will let the background move up and down or left and right. We won't go over the details of the Game1.cs file for this project, we'll just look at the MultiBackground.cs, which contains the code that handles the background. The MultiBackground class works by keeping a list of the different background layers and moving and drawing each based on how they are set. Here is the full listing for the code:

 

class BackgroundLayer

{

public Texture2D picture;

public Vector2 position = Vector2.Zero;

public Vector2 offset = Vector2.Zero;

public float depth = 0.0f;

public float moveRate = 0.0f;

public Vector2 pictureSize = Vector2.Zero;

public Color color = Color.White;

}

 

    class MultiBackground

    {

        private bool moving = false;

        private bool moveLeftRight = true;

 

        private Vector2 windowSize;

 

        private List<BackgroundLayer> layerList = new

List<BackgroundLayer>();

 

        private SpriteBatch batch;

 

        public MultiBackground(GraphicsDeviceManager graphics)

        {

            windowSize.X = graphics.PreferredBackBufferWidth;

            windowSize.Y = graphics.PreferredBackBufferHeight;

            batch = new SpriteBatch(graphics.GraphicsDevice);

        }

 

        public void AddLayer(Texture2D picture, float depth, float moveRate)

        {

            BackgroundLayer layer = new BackgroundLayer();

            layer.picture = picture;

            layer.depth = depth;

            layer.moveRate = moveRate;

            layer.pictureSize.X = picture.Width;

            layer.pictureSize.Y = picture.Height;

 

            layerList.Add(layer);  

        }

 

        public int CompareDepth(BackgroundLayer layer1, BackgroundLayer

layer2)

        {

            if (layer1.depth < layer2.depth)

                return 1;

            if (layer1.depth > layer2.depth)

                return -1;

            if (layer1.depth == layer2.depth)

                return 0;

            return 0;

        }

 

        public void Move(float rate)

        {

            float moveRate = rate / 60.0f;

          

            foreach (BackgroundLayer layer in layerList)

            {

                float moveDistance = layer.moveRate * moveRate;

 

                if (!moving)

                {

                    if (moveLeftRight)

                    {

                        layer.position.X += moveDistance;

                        layer.position.X = layer.position.X % layer.pictureSize.X;

                    }

                    else

                    {

                        layer.position.Y += moveDistance;

                        layer.position.Y = layer.position.Y % layer.pictureSize.Y;

                    }

                }

            }

        }

 

        public void Draw()

        {

            layerList.Sort( CompareDepth );

 

            batch.Begin();

 

              for (int i = 0; i < layerList.Count; i++)

                {

                    if (!moveLeftRight)

                    {

                        if (layerList[i].position.Y < windowSize.Y)

                        {

                            batch.Draw(layerList[i].picture, new Vector2(0.0f,

layerList[i].position.Y), layerList[i].color);

                        }

                        if (layerList[i].position.Y > 0.0f)

                            batch.Draw(layerList[i].picture, new Vector2(0.0f,

layerList[i].position.Y - layerList[i].pictureSize.Y),

layerList[i].color);

                        else

                            batch.Draw(layerList[i].picture, new Vector2(0.0f,

layerList[i].position.Y + layerList[i].pictureSize.Y),

layerList[i].color);

                    }

                    else

                    {

                        if (layerList[i].position.X < windowSize.X)

                        {

                            batch.Draw(layerList[i].picture, new

Vector2(layerList[i].position.X, 0.0f ), layerList[i].color);

                        }

                        if (layerList[i].position.X > 0.0f )

                            batch.Draw(layerList[i].picture, new

Vector2(layerList[i].position.X - layerList[i].pictureSize.X,

0.0f), layerList[i].color);

                        else

                            batch.Draw(layerList[i].picture, new

Vector2(layerList[i].position.X + layerList[i].pictureSize.X,

0.0f), layerList[i].color);

                   

                    }

                }

         

 

            batch.End();

        }

 

        public void SetMoveUpDown()

        {

            moveLeftRight = false;

        }

 

        public void SetMoveLeftRight()

        {

            moveLeftRight = true;

        }

 

        public void Stop()

        {

            moving = false;

        }

 

        public void StartMoving()

        {

            moving = true;

        }

 

        public void SetLayerPosition(int layerNumber, Vector2 startPosition)

        {

            if (layerNumber < 0 || layerNumber >= layerList.Count) return;

 

            layerList[layerNumber].position = startPosition;

        }

 

        public void SetLayerAlpha(int layerNumber, float percent)

        {

            if (layerNumber < 0 || layerNumber >= layerList.Count) return;

 

            float alpha = (percent / 100.0f);

 

            layerList[layerNumber].color = new Color(new Vector4(0.0f, 0.0f, 0.0f,

alpha));

        }

 

        public void Update(GameTime gameTime)

        {

 

            foreach( BackgroundLayer layer in layerList )

            {

                float moveDistance = layer.moveRate / 60.0f;

               

                if (moving)

                {

                    if (moveLeftRight)

                    {

                        layer.position.X += moveDistance;

                        layer.position.X = layer.position.X % layer.pictureSize.X;

                    }

                    else

                    {

                        layer.position.Y += moveDistance;

                        layer.position.Y = layer.position.Y % layer.pictureSize.Y;

                    }

                }

            }

        }

}

 

The idea when going through this is that you don't need to understand every detail of this file, but just enough to be able to use it. We'll go through a few big points in the code here and then see how to use it in the Ship Fighter game.

 

The first thing to look at is the definition of a background layer:

 

class BackgroundLayer

{

public Texture2D picture;

public Vector2 position = Vector2.Zero;

public Vector2 offset = Vector2.Zero;

public float depth = 0.0f;

public float moveRate = 0.0f;

public Vector2 pictureSize = Vector2.Zero;

public Color color = Color.White;

}

 

This shows you the different attributes we track abut each layer. We keep a picture of each (of course), its position and any initial start position offset (like if we wanted the layer to start in the middle of the screen).  We also keep the sprite depth, which is very important since that decides what order the layers are drawn in. We also keep how fast it is moving (moveRate) which is in pixels per second. And we keep any tint for the layer, which is the color member.

 

For the ScrollingBackground class, which manages the background layers, we'll only look at a few important methods. The first is the constructor for the ScrollingBackground, that takes in a GraphicsDeviceManager, which it uses to get the current windowSize:

 

public MultiBackground(GraphicsDeviceManager graphics)

        

So to create a ScrollingBackground we initialize it by passing the device manager in:

 

MultiBackground background;

background = new MultiBackground(graphics);

 

To create different layers we use the AddLayer method:

 

public void AddLayer(Texture2D picture, float depth, float moveRate)

 

There are three parameters to this method. The first takes in the sprite to be used for this layer. The second parameter is the sprite depth for this layer, with zero being on top and 1 at the bottom. The last parameter is the moveRate, which is how fast the later will move when the default moving is turned on. An example of using this method is:

 

Texture2D sky = Content.Load<Texture2D>("SkyLayer");

 background.AddLayer(sky, 0.5f, 200.0f);

 

Another important method is Move():

 

public void Move(float rate)

 

This moves everything based on a rate, which is the amount of time for everything to move. So if a layer has a moveRate of 150 pixels per second to move it 75 pixels we would do this:

 

background.Move(0.5f);

 

Another big method is the Draw one:

 

public void Draw()

 

Which is pretty simple to use, we just add to the draw loop:

 

background.Draw();

 

likewise there is a Update method that belongs in the game Update loop:

       

public void Update(GameTime gameTime)    

 

Two other methods:

 

public void SetMoveUpDown()

public void SetMoveLeftRight()

 

Just set of the background should move up and down or left and right.

 

The next two methods stop and start the animation (automatic scrolling):

 

public void Stop()

       

public void StartMoving()

 

And there are methods to set the default start position for a layer and an alpha tint to a layer.

 

Adding a Background to the Ship fighter

We've seen how our scrolling background works, let's add our new background to the ship game. We'll only use one layer of it, the same space background it currently has, but will make it scroll down the screen. The source for this project will take up where we left off on SimplePlayer Extended and this project is at xnagamemaking.com called SimplePlayer Extended with Background.

 

The first thing we'll need to do to our project is add the MultiBackground.cs to it. You can find the MultiBackground.cs in the ScrollingBackground solution or at xnagamemaking.com. Copy MultiBackground.cs to the source folder of the SimplePlayer Extended project (where the Game1.cs file is) and add it to the project by right clicking on the project name in Solution Explorer and choosing Add->Existing Item and select MultiBackground.cs. (This is similar to the way we added the Player.cs file in the last chapter, except here we are not creating a new item but adding an existing one.)

 

After we have MultiBackground.cs added to the project the first thing we want is to have that code available for the Game class. The namespace of the MultiBackground.cs is xnaExtras, so in the using statements in the Game1.cs file add the following:

 

using xnaExtras;

 

This allows us to put a MultiBackground object into the Game class. Remember the four basic steps to adding an object to our game:

 

1.      Declare the object

2.      Initialize the object, usually in the LoadContent() method

3.      Add the item to our Draw loop so we can see it onscreen

4.      Add the item to our Update loop so we can do any logic updates on it

 

So the first thing we need to do is declare the MultiBackground object. To do this we'll delete the line:

 

Texture2D spaceTexture;

 

and replace it with:

 

MultiBackground spaceBackground;

 

since we won't be drawing a sprite for the background but will be using the MultiBackground object instead. Now we have a MultiBackground object declared with the name spaceBackground. Next we need to initialize it, so change the LoadContent method to:

 

protected override void LoadContent()

{

spriteBatch = new SpriteBatch(GraphicsDevice);

           

// This method is the constructor found in MultiBackground.cs

spaceBackground = new MultiBackground(graphics);

// Create the texture/sprite for the hill

Texture2D spaceTexture =

Content.Load<Texture2D>("spaceBackground");

// Add the space layer to the background, this is also in

// MultiBackground.cs

spaceBackground.AddLayer(spaceTexture, 0.0f, 200.0f);

spaceBackground.SetMoveUpDown();

spaceBackground.StartMoving();

 

Texture2D playerTexture = Content.Load<Texture2D>("shipSprite");

playerShip = new Player(GraphicsDevice, new Vector2(400, 300),

playerTexture);

}

 

Here to initialize the background first we created it and the spaceTexture for it. Then we added a layer. Remember the three parameters we pass to the AddLayer method is the sprite name, it's sprite depth (zero being closest to the bottom), and default speed (200 pixels per second). After this we set the background to move up and down (instead of the default left to right) and then tell it to start moving. If you run the project now it won't work yet. Next we have to draw it.

To draw the background change the SpriteBatch drawing method in the Game class to draw the background right after clearing the screen:

 

graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

 

spaceBackground.Draw();

 

We want the background to be the lowest layer on the screen so we'll always draw it first right after clearing the screen. If your run the program you'll see the background on the screen again, but it's not moving. We still need to update it, so add the following to the Game update loop:

 

spaceBackground.Update(gameTime);

 

Now we have a scrolling background behind the ship.

 

 

Summary

This chapter has been a little different than the previous ones, in that we just took a source file, looked at the key parts of it, and added it to our ship project. The next chapter will be similar to this in that we'll be adding animation to the project.

 

NEXT