Limiting 2D Camera Movement with Zoom

On my 2D Camera with Parallax Scrolling in XNA article, one of the topics I described down in the FAQ section was how to limit the range of movement of a 2D camera so that only part of the game world may be seen.

I implemented it as a simple optional rectangle property called Limits which restricted camera movement so that the player could not see anything beyond that region.

However, I considered the simplest of the scenarios where you don’t need this functionality AND camera zooming/rotation at the same time. To answer a question posted under that article, this time I’ll be presenting a more elaborate implementation that also takes zoom into consideration.

Camera rotation will still be kept out of the equation, since I can’t imagine how it could be made to work with sensible results considering that the viewport is rectangular.

Introduction

I’ll start by presenting a little video of what the purpose of this article is. You can download the source code at the end as usual:

Now onto how it’s done. Let us consider the following simple Camera interface for our example:

What we’d like to achieve is that whenever the Limits property has a value assigned to it, any changes to Position and Zoom should be validated to ensure the player never sees anything beyond the limited region. Changing the camera’s limit should also revalidate everything.

To make this task easier, we’ll create two helper methods, ValidateZoom() and ValidatePosition() which will be called from within our setters above and correct any invalid values for us. Once we have these two methods implemented, the rest is trivial.

Validate Zoom

Making sure the camera’s zoom is valid is not too hard once you think about it. In general terms what you’d like to achieve is to ensure that the area that the camera can see is never larger than the limiting rectangle.

So, exactly how big is this area that the camera can see? Well, we know that when the camera isn’t zoomed at all (i.e. the Zoom property is 1), then the area the camera can see corresponds exactly to the game’s Viewport. It is also intuitive to think that if you’re zoomed in, you can see less of the world. Conversely, if you’re zoomed out you can see more than you could before.

This implies that the camera “size” (i.e. the size of the area visible by the camera) is inversely proportional to the camera’s zoom, or in mathematical terms:

Now that we know how large the camera is, let us revisit our initial statement – “Ensure that the area that the camera can see is never larger than the limiting rectangle”. We can just as easily model that with a mathematical expression:

Combining both equations into one and rearranging it we arrive at the following conclusion:

All that remains is to implement it. The only problem here is that while Zoom is a scalar (float), ViewportSize and LimitSize are both vectors so we can’t compare them directly. The solution is to compare separatedly for the X and Y axes and ignore the smallest value of the two. In code:

Validate Position

Validating the camera’s position after moving it or changing zoom is a bit harder. First you need to know where the top left corner of the camera stands in World space. For that you need to take your ViewMatrix and invert it so that you can transform points from View space back into World space, and apply it to the camera’s local top left corner (which in View space is simply Vector2.Zero), which gives you its position in world space. That position is then clamped inside the limiting rectangle’s min and max corners. However there are two catches that you need to take care of.

The first one is that, we’re clamping our top LEFT corner against the limiting rectangle’s bottom RIGHT corner, so in order to compensate we need to subtract it the camera’s size too (remember the formula above?). The second one is that the camera’s top left corner in world space doesn’t necessarily correspond to the camera’s position (because of zoom). To solve that I start by taking note of how much both of these values differ in order to correct that offset at the end. Here’s the code:

Updating the Properties

All that remains is to make use of these two methods in order to validate our data. Just add them inside your property setters. When changing position you don’t need to do any zoom validation. When changing zoom however, you have to follow it with a position validation because if you were standing close to the border, the position might end up being invalid after zooming out. Same thing when changing the camera’s limit, don’t forget to validate everything.

Usage Example

Using this technique is as simple as setting a new Rectangle to the Limits property, such as:

No matter how big or small your Viewport and Limits rectangle currently are, the camera’s position and zoom are initialized so that the player is guaranteed to only see the inside of that region. As you saw on the video earlier, I cycle through rectangles of many different sizes and locations and the camera is automatically updated to focus on that content.

Source Code
10 Comments.
  1. Paolo P says:

    Kudos to you. Thanks for solving my problems that I posted on the previous blog. I have a minor problem though. When I zoom out until my whole background screen fits the view screen, I don’t know how to explain this but all of the sprites’ drawings starts to shake infinitely probably because of the way the camera position clamps up. I might be able to resolve this if I do a trial and error on my debugging

    • That’s strange!

      I tried recreating that problem but it worked well here.I even tried setting prime numbers for both my viewport and limiting rectangle sizes to ensure all my divisions were uneven, and also tried both small and large limiting rectangles.

      Just like in the video, there were no shaking issues under any circunstances. So please tell me the exact size of your viewport and what rectangle you’re using to limit the camera so I can investigate further. :)

    • Paolo P says:

      My viewport size is 800 by 600 and my rectangle size is about 1500 by 1000 times 2 (its scale). Do you think its better to make my rectangle size smaller than the viewport size? but then I have to go through all the trouble configuring all the speed of all my ships to match with the small size

    • The size is irrelevant. And I just tried on my side creating a 3000×2000 background, limiting my camera to it while using a 800×600 viewport, and also filled the scene with some other sprites randomly, and I can confirm that everything worked fine. For example, I zoomed all the way out, moved around the borders, and my sprites didn’t flicker or anything.

      So I’m thinking it’s probably something else on your code that is causing it, not the camera limiting code here. Care to show me your Camera class (use pastebin.com)?

    • Paolo P says:

      After Ive done some tracing in my code, I’ve found that its not relevant to what I previously posted but rather that the way I used the zoom function is buggy. So I already fixed that part and now my game isnt shaky anymore. Sorry about that.

      Now I have a new problem. I want to add the parallax within your source code but when I replaced the ViewMatrix with GetViewMatrix(new vector(0.5f)) in the ValidatePosition() and zoom out, it didnt work the same as the one without the parallax. So I’m stuck right now

    • I think it’s natural that it won’t look the same way since the parallax changes our perception of how far away we are from the object, by moving it faster or slower. But isn’t it working *correctly*?

      I haven’t tried it yet, but perhaps all you need to do is adjust your limit rectangle to have half/double size or something? I’ll give it a try.

    • I’ve given it a try, and indeed it’s not as simple as just adding the parallax. It messes up the calculations… I’ll look into it, but let me know if you find out the solution too. :)

      But I figure there’s a simple way to “hack” it. Just set your background’s parallax to 1, and instead increase your sprite parallaxes (adjusting their speeds). That way the camera will limit correctly to the background.

    • Paolo P says:

      I tried your suggestion by decreasing the speed of each object by an parallax whenever the camera moves but it doesnt look pretty at all.

  2. Jens-Axel Grünewald says:

    I would like to recommend to enable mipmap generation at the Content Processor for the texture to enhance visualization when zooming out.

  3. Jake says:

    How can I have this camera follow a sprite around?

  1. By 2D Camera with Parallax Scrolling in XNA | David Gouveia on September 19, 2011 at 7:05 pm

    [...] Update: I wrote another article with more information about this matter. Check it out here: Limiting 2D Camera Movement with Zoom. [...]

Leave a Reply