37

What is the best way to create a parallax effect in an XNA game? I would like the camera to follow my sprite as it moves across the world, that way I can build in effects like zoom, panning, shake, and other effects. Anybody have a solid example of how this is done, preferably in a GameComponent?

Khalid Abuhakmeh
  • 10,709
  • 10
  • 52
  • 75

4 Answers4

45

So I figured it out using a combination of the tutorials above and have created the class below. It tweens towards your target and follows it around. Try it out.

public interface IFocusable
{
    Vector2 Position { get; }
}

public interface ICamera2D
{
    /// <summary>
    /// Gets or sets the position of the camera
    /// </summary>
    /// <value>The position.</value>
    Vector2 Position { get; set; }

    /// <summary>
    /// Gets or sets the move speed of the camera.
    /// The camera will tween to its destination.
    /// </summary>
    /// <value>The move speed.</value>
    float MoveSpeed { get; set; }

    /// <summary>
    /// Gets or sets the rotation of the camera.
    /// </summary>
    /// <value>The rotation.</value>
    float Rotation { get; set; }

    /// <summary>
    /// Gets the origin of the viewport (accounts for Scale)
    /// </summary>        
    /// <value>The origin.</value>
    Vector2 Origin { get; }

    /// <summary>
    /// Gets or sets the scale of the Camera
    /// </summary>
    /// <value>The scale.</value>
    float Scale { get; set; }

    /// <summary>
    /// Gets the screen center (does not account for Scale)
    /// </summary>
    /// <value>The screen center.</value>
    Vector2 ScreenCenter { get; }

    /// <summary>
    /// Gets the transform that can be applied to 
    /// the SpriteBatch Class.
    /// </summary>
    /// <see cref="SpriteBatch"/>
    /// <value>The transform.</value>
    Matrix Transform { get; }

    /// <summary>
    /// Gets or sets the focus of the Camera.
    /// </summary>
    /// <seealso cref="IFocusable"/>
    /// <value>The focus.</value>
    IFocusable Focus { get; set; }

    /// <summary>
    /// Determines whether the target is in view given the specified position.
    /// This can be used to increase performance by not drawing objects
    /// directly in the viewport
    /// </summary>
    /// <param name="position">The position.</param>
    /// <param name="texture">The texture.</param>
    /// <returns>
    ///     <c>true</c> if the target is in view at the specified position; otherwise, <c>false</c>.
    /// </returns>
    bool IsInView(Vector2 position, Texture2D texture);
}

public class Camera2D : GameComponent, ICamera2D
{
    private Vector2 _position;
    protected float _viewportHeight;
    protected float _viewportWidth;

    public Camera2D(Game game)
        : base(game)
    {}

    #region Properties

    public Vector2 Position
    {
        get { return _position; }
        set { _position = value; }
    }
    public float Rotation { get; set; }
    public Vector2 Origin { get; set; }
    public float Scale { get; set; }
    public Vector2 ScreenCenter { get; protected set; }
    public Matrix Transform { get; set; }
    public IFocusable Focus { get; set; }
    public float MoveSpeed { get; set; }

    #endregion

    /// <summary>
    /// Called when the GameComponent needs to be initialized. 
    /// </summary>
    public override void Initialize()
    {
        _viewportWidth = Game.GraphicsDevice.Viewport.Width;
        _viewportHeight = Game.GraphicsDevice.Viewport.Height;

        ScreenCenter = new Vector2(_viewportWidth/2, _viewportHeight/2);
        Scale = 1;
        MoveSpeed = 1.25f;

        base.Initialize();
    }

    public override void Update(GameTime gameTime)
    {
        // Create the Transform used by any
        // spritebatch process
        Transform = Matrix.Identity*
                    Matrix.CreateTranslation(-Position.X, -Position.Y, 0)*
                    Matrix.CreateRotationZ(Rotation)*
                    Matrix.CreateTranslation(Origin.X, Origin.Y, 0)*
                    Matrix.CreateScale(new Vector3(Scale, Scale, Scale));

        Origin = ScreenCenter / Scale;

        // Move the Camera to the position that it needs to go
        var delta = (float) gameTime.ElapsedGameTime.TotalSeconds;

        _position.X += (Focus.Position.X - Position.X) * MoveSpeed * delta;
        _position.Y += (Focus.Position.Y - Position.Y) * MoveSpeed * delta;

        base.Update(gameTime);
    }

