We now have the knowledge to draw sprites anywhere we want on the screen, and could try and draw as many of them as we like, but there’s a problem…
If we try to draw two of our ‘smiley faces’ next to each other, then one will overwrite the other, as below:
The problem only becomes worse if we try to draw over a background:
This is because when we draw our graphic we simply copy it (or a shifted version of it) directly to the screen, with no consideration as to what it will be drawn on top of – using similar code to this:
; Read a byte from the graphic ld a, (hl) ; Write the byte to the screen: ld (de), a
Luckily there’s a straightforward solution to this problem, by using the logical binary operators of AND, OR, and XOR.
The OR operator combines two bytes into one by setting bits where they are in either value. If we use our graphic as one value, and the data from the screen as the other, we can combine the two:
; Read a byte from the screen: ld a, (de) ; Logically OR it with the graphic or (hl) ; Write the combined value to the screen: ld (de), a
Using this would immediately solve our first problem, but doesn’t look too good when we try it on the bricks:
The set pixels come from both the face and the bricks, but as the bricks are solid and cover most of the graphic, combining those and the face leaves an indistinct mess.
The XOR operator combines two bytes into one by settings bits which are only in one, but not both, of the values.
; Read a byte from the screen: ld a, (de) ; Logically XOR it with the graphic xor (hl) ; Write the combined value to the screen: ld (de), a
This doesn’t look much better on our bricks though:
This method does have a rather nice benefit however – if we were to XOR the face onto the bricks again, the face would disappear entirely and leave the bricks exactly as they were before the face was placed on them. This makes it very useful for drawing, and subsequently removing, any graphic from the screen. If your game doesn’t using any background graphics then this is an excellent solution, and used in most games of that kind. If you do this, you need to ensure that the graphic doesn’t move between the draws – so draw -> undraw -> move.
The third solution is to use a combination of the AND and OR operators, to provide a method called ‘Masking’.
The AND operator combines two bytes into one by setting only those bits which exist in both values. By using that we could ‘cut’ a hole in the wall where the face is to go if we have a graphic which has bits set where the face isn’t, like so:
This is called a mask graphic, and if we combine it with the wall using AND we get the following:
Now if we use the OR operator to draw the face, you can see that it no longer collides with any other pixels, and so is shown cleanly:
; Read a byte from the screen: ld a, (de) ; Logically AND it with the mask and (hl) ; Move onto the next byte of graphic data inc hl ; Logically OR it with the graphic or (hl) ; Write the combined value to the screen: ld (de), a
This undoubtedly produces the best effect – it’s what’s always used in isometric games such as Knight Lore – and also in a lot of 2D games, but it does have it’s drawbacks.
Firstly, you need to have not only your main graphics, but each one of those also requires a mask graphic – so that’s double the memory already. You can be crafty with those perhaps, by using the same mask for multiple graphics (so long as what you need to mask is the same), and a few games – such as Odin’s Heartland – had code which created the mask for each graphic on a screen when it was required. That only works for graphics without many ‘holes’ in them, but they made it work, so could you.
The other drawback is that it takes more time to draw – we need to read two bytes of the graphic instead of one, and do two combines, but that’s the price you pay for the perfect result. This applies doubly-so when you consider shifted graphics, as you need to shift the mask as well as the graphic, which takes even more time.
It’s usual to have the mask interleaved with the graphic data, so you read the mask, then the graphic, then the next mask, and so on, but it doesn’t have to be that way – the mask could come from a separate register pair (BC) for example – but having the graphics combined means we just use the single register pair to read them.
And that’s about it for graphics! 🙂