Sprites

Types of sprites

Software sprite

A software sprite is a sprite which is positioned, clipped, drawn and erased by software functions.

Compound sprites

A compound sprite is constructed from more than one sprite.

Each sprite has an (x,y) position which is relative to an origin position, a width and a height.

Each sprite is drawn in a defined order to make the final compound sprite.

For software sprites the order of drawing defines the priority of the image so that an image which is drawn last has a higher priority over an image which is drawn first. The pixels of the last image will be shown in preference to the images from a sprite which is drawn before it.

For a CPC+ hardware sprite the order is pre-defined by the hardware.

An example of a compound sprite is an animating character. The character is constructed from two images. An image for the body, and a an animating image for the legs.

Hardware sprites

A hardware sprite is a sprite which is positioned, drawn, erased and clipped by the hardware.

The advantages:

The disadvantages:

The CPC+ has 16 hardware sprites with these properties:

The CPC and KC Compact do not have hardware sprites.

Compiled software sprites

A compiled sprite is a special form of the software sprite.

The pixel and mask data for the sprite image is contained within the instructions to plot the sprite, the original sprite pixel data is not stored. Each sprite drawing function is dedicated to a single sprite image, each function can therefore only draw one sprite.

Drawing a compiled software sprite is much faster than drawing a standard software sprite.

The draw function doesn't use as much CPU time as a standard software sprite because we know in advance which pixels are opaque and which are transparent.

Therefore, we can create a dedicated plot function which doesn't need to perform any unnecessary functions:

In a standard software sprite plotting function, the masking is performed for every byte regardless of whether it is completely opaque or completely transparent, as a result some of the CPU time is being wasted by executing these unnecessary instructions. In contrast, the compiled sprite only executes the necessary instructions, therefore it is faster.

However, because we can only plot one sprite per function, we need one function for each sprite.

The instructions for the compiled sprite are generated from the sprite pixel data itself using another function. This reads the byte of sprite pixel data, and outputs the instructions required to draw the pixels in that byte. The output of the function is either the opcodes themselves, or the assembler mneumonics which can then be assembled using an assembler.

Compressed/RLE software sprites

A compressed sprite is a special form of the software sprite. It can be drawn faster than a standard software sprite but slower than a compiled sprite.

But, in contrast to the compiled sprite, only one draw function is required for all compressed sprites.

The pixel data for the sprite is processed to identify which bytes of pixel data contain:

  1. all transparent pixels,
  2. all opaque pixels,
  3. a mixture of opaque and transparent pixels.

From this we generate an "instruction stream" which is stored in memory. The original sprite pixel data is not stored. The instruction stream is normally generated using a function which takes the original sprite pixel data as an input, and outputs the instruction stream.

Like the compiled sprite, the compressed sprite does not perform unnecessary instructions to plot the sprite. The "instruction stream" guides the plot function, so that unnecessary instructions are not required.

Example instructions:

"Leave screen pixel data unchanged"
for a byte of sprite pixel data which contains all transparent pixels
"Write sprite pixel data to screen"
for a byte of sprite pixel data which contains all opaque pixels
"Mask screen pixel data, combine with sprite pixel data and write to screen"
for a byte of sprite pixel data which contains a mixture of opaque and transparent pixels
"End of Line/Go to next Line"
where the remainder of the line in the sprite contains only transparent pixels, no plotting is necessary for those pixels. Therefore to reduce CPU time furthur we can ignore those pixels and plot the next line of the sprite.

We can extend the instructions furthur to include a count parameter. This would define the number of continuous bytes which would be processed by the instruction. e.g. "Leave screen pixel data unchanged" with a count of 4, would indicate 4 bytes that would remain unchanged.

Using this method, the "instruction stream" would be smaller than the equivalent uncompressed sprite pixel data. This is ideal for large sprites which would otherwise consume a lot of memory.

Pre-shifted software sprites

Pre-shifted sprites are a special form of the software sprite. They are used to draw sprites at a pixel position.

Generally sprites are plotted byte-by-byte.

The image is duplicated, and each image represents the

Each image is "shifted"/offset by one pixel to the right compared to the previous image.

Image 0 is located at pixel offset 0, Image 1 is located at pixel offset 1... Image n is located at pixel offset n.

The number of images is defined by the number of pixels per byte:

In order to draw the sprite to a pixel position we now do the following.

  1. From our pixel based coordinate We now use a pixel based coordinate scheme. This is converted into a byte coordinate and a "pixel offset". The pixel offset is a number between 0 and "pixels per byte"-1. Using the pixel offset we can choose the appropiate image to draw, so that the image is located at the correct pixel position.

Disadvantages:

The alternative is to use a custom plot routine which shifts the data before it is plotted to the screen

RLE hardware sprites

One of the disadvantages of the CPC+ hardware sprites is that you must copy the sprite pixels into the ASIC's sprite RAM. If the sprite is animating, then you must do this for every frame of the animation.

RLE hardware sprites is one method to speed up the update of the sprite pixel data.

