(Edit, got it webstarted now, see later post)
An idea I had for speeding up the tiling part of isometric games (Note this does not involve object sprites, only terrain tiles). I guess somebody else has (as always) done this before but here goes:
Problem 1: The typical diamond shaped tile consists of 50% transparent because you have to draw the bounding rectangle- this translates into drawing twice as many pixels than necessary when tiling.
Problem 2: Large tilesets take up a lot of vram if all of the tiles are managed images.
Solution: prerender into a number of large rectangular images that can be reused every time the area they cover should be drawn.
So here is the basic algorithm, I use 2 collections of buffers: pool and active
pool is just a Vector, while active is a Hashtable, where the images are stored with their topleft corner as key (I’m just using new Point(x, y))
for each image in active
if (distance(image, screen ) >maxDistance)
{
move image from active to pool
}
for each rectangular area that should be drawn on screen
if an image for that area already exists in active use that image
else fetch an image from pool (or create a new image if pool is empty) and render to it
draw the required image to screen.
I call this “walking buffers” because the old buffers become new buffers as you move across the map.
This picture illustrates:
Gray lines mark the boundaries of the buffers as they’re drawn onto the screen.
The yellow rectangle is a “mini” map, blue rectangle is the screen, red rectangles are tiles in the pool, green are active tiles.
http://www.javaengines.dk/images/walkingtiles.png
Now the actual images to be drawn on screen each frame can be opaque and no drawing is wasted.
Also - the original images need not be managed, since they’re only used for drawing onto the buffers - not onto the screen.
One problem is of course that those new images take up both VRAM and main memory - but with large tilesets the extra VRAM needed could be less that what is saved by not using managed original tiles.
The actual drawing code, notice that I wrap the images in BigTile objects to supply information on the topleft corner coordinates - the object also handles rendering onto the image. The Point origo is the screens position relative to the map coordinate set (which is not the world coordinate set, I work with 3 coordinate sets in my engine - I can expand on this if it’s confusing)
//Set up limits for the rectangles to draw
int xStart = (int) Math.floor(origo.x / (double) bigTileWidth) * bigTileWidth;
int xEnd = (int) Math.floor((origo.x + getSize().width) / (double) bigTileWidth) * bigTileWidth;
int yStart = (int) Math.floor(origo.y / (double) bigTileHeight) * bigTileWidth;
int yEnd = (int) Math.floor((origo.y + getSize().height) / (double) bigTileHeight) * bigTileWidth;
//Move old buffers to pool
Vector<BigTile> tiles = new Vector<BigTile>(activeTiles.values());
for (int i = 0; i < tiles.size(); i++)
{
BigTile tile = tiles.get(i);
if (
(origo.x - (tile.x + bigTileWidth) > maxDistance) ||
(tile.x - (origo.x + getSize().width) > maxDistance) ||
(origo.y - (tile.y + bigTileHeight) > maxDistance) ||
(tile.y - (origo.y + getSize().height) > maxDistance))
{
activeTiles.remove(new Point(tile.x, tile.y));
tilePool.add(tile);
}
}
//do the drawing
g.translate(-origo.x, -origo.y);
for (int x = xStart; x <= xEnd; x += bigTileWidth)
for (int y = yStart; y <= yEnd; y += bigTileHeight)
{
BigTile tile = activeTiles.get(new Point(x, y));
if (tile == null)
{
if (tilePool.isEmpty())
{
tile = new BigTile(x, y, bigTileWidth, bigTileHeight);
activeTiles.put(new Point(x, y), tile);
} else
{
tile = tilePool.get(tilePool.size() - 1);
tilePool.removeElementAt(tilePool.size() - 1);
tile.x = x;
tile.y = y;
activeTiles.put(new Point(x, y), tile);
}
tile.paint();//Render the offscreen buffer
}
g.drawImage(tile.img, x, y, this);
}
g.translate(origo.x, origo.y);
I use 256 X 256 images, didn’t notice any difference between 512 and 256 but 256 wastes less memory.