본문 바로가기

Programming/C#/Xna/Kinect/WPF

[XNA] A Simple 2D Camera

What is a camera? Intuitively we know what a camera is:  simply a way to show the action.

A Camera allows us to deal with the display of the action in a detached way from the action.

Implementation

I like to start with what we are trying to achieve. For the purposes of this post, I want to have two cameras showing the same action at different zoom levels, like this:

Capture

In this case we want 2 cameras, with each camera having it’s own ViewPort that we assign when creating like this:

int halfScreenWidth = GraphicsDevice.Viewport.Width/2;
_camera1 = new Camera(new Viewport(0, 0, halfScreenWidth - 3, GraphicsDevice.Viewport.Height));

What we are saying with that is that the Camera class will have a Viewport that occupies the left side of the screen.  The – 3 is there to add a visible gap between the left and right sides of the screen. Lets look at the following portion of the Draw method. Please note you could have all the parameters for _spriteBatch.Begin() null, except for the transform.

 1:             GraphicsDevice.Viewport = _camera1.Viewport;
 2: 
 3:             _spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, DepthStencilState.None, RasterizerState.CullNone, null, _camera1.Transform);
 4:             _level.Draw(_spriteBatch);
 5:             _spriteBatch.Draw(_tankTexture, _tankPosition, null, Color.White, _rotation, new Vector2(_tankTexture.Width / 2, _tankTexture.Height / 2), 1, SpriteEffects.None, 0);
 6:             _spriteBatch.End();

Since we have a viewport  per camera, the graphics device needs to know which one to use, and that’s what we are doing in line 1 in the code above. The other important part of the code is the last parameter of the _spriteBatch.Begin call, where we are using the transformation matrix from the camera. This transform is calculated on each update.

So finally lets look at the Camera class:

 1:     public class Camera
 2:     {
 3:         public Matrix Transform { get; private set; }
 4:         public Viewport Viewport { get; private set; }
 5: 
 6:         public Camera(Viewport viewport)
 7:         {
 8:             Transform = Matrix.Identity;
 9:             Viewport = viewport;
 10:         }
 11: 
 12:         public void Update(GameTime gameTime, float rotation, Vector2 position, float zoom)
 13:         {
 14:             Transform = Matrix.CreateTranslation(-position.X, -position.Y, 0) *
 15:                         Matrix.CreateRotationZ(rotation) *
 16:                         Matrix.CreateScale(new Vector3(zoom, zoom, 1)) *
 17:                         Matrix.CreateTranslation(Viewport.Width / 2, Viewport.Height / 2, 0);
 18:         }
 19:     }

The least obvious part of this code lies in the matrix transformation. It is important to understand that we have two coordinate systems in place, the screen and the world. With the transformation matrix we are trying to project the world coordinate system onto the screen system. With that in mind, the first translation matrix (line 14 in the code above) will reposition the world so that point (position.X, position.Y) lines up with the screen’s origin. The result is as follows:

CaptureCapture

What you see at the top left of the pic is a quarter of the tank. If you change the tank’s position then you will see that the tank remains static at the top left (tho rotation applies) and the world moves underneath it.

Capture

The next line, Matrix.CreateRotationZ, creates a matrix representing a rotation around the Z axis. The Z axis points straight out of the screen. In 2D, to perform a rotation, we always rotate around Z.

Capture

Then we need to scale, using the parameters that we sent on update. Don’t forget we set different levels of zoom in each of the two cameras. We use the overload of Matrix.CreateScale() that takes a Vector3 to create a scaling matrix. However we could use the overload that just takes a float and pass the zoom value as a parameter with the same result. Unsurprisingly applying the scale, scales Smile.

Finally we want to center the tank in the middle of each viewport and that’s why you apply the last line (line 17 on the sample above).

Capture

And we are done Smile tho I m sure there are plenty of ways to improve the code, but hopefully it will help as a simple example.

If you want to have a better look at the code, a working sample is available here. If you have any comments, improvements, questions, as always you are very welcome.

UPDATE: I followed up with a post about Spring Camera here.


출처 : http://roundcrisis.com/2012/04/19/xna-a-simple-2d-camera/