C for Yourself - part 37

Steve Mumford introduces the basics of sprite handling

Until now, we've been dealing with WIMP applications that restrict their graphical interfaces to buttons, static sprites and writable icons. This has allowed us to sidestep the whole tedious process of redrawing the screen in one neat movement but it does limit the problems that can be tackled. In the next few articles I'll be moving on to more adven- turous terrain and describing how to incorporate custom-written plotting routines that allow programmers to break away from the WIMP's standard restrictions. In this column I'll cover the basics of sprite plotting using the standard system call, OS_SpriteOp.

To plot sprites to the screen, the computer must have them stored in memory beforehand. There are two forms of storage. The sprites can be loaded into the main system sprite pool where they can be accessed using the standard star commands provided by the SpriteUtils module, but this leaves them at the mercy of other applications. A more preferable method is to create a 'user' sprite area, storing the sprites within the application's memory and safeguarding them from the hideously dangerous *SNew command.

Setting up a user sprite area is fairly simple and it involves putting aside a block of memory, poking a couple of values into the start of the area and initialising it with OS_SpriteOp:

#define SPRITE_AREA_SIZE 10240

unsigned char sprite_area[SPRITE_AREA_SIZE];

_kernel_swi_regs regs_in, regs_out;

au_wordtobyte (SPRITE_AREA_SIZE, sprite_area, 0);
au_wordtobyte(l6, sprite_area, 8);

regs_in.r[0] = 256+9; /* Initialise user sprite area */
regs_in.r[l] = (int) sprite_area;
_kernel_swi(OS_SpriteOp, &regs_in, &regs_out);

The code fragment above creates a user sprite area around 10K in length. After the area of memory has been set aside, its total size is written into the first word of the block, starting at an offset of zero. The second au_wordtobyte() function writes a value of 16 into the word beginning eight bytes into the block this value may look as if it's chosen at random but it simply indicates the offset from the start of the area to the first sprite definition in the block; it's possible to have an extension area which would change this value.

Before OS_SpriteOp is called, we have to select which base function to use (in this case, 9 for 'initialise sprite area') and then modify that according to whether we're using the system sprite area or one of our own. Adding 256 to the value in register 0 tells the system that a user sprite area is being referenced and a pointer to this area is held in register 1. Finally, OS_SpriteOp is called and the user sprite area is initialised.

Loading sprites into a prepared sprite area is trivial and saving them again is just as easy call OS_SpriteOp with r0 set to 10 for loading and 12 for saving:

char filename[] = "<Spriteop$Dir>.Sprites";
char text buffer[255];

regs_in.r[O] = 256+10; /* Load a sprite file */
regs_in.r[l] = (int) sprite_area;
regs_in.r[2] = (int) filename;
_kernel_swi(OS_SpriteOp, &regs_in, &regs_out);

regs_in.r[0] = 256+12; /* Save a sprite file */
regs_in.r[1] = (int) sprite_area;
strcpy(textbuffer, "<SpriteOp$Dir>.SpritesOut");
regs_in.r[2] = (int) text_buffer;
_kernel_swi(OS_SpriteOp, &regs_in, &regs_out);

Plotting a sprite to the screen isn't so clear-cut as a large number of different calls can be used depending on whether the sprite has a mask, whether it's got to be scaled before plotting and so on. Assuming for the moment that the sprite being plotted was defined in the screen mode currently being used, we can perform the operation in the following manner:

regs_in.r[0] = 256+34; /* Plot sprite from user bank */
regs_in.r[1] = (int) sprite_area;
strcpy(text_buffer, "my sprite");
regs_in.r[2] = (int) text_buffer;
regs_in.r[3] = 256; /* x coordinate */
regs_in.r[4] = 256; /* y coordinate */
regs_in.r[5] = 0; /* plotting action */
_kernel_swi(OS_SpriteOp, &regs_in, &regs_out);

Counting from zero, the registers hold the OS_SpriteOp action (OS_SpriteOp 34 is a simple plot routine that allows a sprite to be displayed at a pair of coordinates), a pointer to the user sprite area, a pointer to the name of the sprite to be used, x and y coordinates and finally a number that specifies a plotting action. This is normally set to zero which means that the sprite's pixel colours are transferred without modification, but it's possible to OR, AND or EOR the sprite onto the screen instead.

And now the bad news. When dealing with the WIMP it's impossible to make the above assumption and, unless we do something about it, strange things will happen if sprites are plotted in screen modes other than the ones in which they were created. Luckily, there's a method of mapping between palettes with differing numbers of colours and by using a combination of pixel translation tables and scaling factors, our sprite can look the same no matter which mode it's plotted into. I'll delve deeper into this next month.

Source: Acorn User - 181 - May 1997
Publication: Acorn User
Contributor: Steve Mumford