Acid Rain TileMap and Camera System XNA Tutorial

There's nothing quite as motivating as having someone contact you via your game's website and ask for instruction on how you accomplished something in your game.  It means they looked at your game.  And that's great for the ego.  And let's face it.  In today's world of hobbyist/Indie game development, programming is more of a mashup process than actually coding from scratch.  I would equate it more to the musicindustry.  At some point in your 30s, most music starts to sound a lot like the music you used to listen to only not as good.  And that's because all of the young artists are just rehashing sounds from the past and mashing them into new compositions.  Andthere's nothing wrong with that at all. 

Raise your hand if you plan to code your own physics engine, particle system, rendering engine, content loading system etc.  That's what I thought.  Coding today is a mashup of existing  ideas and code.  When I developed Paladin's Legacy in the mid 80s, in 6809 assembly, this wasn't the case.  I had one book that gave me the assembler commands and that was it.  Today we have tons of open source projects, blogs, education samples etc. etc.  For that reason, I felt the urge to post this tutorial in order to contribute to the great mashup in the cloud and to respond to that one request.

Acid Rain is organized around the GameStateManagement sample over at the creatorsclub website.  This new link is for the XNA 4.0 version.  I guess XNA 3.1 is on the way out.  I'm not going to review how the GameStateManagement sample works.  There's plenty of posts about that.  Suffice it to say that all of the game play is directed out of the gameplayscreen.cs.  My game1.cs has been renamed to Engine.cs.  All Engine.cs does is provide a starting class with basic screen dimension setup and it adds the first screen to be displayed by the menu system.  There are a lot of classes but most of them are used by the gamestate menu system.  The actual gameplay is accomplished with Player.cs, Terrain.cs, FrontOfPlayer.cs, Camera.cs and of course Engine.cs.  Also during gameplay, you are accessing Screenmanager.cs and InputState.cs which are provided by the gamestate sample.

So to start, let's look at Engine.cs.  We first declare the basics....A GraphicsDeviceManager and we need to instantiate the Screenmanager game component from gamestatemanagement, add a static random function and set the screen sizes.       

    public class Engine : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        ScreenManager screenManager;

        // a random number generator that the whole game can share.
        public static Random Random = new Random();

        //random number generator for floats that the whole game can share
        public static float RandomBetween(float min, float max)
        {
            return min + (float)Random.NextDouble() * (max - min);
        }

        #region setScreenSizes Method
        private void setScreenSizes()
        {
            graphics.PreferredBackBufferWidth = 1280;
            graphics.PreferredBackBufferHeight = 720;
            graphics.IsFullScreen = false;
            graphics.ApplyChanges();
            Window.Title = "Acid Rain";
        }
        #endregion

We then need to add the screenManager game component to the game services collection.  And then we use the AddScreen method within Screenmanager.cs to add our first screen that will be displayed by the menu system.

    // Create the screen manager component.
            screenManager = new ScreenManager(this);
            Components.Add(screenManager);

            // Activate the first screens.
            screenManager.AddScreen(new MainMenuScreen(), null);

Now in Acid Rain, the game, I had several screens prior to the Main Menu screen but for the purposes of this tutorial we're just plowing along trying to get to the gameplayscreen.cs which is where the game actually begins.  So take a look at MainMenuScreen.cs in the Screens folder.  This is the class used to display the menu system.  It inherits from MenuScreen which in turn inherits from GameScreen.cs.  If you are new to Object Oriented Programming, this is a real mind screw.  It basically means MainMenuScreen can use anything from MenuScreen which can use anything from GameScreen.  2 levels of inheritance.  This is why Menuscreen and GameScreen are abstract classes with virtual methods.  But I digress.  Note in MainMenuScreen.cs, we have an event that is used to load GamePlayScreen.cs when the user chooses the New Game menu option.

       public MainMenuScreen()
            : base("Acid Rain", "A Hero's Journey") //base("Acid Rain", "A Hero's Journey")
        {
            
            // Create our menu entries.
            MenuEntry playGameMenuEntry = new MenuEntry("New Game");
            MenuEntry exitMenuEntry = new MenuEntry("Exit");

            // Hook up menu event handlers.
            playGameMenuEntry.Selected += PlayNewGameMenuEntrySelected;
            exitMenuEntry.Selected += OnCancel;

            // Add entries to the menu.
            MenuEntries.Add(playGameMenuEntry);
            MenuEntries.Add(exitMenuEntry);
        }

        #region New Game
        /// 
        /// Event handler for when the Play Game menu entry is selected.
        /// 
        void PlayNewGameMenuEntrySelected(object sender, PlayerIndexEventArgs e)
        {
            LoadingScreen.Load(ScreenManager, true, e.PlayerIndex, new GameplayScreen());
        }
        #endregion

