A lot of video games are 3D. This makes things difficult since a computer or television screen is 2D. So the graphics programmer does a lot of complicated math to represent a 3D world in a 2D medium.
I don’t have to do that as I’m going to keep things simple with a 2d world.
So instead of drawing 3D models, I’m going to be using 2D Sprites. Remember the
spriteBatch <- new SpriteBatch(this.GraphicsDevice)
I mentioned last time? This is what draws 2d sprites for me.
The attributes I require for a 2d world, is the ability to zoom in and out and the ability to scroll up, down, right, and left. I accomplish this through 2D transformations. The great answer provided here is probably the best solution but I don’t require camera rotation so I rolled a subset of that code:
namespace Morgemil.View open Microsoft.Xna.Framework /// The spriteBatch draw transform type Camera2d(Zoom : float32, Position : Vector2) = ///Apply this to SpriteBatch Draw member this.Matrix = Matrix.CreateTranslation(Position.X, Position.Y, 0.0f) * Matrix.CreateScale(Zoom) member this.Zoom = Zoom member this.Position = Position //1px tiles. position (0,0) static member Default = Camera2d(1.0f, Vector2.Zero)
So to apply this I changed a couple of things. I removed
let tileSize = 2
and changed the screen size to be independent of the map tile count.
do graphics.PreferredBackBufferWidth <- 1024 do graphics.PreferredBackBufferHeight <- 768
The dungeon generation was tweaked a little also to make smaller dungeons and smaller rooms. The color chooser was also changed for easier differentiation between tiles
let ChooseColor(tileDef : Morgemil.Map.TileDefinition) = match tileDef.BlocksMovement with | true -> Color.Red | false -> Color.White
I’ve added a default camera instantiation
let mutable camera = Camera2d.Default
And I draw the world using the new camera
spriteBatch.Begin (SpriteSortMode.Deferred, null, null, null, null, null, new System.Nullable<Matrix>(camera.Matrix))
Now to give some context. Since I removed tileSize, each tile is drawn in this manner:
let DrawTile(pos : Morgemil.Math.Vector2i, tileDef : Morgemil.Map.TileDefinition) = let drawArea = Rectangle(pos.X, pos.Y, 1, 1) spriteBatch.Draw(spriteTexture, drawArea, ChooseColor tileDef)
Each tile has a draw area of 1px by 1px therefore. This is unacceptable. I cannot differentiate between tiles at this resolution. So I’m going to increase the default zoom level on the camera.
let mutable camera = Camera2d.Default.SetZoom(8.0f)
Hey, that’s a lot better. I can see things. What’s happening is that the tiles are still being drawn with an area of 1. But it is now independent of pixels since the entire “batch” of sprites is being transformed by the same zoom level.
Let’s tack on a translation matrix
//Adjust the camera by the buffer size let transform = camera.Matrix * Matrix.CreateTranslation (float32 (graphics.PreferredBackBufferWidth / 2), float32 (graphics.PreferredBackBufferHeight / 2), 0.0f)
I can then create a function to return a camera that is centered on a specific tile. This is useful because the camera following the player can be as simple as passing the player’s tile position to this function
///Returns a centered camera. Given the tile location let CenterCamera zoom (tileLocation : Morgemil.Math.Vector2i) = Camera2d(zoom, Vector2(float32 (-tileLocation.X), float32 (-tileLocation.Y)))
Until I add a player to this XNA window, let’s put it back to centering the map on the screen. This is done by centering the camera on the center tile.
let mutable camera = CenterCamera 8.0f (Morgemil.Math.Vector2i(chunk.Area.Width / 2, chunk.Area.Height / 2))
A good subject for next time is to zoom in a lot closer and have the camera follow the player.