Back2BASIC

You are here: Home > Issues > Issue #3 > Retro Tricks

Retro Tricks

by angros47

If you look at old arcade machines, consoles, and even home computers, you might wonder how they can manage any kind of game on such limited hardware. Many of us have tried to build at least one retro game (using old style graphics and sounds): but... why are two megs (or more) of disk space required for a game that originally fit in 64 kb?

The answer is complex: first of all, many old games used 8-bit code, while now 32-bit or 64-bit code is used. Since a single assembly instruction, even the simplest one, requires 32 (or 64) bits, it's obvious that the same program, written in 32 bit assembly, will use four times as much memory as an 8-bit version; but, this is still not enough to cover the difference. Old games had features that nowadays seem nearly impossible to do on such a limited hardware; to achieve the 64kb size limit, old games used many interesting tricks, some of which are still useful today.

Redefining characters

This was a widely used trick: many computers (Vic 20, Commodore 64, Sinclair Spectrum) allowed custom ASCII characters: usually, a character was made from an 8x8 bit square (8 bytes), so a character set required only 256 * 8 = 2048 bytes. After that, characters could be used as tiles, and very complex images were rendered very quickly; also, built-in scrolling routines could be used.

Advantages: Fast, little memory required, very useful for repetitive patterns (bricks, gates, stairs...), very good for large worlds. (due to easy scrolling)

Disadvantages: Image was “boring” since the same elements were repeated; big structures were not possible (or were drawn by combining small pieces, as in a puzzle); there were limits in color choices (for every character, only one foreground color and one background were possible)

Today: This technique is still available in text mode, and some “modern” games/game engines (like MegaZeux) use it now: it can also be used in FreeBasic for DOS, with something like this:

 

	#include "dos/dpmi.bi"
	#include "dos/dos.bi"
	#include "dos/sys/movedata.bi"
	dim r as __dpmi_regs
	dim dos_seg as integer, dos_sel as integer
	'r.x.ax = &H13
	dim chardata as string
	chardata = chr$(60) + chr$(66) + chr$(66) + chr$(129) + chr$(129) + chr$(66) + chr$(24) + chr$(24) + chr$(126) + chr$(24) + chr$(24) + chr$(24) + chr$(36) + chr$(36) + chr$(195)
	r.x.ax = &H1100
	r.x.bx = &H1000
	r.x.cx = &H1
	r.x.dx = 65
	dos_seg = __dpmi_allocate_dos_memory((len(chardata) + 15) \ 16, @dos_sel)
	dosmemput(strptr(chardata), len(chardata), dos_seg * 16)
	'SSEG (chardata)
	r.x.es = dos_seg
	'SADD (chardata)
	r.x.bp = 0
	__dpmi_int(&H10, @r) ' change char A

Anyway, under Windows or Linux, this technique is useless: text is rendered by a software routine that is not faster or less memory consuming than a graphic engine: so, it's better to use a real tile map engine; that will surely be more versatile and will allow a better result.

Sprites

A sprite is, basically, an image that can be moved anywhere on the screen, without changing the background. Many old computers (Commodore 64, MSX, Amiga) and almost all old consoles supported sprites via hardware. A sprite was usually independent from the background, and could have its own color map. Also, sprites could be used over text, graphics. and redefined characters.

Advantages: Sprites were perfect for representing characters of a game: the player and the enemies were usually made with sprites. Since collisions (even pixel-perfect ones!) were managed by dedicated hardware, most game engines were really simple to build.

Disadvantages: The main problem, with hardware sprites, was the small number of them: on a Commodore 64, for example, there were only 8 sprites available: it was not much. With some tricks, it was possible to get more sprites, but it wasn't easy. Also, many computers offered only sprites with a small color palette: so, to get multi-color sprites, you had to merge two or more sprites. Last but not least, sprites were small: they could not be used to draw big objects.

Today: Modern computers don't support hardware sprites anymore: only software sprites are available. There are many ways to emulate sprites in software: it's possible to redraw the background at every frame, for example. It's slow, but in many cases you need to redraw the frame anyway because of animation/scrolling. One way to save and restore the scene behind the sprite is with something like:

 

	screenRes 800, 600, 32
	' Draw a simple background.
	for i as integer = 0 to 800 step 20
	 line (0,i) - (800, i)
	 line (i,0) - (i, 600)
	next i
	' Set up an image and draw something in it.
	dim img as any ptr = imageCreate (32, 32, rgba(255, 0, 255,0))
	dim back as any ptr = imageCreate (32, 32, 32)
	circle img, (16, 16), 15, rgb (255, 255,   0),     ,     , 1, f
	circle img, (10, 10),  3, rgb (  0,   0,   0),     ,     , 2, f
	circle img, (23, 10),  3, rgb (  0,   0,   0),     ,     , 2, f
	circle img, (16, 18), 10, rgb (  0,   0,   0), 3.14, 6.28
	dim as integer x, y, ox, oy
	ox =- 100: oy =- 100
	for x = 0 to 768
	 y = 300 + sin(x/57) * 200
	 ' Restore the background.
	 put (ox, oy), back, pset
	 ' Save the background that will be overwritten by the sprite.
	 get (x, y) – (x+32, y+32), back
	 ox = x: oy = y
	 ' Draw the sprite.
	 put (x,y), img, alpha
		 
	 sleep 2
	next x
	' Wait for a keypress.
	sleep

