Contact Home

How to Create a Parallax Effect in Monogame

Posted on: August 5th, 2013 by admin 5 Comments

This post will explain how to create a parallax effect in Monogame. First, take a look at the demo video to see what we will be doing:


This effect can be accomplished quite easily in Monogame. We create a Background class, so that we can layer multiple backgrounds together. This class will keep track of individual background images, speed, and zoom level.

Have a look at the documented code below:
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace MonogameParallax
{
    public class Background
    {
        private Texture2D Texture;      //The image to use
        private Vector2 Offset;         //Offset to start drawing our image
        public Vector2 Speed;           //Speed of movement of our parallax effect
        public float Zoom;              //Zoom level of our image

        private Viewport Viewport;      //Our game viewport

        //Calculate Rectangle dimensions, based on offset/viewport/zoom values
        private Rectangle Rectangle
        {
            get { return new Rectangle((int)(Offset.X), (int)(Offset.Y), (int)(Viewport.Width / Zoom), (int)(Viewport.Height / Zoom)); }
        }

        public Background(Texture2D texture, Vector2 speed, float zoom)
        {
            Texture = texture;
            Offset = Vector2.Zero;
            Speed = speed;
            Zoom = zoom;
        }

        public void Update(GameTime gametime, Vector2 direction, Viewport viewport)
        {
            float elapsed = (float)gametime.ElapsedGameTime.TotalSeconds;

            //Store the viewport
            Viewport = viewport;                

            //Calculate the distance to move our image, based on speed
            Vector2 distance = direction * Speed * elapsed;       

            //Update our offset
            Offset += distance;
        }

        public void Draw(SpriteBatch spriteBatch)
        {
            spriteBatch.Draw(Texture, new Vector2(Viewport.X, Viewport.Y), Rectangle, Color.White, 0, Vector2.Zero, Zoom, SpriteEffects.None, 1);
        }
    }
}
In order for these Backgrounds to fill our entire viewport area, they need to be drawn using a LinearWrap SamplerState. The SampleState is specified during our SpriteBatch initialization.

Since the background textures are drawn to wrap, we create the illusion of moving the image by setting an Offset point to begin drawing our texture. This offset is manipulated by a Direction vector during the Update call.

I have provided a main Game class to demonstrate an implementation of this technique:
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Collections.Generic;
using Microsoft.Xna.Framework.Input;

namespace MonogameParallax
{
    public class MonogameParallax : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        List<Background> Backgrounds;

        public MonogameParallax() : base()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        protected override void Initialize()
        {
            base.Initialize();
        }

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);

            //Load the background images
            Backgrounds = new List<Background>();
            Backgrounds.Add(new Background(Content.Load<Texture2D>(@"Clouds1"), new Vector2(300, 300), 0.6f));
            Backgrounds.Add(new Background(Content.Load<Texture2D>(@"Clouds2"), new Vector2(500, 500), 0.8f));
            Backgrounds.Add(new Background(Content.Load<Texture2D>(@"Clouds3"), new Vector2(700, 700), 1.1f));
        }

        protected override void UnloadContent()
        {
        }

        protected override void Update(GameTime gameTime)
        {
            KeyboardState kbState = Keyboard.GetState();

            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();

            //Get directional vector based on keyboard input
            Vector2 direction = Vector2.Zero;
            if (kbState.IsKeyDown(Keys.Up))
                direction = new Vector2(0, -1);
            else if (kbState.IsKeyDown(Keys.Down))
                direction = new Vector2(0, 1);
            if (kbState.IsKeyDown(Keys.Left))
                direction += new Vector2(-1, 0);
            else if (kbState.IsKeyDown(Keys.Right))
                direction += new Vector2(1, 0);

            //Update backgrounds
            foreach (Background bg in Backgrounds)
                bg.Update(gameTime, direction, GraphicsDevice.Viewport);

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            //Draw our parallax backgrounds, using a Linear Wrap
            spriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.LinearWrap, null, null);
            foreach (Background bg in Backgrounds)
                bg.Draw(spriteBatch);
            spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}

Feel free to download the source code.

Contact me with any questions or comments.

5 Responses

Hathor Gaia commented on April 30, 2016 at 1:17 pm

Unfortunately your source code link is dead…

Benoit Archambault commented on November 20, 2016 at 7:48 am

xnb files are not included… don’t know if this was on purpose?

seppe geerinckx commented on November 30, 2016 at 2:14 pm

how to only scroll trough the background horizontal and not vertical please could you explain that to me

    joshua R Holden commented on April 10, 2017 at 10:30 pm

    //if (kbState.IsKeyDown(Keys.Up))
      //              direction = new Vector2(0, -1);
      //          else if (kbState.IsKeyDown(Keys.Down))
     //               direction = new Vector2(0, 1);

Respond

Leave a Reply to joshua R Holden

You must be logged in to post a comment.