2

I am trying to create a simple 2d scrolling game as a learning exercise in monogame (XNA). Currently I load tile information from a text file, creating an array (tiles[,]) with a tile object storing information about where the tile is written and what texture should be used when the draw method is called. Currently I draw every tile in the array at once and then simple update the position of the tile to move it across the screen. Obviously drwaing all tiles at once is a bit of a pain, and I think it would be better if I only drew the tiles visible on the screen, so as the tiles move across the screen more tiles are drawn. I am not sure how best to do this? I am a bit lost as to how I can tell what tiles should be drawn. Any help would be greatly appreciated. Here is my draw and update methods in my level class - it simply calls methods in the Tile class to draw/update position.

    /// <summary>
    /// Draws each individual tile in the level.
    /// </summary>
    private void DrawTiles(SpriteBatch spriteBatch)
    {
        // For each tile position
        for (int y = 0; y < Height; ++y)
        {
            for (int x = 0; x < Width; ++x)
            {
               tiles[x, y].Draw(spriteBatch);
            }
        }
    }



    public void Update(GameTime gameTime, int scrollSpeed, ScrollDirection direction)
    {
        // Update the positions of each of the Background sprites
        foreach (Tile tile in tiles)
        {
            tile.Update(gameTime, new Vector2(0, scrollSpeed), scrollDirection);
        }
    }
}

Thanks in advance

I am now trying to only loop through the tiles visible on the screen to draw, using a viewport passed from the graphics device. I basically try to get the number of tiles that can fit on the screen before trying to draw them, by dividing the height of the screen by the height of a tile and the same for width. This works just fine, and it draws all tiles visible on in the viewpoint.

The only problem is it no longer draws any of the tiles which began outside of the viewport, so even though in the update tiles are moved into the viewport they are not drawn as the loop uses their original location. I cant think of a way of fixing this without looping through all tiles though :-(

As a level can be big it would be very inefficient to loop through all tiles to only draw what was in the screen.

Any advice would be greatly appreciated.

here is the updated draw:

    private void DrawTiles(SpriteBatch spriteBatch)
    {
        float tileWidth = 40;
        float tileHeight = 32;


        for (int y = 0; y < (int)Math.Ceiling(mViewport.Height / tileHeight); ++y)
        {

           for (int x = 0; x < (int)Math.Ceiling(mViewport.Width / tileWidth); ++x)
            {
                tiles[x, y].Draw(spriteBatch);
            }
        }
    }
Pectus Excavatum
  • 3,593
  • 16
  • 47
  • 68
  • Simple 2D Camera example that I just created: http://pastebin.com/jrGXWfNh It only demonstrates what the camera does and how it works with a drawn rectangle as reference. It's quite simple to work with, quite advanced to fully understand. If you then know you screen has limits, you just check where the camera is and check if another rectangle is "inside" the screen then just draw or not draw. – Deukalion Jun 03 '13 at 17:05
  • Thanks Deukalion, I think I get it. – Pectus Excavatum Jun 03 '13 at 22:04

2 Answers2

1

You need information about your camera. If you know what your camera is center on and what your screen size is, you could have a draw call like this:

private void DrawTiles(SpriteBatch spriteBatch)
{
    // For each tile position
    for (int y = cameraOffset.Y - screenHeight / 2; y < cameraOffset.Y + (int)Math.Ceiling(screenHeight / 2); ++y)
    {
        for (int x = cameraOffset.X - screenWidth / 2; x < cameraOffset.X + (int)Math.Ceiling(screenWidth / 2); ++x)
        {
           tiles[x, y].Draw(spriteBatch);
        }
    }
}

The reason I'm using Math.Ceiling is to cover if tiles are partially on screen. The Math.Floor on the other side is implied, since it's integer division.

Note that this assumes cameraOffset, screenWidth, and screenHeight are in units of tiles. If they aren't, convert them appropriately.

Mike Precup
  • 4,148
  • 21
  • 41
  • Thanks again Mike, I am bit lost with this concept of a camera. Currently I only position the tile based on the array position then change the position of the tile according to: theSpriteBatch.Draw(mSpriteTexture, Position * sizeVector, Color.White); Do you know of any tutorials which I could look at for getting my head around setting up a camera? – Pectus Excavatum Jun 02 '13 at 16:20
  • The camera is the center of the screen in this example, so just change it to your player position or something so it "follows" it – Cyral Jun 02 '13 at 23:26
  • @Pectus Excavatum If in your spriteBatch set a matrix for the world, you can easily transform the whole "2d screen". If your screen is 400x400 and your camera is at X = 0, Y = 0 it will start to render everything at 0,0 to 400, 400. Unless you scale it or rotate it. Matrix camera = Matrix.CreateScale(Zoom) * Matrix.CreateRotationZ(MathHelper.ToRadians(Degrees)) * Matrix.CreateTranslation(PositionX, PositionY, 0); and then you just adjust the position to whatever you want to show. And you can now set an object's position at 400, 0 (not rendered) but if you move camera a bit it will show. – Deukalion Jun 03 '13 at 16:30
  • There are a whole heap of 2D camera examples around. I can't find the one I used myself but here's a similar one http://adambruenderman.wordpress.com/2011/04/05/create-a-2d-camera-in-xna-gs-4-0/ – craftworkgames Jun 04 '13 at 03:31
  • ok, I get the premise Mike is suggesting, to only loop through tiles on the screen. I think its a good plan. But I cannot figure out why I cant get it to work using a viewport - please see my revised question for the code. ANy ideas would be greatly appreciated as when I try to only loop through tiles on the screen, no tiles are drawn :-( – Pectus Excavatum Jun 04 '13 at 20:18
0

Make constants called WINDOW_WIDTH and WINDOW_HEIGHT that store the window's width and height. Then, draw the tiles like you showed, but only with the ones within the WINDOW_WIDTH and WINDOW_HEIGHT values. You also need X and Y properties in your Tile class

private void DrawTiles(SpriteBatch spriteBatch)
{
    float tileWidth = 40;
    float tileHeight = 32;


    for (int y = 0; y < (int)Math.Ceiling(mViewport.Height / tileHeight); ++y)
    {

       for (int x = 0; x < (int)Math.Ceiling(mViewport.Width / tileWidth); ++x)
        {
            if (tiles[x, y].X <= WINDOW_WIDTH &&
                tiles[x, y].X >= 0 &&
                tiles[x, y].Y <= WINDOW_HEIGHT &&
                tiles[x, y].Y >= 0)
            {
                tiles[x, y].Draw(spriteBatch);
            }
        }
    }
}
Daniel G
  • 245
  • 4
  • 15