3

I am attempting to create a 2d scrolling XNA game as a learning exercise, but have run into some issues with the scrolling background. I am loading a level from a text file, parsing through to create the appropriate tiles and store them in a matrix (tiles[,]).

I then have an update method which alters the position of the tile so when it is redrawn it will move.

Currently, I loop through all tiles to draw them all before moving. This is clearly not very efficient. Ideally, I only want to draw the tiles on the screen. I can do this by taking the viewport and using the height/width of a tile to determine how many tiles will fit on the screen and only loop through those tiles, as follows:

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

However, this only draws the iles in the original viewport. Tiles outside will never be drawn even though their position does come into view.

I think this can be resolved by using a counter to start and end the loop, incrementing it each time the draw method is called. However, I do not think this is a great solution, but alas I cannot think of a better way to ensure only tiles in the viewport are drawn.

John Saunders
  • 160,644
  • 26
  • 247
  • 397
Pectus Excavatum
  • 3,593
  • 16
  • 47
  • 68
  • As a side note, it's a good idea to take the calculations you've got in your loops and put them in variables. That way you're not doing extra calculations every iteration of the loop. – craftworkgames Jun 06 '13 at 00:09

2 Answers2

0

You need to keep track of the starting X and Y of the ViewPort, as you're always starting at 0 in your example. e.g.

var startX = 10;  // Should increment as viewport scrolls
var startY = 10;  // Should increment as viewport scrolls

...

for (int y = startY; y < (int)Math.Ceiling(mViewport.Height / tileHeight); ++y)
{
   for (int x = startX; x < (int)Math.Ceiling(mViewport.Width / tileWidth); ++x)
   {
      tiles[x, y].Draw(spriteBatch);
   }
}

On a side note, your ViewPort probably has a Top and Left or X and Y to keep track of this as well. In that case, replace startX and startY with the appropriate property from your ViewPort.

George Johnston
  • 31,652
  • 27
  • 127
  • 172
  • Thanks, George. This solution was what I meant when I spoke of the use of a counter which is incremented every time draw is called. Also, in my solution, the viewport is not moved, it remains where it is, instead the tiles move into the viewport. So I would need to add the counter to the last position in the loop also. I was curious, however, if there is a better approach? – Pectus Excavatum Jun 05 '13 at 15:19
  • 1
    @PectusExcavatum I never move my viewport either, mainly because none of the tutorials I followed ever told me too. Instead I have a 'Camera' class that tracks the position of the screen. This way you can feed the camera's position into the draw function and then easily decide the range of tiles you need to draw using the method provided, by using the range between (Camera.X, Camera.Y) and (Camera.X + ScreenWidth, Camera.Y + ScreenHeight). – MatthewMcGovern Jun 05 '13 at 15:32
0

What I implemented in my design was an inherited interface that contained the properties IsInView and a collision property or in your case a position could be substituted. I then created a seperate thread that would loop through and determine if the object is in view. you can then in each object have the InView and OutView add and remove it from a draw list.

Run from seperate thread with a loop. -- This could be adapted to determine if the tile is visible

    public void CalculateObjsInView()
    {
        foreach (Obj o in new List<Obj>(ObjInstanceList))
        {
            if (o == null)
                continue;
            if (Camera.CollisionMask.Intersects(o.CollisionMask))
                o.IsInView = true;
            else
                o.IsInView = false;
        }
    }

In Obj class

    private bool _isInView = false;
    public bool IsInView
    {
        get { return _isInView; }
        set
        {
            if (_isInView != value)
            {
                if (value)
                    InView();
                else
                    OutView();
            }
            _isInView = value;
        }
    }

    private void InView()
    {
        Game.ObjectDrawEventHandler.Add(Draw);
    }
    private void OutView()
    {
        Game.ObjectDrawEventHandler.Remove(Draw);
    }
Daniel Johnson
  • 691
  • 4
  • 14