skip navigation

More Sprites & Color on the ATARI VCS

Description

In this episode we explore how to add color to our graphics, the pitfalls of poor timing, spanning multiple scanlines, drawing duplicates of our graphics and spacing them apart. Lots of information is packed in here and plenty of code examples in our github.

Released:
November 15, 2021

Original Link:
https://youtube.com/watch/T-6WY-JdjFo

This episode is part 2 of 'Drawing Sprites on an ATARI VCS?'

Transcription

Your support is greatly appreciated! https://www.patreon.com/8blit

Welcome back everyone! In this episode we’re going to continue exploring how to draw graphics on the atari vcs, some difficulties you may encounter, as well as some really interesting modifiers you can apply to your player, missile, and ball graphics. Stick around because we’ll be taking you on a wild ride as we attempt to get our graphics from our code on to the screen. let’s get started.

In the last episode we introduced the player graphics registers, briefly touched on the missiles and ball registers, as well as learning how to position our graphics along our x and y axis. If you missed that episode, you can scan the qr code on the screen, or find the link in the description below. We ended the episode with a quick example of a two dimensional sprite as we smoothly scrolled it across the screen. Let’s look at another example of a two dimensional sprite and see if we can add a little color to our players.

Let’s start by defining what our graphic is so we can better understand the issues we need to address when drawing it. As we know from the previous episode, the player graphics are 8 bit registers, with each bit representing a pixel that’s either on or off. With this… We can use multiple bytes to define our two dimensional graphic. Here’s a basic cat graphic made with several bytes represented as binary values… and this… Is what we’d expect to see when it’s drawn to the screen. However, due to the limited number of cycles we have per scanline, we need to be very careful with how we choose to draw our sprites.

At first glance we could do something basic like checking which scanline we’re on, and if the graphic should be on it, by maintaining an index of which line of the graphic we’re currently on. We could implement this by first initializing our graphic index to zero and then setting the default value to write to the player graphics register to be zero as well. We then load the scanline, and then subtract the y position of the player graphic from the current scanline.

If the result of the subtraction is less than 0 then our scanline is already past our graphic. So we would jump down to clear the bits on our player graphic. If it’s more than zero we’ll subtract the height of the graphic to determine if we need to draw a line. If so then we’ll load the current index for the graphic, and use that to obtain the correct byte of our player graphic. When we have that we’ll increase the graphic index for the next pass. After that, all we need to do is wait for a horizontal blank, write our player graphics to the register, move down to the next scanline and then jump back up to the start of our kernel to work on the next scanline. Now, this doesn’t look like much, but what it does… Uses a lot of machine cycles, and we’ll probably want to save some of those to handle other objects we might also want to draw to on the screen. Infact… adding up all those instructions brings us up to a whopping 39 machine cycles when we’re drawing our cat, and 41 maximum cycles when we’re not. That’s more than 50 percent of our available cycles per scanline. In the end though, if we execute our code, we’re introduced to our brand new buddy.

Using this method to draw our graphics is very limiting. At best we could probably squeeze in a second player graphic on the screen… But that wouldn’t leave time for any playfield graphics… color… player missiles… Or the ball. So… we’ll need to revise how we approach this…

So this time, we’re going to do a couple things differently. The first change may seem a little weird, but we’re going to flip our graphic definition. This is due to the calculation we’re going to perform to determine which line of our graphic to draw on each scanline. So we’ll carefully reverse the order of the bytes to start at what will be the bottom of the graphic. We’ll also add a zero’d byte at the beginning… To use to clear the graphic register when we’re not drawing. Second, we’re not going to be checking which scanline we’re on. Instead, we’re going to create an offset variable that will count upwards towards zero. Or more specifically, it will count up… to 255 which is the maximum value an 8 bit register can hold…. And when increased again by one it rolls over to zero. We get this offset by creating a copy of our graphics y position at the start of the frame, and increase it by one. Each scanline. When it rolls over to zero we display a line from our graphic. Which brings us to our third change, we’re going to replace our index that tells us which line of the graphic we’re going to draw, with a simple calculation. Now, we’re going to determine which line to display by substracting our temporary y position from the totalnumber of lines in our graphic. The way we’ve set this up, on lines where our graphic should be drawn, the result of that calculation will be the index of which line in our graphic to draw. Any other result will be negative… in which case we’ll clear the graphic register and draw nothing. So let’s have a look at how that would work.

We start by loading our y position, and storing it into a new temporary variable. Then… For each scanline we load the height of our player graphic… set the processor status register’s carry flag… and use the isb instruction to increase our temporary y position by one, and then subtract it from the height of the graphic… The result of this calculation will be stored in the a register. We’ll talk more about the isb instruction in a future episode where we’ll cover illegal opcodes of the 6502 processor. After isb is executed it will set the processor status carry flag if the instruction resulted in the register rolling over. Then we override the results of our calculation currently in the a register and set it to zero to pull the first byte of our graphic which we had set to eight empty bits. Next we’ll transfer our new graphic index from the a register to the y register. We do this because the load instruction can only use the y register to pull a memory offset. So in this case we’re asking it to give us the value located at the nth byte starting at the memory address represented by cat underscore a. After this we just wait for the next horizontal sync, and then push the graphics byte to the graphics player zero register… And finish up the rest of the scanlines. All these changes reduce our maximum cycle count from 41 down to 34. A modest improvement.

