Part Three - Scrolling Background



As we have discussed, our game will play out on a multi-level scrolling star field background. In order to accomplish the parallax effect we are looking for we will use two different star field backgrounds, scrolled at different rates.

We will start by creating our two images. I got Jason to put these together for me. The first is a star field with a solid background. It is 1920x720 pixels.



Save this PNG file into your Content/Textures folder for your project and use the Solution Explorer to include it in the project.

Our second star field is a mostly transparent image with a few white stars scattered about it. This image is 1620x480, which means we will be stretching the image when drawing it to fill our 720-pixel high screen, but it will look fine when we do.



Again, save this image to your Content/Textures folder and add it to your solution.

The Background.cs class

We will create a new class to handle our scrolling background just to keep things neat. Right click on your project in Solution Explorer and add a new class called "Background.cs".

We will need this class to have access to a few of the XNA Framework namespaces, so add the following "using" statements to the top of the class file:

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;


Next, we will declare the variables we will be using in this class. Inside the class itself, add:

        // Textures to hold the two background images
        Texture2D t2dBackground, t2dParallax;


We will use these two Texture2D objects to hold the background images for our scrolling background.
Next, add the following:

        int iViewportWidth = 1280;
        int iViewportHeight = 720;
 
        int iBackgroundWidth = 1920;
        int iBackgroundHeight = 720;
 
        int iParallaxWidth = 1680;
        int iParallaxHeight = 480;


The iViewportWidth and iViewportHeight determine how large our background image will be displayed when rendered to the screen. Here we are targeting a 720i/720p display of 1280x720.

The next four integers hold the width and height of our background images. The actual values here happen to match our images, but they are really just placeholders since we will set to match whatever images we load in our constructor.

Next we will need a couple of values to determine how far along we should start drawing the background images.

Essentially, these hold the leftmost position in the texture to start drawing at. Another way to think of this would be that we are starting to draw our background image "iBackgroundOffset" pixels BEFORE the screen begins (ie, off the left edge of the display).



In addition to the variables themselves, I have included a couple of properties to set them from outside the class:

        int iBackgroundOffset;
        int iParallaxOffset;
 
        public int BackgroundOffset
        {
            get { return iBackgroundOffset; }
            set
            {
                iBackgroundOffset = value;
                if (iBackgroundOffset < 0)
                {
                    iBackgroundOffset += iBackgroundWidth;
                }
                if (iBackgroundOffset > iBackgroundWidth)
                {
                    iBackgroundOffset -= iBackgroundWidth;
                }
            }
        }
 
        public int ParallaxOffset
        {
            get { return iParallaxOffset; }
            set
            {
                iParallaxOffset = value;
                if (iParallaxOffset < 0)
                {
                    iParallaxOffset += iParallaxWidth;
                }
                if (iParallaxOffset > iParallaxWidth)
                {
                    iParallaxOffset -= iParallaxWidth;
                }
            }
        }


You will notice that the two properties above check to see if we have gone off of either end of each texture and wrap around if necessary. If we don't do this, we can scroll right off the end of the background image.

Finally, though we won't have a need to use it in our case, I've added a toggle to turn the star field overlay on or off:

        // Determines if we will draw the Parallax overlay.
        bool drawParallax = true;
 
        public bool DrawParallax
        {
            get { return drawParallax; }
            set { drawParallax = value; }
        }


In our draw code we will simply check this bool value and not draw the mostly transparent star overlay if it is set to false.

Now we are ready to write a constructor for our class:

        // Constructor when passed a Content Manager and two strings
        public Background(ContentManager content, 
                          string sBackground, 
                          string sParallax)
        {
           
            t2dBackground = content.Load<Texture2D>(sBackground);
            iBackgroundWidth = t2dBackground.Width;
            iBackgroundHeight = t2dBackground.Height;
            t2dParallax = content.Load<Texture2D>(sParallax);
            iParallaxWidth = t2dParallax.Width;
            iParallaxHeight = t2dParallax.Height;
        }


You will remember that in our AnimatedSprite class we passed the textures we would be using directly into the animated sprite (we used Content Manager outside the AnimatedSprite to get them into our game.) We are taking a slightly different approach here and will be passing in a Content Manager instance and using it to load images from our class. This is purely to illustrate that it can be done this way as well, as it would be just fine to use the same texture passing method we used in AnimatedSprite here too.

When we use this constructor to create an instance of our Background class, we will pass in a content manager and use it to load the two textures passed to the function. Our constructor will set iBackgroundWidth, iBackgroundHeight, iParallaxWidth, and iParallaxHeight to the appropriate values from the textures we loaded.

I've also got a second constructor set up for the Background.cs class. We won't actually be using this constructor in our tutorial series, but it could be used in the instance that you simply wanted a scrolling background without the parallax overlay:

        // Constructor when passed a content manager and a single string
        public Background(ContentManager content, string sBackground)
        {
            
            t2dBackground = content.Load<Texture2D>(sBackground);
            iBackgroundWidth = t2dBackground.Width;
            iBackgroundHeight = t2dBackground.Height;
            t2dParallax = t2dBackground;
            iParallaxWidth = t2dParallax.Width;
            iParallaxHeight = t2dParallax.Height;
            drawParallax = false;
        }


