Advertisement

iso's

Started by September 01, 1999 08:15 PM
6 comments, last by drago 25 years, 3 months ago
The speed in an iso engine is almost always determined by the time it takes to blit the graphics, not how the tiles are positioned or connected to each other. That is a trivial access time.

-Geoff

As you probably already know, there is no fastest ANYTHING. i.e. no matter how optimized you make something, there is someone out there who can come up with a faster way.

having said that, there are several methods that can make an iso engine (or any engine for that matter) faster. the easiest way is to not render any image that isnt seen, and to only render the parts of a partially seen object that are actually seen. Also, your method of rendering can play a factor in speed.

in addition to these (when and how to blit), there is also a method ignored by most programers, and that topic is when NOT to blit. if something doesnt change, dont rewrite it.

Get off my lawn!

Advertisement
Good point from TAN - I have found the Blit is one of the slowest things in the main draw loop so the less the better.

(Has anyone else figures on how much slower Blt is under windowed mode than full screen. Currently writing an ap that should switch but concentrating on windowed at the moment and only geting around 9.7fps. It is doing io checking etc but still crawls)

As to your first point the tile engine I am trying at the moment is a cross - has a basic array to hold the ground level (or whatever) tiles eg Tile[100][100] and each tile has the capability to belong to a liked list so each square can grow up a different amount without using a full [100][100] array for each required level.
maybe no good but nearly there anyway.
Anyone else tried this?

This is a problem we first thought about
in our game, but when you really get down
to it - memory is cheap now-a-days and
using over a meg or two of memory for map
storage is nothing. You can use the
statis array of map[100][100] and have
a couple of layers. I would recomend
using a structure to hold the information
for the map, then either use that structure
for each layer, or have each layer's info
inside the structure:

typedef struct {
char tile[4]; // 1 tile for 4 layers
char flags[4]; // a flag each for 4 layers
} MAP_INFO;
MAP_INFO map[100][100];

or

typedef struct {
char tile; // 1 tile for 1 layer
char flags; // a flag for this layer
} MAP_INFO;
MAP_INFO map[4][100][100];

As long as you don't go crazy stuffing the
structure with data, you can keep the
map size reasonably small (<3 megs).


As for the fastest, as previously mentioned,
somebody is going to find a better way
somewhere along the line, so I would find
a method you like and stick with it.

Blitting is not a big problem if you are
using DirectX w/hardware acceleration.
Let me say this - do not listen to people
saying use software rendering or your
own rendering - once we switched our engine
to DirectDraw, the frame rate was increased tremendously and I am very pleased.

As TAN mentioned, try not to draw what's
not seen. For instance, tiles covering
others - why bother - they won't be seen.
Make sure to optimize your maps when
creating them.

But, the idea when something has not been
altered, don't re-render it may not work
completely. When drawing Iso, you have
to remember that once you blit something,
you have to cascade down and outwards to
create the distance effect of closer tiles
over-writing further tiles. So a tile at
top of the screen would force a redraw of
pretty much most below it, and it fans
out left and right.

------------------
Jim Adams
Co-Designer 'The Light Befallen'
tcm@pobox.com
http://www.lightbefallen.com

Im not writting a isometric engine but its still tile based somewhat. I sort all the entities, including tiles, in my world into a small grid, 32x32. This grid is useful for querying the world for entities within an area. So when i want to draw a screen, i query the visible area for the set of tiles in that area and draw then. Im finding that the slowest part of my code is the querying and my linked list, as they are being called so often. Good luck!

-ddn

a little trick to help you optimize which tiles to blit, and which tiles not to blit:

(note: doesnt matter what style of tile map you use, this will help)

one, you need a bit flag for each map tile. this can either be in the map structure itself, or a separate group of bitfields. if for example, your maps use WORDs or higher, there is a likelihood that bit 15 isnt being used, and it is perfectly suited for this.

yes, i know it is backwards, or seems so right now.

next, fill in a RECT with the viewable area of the screen, represented in world coordinates.

code:
SetRect(&rcView,WorldX,WorldY,WorldX+VIEWWIDTH,WorldY+VIEWHEIGHT);

now, set all of the visible bits for the map to "true", and then loop through the map:

within the loop take these steps:

first, check if the visible bit is already false. if it is, you need check no further, and can skip to the next using continue;

next, determine the extent RECT of the map tile at x,y, and pad this rect for how tall an object might be within it.

code:
//this calc works for diamond mapsxpos=(x-y)*(TILEWIDTH/2);ypos=(x+y)*(TILEHEIGHT/2);//this calc works for staggered mapsxpos=(x*2+y&1)*(TILEWIDTH/2);ypos=y*(TILEHEIGHT/2);//this calc works for rectangular mapsxpos=x*TILEWIDTH;ypos=y*TILEHEIGHT;SetRect(&rcTile,xpos,ypos,TILEWIDTH,TILEHEIGHT);rcTile.top-=TopPad;

picking the proper value for TopPad is essential. you dont want it too high, because using it makes the system blit more tiles than it has to.

your characters and objects may have wildly varying heights... some not stick out of the tile at all, other may stick out by 100 pixels. however, if most of them dont stick out anymore than 64, use 64. this means you will lose some data that probably should be seen, but choosing the proper value means it wont be much, maybe up to 25% of the tiles below the bottom border.