These types of solutions will require much more work from the CPU versus just using the (no longer available) hardware sprites.

Color cycling

This trick has been used widely on Amiga and on earlier PCs. It works by changing the palette of an image: if a pixel get the color of an adiacent pixel, the illusion of movement is possible. Many games (including most point-and-click adventures) used it. (Ed: even Simcity 2000 used it to great effect, it was used to simulate traffic lights, cars, pollution, rioters, building lights, etc.)

Advantages: Very complex animations could be made with this trick: the charge of the CPU was minimal (for every frame, no more than 256 bytes were changed), no memory was wasted (no overlays or extra frames were used: all the needed data was included in the image itself), and the effect was awesome. Also, a bit of interaction was possible (for example, fading effects were useful to simulate lights and torches). To get an idea, have a look at http://www.effectgames.com/demos/canvascycle/?sound=1

Disadvantages: It works only with a limited number of colors (it cannot work in true color mode, of course): so, pictures with many colors will require dithering. Also, the image has to be drawn by hand, in pixel art: drawing in pixel art is slow, and not easy, even for static images. Mark J. Ferrari (http://www.effectgames.com/effect/article.psp.html/joe/Q_A_with_Mark_J_Ferrari) is one of the greatest masters in this technique, but he is alone in his mastery... even drawing a single high quality image takes him three weeks! So, using color cycling could be very time consuming – just a single image, or a set of unrelated images, is nearly useless when developing a game; dozens of images are needed.

Today: Indexed color modes are still available, so color cycling can still be used. Here is a simple demo in FreeBasic:

 

	screenRes 640, 480, 8
	for y as integer = 0 to 480
	 for x as integer = 0 to 640
	  pset (x, y), (x + sin(y/57) * 90) mod 255
	 next x
	next y
	dim shared as integer p(255, 2)
	for i as integer = 0 to 255
	 p(i, 0) = i/2
	 p(i, 1) = i/2
	 p(i, 2) = i
	 palette i, p(i, 0), p(i, 1), p(i, 2)
	next i
	sub cycle (lowest as integer, highest as integer) 
	 dim as integer a, b, c 
	 a = p(highest, 0)
	 b = p(highest, 1)
	 c = p(highest, 2)
	 for i as integer = highest to lowest +1 step -1
	  p(i, 0) = p(i-1, 0)
	  p(i, 1) = p(i-1, 1)
	  p(i, 2) = p(i-1, 2)
	  palette i, p(i, 0), p(i, 1), p(i, 2)
	 next i
	 p(lowest, 0) = a
	 p(lowest, 1) = b
	 p(lowest, 2) = c
	 palette lowest, a, b, c
	end sub
	do
	 cycle (0, 255)
	 sleep 1
	loop until inkey <> ""

Vector graphics

On a cathode ray tubes (CRT), the raster is controlled electronically; on a television, it's used to scan the entire screen, but on an oscilloscope, it's used only to draw the graph. However, it's possible (and not difficult) to use a computer to control it, and use the raster as a pen; in this way, no graphic card is required, and a graphic command (i.e. drawing a line) is directly executed on the CRT.

Instead of mapping an image to video memory pixel by pixel, the image is stored as a sequence of bi-dimensional vectors, and sent to the monitor at every frame. Among others, Battle Zone and various Star Wars games used this trick.

Advantages: This solution allows fast rendering of high resolution images: translations and rotations are very easy to do, since only vertex data needs to be updated. Vector graphics made possible the first 3d games (a simple routine to convert 3d coordinates to 2d coordinates, and the 3d world was ready!)

Disadvantages: It cannot use a standard monitors; also, only wireframe graphics were possible: drawing a full surface would have been too time-consuming. Additionally, only one or two colors were available. Last but not least: sprites, or bitmap backgrounds, were not possible at all.

Today: Unless your computer is directly connected to an oscilloscope, there is no way to do true vector graphics. Even some modern oscilloscopes don't use CRT anymore, either. Vector graphics can be easily emulated in FreeBasic, with the LINE command:

 

	screenRes 800, 600, 32
	window (-400, -300) – (400, 300)
	dim as double x, y, z, x0, y0, s
	dim as double rx, ry, rz
	do
	 rx = rx + .01
	 rz = rz + .001
	 cls
	 color 0
	 dim as double a = cos(rz) * cos(rx)
	 dim as double b = sin(rz) * cos(rx)
	 dim as double c = sin(rx)
	 dim as double d = -sin(rz)
	 dim as double e = cos(rz)
	 dim as double f = 0
	 
	 restore
	 do
	  read x, y, z
	  if x =-1000 then exit do
	  s = 1
	  x0=(x*a + y*b + z*c) / s
	  y0=(x*d + y*e + z*f) / s
	  line -(x0, y0)
	  color rgb(0, 255, 0)
	 loop
	 sleep 1
	loop until inkey <> ""
	data -50, 0, -50
	data 50, 0, -50
	data 50, 0, 50
	data -50, 0, 50
	data -50, 0, -50
	data -50, 100, -50
	data 50, 100, -50
	data 50, 100, 50
	data -50, 100, 50
	data -50, 100, -50
	data 50, 0, -50
	data 50, 100, -50
	data 50, 0, 50
	data 50, 100, 50
	data -50, 0, 50
	data -50, 100, 50
	data -1000, 0, 0

 


| top |