This simply eliminates the overlay and disables it from drawing (drawParallax=false

All that is left now is to actually draw our background images. Lets add the following Draw method, which I will explain afterwards:

        public void Draw(SpriteBatch spriteBatch)
        {
            // Draw the background panel, offset by the player's location
            spriteBatch.Draw(
                t2dBackground,
                new Rectangle(-1 * iBackgroundOffset, 
                              0, iBackgroundWidth, 
                              iViewportHeight), 
                Color.White);
            
            // If the right edge of the background panel will end 
            // within the bounds of the display, draw a second copy 
            // of the background at that location.
            if (iBackgroundOffset > iBackgroundWidth-iViewportWidth) { 
                spriteBatch.Draw(
                    t2dBackground,
                    new Rectangle(
                      (-1 * iBackgroundOffset) + iBackgroundWidth, 
                      0, 
                      iBackgroundWidth,
                      iViewportHeight), 
                    Color.White); }
 
            if (drawParallax)
            {
                // Draw the parallax star field
                spriteBatch.Draw(
                    t2dParallax,
                    new Rectangle(-1 * iParallaxOffset, 
                                  0, iParallaxWidth, 
                                  iViewportHeight), 
                    Color.SlateGray);
                // if the player is past the point where the star 
                // field will end on the active screen we need 
                // to draw a second copy of it to cover the 
                // remaining screen area.
                if (iParallaxOffset > iParallaxWidth-iViewportWidth) { 
                    spriteBatch.Draw(
                        t2dParallax, 
                        new Rectangle(
                          (-1 * iParallaxOffset) + iParallaxWidth, 
                          0,
                          iParallaxWidth,
                          iViewportHeight), 
                        Color.SlateGray); }
            }
        }


Our Draw method is passed a SpriteBatch to use, and we will assume that we are within a SpriteBatch.Begin and SpriteBatch.End call set.

Our first statement draws the background image, offset by the background offset:

spriteBatch.Draw(
t2dBackground,
new Rectangle(-1 * iBackgroundOffset,
0, iBackgroundWidth, iViewportHeight),
Color.White);

When we create the destination rectangle, we set the left position to "-1 * iBackgroundOffset", which results in shifting our image to the left by a number of pixels equal to iBackgroundOffset.

This works great except when we get to the point where drawing the offset image doesn't fill our entire display. If we don't account for that, we will end up with a partially filled background and then the XNA Blue Window. This is where the next statement comes in:

// If the right edge of the background panel will end within the bounds of
// the display, draw a second copy of the background at that location.
if (iBackgroundOffset > iBackgroundWidth-iViewportWidth) {
spriteBatch.Draw(
t2dBackground,
new Rectangle(
(-1 * iBackgroundOffset) + iBackgroundWidth,
0,
iBackgroundWidth,
iViewportHeight),
Color.White); }

First we check to see if we need to draw a second copy of the image. If so, we repeat the above draw call except that we modifiy the position of the second destination rectangle by adding the width of the background image to the call. This will line the second copy of the image up to start at exactly the point where the first copy ended.

We will never need to draw more than two of these images to fill the screen, since the width of the background image is greater than the width of the screen.

The rest of our draw function does exactly the same process with the parallax star overlay after checking to see if we should be drawing it. It uses the same offsetting and second copy drawing logic as the background.

Adding the Background to our Game

Now that we have our background class, lets make it do something. Currently our "Game1" code is loading and explosion and animating it. We can leave this code here for now, and add our Background object to the game.

Lets begin by setting our window/display resolution. In the Initialize method for your game, add the following code (right before the "base.initialize();" line:

            graphics.PreferredBackBufferHeight = 720;
            graphics.PreferredBackBufferWidth = 1280;
            graphics.ApplyChanges();


All we are doing here is letting the grpahics device know that we want a 1280x720 screen size. On the Xbox 360 this will be automatically scaled by the system to fit the display, adding letterboxing as needed if the user is on a standard definition television. On Windows, this will result in a 1280x720 window being created when we run the game.

In you declaration section, right after the SpriteBatch declaration, add the following:

Background background;


This will add a new (but still uninitialized) background object to our game. Next, we'll need to actually initialize the background by running it's constructor. In the LoadContent method of our game, lets add the following code:

            background = new Background(
                Content, 
                @"Textures\PrimaryBackground", 
                @"Textures\ParallaxStars");


Here, we call the Background object's constructor and pass it our Content Manager object, along with the asset names of the two textures we will be using.

That handles the setup, so all that is left is to draw our background. Scroll down to the Draw method and add the following line BEFORE the draw code for the explosion, but inside the spriteBatch.Begin and spriteBatch.End block:

            background.Draw(spriteBatch);


That's all we need to draw our background to the screen. Of course, we will want to be able to scroll it, so lets add a couple of lines to the game's Update method:

            if (Keyboard.GetState().IsKeyDown(Keys.Left))
            {
                background.BackgroundOffset -= 1;
                background.ParallaxOffset -= 2;
            }
 
            if (Keyboard.GetState().IsKeyDown(Keys.Right))
            {
                background.BackgroundOffset += 1;
                background.ParallaxOffset += 2;
            }


(Note that for this simple test code, I'm using a Windows based project, and thus using the keyboard. If you have set up an XBox 360 project and want to use the GamePad, replace "Keyboard.GetState().IsKeyDown(Keys.Left)" with "GamePad.GetState(PlayerIndex.One).ThumbSticks.Left.X<0" and the right key with >0. In part 4 when we add the player's space ship, we will add input handling routines to check for both keyboard and gamepad input.

If you run your project now, you should be able to scroll your background left and right with the arrow keys. We have set the scroll rate to 1 and 2 pixels here, but you can make the background change faster by upping these values. We will actually use a range of values to allow our player to move at different speeds.

In our next installment, we will draw the player's ship onto the screen and start setting up the variables we will need to handle gameplay.

(Continued in Part 4...)


































 

 
 
Site Contents Copyright 2006 Full Revolution, Inc. All rights reserved.
This site is in no way affiliated with Microsoft or any other company.
All logos and trademarks are copyright their respective companies.
RSS FEED