Tuesday, 8 February 2011

Spectrum sprite rendering techniques

There's a great discussion on sprite rendering techniques taking place on the World of Spectrum forms. A few home computers such as the ZX Spectrum and Amstrad CPC 464 did not provide any special hardware for handling sprites, unlike the Commodore 64. This hardware limitation forced programmers to invent their own routines for rendering graphical images on to the screen.

Not having support for hardware sprites adds an extra strain to the poor old CPU. The CPU must perform all the sprite drawing work and run all the game logic within a frame. As the CPU's time is divided between many tasks, we will tend to see fewer sprites on screen on these platforms.

However, by not being forced to draw graphics in a certain way, the programmer is freed to implement custom drawing routines for different types of object! For example, isometric game rendering is made much easier by having full control over a sprite's rendering function. Being able to partly mask sprites allows characters to be visibly obstructed by the pseudo 3d geometry in isometric games.

In game's such as Gloop Troops and Crimbo, we were able to take advantage of a custom rendering function when drawing enemy sprites, as opposed to drawing the player! The player sprite had more rendering restrictions placed on them than the enemies. For example, the player's sprite could regularly become partly obscured when jumping through a platform. Therefore his rendering function needed to only draw the visible part of his image. The enemy sprites however, did not have to pass in front of any geometry. This allowed us to simply 'stamp' their image on screen, avoiding any costly pixel comparisons. We performed this stamping through the use of the LDI instruction. The Spectrum screen is effectively monochrome with 1 bit representing 1 pixel. One byte is therefore 8 pixels of data. When writing a pixel to the screen you need to see which byte that pixels lies inside and then set the appropriate bit.

To speed up our sprite rendering we'd go down a different logic path depending on whether the enemy was on an 8 pixel boundary or not. All of our enemies were 16 pixels wide. Therefore, when on an 8 pixel boundary, we can copy the data to the screen through the use of 2 LDI instruction pairs for each row of data. If the enemy didn't have an X co-ordinate on an 8 pixel boundary then we knew the enemy would require 3 LDI instructions per row line of pixel data.

Also - to speed things up we didn't AND the enemy sprite data with the background image. We thought this was a reasonable compromise at the time, but did result in the partial masking out of any overlapping sprites. We also sped up the image copying process by storing pre-bit shifted versions of our sprite data. This was expensive in terms of memory, as each sprite frame had to be stored 8 times ( once for each possible bit shift ). However - this meant we could easily copy the data to the screen without the need to calculate the bit pattern for each row of 16 pixels. Otherwise we'd have to load 16 pixels of data ( 2 bytes ) then shift that data along before rendering.

So as you can see, by not having hardware sprites, the programmer is given opportunities to tailor rendering code for game objects and create effects, such as those seen in isometric games :-)

No comments:

Post a Comment