An Experiment in Real-Time Networking.



by

Stuart Cheshire

Sidney Sussex College


Previous | Table of Contents | Next

Chapter 4 -- Graphics

Discussion
The Background
The Foreground
Making Pictures
The `setob' Routine
The `refresh' Routine

Discussion

The purpose of the graphics routines is to provide a display function for the model of the World being simulated by the program. They could in principle be used by any program which requires this kind of display -- a background over which small foreground objects move.

It is important to notice that the program only uses the routines in a one-way fashion. This means that it only ever makes calls to instruct the graphics routines to display something on the screen, never to enquire about what is currently displayed. This is because the behaviour of the simulated World is in no way altered by whether or not the player is watching it on the screen. It cannot even be assumed that the player's tank[13] will be on the screen, because, although it normally has to be, it may not be, if the player has selected a remote view from one of his pillboxes.

The Background

The map data is relatively simple, with only 10 different kinds of terrain. It would be easy just to draw a blue square for water, a black square for road, and green square for grass etc. but this would produce a very monotonous screen display. Even if patterned squares were used instead, corners of lakes, for example, would look very square and unnatural. It would present a faithful representation of the World, but would not be a pleasing picture to look at. The goal was therefore to present on the screen something a little more natural looking than a just raw display of the map data structure.

The solution was to define over 200 map characters, including corners in roads, T-junctions, bends in rivers, large compound buildings etc. A complex set of adjacency rules were designed, to decide which character to draw for each map square, according to what terrain surrounds it. For instance, a piece of road with water on both sides of it is drawn to look like a bridge. The rules are very complicated, and very arbitrary, so they are not worth describing here, but when looking at the screen photographs remember the meagre data from which the picture is derived.

The screen display,

including the status indicators down the right-hand side of the screen.

The Garden

The Lake

The Marsh

A Building Complex

The Forest

The Harbour

Rivers

The Maze

A sea-based fortress.

The Foreground

A prominent point of difference between the project and conventional computer games is the use of an object list.

Most games have a single main loop which goes through all the objects on the screen -- the player, bullets, the monsters etc. -- and for each one it rubs them out, moves them by altering the coordinates in some appropriate way, and then redraws them. This approach was not applicable to the project for two reasons.

(i)
There is a background on the screen -- the map. Erasing the character is not as simple as just overwriting it with black pixels -- that would leave a black hole in the background. Instead, the background must be redrawn. Since the background is drawn as squares, this could erase parts of other foreground characters which overlap the same square, so they would need to be redrawn too. This would be extremely inefficient since each character which is erased, moved, and redrawn could cause several others to be redrawn too, often unnecessarily, if they were about to be moved and redrawn themselves.

It would seem sensible to consolidate these changes, so that first, all the erasing (redrawing background squares) is done, then all the characters are moved, and then all the redrawing is done.

(ii)
The program is not based around a main loop. Events happen in an undefined order. The player's tank is moved regularly, every 1/50 of a second, regardless of what the foreground routine is doing at the time. Packets, which cause other players tanks and shells to be drawn on the screen, are under the control of some other machine, and can be received at literally any time. In both of these cases, the graphics routine is being called from an interrupt routine, so the response must be fast. It would be too slow if it actually drew the pixels in the bitmap, especially if, as explained above, it could be forced to redraw other characters too -- which would result in it taking an unpredictably long length of time before it returned.
The solution to these problems was, as indicated, to use an object list. Various routines call setob to specify a character to be drawn on the screen -- either a new character appearing on the screen or an old one being moved or altered in some way. The foreground routine refresh is called every time round the main loop. This looks at the data in the object table, sees what has changed, and makes the appropriate changes on the screen. When the screen is crowded and the program is busy, the screen may be updated as little as four times per second, but the setob routine will continue to be called by interrupt routines, and will continue to provide the fast response required.

In the case where setob is called to alter an existing object, the question arises of how the routine knows which object on the screen to alter. For example, when a call is made to move a tank, there may be many tanks on the screen, so, while it is clear where the new image should be drawn, it is not clear which old image should be erased.

For this reason, all objects must be identified. A simple naming convention is used to assign a one byte identifier to each character on the screen, and this identifier must be used whenever calling setob to draw a new object on the screen, move an existing object, or erase an old object. The identifier is generated in a simple way. The bottom four bits are the player number of the player that the object belongs to. The top four bits identify which actual object in the game the character represents -- whether a tank, or a shell, or a running man or any other kind of object.

All shells belonging to a particular player are given the same identifier. This is because they move so quickly that it is not worth identifying them individually. It is only important to identify a character on the screen if it is likely not to have moved between screen refreshes. If it hasn't moved, then we don't want to waste time (or make it flicker) by erasing and redrawing it unnecessarily. Since shells will always be moving, it will never be the case that we want to leave one as it is and not redraw it in a different position.

