Morgemil Part 13 – Everything is relative.

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))

13_2

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)

13_3

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)

13_4
The purpose of this is to align the camera’s position (0,0) to the center of the screen.

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))

13_5

A good subject for next time is to zoom in a lot closer and have the camera follow the player.