    /// <summary>
    /// Determines whether the target is in view given the specified position.
    /// This can be used to increase performance by not drawing objects
    /// directly in the viewport
    /// </summary>
    /// <param name="position">The position.</param>
    /// <param name="texture">The texture.</param>
    /// <returns>
    ///     <c>true</c> if [is in view] [the specified position]; otherwise, <c>false</c>.
    /// </returns>
    public bool IsInView(Vector2 position, Texture2D texture)
    {
        // If the object is not within the horizontal bounds of the screen

        if ( (position.X + texture.Width) < (Position.X - Origin.X) || (position.X) > (Position.X + Origin.X) )
            return false;

        // If the object is not within the vertical bounds of the screen
        if ((position.Y + texture.Height) < (Position.Y - Origin.Y) || (position.Y) > (Position.Y + Origin.Y))
            return false;

        // In View
        return true;
    }
}

And Here is how you would use it with SpriteBatch:

spriteBatch.Begin(SpriteBlendMode.AlphaBlend,
                  SpriteSortMode.FrontToBack,
                  SaveStateMode.SaveState,
                  Camera.Transform);
spriteBatch.Draw(_heliTexture,
                 _heliPosition,
                 heliSourceRectangle,
                 Color.White,
                 0.0f,
                 new Vector2(0,0),
                 0.5f,
                 SpriteEffects.FlipHorizontally,
                 0.0f);
spriteBatch.End();

Let Me know if this helps you out, and thanks to StackOverflow and the community. W00t!

Leri
  • 12,367
  • 7
  • 43
  • 60
Khalid Abuhakmeh
  • 10,709
  • 10
  • 52
  • 75
  • I was trying this code, but when I initialized it `Camera2D camera= new Camera2D`, I noticed I needed to pass in a Game variable. What variable would that be? – Dominic K Apr 03 '10 at 00:19
  • 5
    If the `IFocusable` Focus property should never be empty, it would be better if the ctor was: `Camera2D(Game game, IFocusable focused)` – Andreas Grech Feb 16 '11 at 17:32
  • 4
    In the Update method, you're probably going to want to calculate the Origin property before calculating the transformation matrix. Otherwise there will be a temporary disparity between the origin used the the transformation and the latest calculated origin based on scale. Also, you can just do Matrix.CreateScale(Scale) without explicitly creating a new Vector3. – Factor Mystic Mar 06 '11 at 05:26
  • I know this is an older thread, but it's a terrific solution and I'd love to implement it to understand it better. The only issue is that I can't seem to get it working. I have all the parts of the class wired up, it's initialized, it's updating, no errors are thrown in code. However, at runtime, a NullReferenceException is thrown at this point in the class Update override: _position.X += (Focus.Position.X - Position.X) * MoveSpeed * delta; _position.Y += (Focus.Position.Y - Position.Y) * MoveSpeed * delta; the debugger says that camera.Focus is null...I've been trying everything. Any ideas?? –  Dec 27 '09 at 05:01
  • Wow, it's been a while. But if I am reading my code correctly then you need a sprite to implement a IFocusable interface. Then you need to pass that sprite to the camera. Hope this helps. – Khalid Abuhakmeh Dec 28 '09 at 00:12
  • You're right! So elegant, thanks! I was trying to brute force it by just slapping the focus into a Vector2 and circumventing the whole point of using an interface, but now I see the light! Thanks so much for the response :) –  Dec 29 '09 at 06:33
  • I've had some trouble implementing this at first. For those who are having the null camera problem `Components.Add(camera);` inside the `Initialize()`. Awesome camera btw :) – Bogdan Rybak Dec 18 '13 at 09:37
19

Here are some tutorials on implementing a 2D camera in XNA:

  1. http://www.paradeofrain.com/?page_id=32
  2. http://gamecamp.no/blogs/tutorials/archive/2008/01/29/creating-a-simple-xna-camera-class.aspx
Martin.
  • 10,494
  • 3
  • 42
  • 68
David Brown
  • 35,411
  • 11
  • 83
  • 132
4

Here's another one

http://www.david-amador.com/2009/10/xna-camera-2d-with-zoom-and-rotation/

DJLink
  • 41
  • 1
2

I know it's an old question but i had the same one and i have found this great Monogame camera library so i thought i should share...

https://github.com/aloisdeniel/Comora

It is really easy to install and to follow sprite you only need to add

this.camera.Position = position of your sprite;