We’ve given ourselves a little more breathing room to add more complexity to our graphics, so how about we give our little buddy some color? One of the limitations we have with our player graphics is that there can only be one colour per scan line. There’s just no time to change our color mid graphic on a scanline. There is a way to fake this using two overlapping player graphics. This was in a few games, but we’ll touch on that in a future episode.. For now we’ll be applying our colour at the beginning of each scanline by writing a value to the color player zero register. We’ve already seen this in our previous video, but this time… We’re going to create a data table with the color values.

Just like we had done with the cat graphic, our data table will be a list of 11 bytes, in reverse order, with the first byte being a placeholder so the graphic and color data tables are the same length to avoid any additional calculations. In our kernel, we’re going to make only a few small adjustments. First, after we load the current scanline’s graphic, we’re going to move that value into the x register to save for later. Then we’ll use the same graphic line index we stored previously in the y register, to load the corresponding color value from our new data table. Now all we need to do is wait for the horizontal blank and then apply our changes. First to the graphics player zero register… Which will be set with our value stored in the x register, and then to the color player zero register with the value currently in the a register. With this done, we can run our code and see our little orange buddy with a white tipped tail and toes.

we’ve managed to bring our buddy into the technicolor world…; now let’s see about giving him a friend to keep him company, while using the last few remaining cycles we have left. It’s going to be tight, but there’s still a few tricks we can use to help us out.

Just like we did with our cat graphic, we’re going to create a data table to define bits in the shape of a rough estimation of a dog. We’re going to reverse the order of bytes in our data table… just like the cat because we’re going to be building upon the base we’ve already created. This time we’ll create an extra variable to temporarily store the player zero’s color in memory and load it again when we need it. Additionally, we’ll need to create our variables to hold our player 1 y position, as well as our temporary copy of the player 1 y position. Our routine to get the next line of the cat graphic is almost the same, except we will be storing the line color in our temporary color variable, and after we load the graphic line… we immediately store it in the x register. We need to reuse the a register later for our dog so we need to shuffle the registers a bit, and we can’t use the y register for this purpose, because the load a instruction can only use the y register for the memory offset when loading our graphic byte from its data table location in memory. Now we can work on our routine to draw our dog. Most of this is the same, except we’re not yet implementing our dog in color so we’re not loading from a color table. After the horizontal sync we’re pushing our player 0 graphic from the x, our player 1 graphic from the accumulator, and then loading and pushing our player 1 color and when we execute our code we can see our buddy has a new companion. Now that we have our dog on the screen, let’s add a little color as well.

Just like we did with our cat, we’ll create a data table for our new friend to hold the color values for his graphics. We’ll also use another variable to temporarity hold the player one color until we need to use it. Now looking at the drawing routine again, we’re now loading out graphic color and storing it into a temporary variable. Loading the grapic into the accumulator, and then loading our cats color from it’s temporary variable into the y register. After our horizontal sync we push our player graphics into thier registers, our player zero color from the y register, and then load and push our player one color. That’s all it takes to draw two player graphics with color. Except… It doesn’t work.

When we added the extra code we went over the maximum machine cycles for a single scanline, ending up with a total of 94 cycles in our kernel!

We’ll need to carve out 16 cycles to fix this, but there’s not too much we can do. If we revisit our code, there’s one interesting feature of the tia that we can try. There are three registers that allow you to delay the drawing of the graphics. These are vertical delay player 0 and 1, and vertical delay ball.

Vertical delay player zero will delay writing the graphics player zero register until the graphics player one register is written…. While vertical delay player one works in the opposite manner. Vertical delay ball will delay writing the enable ball register until graphics player one is written. Each of these register can initialize their delays by setting the first bit of their registers. we’ll initialize our delay on our graphics player zero to wait until graphics player one is written. This will be done outside of our kernel by setting the first bit of the vertical delay player zero. Down in our kernel… now we can change our cat drawing routine to immediately store our player graphic directly to graphics player zero. This means that we can save some cycles by not having to save the value, and then reload and store it again. By tweaking our code and moving some things around we’ve managed to reduce our kernel down to 78 machine cycles exactly. so let’s execute our code and bring our dog into our colorful world. Except.. It still doesn’t work….

