By Andrew Davie (adapted by Duane Alan Hahn, a.k.a. Random Terrain)
As an Amazon Associate I earn from qualifying purchases. (I get commissions for purchases made through certain links on this page.)
Page Table of Contents
It's time to begin our understanding of sprites.
What are sprites? By now, sprites are well-known in the gaming industry. They are small, independently movable objects which are drawn by hardware anywhere over the top of playfield graphics. The Atari 2600 was the first console to introduce general-purpose sprites—back in the day they were called 'player missile graphics'. It was the Commodore 64 which introduced the term 'sprites', which we know and love.
The Atari 2600 has two 'players', two 'missiles' and a 'ball'—all of these are sprites, and each has various parameters which can be adjusted by the programmer (position, size, color, shape, etc). We're going to concentrate, this session, on the 'players' and how they work.
Player graphics have much finer resolution than playfield graphics. Each player is 8 pixels wide, and each pixel in a player is just a single TIA color-clock in width. In other words, the pixels in player graphics are a quarter of the width of the pixels in playfield graphics. The graphics of each player are controlled by a single 8-bit TIA register. The register for player 0 (the first player) is GRP0 (standing for 'Graphics, Player 0') and the register for the second player is GRP1. When you write data to either of these registers you change the visuals of the relevant player sprite being drawn on the screen.
Just like playfield graphics, the player graphics registers only hold a single 'line' of data. If you do not modify the data on-the-fly (that is, changing it every scanline), then the TIA just displays the same data on every scanline. So kernels using sprite graphics typically modify these player graphics registers constantly.
Surprisingly, though player sprites can be (effectively) positioned anywhere on the screen, they do NOT have position registers. Most more modern machines (Nintendo, C64, etc.) provided an x,y coordinate which was used to position a sprite on the screen. The Atari 2600 is a much more primitive beast.
The horizontal position of a player sprite is controlled by writing to a 'reset position' register (RESP0 for sprite 0 and RESP1 for sprite 1). When you write to these registers, you cause the hardware to begin drawing the relevant sprite . . . immediately! This is very strange and a bit hard to get used to at first. To move a sprite horizontally to any x-position on a scanline, one has to make sure that the RESP0 write happens just before the position on the scanline at which you want the sprite to appear. Since the 6502 is running at 1/3 of the clock speed of the TIA, this makes it incredibly difficult to write to RESP0 at exactly the right time. For every cycle of 6502 time, three pixels (cycles of TIA time) pass. So it's only possible to position sprites (through RESPx writes) with an accuracy of 1 6502 clock period, or in other words three TIA pixels.
To facilitate fine-positioning of sprites, the TIA has additional registers which allow the sprite to be adjusted in position by a few pixels. We are not going to cover that this session—but instead we'll have a look at how sprite graphics are written, how the course RESPx registers are used, and how sprite colors are controlled. Fine positioning of sprites is an art in itself, and many solutions have been proposed on the [stella] list. We'll get to that in a session or two, but for now, let's stick with the basics.
The sample kernel shows a fully working sprite demo.
There are very few additions from our earlier playfield demos. . .
lda #$56 sta COLUP0 lda #$67 sta COLUP1
In our initialization (before the main frame loop) the above code is initializing the colors of the two player sprites. These are random purplish colors. You may also change the color on-the-fly by rewriting it every scanline. Remember, though—you only have 76 cycles per scanline—so there's only so much you can cram into a single line before you run out of 'space'.
MiddleLines SLEEP 20 sta RESP0 SLEEP 10 sta RESP1 stx GRP0 ; modify sprite 0 shape stx GRP1 sta WSYNC inx cpx #184 bne MiddleLines
The above code sample is the 'guts' of our sprite demo. It doesn't do a lot of new stuff. You should already be familiar with the SLEEP macro—it just causes a delay of a certain number of 6502 cycles. The purpose of the SLEEP macros here is to delay to a position somewhere in the middle of the scanline—you may play with the values and see the effect on the positioning of the sprites.
Immediately after each SLEEP, there's a write to RESPx for each of the player sprites. This causes the TIA to begin drawing the appropriate player sprite immediately. And what will it draw?
stx GRP0 ; modify sprite 0 shape stx GRP1
Since, in this kernel, the x register is counting the scanline number, that is also the value written to both of the graphics registers (GRPx) for the player sprites. So the graphics we see will change on each scanline, and it will represent a visual image of the scanline counter. This should be pretty evident by the image below:
Here's the sample kernel:
That's pretty much all there is to getting sprites up and running. There are a few interesting things we need to cover in the coming sessions, including sprite size, sprite repeating, priorities, buffered sprite drawing, drawing specific images/shapes and lots of other stuff. But now you have the basics, and you should be able to do some experimenting with what you see here.
See you next time!
The answer to each question below is hidden in a black box that has a green border. Hover your mouse pointer over the box (or tap it if you're using something like an iPhone) to reveal the answer.
It takes 3 cycles per write to a color register (eg: stx COLUP1), but it takes two or more additional cycles if you want to load a specific color. The variation in time depends on the addressing mode you use to load the color (eg: an immediate value = 2 cycles, but loading indirectly through a zero page pointer to a memory location, indexed by the y register, would take 6 cycles!).
lda #34 ; 2 sta COLUP1 ; 3 lda (colour),y ; 6 sta COLUP1 ; 3
This really is too tricky to answer here. Future sessions will cover this problem thoroughly, as its fundamental to drawing sprites in your game.
Remember that the TIA and the TV beam are in synch. The timing is such that precisely 76 cycles of 6502 time, or 228 cycles of TIA time, correspond to *exactly* one scanline on the TV. Currently we've been using "sta WSYNC" to synchronize our kernel to the start of every scanline. This isn't necessary IF our code makes sure that our kernel lines take EXACTLY 76 cycles to execute.
But since the above code DOES use "sta WSYNC", a 3 cycle instruction, we really only have 73 cycles per line available for other processing. If we exceed these 73 cycles, then that pushes the "sta WSYNC" past the point at which it's on the current scanline and onto the point where it's really on the NEXT scanline. And if it happens on the NEXT scanline, it will operate as expected (and that, as we know, is by halting the 6502 until the start of the NEXT scanline).
So essentially, if our code exceeds 76 cycles, then each scanline will actually be two scanlines deep! And instead of sending, say, 262 scanlines per frame, we'd be sending 524. Most TVs cannot cope with this and they will, as noted, 'roll'. I just wanted you to understand WHY.
A good lesson in how the TIA works. The TIA registers hold whatever you put into them, until you next put something in to them. So after our last write to the sprite registers, the TIA keeps displaying the same shape for sprites, on each scanline, until we write again. So what we're really seeing in those border areas is the last write (which is actually at the bottom of the changing shape area of sprites) repeated on the bottom, and then on the top again, until we start writing sprite shapes again.
The solution is to write 0 to GRP0 and GRP1 when we've finished drawing our sprites—and, of course, on initialization of the system.
Barring minor timing changes which will cause the positions to shift slightly, the effect I was trying to show was that it is not necessary to rewrite the RESPx registers every scanline. You only need to position your sprites once each, and they will remain in that position until you reposition them. By moving the reposition outside the loop, we've freed up extra cycles in the kernel code for each scanline.
Positioning sprites to any arbitrary horizontal position is quite complex, and usually takes at least one whole scanline to do in a generic fashion. This is why games which use multiple sprites rarely allow those sprites to cross over each other, and also the reason why you see distinct 'bands' of sprites in other games—the gaps between the bands is where the horizontal movement code is doing its stuff.
Other Assembly Language Tutorials
Session 21: Sprites
This book was written in English, not computerese. It's written for Atari users, not for professional programmers (though they might find it useful).
This book only assumes a working knowledge of BASIC. It was designed to speak directly to the amateur programmer, the part-time computerist. It should help you make the transition from BASIC to machine language with relative ease.
The 6502 Instruction Set broken down into 6 groups.
Nice, simple instruction set in little boxes (not made out of ticky-tacky).
This book shows how to put together a large machine language program. All of the fundamentals were covered in Machine Language for Beginners. What remains is to put the rules to use by constructing a working program, to take the theory into the field and show how machine language is done.
An easy-to-read page from The Second Book Of Machine Language.
A useful page from Assembly Language Programming for the Atari Computers.
Continually strives to remain the largest and most complete source for 6502-related information in the world.
By John Pickens. Updated by Bruce Clark.
Below are direct links to the most important pages.
Goes over each of the internal registers and their use.
Gives a summary of whole instruction set.
Describes each of the 6502 memory addressing modes.
Describes the complete instruction set in detail.
Cycle counting is an important aspect of Atari 2600 programming. It makes possible the positioning of sprites, the drawing of six-digit scores, non-mirrored playfield graphics and many other cool TIA tricks that keep every game from looking like Combat.
Atari 2600 programming is different from any other kind of programming in many ways. Just one of these ways is the flow of the program.
The "bankswitching bible." Also check out the Atari 2600 Fun Facts and Information Guide and this post about bankswitching by SeaGtGruff at AtariAge.
Atari 2600 programming specs (HTML version).
Links to useful information, tools, source code, and documentation.
Atari 2600 programming site based on Garon's "The Dig," which is now dead.
Includes interactive color charts, an NTSC/PAL color conversion tool, and Atari 2600 color compatibility tools that can help you quickly find colors that go great together.
Adapted information and charts related to Atari 2600 music and sound.
A guide and a check list for finished carts.
A multi-platform Atari 2600 VCS emulator. It has a built-in debugger to help you with your works in progress or you can use it to study classic games.
A very good emulator that can also be embedded on your own web site so people can play the games you make online. It's much better than JStella.
If assembly language seems a little too hard, don't worry. You can always try to make Atari 2600 games the faster, easier way with batari Basic.
Some people appear to have a mental illness because they have a vitamin B deficiency. For example, the wife of a guy I used to chat with online had severe mood swings which seemed to be caused by food allergies or intolerances. She would became irrational, obnoxious, throw tantrums, and generally act like she had a mental illness. The horrid behavior stopped after she started taking a vitamin B complex. I’ve been taking #ad Jarrow B-Right for many years. It makes me much easier to live with.
Unfermented soy is bad! “When she stopped eating soy, the mental problems went away.” Fermented soy doesn’t bother me, but the various versions of unfermented soy (soy flour, soybean oil, and so on) that are used in all kinds of products these days causes a negative mental health reaction in me that a vitamin B complex can’t tame. The sinister encroachment of soy has made the careful reading of ingredients a necessity.
If you are overweight, have type II diabetes, or are worried about the condition of your heart, check out the videos by William Davis and Ivor Cummins. It seems that most people should avoid wheat, not just those who have a wheat allergy or celiac disease. Check out these books: #ad Undoctored, #ad Wheat Belly, and #ad Eat Rich, Live Long.
Negative ions are good for us. You might want to avoid positive ion generators and ozone generators. Whenever I need a new air cleaner (with negative ion generator), I buy it from surroundair.com. A plain old air cleaner is better than nothing, but one that produces negative ions makes the air in a room fresher and easier for me to breathe. It also helps to brighten my mood.
Never litter. Toss it in the trash or take it home. Do not throw it on the ground. Also remember that good people clean up after themselves at home, out in public, at a campsite and so on. Leave it better than you found it.
Seems like more people than ever finally care about water, land, and air pollution, but the climate change cash grab scam is designed to put more of your money into the bank accounts of greedy politicians. Those power-hungry schemers try to trick us with bad data and lies about overpopulation while pretending to be caring do-gooders. Trying to eliminate pollution is a good thing, but the carbon footprint of the average law-abiding human right now is actually making the planet greener instead of killing it.
Watch these two YouTube videos for more information:
Take a look at my page called The H Word and Beyond.
View this page and any external web sites at your own risk. I am not responsible for any possible spiritual, emotional, physical, financial or any other damage to you, your friends, family, ancestors, or descendants in the past, present, or future, living or dead, in this dimension or any other.
Use any example programs at your own risk. I am not responsible if they blow up your computer or melt your Atari 2600. Use assembly language at your own risk. I am not responsible if assembly language makes you cry or gives you brain damage.