Drawing Moving Tilemap

Here some code I’ve modified, given by player_03, for rendering the background.

var startColumn:Int = Math.floor(playerPosition.x / TILE_WIDTH);
var startRow:Int = Math.floor(playerPosition.y / TILE_WIDTH);
		
for(column in startColumn...(startColumn + 9 + 1)) {
          for(row in startRow...(startRow + 20 + 1)) {
              var bgTile:BGTile = new BGTile();
              bgTile.x=(column-startColumn)*90-roughColumn;
              bgTile.y=(row-startRow)*30-roughRow;
              bgTile.id=mapArray[column][row];
              tilemap.addTile(bgTile);
          }
        }

Now I am trying to efficiently draw the tiles while moving with the player. Here is the pseudo-code I have:


If PlayerPosition is different than last iteration then, Calculate startColumn and startRow like above, if either do not equal the last run iteration clear the tilemap array.

If Tilemap empty:

  • Calculate XOffset by: (Math.floor(playerPosition.x / TILE_WIDTH)-(playerPosition.x / TILE_WIDTH))*90.
  • Add each tile to the tilemap at row-startRow-XOffset.
  • Repeat for Y.

If Tilemap Not-Empty:

  • Get XOffset: (Math.floor(playerPosition.x / TILE_WIDTH)-(playerPosition.x / TILE_WIDTH))*90.
  • Get difference between new offset and current offset, Offset - (Tile(0).x/90 - floor(Tile(0).x/90)).
  • For Every Tile in Tiles, add difference between new offset and current offset.
  • Repeat for Y.

Does this make any sense, are there better more efficient ways of rendering?

If you’ve drawn all Tiles anyway, there’s no need to update offsets. You may as well move your DisplayObject.
If you have a viewport, that’s another matter. Then you’ll need to add and remove Tiles when necessary.

Heres my terrible code, it does work surprisingly. Unfortunately I dont know anything about DisplayObject, seems like I have some reading to do on how to actually use TileMap. :joy:

public function render(){
      if (startColumn!=Math.floor(playerPosition.x/90) || startRow!=Math.floor(playerPosition.y/30)){
        tilemap.removeTiles(0,tilemap.numTiles);
        clearArray(bgTiles);
      }
      startColumn = Math.floor(playerPosition.x/90);
      startRow = Math.floor(playerPosition.y/30);



        var roughColumn:Float = (playerPosition.x/90);
        var roughRow:Float = (playerPosition.y/30);
        roughColumn=(roughColumn-startColumn)*90;
        roughRow=(roughRow-startRow)*30;

      if (tilemap.numTiles==0){
        tf.text = "redrawn";
        for(column in startColumn...(startColumn + 9 + 1)) {
          for(row in startRow...(startRow + 20 + 1)) {
              var bgTile:BGTile = new BGTile();
              bgTile.x=(column-startColumn)*90-roughColumn;
              bgTile.y=(row-startRow)*30-roughRow;
              bgTile.id=mapArray[column][row]-1;
              bgTiles.push(bgTile);
              tilemap.addTile(bgTile);
          }
        }
      } else{
          tf.text = "Moved";
          diffNewLastOffsetRow = lastRoughRow-roughRow;
          diffNewLastOffsetColumn = lastRoughColumn-roughColumn;
          for (tile in bgTiles){
            tile.x = tile.x + diffNewLastOffsetColumn;
            tile.y = tile.y + diffNewLastOffsetRow;
            }
          }

      lastRenderedPosition = playerPosition;
      lastRoughColumn = roughColumn;
      lastRoughRow = roughRow;

}


public static function clearArray(arr:Array<Dynamic>){
    #if (cpp||php)
       arr.splice(0,arr.length);
    #else
       untyped arr.length = 0;
    #end
}

class BGTile extends Tile {
	public function new () {
		super ();
	}
}

No.

Create your tiles once, and re-use them. That was the whole point of my post.

//If you find yourself typing out the same number more
//than one time, it's better to make a constant for it.
public static inline var TILE_WIDTH:Int = 90;
public static inline var TILE_HEIGHT:Int = 30;
public static inline var TILES_ACROSS:Int = 9;
public static inline var TILES_DOWN:Int = 20;