The reason for this is pretty simple. While our kernel is 78 machine cycles, the last three are used to write to wsync.. Which means by the time wsync will actually execute, we’ll already be on the next scanline. Resulting is the instability we see here. There is one way we can fix this…. By removing the wsync. If we can pad our kernel to be exactly 78 machine cycles then we don’t even have to wait for the horizontal sync because we’ll already hitting it. So by removing wsync all we need to do is use up another 3 cycles, and we can do this by using a store instruction which takes 3 cycles. For our purposes we’ll just duplicate the last write to the color player one register. Now, when we execute the code we can see that we’re finally generating a stable screen, and we have our two friends… In full color.

so… We have a stable screen… but again… we’ve run out of machine cycles to use to do anything else.

Another approach we can take is to use two or more full scanlines for each graphic line on our screen. We can do this by adding another wsync sometime after writing to the player graphics registers, or padding our code again to use an additional 78 machine cycles.

However, the consequence of this means that each line of our graphics is now at least twice the height. Now that we have our two slightly larger friends, let’s make a slight adjustment so they can be facing each other…. The reflect player zero and one registers will flip the order of bits when drawing the player graphics. We can turn reflect on… By setting bit four of the reflect registers.

as we can see in this example, our cat is now facing our dog. They look happy enough, but…. Let’s see about adding some missiles! Just like the player graphics, the player missiles are positioned horizontally on the screen in exactly the same way. But there are two really big differences. The first difference is the size of the graphic. While the palyer graphics each 8 bits, the missile graphics are only a single bit. You can either turn them on or off. This is done by setting the second bit of the enable missile zero or 1 registers…. The second difference is how we set the color of each missile… or rather, how we don’t set it. You see, the missile graphics don’t have their own color registers. They use the same registers as the player graphics. So if you want to change the color of missile zero, you need to change the color of player zero, and vice versa.

Here’s an example of our two friends along with their missiles drawn behind them reaching all the way from the top to the bottom of the screen….. As you can see here, the scan lines that are shared between each player and their missile use the same color.

We’re already covered how we can position missiles on the screen just like we do with the player graphics… And we move them around the same… but there is another way. Each missile has a reset register associated with them. Writing a value to the second bit of the reset missile player zero, or one registers will reposition the missle to the center of the player’s graphic. So instead of positioning the missiles separately, we can just reset them to be drawn along with the player graphics. In this example we can see both firing a missile at each other…. Which moves across the screen for 60 frames and then resets to their player positions.

along with the two missiles, we also have the ball graphic we can use. The ball graphic works in exactly the same way as our missiles when it comes to placing it horizontally using the reset ball register, and to show the ball graphic on the screen using the enable ball register. Just like the missile graphics, our ball graphic doesn’t have it’s own color register. Unlike the missile graphics, instead of using the player color… it uses the playfield color.

so now that we’ve seen how to position and draw our 2 players, their missiles and our ball, we can talk about another graphics register we have at our disposal. The number and size register. This register is packed tightly with features. Not only can it change the size of your players and missiles, but it can draw duplicates of them on the screen… At the same time. This register only affects the player graphics and the missiles. However, we can also change the size of the ball graphic, but that’s done through the control playfield register…. We included these three registers together because the attributes used for modifying the missile size, is the same that’s used to modify the ball size….. But let’s first have a look at changing our player graphics size.

having a look at the player width values, we can see that we have three sizes available to use. Normal size where one bit is one colour clock in length, double size at 2 colour clocks in length, and quadruple size at 4 colour clocks in length. Likewise, the missiles work the same way with….. Normal size… double size… quadruple size… and this time, also octuple. When it comes to the ball graphic, we’re just writing these same values to a different register, but the outcome is the same. Normal size… double size… quadruple size… and octuple.. Along with increasing the size of our graphics, we can also make duplicates of the player and missile. We can have just a single graphic… Or two…. Or each three…. Not only that, but we can adjust their spacing at 2 copies tight together….. Medium spacing…… And wide spacing. If you’re not content to settle for only two copies, we can push this to three copies, tight together…. Or with medium spacing….. You may have noticed that the binary values for each of the attributes don’t conflict with each other, which enables us to combine attributes together. Here’s an example of a quadruple sized… Tripled player…. With octuple sized missiles.

This has been a huge episode, and we’ve covered a lot of material. We’ll do a deeper dive on each of these topics in future episodes, but in the meantime our github has been updated with nine more examples covering everything we’ve talked about in this episode, and more. Feel free to pull the examples to try and modify for yourself. Each example has plenty of comments, and if you have questions about them feel free to let me know in the comments below. I want to thank everyone for your continued support. 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 just getting started, and there’s a long way to go! As always, the example code for this episode, and all the previous episodes are available in our github… Which is linked in the description below. I’d like to end this episode with a question. What’s the very first game you played on an atari vcs, where were you when you played it, and who was with you? Whether it’s on an original atari vcs in 1978, or playing one for the first timeat a retro gaming meetup. Please share your memories in the comments below.

so…Thanks for watching, and i’ll catch you next time!

Back to top of page