When a new packet arrives from a player, all his shells are erased[14] together, and then they are all redrawn at their new positions using the data in the packet. As long as the identifier correctly indicates which objects in the table are shells belonging to this player, so that they can all be erased, then it is not important which particular shell is which.

Making Pictures

As indicated above, all screen update is consolidated into a single refresh operation, but it is not quite as simple as suggested.

If all the necessary background squares were redrawn (to erase altered objects), and then all the new objects were drawn, and this whole process could take up to 1/4 of a second, then the time between erasing a foreground object and redrawing it again would be noticeable. Objects on the screen would appear to flash on and off when the update was slow.

The time between calls to refresh is negligible -- it is by far the most significant of the foreground routines. If characters are erased-drawn-erased-drawn-erased-drawn, etc, and the time for each process is roughly equal, then characters are invisible for roughly half the time. Even if the screen is redrawn 25 times a second, characters will only appear on alternate frames and will flicker and have a `ghostly' appearance. This is not acceptable. Characters must appear solid and real if the `feel' of the game is to be right. It is important that the player gets `drawn into' the alternative reality presented by the game. Anything -- such as seeing that characters are actually being drawn and rubbed out -- which reminds the player that this is a computer program, will weaken this illusion.

The ideal solution is to use a double-buffered framestore (ie prepare the picture off-screen and then swap it in during the vertical retrace[15]), but there is not enough memory in the BBC micro, so this is out of the question.

The aim is to keep the ratio of `time on screen' to `time not on screen' as high as possible[16]. To this end, we want to redraw a character as soon as possible after it has been erased, and not after we have finished all the erasing. This is what the mechanism described below is designed to do:

The `setob' Routine

Four bytes are passed to setob: the character number to be plotted, (x,y) in P-coordinates, and an object identifier.

The character id is important, because there may be two or more identical tank characters on the screen which belong (obviously) to different players. When, next time, a new location and/or character (if the tank has rotated) is passed to setob, it must know which old tank to erase and replace with the new one.

It does this by searching the object list to see if there is already an object with the given id. If there is, and it has not changed, the routine returns without doing anything. If it has changed, it flags for redrawing all the background squares which fell underneath it[18], and then writes the new character data into the table and flags it for drawing too on the next refresh.

If the object was not there before, then it has nothing to erase, but just writes the character data into the table and flags it for drawing on the next refresh.

This enables us to both draw new objects and move them once they are on the screen. The only remaining action required is to erase the character depicting an object which has now left the screen. Since all characters are 16x16 pixels square, or less, any character with an x coordinate of 0 is totally hidden off the left-hand side of the screen. Thus specifying 0 as the x coordinate of a character causes nothing to appear on the screen. This fact is used to eliminate the need for a separate erase-object routine. Instead, setob is called, with the object id identifying which character is to be erased, and the x coordinate set to 0 to show that nothing should be drawn in its place. The only remaining worry is that this may cause the object list to become cluttered with old characters which are not actually on the screen. This is not in fact a problem. The setob routine automatically decides not to store any information about any character which has no pixels visible because it falls entirely within the border around the edge of the screen, and all characters with an x coordinate of 0 fall into this category.

The `refresh' Routine

The refresh routine is called every time round the main foreground loop to update the screen. If the tank or pillbox from which the view is seen is destroyed then vnoise is called, which just draws static on the screen, otherwise the following is done: This order is chosen because, in principle, it would be possible to synchronize with the start of the video field[20] and then `chase' the electron beam down the screen, safe in the knowledge that the part of the image being modified is definitely not being scanned onto the screen at that moment by the electron beam. The electron beam would have to get a full 1/50 second ahead of the drawing action to catch up with it again. This means that we have nearly 1/25 second[21] to draw the screen to make it totally flicker-free. In practice, the BBC micro only achieves this when there is very little to draw on the screen, but faster machines should manage it quite easily.

As each square is examined, two things are done:

(i) if the flag is set, the square is redrawn.

(ii) to minimize the time between redrawing the background square and replacing the foreground characters on top of it, any foreground objects with this as their key square are immediately redrawn. Since they have been sorted into order, this is easy. At each square, only the first object in the list has to be checked. When it is found that its key square is the one which was just redrawn, it too is drawn and then discarded from the list, so that now only the new front object in the list has to be checked each time, until its key square is found, and so on, to the end of the list.


Previous | Table of Contents | Next

[ Intro | News | Links | Archive | Guides | Gallery | People | Misc ]
The Bolo Home Page is copyrighted by
Joseph Lo & Chris Hwang