# 绘制基于瓦片的地图[英] Drawing a tile based map

### 问题描述

```public void render(Canvas canvas) {
canvas.drawColor(Color.TRANSPARENT);

Drawable myImage;

int tileWidth = 50;
int tileHeight = 50;

int rowBaseX = 0;
int rowBaseY = 0;

int[][] board = new int[][] {
{0,0,0,0,0,0,2,0,0,0,},
{0,0,0,0,0,2,2,0,0,0,},
{0,0,0,0,0,2,0,0,0,0,},
{0,0,0,0,0,2,0,0,0,0,},
{0,0,0,2,2,2,0,0,0,0,},
{0,0,0,2,0,0,0,0,0,0,},
{0,0,0,2,0,0,0,0,0,0,},
{0,0,2,2,0,0,0,0,0,0,},
{0,0,2,0,0,0,0,0,0,0,},
{0,0,2,0,0,0,0,0,0,0,}
};
int mapWidth = 10;
int mapHeight = 10;

for (int row = 0; row < mapHeight; row++)
{

for (int col = 0; col < mapWidth; col++)
{
Resources res = this.getContext().getResources();

switch(board[row][col])
{
case 0:
myImage = res.getDrawable(R.drawable.tile1);
break;
case 1:
myImage = res.getDrawable(R.drawable.tile2);
break;
default:
myImage = res.getDrawable(R.drawable.tile3);
break;
}

int curL = rowBaseX + (col * tileWidth);
int curU = rowBaseY + (row * tileHeight);
int curR = curL + tileWidth;
int curD = curU + tileHeight;

myImage.setBounds(curL,curU,curR,curD);
myImage.draw(canvas);
}
}

droid.draw(canvas);
butt.draw(canvas);
butt1.draw(canvas);
butt2.draw(canvas);
butt3.draw(canvas);
buttz.draw(canvas);
buttz1.draw(canvas);
buttz2.draw(canvas);
buttz3.draw(canvas);
buttx.draw(canvas);
}
```

## 推荐答案

```heroGridX = hero.x % mapWidth;
heroGridY = hero.y % mapHeight;
```

```leftGrid = heroGridX - (canvas.getWidth() / tileWidth) / 2;
topGrid = heroGridY - (canvas.getHeight() / tileHeight) / 2;
rightGrid = heroGridX + (canvas.getWidth() / tileWidth) / 2;
bottomGrid = heroGridY + (canvas.getHeight() / tileHeight) / 2;
```

## 其他推荐答案

```Bitmap mapBitmap = Bitmap.createBitmap(width, height, myConfig);
Canvas c = new Canvas(mapBitmap);
//draw map on c
```

...和每帧一次的部分:

```//draw dynamic stuff
Canvas canvas = getHolder().lockCanvas();
canvas.drawBitmap(mapBitmap, null, targetRect, null);
canvas.drawBitmap(heroBitmap, null, targetRect, null);
getHolder().unlockCanvasAndPost();
```

### 问题描述

This script draws the controls, hero, surface and the map:

