You really just need a 2D camera and I'm sure you will want it locked onto a sprite on your screen, which is what you mean by smooth scrolling. If you just want a working implementation, you can find one over here working at StackOverflow.
With that out of the way, I'm going to explain a bit how it works on a deeper level.
A matrix is used to perform transformations of some sort, there are a few types. The SpriterBatch has an internal projection matrix that will implictly do a lot of work behind the scenes, so that leaves the two you actually need to implement:
- World Matrix. This matrix is actually abstracted away from you in many sorts. When you pass in your coordinates (screen rectangle, or X/Y) and dimensions, you have generated a sort of world matrix. In terms of 2D in MonoGame, you will never need to concern yourself with more than this. You can find more information on the web about this if you want, but it is out of scope for this.
- View Matrix. This is what you wanted to create and what the above link will help you create. A view matrix will essentially take all your world objects and transform the screen to "see" these things. That is, it used to move the camera around. This includes your scaling, translations, and the like.
How is the view matrix composed?
First of all, it is important to note that transformation order is important.
In a composite transformation, the order of individual transformations is important. For example, if you first rotate, then scale, then translate, you get a different result than if you first translate, then rotate, then scale. In GDI+, composite transformations are built from left to right. If S, R, and T are scale, rotation, and translation matrices respectively, then the product SRT (in that order) is the matrix of the composite transformation that first scales, then rotates, then translates. The matrix produced by the product SRT is different from the matrix produced by the product TRS.
This article is referring to GDI+, but the concept is similar here in XNA/MonoGame. Order is important.
The above implementation does the transformations like:
Great! Now, let us take a look at why these are done in the particular order.
So, we have something like:
// Create the Transform used by any
// spritebatch process
Transform = Matrix.Identity*
Matrix.CreateTranslation(-Position.X, -Position.Y, 0)*
Matrix.CreateTranslation(Origin.X, Origin.Y, 0)*
Matrix.CreateScale(new Vector3(Scale, Scale, Scale));
Origin = ScreenCenter / Scale;
The Position is typically where you want the center of your camera to be pointing at. This is usually your sprite / main player object. This is usually updated in an Update method of the camera that will take the player position and update this value accordingly. However, you can definitely change the target to other objects or use fancy interpolation to make the camera snap less. This gives a chase effect.
The identity matrix is simply our starting point, you can read more about it on the web, but you can just think of it as your unit matrix. Next, we translate to the negative position of where our sprite is. By doing this, we end up offsetting. Rotation is then applied, as we want to rotate around this point. Then, a translation to the center is made. Then, we simply apply our zoom.
If you do these steps in single step, you can see how they directly affect each other.
A caveat about the mouse...
It should be noted by doing this, you will transform your game view. However, what about input? For the mouse, this is always returned in screen/view coordinates. You want this in world space (where your objects are being defined) so we fix this by doing something similar to:
Vector2 mouseWorldPosition = Vector2.Transform(mousePosition, Matrix.Invert(viewMatrix));
Where viewMatrix is your matrix you passed into your SpriteBatch begin, to begin with.
For extra reading, consider these questions on the StackExchange network: