Jump to content
IGNORED

software sprites - reference code in CC65


Recommended Posts

I'm pretty sure that any code you find in C for that kind of thing would actually be mostly inline assembler. Also, 4 is a character mode, so smooth movement on a pixel basis is difficult but just moving the character is very easy, since the computer takes care of rendering the character bit pattern.

I don't have any source handy, but I've experimented with mode E a bit. My efforts are nowhere near expert but I will say that lookup tables of various sorts (screen line address, mult and divide, color masks, etc.) are a requirement.

Link to comment
Share on other sites

Hello,

 

1. It is likely you will have to code in assembler, because of the speed.

2. There are certain general principles related to sprites, but you will have to tailor your solution to Atari. But what's written there for VGA is mostly valid for any bitmap mode on Atari.\

 

This thread can provide some insights too. And you can get inspired by X:8 if you decide to go with character sprites.

  • Like 1
Link to comment
Share on other sites

ok,

After quite sometime I was able to come up with a sample app of software sprites in CC65.

the sample is a simple ball moving on mode E screen using the joystick.

 

This maybe a bit long, but I hope it will help those who are seeking:

1. Learn how to work with Mode E

2. Create a display list of mode E with 2 LMS instructions

3. Learn about software sprites basics and how to develop them in CC65

 

let's start.....

 

Mode E (The bitmap mode)

Looking at atari spec, mode E gives a resolution of 160x192. it is referred as bitmap mode and each line is exactly 1 scanline.

If you want to completely build a mode E by yourself that will take up the entire screen, it will take up to 192 scanlines in your display list.

Problem or technical challange in that is that screen can't pass the 4K boundary.

This mode takes up to 7680 bytes which are more then the 4K boundary.

The solution will be to use 2 LMS instructions in the display list.

This means that the display list will be divided into 2 parts.

The first part will set the first 96 scanlines (192/2=96) of the entire screen and the second part will contain the other 96 scanlines.

 

Mode E - The bit pairs

Similar to mode 4 (char mode), mode E also works with pair bits.

If a byte is presented by 8 bits i.e. 00000000 then in mode E this byte will actually present up to 4 pixels "lit" on screen (byte is 8 bits divided by 2 as bits come in pars = 4 pixels to be displayed).

1 scanline provides 40 bytes, so the total pixels that can be displayed in 1 scanline is 40 * 4 = 160.

The bit pairs are corresponding to the playfield colors of atari:

 

00 COLBAK

01 COLPF0

10 COLPF1

11 COLPF2

 

In decimal the values for bit pairs are : 0,1,2,3

 

In order to "lit" 3 out of 4 pixels in 1 byte of screen memory with the color blue (let's say that COLPF0 is blue) all you need to do is write a byte that holds the bitpairs for the lit pixels with the value of COLPF0:

The byte will look like this: 01010100

01 is the pair value for COLPF0 (blue in this example) and that bitpair appears 3 times in the byte sequence(010101)

The last bitpair is 00 which is the value of COLBAK and in our case this pixel will not be "lit up".

 

Define mode E display list

 

consider the following display list:


unsigned char DisplayList[] = 
{
	DL_BLK8, // 0
	DL_BLK8, // 1
	DL_BLK8, // 2
	DL_LMS(DL_MAP160x1x4), // 3
	0, // 4
	0, // 5
	// 6
	DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,
	// 13
	DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,
	DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,
	DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,
	DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,
	DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,
	DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,
	DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,
	DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,
	DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,
	DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,
	DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,
	// 101
	DL_LMS(DL_MAP160x1x4),
	0, // 102
	0, // 103
	DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,
	// 110
	DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,
	DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,
	DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,
	DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,
	DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,
	DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,
	DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,
	DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,
	DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,
	DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,
	DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,DL_MAP160x1x4,
	// 198
	DL_JVB,
	0, // 199
	0 // 200
};

The above display list is set to create Mode E (used by the value: DL_MAP160x1x4) on screen.

It uses as described above 2 LMS instructions to point to 2 memory locations that will hold the enitre screen bytes.

The zeros in the display list above must be filled in with the proper addresses.

Basically we need to fill in the following 3 memory address:

1. Display list address (where the JVB (jvb is jump vertical blank instruction) will go to)

2. LMS 1 - first memory address

2. LMS 2 - second memory address

 

Here is a code snippet to fill in the memory addresses respectivly:

void setupDisplayList(void)
{	
	DisplayList[4] = SCREEN_MEM%256;
	DisplayList[5] = SCREEN_MEM>>8;
	DisplayList[102] = SCREEN_MEM_BOTTOM %256;
	DisplayList[103] = SCREEN_MEM_BOTTOM >>8;
	
	DisplayList[199] = DISPLAY_LIST_MEM%256;
	DisplayList[200] = DISPLAY_LIST_MEM>>8;
	memcpy((void*)DISPLAY_LIST_MEM,&DisplayList,sizeof(DisplayList));
	OS.sdlst = (void*)DISPLAY_LIST_MEM;
	bzero((void*)SCREEN_MEM,7680);
}

The modulu (%) give the hi-byte part and the >>8 (which is divide by 256) gives the lo-part.

Last line clears the entire screen (screen size is 7680 bytes)

 

Software Sprite

So, what exactly does the term software sprite refers to?

Well, it is basically anything that can be drawn on screen and be moved arround.

This is done on the software layer in oppose to things on screen donw in the HW layer such as player missile graphics).

 

When thinking about software sprites you need to consider the following aspects:

1. Memory allocations - these type of developing can be very of high memory consumption

2. Performance - dealing with a lot of objects on screen can slow down the performance as everything is done on the software side and consume lots of memory.

3. Timing - drawing on the screen memory must be done at the right time to avoid conflicts and issues.

 

In the example I have prepared, I draw a ball using bitpair '11' which is represent by COLPF2.

 

This is the ball represented by the bit pairs:

00000000

00111100

11111111

11111111

00111100

00000000

 

It is basically a byte array in memory which looks like this:

unsigned char ball[6] = { 0,60,255,255,60,0 }; 

or in hex

unsigned char ball[6] = {0x00, 0x3C,0xFF,0xFF,0x3C,0x00}

Draw on screen

To draw this ball on screen mode E, all I have to do is to copy the byte array into screen memory where-ever I want to show the ball, for example:

for (i=0;i<6;i++)
{
    POKE(SCR_MEM+21+40*i,array[i]);
}

Note that a CC65 memcpy routine can be used to replace the for loop.

 

Moving the ball - shifting the frame

Now we want our ball to move, let's say by the joystick corresponding movements.

how do we move the ball?

One possibility would be to draw the ball one byte next to the byte that it was already drawn in and erase the ball from the original byte it was drawn.

This could work, but the movement will not be as smooth as we desire and will look jerky and inconsistent.

The solution is to shift the ball within the byte itself so the "lit pixels" will move left or right.

Shifting the pixels inside the array will cause parts of the ball to disappear.

in order to solve that we will need another byte next to the one the ball is drawn in that will hold the "mirror" shift of the first byte.

The shifted frames should be resulted from the orginial ball frame.

We shift the orginial ball in the left byte and in the right byte and then on the shifted frames we do the second third shifts.

To understand better, let's see how the shifting will look like:

 

Ball original on the left byte:

Left byte Right byte

00000000 00000000

00111100 00000000

11111111 00000000

11111111 00000000

00111100 00000000

00000000 00000000

 

(ball appears on screen, on the left byte)

 

Ball shift once:

Left byte Right byte

00000000 00000000

00001111 00000000

00111111 11000000

00111111 11000000

00001111 00000000

00000000 00000000

(ball appear on screen, now moved 1 pixel to the right, it is shown in the 2 bytes the left and right)
Ball shift twice:

