|
Rendering
Description of how the game world is drawn
Phase-Design ScanlinesThe first important concept in rendering is the idea of a scanline. These are a line of map tiles which have the same on-screen Y co-ordinate. For example, consider the following scene:
Considering the same scene, the scanlines can be pulled apart:
Another way of thinking of scanlines is by considering the X and Y co-ordinates of each tile, at which point each scanline is a set of tiles for which the sum X+Y is constant. Order of RenderingObviously, drawing the game world simply involves drawing all the tiles, walls, and entities which make up the world. The difficult part is drawing all these things in the correct order. Rendering is done in a 2D manner, and no depth sorting is performed, so things appear on-screen in the order in which they are rendered - if two things overlap, then the one which was rendered last will be the one appearing on-screen. The first things to be rendered are the floor tiles. The order in which floor tiles are rendered is unspecified, as floor tiles do not overlap. The rendering engine is free to render the floor tiles in whatever order is most convenient or efficient for it. Scanlines are important as they form the first level of ordering. For everything after the floor (i.e. shadows, walls, and entities), the world is drawn one scanline at a time, starting at the screen top, and working downward. Letting "scanline N" refer to the scanline whose tiles satisfy X+Y=N, everything on scanline N is rendered before everything on scanline N+1. Contents of a ScanlineBefore considering how a scanline is rendered, it is useful to recap on exactly what needs to be rendered. A scanline is a set of tiles. If we ignore the floor, as that has already been rendered, then each tile has (or can have):
That last item is important; for the purposes of rendering, each entity is associated with a single tile. For entities which span exactly one tile, the tile they are associated with is trivial, unless the entity can move (i.e. walk) between tiles. For entities which span multiple tiles, properties like render_attach_position and use_animate_from_use_position determine which tile is associated with the entity. As a brief aside, entities which can move are classed as Humanoids, and all occupy a single tile, whilst entities which are static are classed as Objects, and can span multiple tiles. Rendering of a ScanlineUp until now, a scanline has been a set of tiles, and sets have no defined order. This isn't particularly useful, as rendering is all about having an order. There are two natural ways to order a scanline:
Slightly confusingly, both of these orders are used when rendering a scanline. This is because a scanline is rendered in three phases:
Splitting up the rendering of entities might initially seem a bit odd, but there are good reasons for doing so. Consider the following two situations:
In these two situations, we have a humanoid walking between two tiles, with a line of walls on two sides. Let us de-construct the first of these situations:
Here we have one entity (the humanoid walking between tiles), two floor tiles, three scanlines, and four walls. The order of rendering should be:
Now let us de-construct the second of these situations:
Again we have one entity, two floor tiles, three scanlines, and four walls. This time, the order of rendering should be:
To get the correct rendering order for the first situation, we need to draw west walls from left to right, and entities from left to right after the west wall. To get the correct rendering order for the second situation, we need to draw north walls from right to left, and entities from right to left after the north wall. Hence for each tile, we need to split the list of entities into two parts, and render them at different points in the scanline rendering process. The Entity Rendering ListsIn the Contents of a Scanline section, it was stated that each tile had a list of entities, and in the Rendering of a Scanline section, it was concluded that this list is infact two lists. These lists have names:
By default, entities are placed on the normal list. To place an entity on the early list, make sure that the THDF_EarlyList flag (value 1024) is set on the entity's animation when setTile is called. To place an entity on the normal list, make sure that flag is not set when setTile is called. Note that changing the flag has no effect until you set the tile, which is why code like humanoid.th:setTile(humanoid.th:getTile()) is occasionally seen after setting the animation flags. At a higher level, an object is placed on the early list when idle if the early_list property is set to true in the object's orientation, and is placed on the early list when in-use if the early_list_while_in_use property is set to true in the object's orientation. For most entities, correct rendering behaviour can be achieved by carefully selecting the correct tile and list to render the entity from. However in some cases, the correct rendering behaviour cannot be achieved in this manner. In such cases, an alternative solution is available: splitting the rendering of an entity across multiple tiles. In this system, the entity is placed in the normal list for multiple tiles, and when it comes to rendering a tile, only those columns of pixels of the entity which intersect said tile are drawn. For example, in the following image, the rendering of the sofa is split over two tiles. The two tiles are marked with strong red and blue outlines. All of the sofa's pixels falling within the red rectangle get rendered in the normal list for the red tile, and all the sofa's pixels falling within the blue rectangle are rendered in the normal list for the blue tile.
| |