next, check this rectangle against the view rectangle, using IntersectRect.

code:
bIntersect=IntersectRect(&rcIntersect,&rcTile,&rcView);

now, if bIntersect is nonzero, then the tile is visible, and you can move on using continue.

if its not, then you must clear the visible bit, and do some tests.

check to see if the tile is above the viewable area: i.e. rcTile.bottomif it is, then by deduction, all of its neighbors to the North, East, West, Northwest, and Northeast are also not visible, and neighbors of those neighbors in the same direction. and you can set that recursively.

similarly, if rcTile is off to the left, (rcTile.rightsimilar idea for below and off to the right.

in fact, you dont even have to loop through the tiles, or make tile extent rects.

in fact, you can utilize the mousemap, or whatever you use to translate screen coordinates into world coordinates, to determine just TWO non visible tiles that can be used to determine all other non-visible rectangles.

to do this, determine the tile at screen coordinate (0,0). save this coordinate somewhere(we're going to us it later) then move one tile to the northwest. this tile is non-visible, and all neighbors to the east, northeast, north, northwest, west, southwest and south are non-visible.

do the same thing at screen coordinate (VIEWWIDTH,VIEWHEIGHT+TopPad), move to the southeast, do a similar thing for all directions but northwest.

now you should have two coordinates saved,

we're now going to use them to clip the tile map, so that when we loop through for drawing later, we dont loop through everything.

if you are using a staggered map, or a rectangular map, you need do very little, the point from (0,0) gives the left and top of the rectangle, and the point from (VIEWWIDTH,VIEWHEIGHT+TopPad) gives you the bottom and right. only tiles in the range of this rect will be viewable, so dont bother looping through all of the tiles.

there's an extra step here for diagonal maps, because in these types of maps, a RECT actually represents a rhombus.

so, set the RECT initially to the lowest x value of the two tile coordinates, the lowest y to top, etc.

now, starting at the upperleft tile coordinate, move to the right. if the tile at this new position is visible, adjust the RECT, i.e. if the x value is less than left, make left the new x, and so on.

now, go to the upperleft tile coordinate again, and go down, doing the same thing.

next, go the the lower right coordinate, go to the right then to the top from it, adjusting the rect all the way.

when you are done, you'll have a rect that you can use for the extent of the map. you can loop through this, and test for visibility prior to blitting.

this is, of course, not exhaustive of the methods you can use to optimize your map blitting operations, but it is a step in the right direction.

and dont worry about calls to functions that deal with RECTs... they are pretty darn optimized....

code:
BOOL IntersectRect(LPRECT lprcDst,const RECT * lprcSrc1,const RECT * lprcSrc2){lprcDst->left=(lprcSrc1->left<lprcSrc2->left ? lprcSrc2->left : lprcSrc1->left);lprcDst->top=(lprcSrc1->top<lprcSrc2->top ? lprcSrc2->top : lprcSrc1->top);lprcDst->right=(lprcSrc1->right>lprcSrc2->right ? lprcSrc2->right : lprcSrc1->right);lprcDst->bottom=(lprcSrc1->bottom>lprcSrc2->bottom ? lprcSrc2->bottom : lprcSrc1->bottom);return((lprcDst->right>lprcDst->left)&&(lprcDst->bottom>lprcDst->top))}

[This message has been edited by TANSTAAFL (edited September 01, 1999).]

Get off my lawn!

Advertisement
How does one make the fastest iso engine? Would one use linked lists? I would like to hear your oppinions...

------------------
Drago

osu!
Your choice of array or linked-list storage for your world should not matter at all if you are designing a proper OOP. For instance, in my engine I have a World class that stores an array of Cell objects. The implementation - either array or linked-list - is hidden from the rest of my code inside of the Cell class. If a developer wants to use three global layers or 15 local layers, the code will not change in any way. When drawing the world, my WorldDrawer class gets a visible Cell from the world and then renders the cell based on z-order. To clarify things:

// A container for DrawableObjects.
class Cell
{
// implement in terms of arrays or linked-lists.

// overload the operator [] would be better.
const DrawbaleObject& getObject( const byte requestedNum ) const {}

private:
byte numObjects;
};

// in my case, the world grid is made up of an array of Cell pointers.
World
{
public:
const Cell& getCell( const Position pos ) const {}

private:
Cell** ppArray;
word width, height;
dword size;
};

// This class takes a IDisplay interface, WorldCamera, A rectangle to display at, and a world.
WorldDrawer::displayWorld( const IDisplay* const pDisplay, const Rect& displayWindow, const Camera& camera, const World& world )
{
// loop.
// get each visible cell based on camera position.
// loop through cells contents until all layers are displayed.
}

Having each cell store a DrawableObject type means that the engine render is able to display all objects which are visible without having to have seperate non-OO routines for each. All objects are derived from a base Object class that gives each engine object the ability to be controlled by scripts along with unique id's etc. All tiles, units, and AtmosphericDistortions are derived from DrawableObject ( a protocol class ) which is itself derived from the base Object class. Anything can be added to the engine without much fuss.

I hope this helps people who are seeing the "arrays vs. linked-lists" debate to find a more object-oriented approach to engine design.

Any comments?

P.S The examples above are obviously not perfect, but are mean't to offer a general idea to an engine design.

This topic is closed to new replies.

Advertisement