Left byte Right byte

00000000 00000000

00000011 11000000

00001111 11110000

00001111 11110000

00000011 11000000

00000000 00000000

(ball appear on screen, now moved another pixel to the right, it is shown in the 2 bytes the left and right)
Ball shift 3rd time:

Left byte Right byte

00000000 00000000

00000000 11110000

00000011 11111100

00000011 11111100

00000000 00111100

00000000 00000000

(ball appear on screen, now moved another pixel to the right, it is shown in the 2 bytes the left and right)

Ball original on the right byte:

Left byte Right byte

00000000 00000000

00000000 00111100

00000000 11111111

00000000 11111111

00000000 00111100

00000000 00000000

 

(ball appears on screen, basically now on the right byte)

 

at this point if the ball continues to move to the right, we will need to do the same but now the right byte will become the left byte and the byte next to the right will become the right byte and so on......

 

to move the ball to the left, we will do the same thing but in reverse

to move the ball up or down we will need to move it 40 bytes above or below the current byte. the ball starts with leading zeros and end with trailing zeros and that is not to leave trails while it moves up or down.

 

Pre-shifting

to increase performane and to make usage of atari fast memory operations we can "prepare" the ball shifted images in memory and then just copy each shifted frame to the screen whenever is needed. this will be done very fast.

This technique is called "pre-shifting", which means, we prepare the shifting frames upfront.

 

Here is a code snippet on how to prepare the shifted images of the ball:

void createShiftedFrames()
{
	for (i=0;i<HEIGHT;i++)
	{
		spriteShifted[2][i] = spriteShifted[0][i] >> 2; // left byte shift >> 2
		spriteShifted[3][i] = spriteShifted[0][i] << 6; // right byte shift << 6

		spriteShifted[4][i] = spriteShifted[0][i] >> 4; // left byte shift >> 4
		spriteShifted[5][i] = spriteShifted[0][i] << 4; // right byte shift << 4

		spriteShifted[6][i] = spriteShifted[0][i] >> 6; // left byte shift >> 6
		spriteShifted[7][i] = spriteShifted[0][i] << 2; // right byte shift << 2
	}
}

Wait for VBLANK

it is most common to wait until a vblank occurs and only then do the screen calc and drawing.

A good reference for vblank can be found in books like de-re-atari.

I used a common trick (learned here in AA forums) to determine that a VBLANK already occured.

It is basically using the atari clock at address 0x20:

void waitForVBLANK(void)
{
    currClockFrame = OS.rtclok[2];
    while (OS.rtclok[2] == currClockFrame);
}

When this routine finishes, it is almost definately that a VBLANK has occured. some might aruge and say that this trick is not 100% accurate and that there will be situations where this will fail.

I haven't encountered with any and it seems to be working perfectly well for me.

plus establishing a true VBI routine in CC65 is not easy as these 2-line routine code bove :)

 

Differentiating joy movement and drawing

A good programming practice is to handle the joystick movement and just update some flags, then have a drawing routine that draw the current state based on the current flags.

 

code snippet for main routine that can looks something like this:

waitForVBLANK(); 
handleJoystick();
updateBall();

The joystick routine will determine if joystick was moved up,down,left,right and will set positions on screen accordingly.

updateBall will make sure the proper frame is drawn on screen based on those positions updates.

 

Wrapping up

I know this was a bit long, but I hope it was useful to someone.

The code attached is not optimal and can be refactored for better results, but I think it that for this purpose it is just fine.

 

At this point I would like to say special thank you to my good friend popmilo(Vladimir J.) - you are my true guru :)

 

Any comments notes remakrs dislikes or other share thoughts would be happily accepted :)

 

Attached is xex and a zip containing the code

 

 

EDIT: I've edited the post as i found many language mistakes and some phrasing i didn't like. hope it came out better....

 

soft sprites.rar

softsprite.xex

Edited by Yaron Nir
  • Like 8
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...