views:

6253

answers:

5

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?

+7  A: 

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

  1. http://www.paradeofrain.com/?page_id=32
  2. http://blog.rajprasad.net/tutorials/2d-camera-in-xna/
  3. http://arcez.com/blogs/2007/11/17/Basic2DcameraXNA.aspx
  4. http://gamecamp.no/blogs/tutorials/archive/2008/01/29/creating-a-simple-xna-camera-class.aspx

I hope that helps!

David Brown
cool, thanks. I have to look at them and see if they will work first.
Khalid Abuhakmeh
http://arcez.com/blogs/2007/11/17/Basic2DcameraXNA.aspx is a great start. Now I just need to make it a game component or XNA Service. Thanks.
Khalid Abuhakmeh
+6  A: 

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!

Khalid Abuhakmeh
That seems hardly fair. You take the information that I provided and then mark the results of your learning as the answer rather than the answer that gave you the resources in the first place? I hate to sound selfish, but that seems contradictory to what SO's rep system is all about.
David Brown
Alright, I'll switch you back to having given the answer. But I thought the SO answer system meant check the best answer. Not check the other guys best answer even if you have a better one.
Khalid Abuhakmeh
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?
DMan
A: 

Hi, 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??

Jon
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
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 :)
Jon
A: 

It's been an even LONGER time now, but I'm kinda struggling with your reply to the problem above.

Obviously, I'm also getting the exception, but I don't get what you mean by "you need a sprite to implement an IFocusable interface".

Do I need to add anything else to the code you supplied above? Like a function?

-Thanks

Tony
Perhaps it would be appropriate to ask this as a new *question*?
Andrew Russell
Hello Tony, well the IFocusable interface has a Vector2 Property which designates the location of your sprite or thing of interest. This is basically used to determine where the camera needs to be. Without it set the camera doesn't know where to center itself. Does that make sense? So when you create a Helicopter, you just need to implement the IFocusable interface and then set that as your Focus on the Camera.
Khalid Abuhakmeh