I love scrolling, yes I love scrolling a lot, I think I try and do some form of scrolling in every project I do. One of my main reasons to get into retro coding was to work within tight restrictions to see what solutions I could come up with to implement “non standard” features in hardware that didn’t support them. That was a real big push for the work I do on Pac-Man hardware.
(the video below shows a character scroller, sine wave scroller and full screen pixel scrolling together !)
With my first Vectrex programming attempts I am working on a homage to Stunt Cycle the classic ttl game from Atari (available on lot’s of 70’s TV consoles)
The Atari original features a monitor in landscape orientation, but the Vectrex features a portrait orientated monitor.
This in basic terms means the horizontal screen area on the Vectrex is only 75% of that available on a landscape monitor. After some initial rendering tests it become very clear that I needed more width. There are 2 solutions that come to mind:
1) Connect 2 Vectrexes together via the player 2 port and synchronise them to allow the 2 machines to render a double width screen.
This would be cool (very cool) and is an idea for a project for the future, once I’ve learned how to do this communication stuff (sync may be a big problem, wouldn’t know until I did some experiments – greater coders than me have probably been here a long time ago).
2) Scrolling the screen as the bike moves to reveal more content to the right (or left) of the visible viewport i.e the Vectrex screen.
To implement this we need to introduce the concept of world and view coordinates. Let’s take a look in simple terms (ignoring scaling) at how the Vectrex drawing system works:
The centre of the screen is referenced as 0,0 a pair of bytes are used to hold the x and y position of the beam.
if both these bytes are cleared (set to zero) then the beam will be positioned at the centre. a positive value in the byte representing x will move the beam towards the right, a negative value will move the beam to the left. Using just a single byte to represent this position information allows us to represent the values -128 <= x < 127 for x. Signed integers are stored in 2’s Compliment format, a very special coding system that makes the sign bit part of the number (different to how we write numbers called sign and magnitude), which enables quick arithmetic with positive and negative values.
So for an object to be on screen we have a range of 256 positions (in this simplistic non-scaled view) to place an object horizontally and vertically. In Stunt Man stories I only need horizontal scrolling so the Y positioning uses just a single byte (well it doesn’t cause I built a general multi-way scrolling system – but hey).
We can think of the viewport as a byte wide. The world can be the same width, but there is nothing to stop us making it wider than the screen, by implementing a different co-ordinate system. One obvious choice is to use 2 bytes (16 bits) to store a horizontal position of an object in the world. Doing this gives us a potential world width of 65536 positions or 256 viewports wide. Each high byte of the position represents the number of viewports across the object is in the world (as we move right 256 positions the high byte increases by one). We can then use another 16bit value to state the position of the viewports left hand side in relation to the world.
When we draw the screen we only draw objects that are in the current viewport window. The neat thing is how we do this.
To draw an object on screen we need to transform the world position into a viewport position. This sounds complicated but actually just means subtracted the viewport position from the world position of the object. What’s cool is we can inspect the high byte of the result and if it is not zero then the object is either to the left or right of the viewport (outside the viewport width of 256). If we cared we could look at the sign of the high byte -ve means to left (as in circle above) +ve means to the right (as in diamond above).
This allows us to disregard these objects for drawing. In the image above I have performed these calculations on four different objects, I showed their positions and the viewport positions in hex,, so you can quickly see the high byte result of the subtraction (the first 2 hex digits).
So this transformation allows us to find out which objects are within the current cireport and need to be drawn, but, we now need to translate their viewport position into Vectrex co-ordinates.
Let’s take the + and X objects from the previous example. IN the image above I have stated their relative positions again (in hex). Looking at the X this needs to be in the centre of the screen as it is sitting in the centre of the viewport. It’s relative viewport position was calculated as $0080 (128). In Vectres co-ordinates these needs to be 0. So we need to subtract a value from this relative position to get 0. as it is a byte value (it’s in the lo-byte of the number) the maximum we can subtract is 127 (because of 2’s Compliment representation for a byte means this is the biggest +ve value 0111 1111 or $7f). So that is what we do, this gives is a value of 1 which is close enough to be in the centre. At the end of the day we will subtract 127 to transform all the viewport co-ordinates into Vectrex co-ordinates so it doesn’t matter that it is out by 1 unit.
We do the same for the + which is at viewport position $0040, when we subtract 127. Let’s do this now in binary, we only need the lo-byte portion of the viewport number:
We want to perform the following arithmetic:
$40 – $7f (64 – 127) the answer is – 63
subtraction is horrible in binary but we can perform the arithmetic by re-writting it like this:
64 + -127
This is better because addition is easy and negation is quick. We just need to perform 2’s compliment on 127 to find the -ve version. This is easy to see in binary:
127 is 0111 1111
(the left most bit is the sign bit which states this number is positive)
to perform 2’s compliment on this value we work from the the left hand side an invert each bit (1 becomes 0, 0 becomes 1) until we get to the last 1, leaving this as a 1.
-127 is 1000 0001 64 is 0100 0000
we now just need to add these values together, using binary addition
The only slight problem with this arithmetic occurs when we have a relative position of 255. If we subtract 127 from this we arrive at 128, unfortunately this is the largest magnitude -ve number in 2’s compliment binary. this would cause an object at the very rightmost part of the viewport to be drawn at the most extreme left hand side (see the screen grid image above). The simple thing to do is to discard any objects that have viewport co-ordinate of 255.
Anyway here’s an example of the code that does all these transformations:
World coords -> Viewport coords -> Vectrex coords
position_and_draw_sprite: ldd spr_x,x ; load up the integer portion of sprite position subd viewport_x ; subtract view position to get screen coordinate cmpa #0 bne pads_offscreen ; > if a register is other than 0 then it's either <0 or > 255 cmpb #255 ; attempt to stop drawing wrapping right to left beq pads_offscreen ; subb #127 ; take 127 away so we can position based on vectrex centre lda spr_y_screen,x ; get the y position information for sprite ; d register now holds the y,x position of sprite