```public void render(Canvas canvas) {
canvas.drawColor(Color.TRANSPARENT);

Drawable myImage;

int tileWidth = 50;
int tileHeight = 50;

int rowBaseX = 0;
int rowBaseY = 0;

int[][] board = new int[][] {
{0,0,0,0,0,0,2,0,0,0,},
{0,0,0,0,0,2,2,0,0,0,},
{0,0,0,0,0,2,0,0,0,0,},
{0,0,0,0,0,2,0,0,0,0,},
{0,0,0,2,2,2,0,0,0,0,},
{0,0,0,2,0,0,0,0,0,0,},
{0,0,0,2,0,0,0,0,0,0,},
{0,0,2,2,0,0,0,0,0,0,},
{0,0,2,0,0,0,0,0,0,0,},
{0,0,2,0,0,0,0,0,0,0,}
};
int mapWidth = 10;
int mapHeight = 10;

for (int row = 0; row < mapHeight; row++)
{

for (int col = 0; col < mapWidth; col++)
{
Resources res = this.getContext().getResources();

switch(board[row][col])
{
case 0:
myImage = res.getDrawable(R.drawable.tile1);
break;
case 1:
myImage = res.getDrawable(R.drawable.tile2);
break;
default:
myImage = res.getDrawable(R.drawable.tile3);
break;
}

int curL = rowBaseX + (col * tileWidth);
int curU = rowBaseY + (row * tileHeight);
int curR = curL + tileWidth;
int curD = curU + tileHeight;

myImage.setBounds(curL,curU,curR,curD);
myImage.draw(canvas);
}
}

droid.draw(canvas);
butt.draw(canvas);
butt1.draw(canvas);
butt2.draw(canvas);
butt3.draw(canvas);
buttz.draw(canvas);
buttz1.draw(canvas);
buttz2.draw(canvas);
buttz3.draw(canvas);
buttx.draw(canvas);
}
```

There is a hero, which has to be redrawn when player moves him with the controls, and all other drawables also has to be redrawn. The problem is that drawing a map is a long process, so the bigger map i create, the slower hero moves, because every tile of the map has to be painted. Is there a way to put all the tiles to a one bitmap in other method and draw that one bitmap in the canvas method?

## 推荐答案

The best option is to only draw the portion of your map that is visible on the screen. That way no matter how big the overall map becomes the drawing of that map is always constant. Since you're on a grid system you can easily figure out which cell the hero is in:

```heroGridX = hero.x % mapWidth;
heroGridY = hero.y % mapHeight;
```

From there you can calculate how many cells around the player you want to draw by using the width and height of the canvas and the constant size of your grid cell's width and height:

```leftGrid = heroGridX - (canvas.getWidth() / tileWidth) / 2;
topGrid = heroGridY - (canvas.getHeight() / tileHeight) / 2;
rightGrid = heroGridX + (canvas.getWidth() / tileWidth) / 2;
bottomGrid = heroGridY + (canvas.getHeight() / tileHeight) / 2;
```

You could use a data structure to store these values independent of the hero and only move them once the player gets close to an edge to scroll the map. That way the hero moves up and down without scrolling the map until they get X or Y pixels to the edge of a tile. Instead of calculating these inside the rendering routine.

This will use far less memory than drawing the entire map into one large bitmap. Drawing into a large bitmap is trading more memory usage for less CPU time. As your map grows larger so does the memory needed to draw that map. This algorithm merely keeps drawing the map a constant because the size of your screen doesn't change at runtime. And, in comparison to the other option your memory usage doesn't grow any larger as your map grows larger (it grows very small in comparison to drawing more tiles in a canvas). One important fact about this is that if you did get a larger screen (say a tablet vs a phone). This algorithm will scale up properly too so the player will see more of the surrounding terrain of the map.

## 其他推荐答案

If creating a static map outside your application is not an option, you could separate the drawing of static content from the dynamic content.

Create a Bitmap, create a Canvas with it and draw your map on that Canvas. You only need to do this once. Render the dynamic content once per frame on another Canvas. Then draw both Bitmaps on the "real" Canvas of your SurfaceView. It could look like this :

the once-per-map part :

```Bitmap mapBitmap = Bitmap.createBitmap(width, height, myConfig);
Canvas c = new Canvas(mapBitmap);
//draw map on c
```

...and the once-per-frame part:

```//draw dynamic stuff
Canvas canvas = getHolder().lockCanvas();
canvas.drawBitmap(mapBitmap, null, targetRect, null);
canvas.drawBitmap(heroBitmap, null, targetRect, null);
getHolder().unlockCanvasAndPost();
```

EDIT:

You draw your tiles like you used to with myImage.draw(canvas), but passing the mapBitmap-Canvas as argument instead of your "real" canvas. myConfig has to be a Bitmap.Config. Take the RGB_565 as it is the internal format.