This method assumes that a sprite is animating, and there is little difference between the current frame and the previous frame of the animation. Therefore it is not necessary to update the entire sprite each frame, we only need to write the pixels where the new frame is different from the previous frame.

The pixel data for each frame is stored in a special form, it is a sequence of instructions and data which is processed by the transfer function.

Drawing a software sprite

Generally a sprite image is drawn from top to bottom one line at a time. Each line is drawn from left to right.

The general method is:

  1. Calculate an initial memory address,
  2. Store the current memory address so we can use it later to calculate the memory address of the next line.
  3. Drawing a line:
    1. Plot a byte to the screen,
    2. Calculate the memory address of the byte immediatly to the right of the current byte
    3. Repeat steps 1 and 2 to draw the entire line, plotting the number of bytes for the width of the sprite
  4. Retrieve the stored memory address. Use this to calculate the memory address of the next line.
  5. Repeat steps 2 and 3 to plot each line of the sprite, plotting the number of lines for the height of the sprite

The next section will describe the various plot methods.

Another document will describe the various methods to calculate a memory address and to manipulate it to calculate the next plot position (to the right, or the next line).

Plot methods

Replace

With this method, each byte of sprite pixel data is written to the screen. The pixels on the screen are completely replaced.

In Z80 assembly language:


.
.
.

;; DE = current memory address of sprite pixel data
;; HL = current screen memory address

ld a,(de)			;; read byte of sprite pixel data
ld (hl),a			;; write byte to screen

.
.
.

This plot method is the fastest, however if this method is used for all of the sprite for both transparent and opaque pixels, the pixels on the screen will be erased where the sprite is drawn.

bitwise XOR

With this method, each byte of sprite pixel data is combined with each byte of screen pixel data using a bitwise XOR logical operation.

In Z80 assembly language:

.
.
.

;; DE = current memory address of sprite pixel data
;; HL = current screen memory address

ld a,(de)			;; read byte of sprite pixel data
xor (hl)			;; combine with a byte of screen pixel data
ld (hl),a			;; write result byte to screen

.
.
.

The advantages:

The disadvantages:

Truth table for the logical XOR operation:

ABResult
000
011
101
110

Transparency/Masked pixels

The mask can either define:

If the mask is stored with the sprite pixel data, we can use this plot method for each byte:

.
.
.
;; HL = current screen memory address 
;; DE = current memory address of sprite pixel and mask data
;;
;; data for sprite:
;;
;; sprite pixel data, mask, sprite pixel data, mask ...

ld a,(de)				;; get byte of sprite pixel data 
ld c,a					;; store sprite pixel data temporarily in C
ld a,(de)				;; get mask byte
and (hl)				;; mask pixels on screen (remove pixels which will be replaced)
or c					;; combine with sprite pixel data
ld (hl),a				;; write result to screen 

.
.
.

However we can reduce the size of the memory used to store the sprite pixel and mask data.

A byte can have 256 possible values: 0 to 255 inclusive.

If the same pen, or combination of pens, is always used to define a transparent pixel within the image, then the mask bytes will always be the same, and there will always be exactly one mask value for each byte value: there will be 256 possible mask values.

We have now reduced the number of pens which can be used to define the sprite image:

e.g. If one pen is used to define a transparent pixel then:

But we no longer need to store a mask for each byte of the sprite pixel data, instead we only need to store a look-up table containing the masks corresponding to each byte value. As a result we can reduce the size of the memory required for the sprite and mask data.

We now use the value of the pixel data to look-up the corresponding mask.

Example source code to generate the mask look-up table.

Plotting each byte of sprite pixel data:

  1. Read byte of sprite pixel data
  2. Using the value of the byte, lookup the corresponding mask byte in the table. Table entry 0 will contain the mask

This now gives us the following advantages:

If we store the look-up table so that it starts on a 256 byte boundary, we can use the following plot function:

;; HL = current screen memory address 
;; DE = current memory address of sprite pixel data
;; B = upper 8-bits (bits 15..8) of mask table memory address
;;
;; data for sprite:
;;
;; sprite pixel data, sprite pixel data ...

ld a,(de)							;; get byte of sprite pixel data
ld c,a								;; C = byte of sprite pixel data/look-up table value
									;; BC = address (in look-up table) of mask corresponding to this sprite pixel data
ld a,(bc)							;; lookup mask from table
and (hl)							;; mask pixels on screen (remove pixels which will be replaced)
or c								;; combine with sprite pixel data
ld (hl),a							;; write result to screen 

OR:

.
.
.
;; DE = pixel data
;; BC = screen address
;; H = upper 8-bits of mask table

pop de								;; get two bytes of sprite pixel data. E is the first byte
									;; D is the second byte
ld l,e								;; L = first byte. HL = address of mask
ld a,(bc)							;; get byte of screen pixel data
and (hl)							;; mask pixels
or e								;; combine with a byte of screen pixel data
ld (bc),a							;; write result to screen
inc bc
ld l,d
ld a,(bc)
and (hl)
or d
ld (bc),a
inc bc
.
.
.