public function render() {
    //Create the tiles on the very first frame, and never
    //call "new BGTile()" again.
    if(bgTiles.length == 0) {
        tf.text = "First frame";
        
        for(column in 0...(TILES_ACROSS + 1)) {
            for(row in 0...(TILES_DOWN + 1)) {
                var bgTile:BGTile = new BGTile();
                bgTiles.push(bgTile);
                tilemap.addTile(bgTile);
            }
        }
    } else {
        tf.text = "Not the first frame";
        
        if(playerPosition.equals(lastRenderedPosition)) {
            return;
        }
    }
    
    //Same formula, but consolidated. You can skip startRow and
    //startColumn because they canceled out anyway.
    var roughColumn:Float = playerPosition.x - Math.floor(playerPosition.x / TILE_WIDTH) * TILE_WIDTH;
    var roughRow:Float = playerPosition.y - Math.floor(playerPosition.y / TILE_HEIGHT) * TILE_HEIGHT;
    
    //Place the tiles and set their id.
    var i:Int = 0;
    for(column in 0...(TILES_ACROSS + 1)) {
        for(row in 0...(TILES_DOWN + 1)) {
            var bgTile:BGTile = bgTiles[i];
            i++;
            
            bgTile.x = column * TILES_ACROSS - roughColumn;
            bgTile.y = row * TILES_DOWN - roughRow;
            bgTile.id = mapArray[column][row] - 1;
        }
    }
    
    //Did you mean "lastRenderedPosition = playerPosition.clone()"?
    lastRenderedPosition = playerPosition;
}


//Delete clearArray() unless you used it somewhere else.

class BGTile extends Tile {
    public function new() {
        super();
    }
}
1 Like

Thanks again for the help, heres my final tile render; other than eventually checking whether the player position has moved. Seems like it gets about 650fps when scrolling around, compared to if I just re-ID the tiles at 450fps. Dont know if I’m just being naive thinking its improving things by doing this.:

public function render(){
    //Initialize tiles
    if (tilemap.numTiles==0){
      startColumn = Math.floor(playerPosition.x/90);
      startRow = Math.floor(playerPosition.y/30);
      var roughColumn:Float = (playerPosition.x/90);
      var roughRow:Float = (playerPosition.y/30);
      roughColumn=(roughColumn-startColumn)*90;
      roughRow=(roughRow-startRow)*30;
      for(column in startColumn...(startColumn + TILES_ACROSS + 1)) {
        for(row in startRow...(startRow + TILES_DOWN + 1)) {
            var bgTile:BGTile = new BGTile();
            bgTile.x=(column-startColumn)*90-roughColumn;
            bgTile.y=(row-startRow)*30-roughRow;
            bgTile.id=mapArray[column][row]-1;
            bgTiles.push(bgTile);
            tilemap.addTile(bgTile);
        }
      }
      lastRoughColumn = roughColumn;
      lastRoughRow = roughRow;
    }

    //If playerPositions start column has changed we need to re-get ID from MAP
    else if (startColumn!=Math.floor(playerPosition.x/90) || startRow!=Math.floor(playerPosition.y/30)){
      startColumn = Math.floor(playerPosition.x/90);
      startRow = Math.floor(playerPosition.y/30);
      var roughColumn:Float = (playerPosition.x/90);
      var roughRow:Float = (playerPosition.y/30);
      roughColumn=(roughColumn-startColumn)*90;
      roughRow=(roughRow-startRow)*30;

      var roughColumn:Float = playerPosition.x - Math.floor(playerPosition.x / TILE_WIDTH) * TILE_WIDTH;
      var roughRow:Float = playerPosition.y - Math.floor(playerPosition.y / TILE_HEIGHT) * TILE_HEIGHT;

      var i:Int = 0;
      for(column in startColumn...(startColumn + TILES_ACROSS + 1)) {
        for(row in startRow...(startRow + TILES_DOWN + 1)) {
            bgTiles[i].x=(column-startColumn)*90-roughColumn;
            bgTiles[i].y=(row-startRow)*30-roughRow;
            bgTiles[i].id = (column>0 && row>0 && row<40 && column<40) ? mapArray[column][row]-1 : 88;
            i++;
        }
      }
      lastRoughColumn = roughColumn;
      lastRoughRow = roughRow;
    }

      //If tilemap is blank, we need to create Tiles and store them inside bgTiles
       else{
         var roughColumn:Float = (playerPosition.x/90);
         var roughRow:Float = (playerPosition.y/30);
         roughColumn=(roughColumn-startColumn)*90;
         roughRow=(roughRow-startRow)*30;
          //If inside same cell, we translate to new rough offset.
          if (lastRoughRow!=roughRow || lastRoughColumn!=roughColumn){
            diffNewLastOffsetRow = lastRoughRow-roughRow;
            diffNewLastOffsetColumn = lastRoughColumn-roughColumn;
            for (tile in bgTiles){
              tile.x = tile.x + diffNewLastOffsetColumn;
              tile.y = tile.y + diffNewLastOffsetRow;
              }
          }
          lastRoughColumn = roughColumn;
          lastRoughRow = roughRow;
      }
}

That can’t be a good idea. If either startColumn or startRow is nonzero, you’ll get an out-of-bounds error. Since you know they’re both zero, you may as well delete them entirely.

for(column in 0...(TILES_ACROSS + 1)) {
    for(row in 0...(TILES_DOWN + 1)) {
        //...
        bgTile.id=mapArray[column][row]-1;

I doubt it makes much difference.

Unless you can detect a significant difference in speed, I’d strongly suggest going back to the shorter version. The less code you have, the easier it is to maintain.