Pectus Excavatum
Pectus Excavatum

Reputation: 3783

how to ensure only sprites visible in the viewport are ever drawn

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.

Upvotes: 3

Views: 1614

Answers (2)

Daniel Johnson
Daniel Johnson

Reputation: 701

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

Upvotes: 0

George Johnston
George Johnston

Reputation: 32258

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.

Upvotes: 0

Related Questions