In this episode we explore how the Atari VCS uses graphics registers to draw what we now call sprites. We'll examine Graphics Player Zero (GRP0), and the Horizontal Motion registers in the TIA to place our graphics anywhere on the screen.
Released:
August 30, 2021
Original Link:
https://youtube.com/watch/GObPgosXPPs
Your support is greatly appreciated! https://www.patreon.com/8blit
Welcome back everyone.
In our last episode we delved deeper into the Playfield, and examined how it was used in a few classic games. In this episode we’re going to take a first look into drawing sprites, and moving them around the screen. Trust me, if you’re unfamiliar with the process, you’re going to want to stick around. But first, a little history.
Atari founder Nolan Bushnell came up with the original concept of a sprite when developing the first arcade video game, Computer Space in 1971. The rockets used in the game were bitmaps that moved around the screen independently of the background. The sprites were hardwired and controlled individually with a dedicated transistor which allowed the background to be processed separately, and the sprites to be composited together with the background by the hardware.
Danny Hillis, an American inventor, entrepreneur, and scientist coined the term ‘Sprites’ while working at Texas Instruments in the late 1970’s. The label “Sprites” was derived from the bitmaps floating around on top of the background… without affecting anything below, much like a ghost… which can also be referred to as a “sprite”. The first commercial use of the term ‘sprites’ was with the Commodore 64 which was released in August of 1982.
Many game systems that followed would offer dedicated hardware sprites right up until 2004 with the Nintendo DS which allowed up to 128 on the screen at the same time, at a maximum size of 64 by 64 pixels. The Atari VCS, with its limited resources, only offered 5 dedicated hardware sprites. A huge difference from game systems that followed in their footsteps, but a big improvement to what was on the market at the time. Let’s have a closer look at what we have to work with.
The Television Interface Adapter, or TIA on the Atari VCS provides us with the following hardwired graphics registers for our sprites. The largest of them are the Graphics Player 0 and Graphics Player 1. Referred to as GRP0 or GRP1 respectively. Each of these registers are 1 byte, or 8 bit wide. With each bit representing a single pixel that is either visual or hidden. The other three graphics objects are even more limited, Coming in at only a single bit per register. We have the Graphics Enable Missile 0 and 1, and the Graphics Enable Ball registers, or ENAM0, and 1 and ENABL respectively.
Now… the height for these sprites don’t work like you may think. On most machines, a sprite is a two dimensional representation of a game object, like a player, or a car. With both a width and a height. A static sprite that’s 8 pixels wide by 8 pixels tall, requires 64 bits. But on the Atari VCS that’s many times more bits than we have to work with. Here, each player is only 8 bits in total, and only represents a sprite that is 8 bits wide, by 1 bit tall. A single scanline..
Each bit of a graphic register takes a single TIA color-clock to render, giving the player object graphics a higher resolution than we saw in the playfield graphics, at one quarter of the width.
When it comes to moving the sprite around the screen, things get a little more interesting. On most other game systems, or frameworks, we can place a sprite anywhere along the x and y coordinates on the screen. We simply give it a position, and when it comes time to render the screen, the sprite manager blits the sprite right where you told it to… usually on a screen buffer, and when the screen is rendered, it simply pushes that buffer to the screen all at once. This is technically impossible on the Atari VCS because (A) it doesn’t have a sprite manager, (B) It doesn’t have a screen buffer, and finally (C) there is no x, y coordinate system. There’s only the colour-clock, and the scanline.
Drawing the sprite at a specific scanline is fairly easy. Generally we’re already counting the scanlines as we go, so we just wait until we get to where we want it and then tell the TIA to draw it. This gives us our Y position, but placing it horizontally along the X position is another matter.
You see, we need to tell the TIA when to draw the player graphics as it’s drawing the current scanline. As soon as we tell it to draw, it does so immediately, and then continues to draw it at that position on every scanline forever, until you tell it to stop. It all comes down to timing. As we’ve learned before in a previous episode, the visible screen is 160 colour-clocks wide horizontally, or rather 76 machine cycles. If we want to draw our player graphics in a specific location on the screen, we need to wait a certain number of machine cycles.
So what exactly is a machine cycle? They’re the steps a processor executes for each instruction you give it. They are Fetch, decode, execute, and store. The processor will first Fetch the instruction from memory, Decode the instruction into commands, execute the command, and then possibly store the results back into memory.
If we follow this piece of our code, it simply counts down from 10. When the BRANCH NOT EQUAL instruction is executed it checks if the Zero flag has been set in the Processor Status register and branches accordingly. Using this as an example we can get a feel for how many machine cycles are used per instruction. Here we can see that the LOAD X instruction takes 2 machine cycles to assign the immediate value 10 to the X register. DECREMENTING X takes 2 machine cycles to execute. BRANCH NOT EQUAL takes 2 machine cycles if a branch is not taken, and 3 machine cycles if it is taken. So let’s try calculating how many machine cycles this loop uses. The first iteration will use 2 to load the value 10 into x, 2 to decrement x, and then 3 to jump back up to our loop point. Using up 7 machine cycles. The 2nd through 9th iterations will use 2 to decrement x, and then 3 to jump back to the loop point using 5 machine cycles each loop, for a total of 40 machine cycles.The last iteration will use 2 to decrement x, and then 2 on the BRANCH NOT EQUAL instruction because the zero flag on the processor status register has been set, so it will continue execution with the next instruction that immediately follows it. This iteration only uses 4 machine cycles.
All together… This simple loop uses 51 machine cycles, that’s 67% of the time it takes to draw the visible screen. That’s pretty tight, but luckily for us we can stand on the shoulders of giants and learn the tips and tricks from over 40 years of game development for the Atari of VCS. So, now that we know the theory behind positioning player graphics horizontally on the screen, let’s have a look at how this can be implemented. Today we’ll only focus on placing Graphics Player 0 because it’s a pretty big topic all on itself. We’ll follow up with the other objects in a future episode, along with some of the modifiers available to change their appearance.
As we’ve already mentioned, the Graphics Player 0 register is 8 bits which represent the pixels used to draw a single scanline of our player. The other registers used to draw our player are as follows.
Reflect Player Zero, is turned on by setting the D3 bit. When on, the bits in our player graphic will be reversed, meaning it will use the least significant bit first. Setting D3 back to zero will restore the bit ordering of the player back to using the most significant bit first.
Reset Player Zero sets the horizontal position of the Graphics to the current position of the beam on that scanline. This is a strobe register so writing any value to this address will set the position.
Horizontal Motion Player Zero allows you to fine tune the position of the graphic by up to 8 pixels to the right, or 7 pixels to the left.
Horizontal Motion Clear will clear all horizontal motion registers including the player and missile for both players and the ball as well. This is also a strobe register so writing any value will clear the motion registers.
Horizontal Move will apply the finetuning set in the Horizontal Motion registers. This is a strobe register as well, so writing any value will apply the fine positioning. This should only be used directly after an WSYNC to ensure the operation starts at the beginning, and finishes before the end of horizontal blanking.
With these 6 registers you will be able to place your player graphics anywhere on the screen… in theory… but in practice… it’s a little more complicated.
Let’s start with the Graphics Player Zero register, or GRP0 for short. As we know, it’s an 8 bit register representing 8 color clocks, or 8 pixels on the screen. It’s fairly easy to work with because each bit is a single pixel, so if we load up the register with an 8 bits binary then we’re assigning which bits will be drawn and which will be empty spaces. The color used for these bits can be assigned to the Color Player Zero register, or COLUP0 for short. In this case we’ll use a white.
Now we’ll just strobe the Reset Player Zero register, or RESP0 for short. A strobe register is a trigger that is activated when ANY value is written to it, so in this case we’ll store whatever value happens to be in the accumulator register. This will trigger the TIA to start writing our Player Graphic on the screen wherever the beam current is on the scan line. Or more precisely, it will be drawn after the 6507 interprets our instruction, and the TIA prepares to draw. This takes about 5 color clocks in all. If we execute this code we can see a couple of things here.
First is the position of the graphic. Since we didn’t take into account any timing at all, it was placed on the screen where the beam happened to be at that time. If we want a bit more control, we’d place a WSYNC right before we call Reset Player 0. There we go, now it’s positioned at the left side of the screen. It’s a few colour clocks away from the edge, and there’s a reason for that, but we’ll cover that in a moment.
The second thing we need to observe is that the player graphic is being displayed on all scan lines at exactly the same place. This is because the TIA will continue drawing the player graphic in the exact same place until you tell it to stop. It maintains this horizontal position because the TIA stores this timing in an internal register.
Let’s introduce a little bit of timing to our code and get it positioned where we want right where we want it.
As we saw moments ago, strobing Reset Player 0 right after a WSYNC sets the position of the graphic close to the left side of the screen. Infact, if we strobe the register at any point during a Horizontal Blank period, which is the first 68 clock-counts of a scanline, then this is exactly where the player graphic will be drawn.
However, if we introduce a little delay after the WSYNC to bring us to a point after the Horizontal Blank then we can position it further away from the left side. We can do this in our code by using the macro function SLEEP. All this does is tell the assembler to insert a number of NO OP instructions into the binary file. A NO OP instruction is just that, No Operation, it just wastes machine cycle… More specifically 2 machine cycles.
Just provide the SLEEP macro function with the number of machine cycles you want to waste and the function will work insert the appropriate number of NO OP instructions.
It’s important to note however, that the sleep function is not practical for your game code, nor can you change the number of machine cycles it uses while your code is running. This is only used to pad your code with a specific number of machine cycles and can’t be changed later. So let’s change the number we’re passing to the sleep function and see what happens.
Anything that is 19 and below will take place during horizontal blank, so we need to set it after that, so let’s try 20. We can see that it’s moved over to the right by 3 color clocks, or three pixels. Increasing this number even more we can move it all the way over to the right.
So the question you may be asking yourself right now is… if each instruction moves by 3 color clocks, is that the smallest move you can make? It seems a little choppy to me…
And the answer to that is…. No, the smallest you can make… is actually 15 color clocks.
You see, you can’t write a game using the sleep function to move your player graphic, because it can’t ever change. Your graphics will always be in the same place, and just not a fun game.
In order to place the player graphic where we want it on the screen we’ll need to create a routine that uses up a variable amount of machine cycles. What we need… is a loop…
This is as basic as it gets. First we set the position we want to to draw the player graphic in x, and then we decrement by one. If the decrement results in a zero, then the zero flag on the processor status register will be set and then branch not equal will exit the loop, if it’s not, then execution will branch back to the loop label.
It’s such a simple loop taking only 5 machine cycles. 2 to decrement the x register, and 3 to take the branch back up to the start of the loop. Since there are 3 color clocks per machine cycle, this leaves us with a granularity of 15 color clocks. Now we can move the player graphic around the screen.
Still…. That would be a pretty boring game if we had to jump around like that. Luckily the TIA can help fill in the gaps, quite literally. The architects of the TIA provided the ability to overcome the limitations of a 15 color clock granularity with the Horizontal Move Player 0 register, or HMP0 for short. It allows the TIA to adjust the graphic player 0 to the left by 7 color clocks, to the right by 8 color clocks, or not to adjust the position at all.
This is an 8 bit register, but only uses the 4 high bits to specify the adjustment you want to make. Looking at this chart we can see that setting all four bits to 0 will disable any adjustment, but setting it to all 1’s will adjust the player graphic one color clock to the right of the course position we set when strobing reset player 0.
The value stored in Horizontal Move player 0 will remain until you change it, However, the adjustment will not be applied unless you strobe another register called Horizontal Move.
Horizontal Move should only be strobed during horizontal blanking so you should do this immediately after a WSYNC. Strobing Horizontal Move will also move all of the other graphics that have had their only horizontal adjustment register set. Like the other player, missiles and ball graphics.
Taking a look at the third example for this episode, we are first loading the x register with all 4 of the high bits set, which will move our graphic by 1 color clock to the right. Then down in our main loop we strobe the horizontal move register right after a WSYNC. Doing this, we’re strobing horizontal move on every frame, moving the graphic by one color clock each time. What we observe here is that the TIA remembers where the player graphic was last positioned, and then applies the adjustment to that position moving the graphic across the screen in a smooth scroll… 1 pixel at a time. The best part about this is that we only set the player graphics position 1 once at the initialization of the code, and then used the player graphics horizontal move register to move it around the screen, taking only 3 machine cycles per frame.
Now we have the capability to position a player graphic anywhere horizontally on the screen… but playing with a long line on the screen still isn’t very interesting after a while, no matter how smoothly it scrolls… but, with a little trickery… we can turn what we have now into something a little more interesting.
We’ll examine the code to this in the next episode, but it’s available right now in our GIT repository. Check it out!. In this episode we’ve only focused on the Graphics player zero object, but everything we’ve covered so far works about the same for Graphics player one, both player missiles and the ball graphics. Next episode we’ll also cover the other objects, size modifiers, creating bitmaps for our objects, and sprucing them up with a little color.
If I’ve peaked your interest in developing for the Atari VCS please let me know by liking and sharing this video, and If you haven’t done so already, please consider subscribing to the channel. We’re slowly growing together and I’m really looking forward to what we can become. As always, the example code for this episode, and all the previous episodes are available in our GIT repository… which is linked in the description below.
Thanks for watching, and I’ll catch you next time!
Back to top of page