Once GamePlayScreen.cs has been added to the ScreenManager screens using the LoadingScreen.cs Load method, we are plowing along through the GamePlayScreen class.  ScreenManager.cs basically unloads all of the content from the menu system and jumps into GamePlayScreen.cs and starts running that class.  Our game stays in that class until we Pause the game and exit, at which point we reload the menu system and jump back into the menu system.  But enough of that stuff.  We're finally in GameplayScreen.cs.

 The first thing I should talk about is Game Components and Game Services.  You'll need to know how these work in order to understand the organization of my game.  I really like Game Components.  A class is made into a game component by having it inherit from the game component interface.....like so.  Using my player class as an example.

   public class Player : Microsoft.Xna.Framework.DrawableGameComponent

Game Components have the unique advantage over a class in that they have their own Update loop.  If it's a DrawableGameComponent as in the case of Player.cs, then it also comes with its own Draw loop.  This means you can have all of the update and draw logic within that class and the game is geared to loop through those methods so you don't have to call them from somewhere else.  It's nice and clean.  All Game Components also have their own copy of Game so you have access to graphics device  and Game Services etc.  This saves you from having to figure out how to pass that stuff into a naked class.  The downside to Game Components is that there is a small performance cost.  I say small, but try using a Game Component class as an enemy.  Then instantiate 50 copies of that game component class into a List for your game.  You will destroy performance.  If you are going to make multiple copies of a class, use a regular class.  In my game I have an ActorManager.cs Game component that manages my enemy and actor classes.  I have lots of enemies and lots of actors so in this case I want those to be regular classes that I can copy and add into lists.  And I call those classes update and draw methods from the ActorManager.cs update and draw methods.  If you take a look at GamePlayScreen.cs, you'll see at the top where I declare my game Components.

  #region Game Component declarations
        Terrain terrain;
        FrontOfPlayer frontOfPlayer;
        Player player;
        #endregion 

Then in the Load method, I need to instantiate those game component classes and add them to the components collection maintained by XNA.  While doing this I have the ability to do something very important.  I can declare the Draworder of that game component.  The Draworder can be changed on the fly during runtime from anywhere in the game that has access to that class.  This is another nice advantage of Game Components.....you get to tell it in what order you wish to do it's draw.  This is really important for a 2D game.  Because Draw Order determines how things are layered on the screen.  For instance, I want my player to run behind some trees but on top of the grass and some rocks and even some of the tree trunks.  So in this case, Terrain has a Draworder of 5, Player is at 8 and the appropriately named FrontofPlayer is at 10.  So everything in Terrain.cs is drawn first, then Player.cs, then FrontOfPlayer.cs.  Having this all in one place let's you quickly alter the layering in your game as needed.  Very important since Acid Rain had dozens of game Componant classes and lots of layering.  Once you are in the game component class the order of drawing is determine by the sequence of spritebatch.Draw calls.

  #region Instantiate Game Component Classes and determine draworder
            //Game components have their own Update and Draw methods.  This allows me to 
            //use a different spriteBatch.Begin in each Component Draw method.  In HUD, I am able to avoid the camera by not calling 
            //camera.transfrom in the HUD spritebatch.begin thus my HUD remains stationary and does not scale.
            //I am also able to use SpriteSortMode.Immediate which is faster than BackToFront.
            //DrawOrder determines the order a component is drawn to the screen and thus it's layerdepth.
            //Lower DrawOrder is back of the screen, higher is close to your face.  Another benefit of Game Components
            //is that they all have access to Game and thus the Game Services Collection.  We can add stuff to the Game Services collection
            //like audioManager, spriteBatch and thus our game components can all share resources. 

            player = new Player(ScreenManager.Game, this, ScreenManager, playerIndex);
            player.DrawOrder = 8;
            ScreenManager.Game.Components.Add(player);

            terrain = new Terrain(ScreenManager.Game, player);  //2-3 secs
            terrain.DrawOrder = 5;
            ScreenManager.Game.Components.Add(terrain);

            frontOfPlayer = new FrontOfPlayer(ScreenManager.Game);
            frontOfPlayer.DrawOrder = 10;
            ScreenManager.Game.Components.Add(frontOfPlayer);
            #endregion 

Another cool thing you can do with a Game component is to utilize the built in Enable and Visible bool functions.  This allows you to start and stop the update and draw loops at any time.  This is really useful when you pause the game.  You may want to disable the Update loop so there is no more movement but keep the Draw on so you see a static image where you paused the game.  If you look at my pause game code, you'll see that functionality.  By default Enable which controls the update loop and Visible which controls the Draw loop are set to true.  At any time, you can say Terrain.Enable = false or Terrain.Visible = false and this will no longer update or draw the Terrain Update and Draw methods.  OK let's get to the TileMap and Camera...finally.  First up is the Camera.  You should notice that Spritebatch has an overload that accepts a Matrix TransformMatrix.

   spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None, ScreenManager.Camera.Transform);

My Matrix TransformMatrix is in my camera.cs class that I declare as static in ScreenManager.cs at the top of ScreenManager.cs.  This way I have access to camera everywhere.  I keep my camera class really simple for the reason that it took me a long long time to really understand what the heck was going on here.  Once I read that Spritebatch is just a very high performance and efficient Shader that the brains at MS wrote and all of those overloads just give you easy access to functions and methods within that shader code, I started to get a picture of how spritebatch worked.  I use SpriteSortMode.Immediate because it is faster than BackToFront or FrontToBack.  It also allows you to use custom shaders.  I use a Shader for my Water in Acid Rain.  And it allows you to manually keep control of your draw layering.  Once I learned that Immediate was the only option that allowed you to use shaders of your own and it was the fastest while allowing transparency and layering, I had to use Immediate.  Here is the entire Camera class.

   public class Camera
    {
        public Vector2 Position = new Vector2(0, 0);  //This positions the camera at upper left corner of tilemap
        
        public float scale { get; set; }
        public float rotation { get; set; }
        public Vector2 Origin {get; set; }
        public float MaxWorldScale { get; set; }
        public float MinWorldScale { get; set; }

        public Matrix Transform = Matrix.Identity;

        public Camera(Game game)
        {
            MaxWorldScale = .5f; //  .5f Zoom Out
            MinWorldScale = 1.5f;  //  2f  Zoom In
            
            scale = 1f;
            Origin = Vector2.Zero;
        }

        public Matrix TransformMatrix(GameTime gameTime)
        {
            Transform = Matrix.CreateTranslation(new Vector3(-Position, 0)) * 
                        Matrix.CreateRotationZ(rotation) *      
                        Matrix.CreateTranslation(Origin.X, Origin.Y, 0) * 
                        Matrix.CreateScale(new Vector3(scale, scale, 0));
           
            return Transform;
         }

The MaxWordScale and MinWorldScale simply limit how far you can scale in and out of the world.  These are used in GamePlayScreen.cs in the SetWorldScale() method.  You use the joystick's right and left triggers to zoom in an out.  Left stick to move player around.    I then declare a Matrix called Transform and set it to Zero. (In the nebulous world of the Matrix, this is called Matrix.Identity.)  This is about the time I wished I had taken the red pill.  Then I alter that Transform in the TransformMatrix method by using those funky CreateTranslation,Rotation, Scale functions.  The key is that you place that Matrix Transform into the Spritebatch Begin Methods in any class where you want the draw stuff to be altered by your camera.  This somehow tools the Matrix math contained within the CreateTranslation,Rotation,Translation,Scale Matrix commands into the Spritebatch shader and puts them to work altering the screen.  You may notice that that Matrix stuff uses Vector3 in order to pass X, Y, Z coordinates.  This Matrix stuff is the same thing used for 3D game development.  By making all of the Zs equal to Zero, we simply eliminate that dimension and Matrix then works great for a 2D camera system.   So to summarize, the SetWorldScale() method is called in the Update loop in GamePlayScreen.cs.  This changes the scale value in the Camera class depending on joystick Triggers or keyboard (Z,X,C) keys. This works because the Matrix method is tooled into Spritebatch in every class where we draw and want to have that camera capability.  If you create a HUD class as I did in Acid Rain, then you would want to not have that Matrix Transform tooled into the Spritebatch.Begin.  That way your HUD remains unchanged by camera changes. 

 The final thing you need to do to make this work is to call that Camera TransformMatrix method from within your game loop.  You aren't doing that by putting the Matrix Transform into your SpriteBatch.Begin calls.  You need to have that Camera TransformMatrix called in every loop cycle so it is always looking for changes to the Matrix Transform.  Look at the Draw method in GamePlayScreen.cs and you see this is where I call it.  You only need to do this once.

   public override void Draw(GameTime gameTime)
        {
            //Call TransfromMatrix method in camera class.  This runs the Matrix math in the draw loop.
            //I'm calling the transforMatrix method from the camera class (camera.Transform) 
            //and passing the return value into the Matrix transformMatirx overload in spritebatch.begin in the Game Component classes draw method.  
            //This turns on the camera matrix math for spriteBatch.Draw 
            //that allows scaling, rotation and panning(translation). I use a 3D camera with z set to 0.  
            ScreenManager.Camera.TransformMatrix(gameTime);
        }

Next we attach the camera to the Player.  I do all of this in the Player.cs class in the CameraPlayerAttach() method.

   //Attach camera to player so camera moves with player.  Camera is set at 0,0 in camera class.
            //Player Origin sets player position in the center of the player texture/animation.
            //First we make camera position = to player position.  Then we need to subtract half the screen width and height
            //So the camera position ends up at the top left of the screen.
            //We adjust the screen width and height by the camera scale by dividing.  This keeps the player in the center of the screen.
            //So as the camera scales, we divide screen dimensions by camera scale.
            ScreenManager.Camera.Position.X = Player.PlayerPos.X - ((screenBounds.Width / 2) / ScreenManager.Camera.scale);
            ScreenManager.Camera.Position.Y = Player.PlayerPos.Y - ((screenBounds.Height / 2) / ScreenManager.Camera.scale);

I also Lock the Camera to the Map boundaries in the CameraScreenBoundry() method in Player.cs and of course I lock the Player to the Screen boundaries in the PlayerScreenBounrdy() method.  I do all of this in Player because I have easy access to Player Position.  And it seemed to me like this was about the player not the camera.  But some people like having all of this code in their camera class.  You can do it either way.  Same goes for that SetWorldScale() method.  I think all of those methods are self explanatory.  And my comments in the code should help you figure out what I'm doing.  One thing is I don't lock the Camera when you move the player to the top of the map.  This is so you can see that pretty/ominous blue sky.  On to the TileMap system.  First I should mention my design is based upon the Nick Gravelyn TileMap video series.  It's worth checking out since he creates an editor etc.  All of my Tile Mapping is done in Terrain.cs.  First I declare the variables for the Ground Tiles.  My base map will be 20 X 10 of 512 X 512 tiles.  So ultimately it's 10240 X 5120 in pixel dimensions.

   #region Setup variables for Ground Tiles
        int mapWidth = 20;
        int mapHeight = 10;
        private Rectangle groundSourceBase;
        private Rectangle caveSourceBase;
        public static int TotalMapWidth;
        public static int TotalMapHeight;
        #endregion

Take a look at the Initialize method and you'll see I use TotalMapWidth and TotalMapHeight to store those pixel dimensions.  This is useful when you are placing stationary stuff around the map.

   public override void Initialize()
        {
            groundSourceBase = new Rectangle(0, 0, 512, 512);
            TotalMapWidth = groundSourceBase.Width * mapWidth;
            TotalMapHeight = groundSourceBase.Height * mapHeight;
            
            base.Initialize();
        }

In the interest of "keeping it simple stupid" which helps for me,I just use two nested for loops in the Draw method to draw the map.  I use the grass texture from my mapObjectsSheet and grab it off of that sheet at location groundSourceBase (0, 0, 512, 512).  That's the source rectangle.  I draw it at position 0,0 and then increment it by X for every 10 times it's incremented by Y.  It draws so fast, it just looks like one big map. 

 Now to mix things up, because let's face it, the map only looks isometric due to the tilt of objects you place on the map.  In my case just about everything is tilted somewhere between 20 and 40 degrees.  But the key is you need other stuff on the map.  So I created a Struct of objects that I can randomly place around the map.  Any of them that I draw in Terrain.cs will go behind the player.  In some cases, I want the player to walk behind a tree but in front of it's trunk.  So I draw some of the tree trunks in Terrain and I draw some of them in FrontOfPlayer.  I draw most of the trees in FrontOfPlayer so the player walks underneath the Trees. This is why you see a TreeStruct in FrontOfPlayer.  If you look through the code, you'll see that all I do is randomly place rocks, trees, lumps of grass and patches of snow on top of the map.  In some cases these are drawn before the player and in some cases after the player.  If you play Acid Rain and you are very observant, you will notice that every time you exit the Tent or the Cave, the Trees are in a different place.  I re-randomize them every time you enter the main map so you're cover from the Acid Rain is always different.  The map is always changing to mix things up.  Plus I wanted to reset the trees so they could be burned down again by Volcanic rocks in all of their flaming glory.     

 The final thing I will mention is about spritebatch and content.  I continue to remain baffled as to why spritebatch and content aren't just static by default.  Since when you make a game, you only want to use one copy of spriteBatch and in most cases you only need one copy on ContentManager.  But hey....I'm not an OOP expert.  I'm only 2 years into this whole OOP game.  What I discovered is it can get really really messy trying top pass spritebatch and content too all of your classes and Game Components yet you need them pretty much everywhere you Draw and Load. 

 I found the easiest way to do this was to add them to the Game Services Collection, then in each class where I need them, just call them back out. Take a look at Player.cs.  In the constructor, I retrieve the spriteBatch and content so I can use them in that class/game component.  Don't forget to declare them at the top of the class so you can use them throughout the entire class.

    public class Player : Microsoft.Xna.Framework.DrawableGameComponent
    {
        GameplayScreen gamePlayScreen;
        ScreenManager screenManager;
        private Rectangle screenBounds;
        SpriteBatch spriteBatch;
        ContentManager content;

 

       public Player(Game game, GameplayScreen gamePlayScreen, ScreenManager screenManager, PlayerIndex playerIndex)
            : base(game)
        {
            this.gamePlayScreen = gamePlayScreen;
            this.screenManager = screenManager;
            this.playerIndex = playerIndex;

            //Retrieve from Game Services
            spriteBatch = ((SpriteBatch)game.Services.GetService(typeof(SpriteBatch)));
            content = ((ContentManager)game.Services.GetService(typeof(ContentManager)));

            PlayerPos = GameplayScreen.PlayerStartPos;
            startPlayerPos = PlayerPos;
   
        }

I add them to the Game Service collection in the LoadContent() methods of ScreenManager.cs and GameplayScreen.cs.  I use the content that is created from GameplayScreen.cs and I create my spritebatch and add it to Game Services in ScreenManager.cs.

    if (content == null)
                content = new ContentManager(ScreenManager.Game.Services, "Content");

            //Game Services is a way for Game Components to communicate with each other since each component has a copy of Game
            //Add to content from GamePlayScreen to Game Services
            //I can then access this contentmanager from any gameplay class or Component and 
            //it will be unloaded when I call content.unload in this class.
            ScreenManager.Game.Services.AddService(typeof(ContentManager), content);  

 

  protected override void LoadContent()
        {
            // Load content belonging to the screen manager.
            //by using the contentManager from Game, I believe I will be able to have content created in screenmanager that can be shared
            //throughout the entire game including the menu system, including ParticleSystem.cs.  Unlike Gameplayscreen.cs that creates a new
            //contentmanager just for content loaded in that screen.  So any content used in both the menu system and the gameplay needs to be
            //loaded in ScreenManager.cs.
            content = Game.Content;
            spriteBatch = new SpriteBatch(GraphicsDevice);
           
            #region Game Services
            //Game Services is a way for Game Components to communicate with each other since each component has a copy of Game
            //SpriteBatch - Add to Game Services
            Game.Services.AddService(typeof(SpriteBatch), spriteBatch);
            #endregion    

I hope this helps anyone interested in a simple Tile Mapping system and Camera system.  The most complex part is simply organizing the game in the world of Object Orientation.  

 Download the Entire Project HERE.  

 Allan Chaney