Flaws in the First Plot Routine
My first plot routine was plotting pixel by pixel on the Dragon 32. Whilst you might be able to run a few thousands of instructions per 50th of a second for arcade speed, you quickly burn up instructions in tight loops to plot objects, and there are 8,000 bytes of information in a 320x200 bitmap screen. Rebuilding the entire picture every frame was not an option back then.
Every byte on the screen shows 8 consecutive pixels, so firstly you want to be getting all 8 pixels right in one go (or two goes, as it often turns out), not plot them one at a time. Doing screen address calculations per pixel is highly inefficient, there's a multiply in there, or at least a table look-up. You can also do clipping in X and Y dimensions en-masse. So, having done it completely the wrong way first time, I set about converting Steve's Spectrum plot routine.
Code Design
When designing any routine, you should focus on the output, what you want to get out of it. In the case of a plot routine it's getting blocks of data onto the screen. Next you need to consider the input data, i.e. the graphics. Now if you can design your own input format then it's best to design your input format to be as close to your output format as possible.
I didn't know Z80 assembler, just that it had a lot more registers than 6809 or 6502, so it would not be possible to do a line-for-line conversion from Z80. We could reasonably convert assembler to Z80 with some efficiency, but I hadn't written anything yet! My conversion process was that Steve would give me an overview of the Spectrum game, routine by routine, and where I needed it I would get a detailed explanation of data formats and what the routines would do. We had both been taught Jackson Structured Design, so we had a common methodology and were able to draw diagrams and understand a common terminology.
I also didn't remember until this week(!) that the Spectrum screen bitmap layout was different from that on the Dragon 32, and Steve's first plot routine was very character-focussed. His input format was to break the bigger graphics into 8x8 pixel blocks, i.e. characters, and plot those. Every character takes 8 bytes on the screen and are consecutive in memory, moving down the screen, but then to move down to the next character below we have to add an extra displacement. Likely this was the same on the C64, not so the Dragon 32. The Dragon 32 bitmap was more linear, not really broken down into characters. It only comes down to which counters go to make up which bits of the address for the graphics chip to fetch the data, but did make a big difference to the construction of the plot routine.
We tended to work from the top left of the object, just as the C64 hardware sprites worked. So we calculated the address on the screen of the top left point of the object, as a starting point. There are good reasons, in hindsight, to work from the middle of an object. It's just as easy to incorporate X and Y offsets back to the top left as it is to check collisions by adding X and Y offsets to the centre. It's also useful for background map positioning to know the centre position. Rainbow Islands tended to need to know what's under the object's centre feet position, and what's just ahead of the feet position in whichever direction it's going.
World Co-ordinates.
The screen pixels on the 8-bit machines go from 0 to 319 left to right and 0 to 199 top to bottom. You might reasonably want objects to exist beyond the screen edges, so we'd run a bigger co-ordinate system for the world, or space. For a wraparound co-ordinate system it's smart to use a power of 2, say 512 pixels in X and Y. That gives us a 9-bit number and we can just remove the top 7 bits from a 16-bit number by ANDing them out, there's no need to check the numeric range and adjust, which would take more instructions and be slower.
For 3D Space Wars the view is first person, so the player view defines what you see and generates the offset to add to the world co-ordinates to make screen o-ordinates. The player view position is similarly ANDed to keep the world co-ordinates from 0 to 511. This effectively gives the illusion of full rotation in X and Y dimensions and if you keep going down you'll see the same view again as it goes from 510 to 511 and back to 0.
Neat Alignment
The initial phase of the plot routine might be to just map one test input block onto an exact character position. That's the simplest form and you can check that you don't plot anything off the edges of the screen. You just work out the address to start your display and OR 8 bytes of data to consecutive bytes on the Spectrum, or 8 bytes 40 bytes apart on the Dragon 32. It was at this point I realised that the 8-bytes a character rule doesn't have as much relevance to the Dragon 32. The object can be split up into 8 pixel wide stripes of any depth. After 1 game I wrote a graphics editor that that was marked up in 8x8 character blocks, but I realised that I could make the plot routine more efficient by not breaking the vertical slices into 8's. I had less registers to do this too.
Not-So-Neat Alignment
For objects that don't happen to be plotted on the 8-pixel boundary horizontally they took much longer to plot. Firstly you have to rotate the 8-bit graphics between 1 and 7 pixels to the right, making them become 16-bits wide and then you have to plot two consecutive bytes onto the screen. It takes more than twice as long to do, and happens 7/8 of the time. Some early games not pressed for space might have considered storing pre-rotated graphics. We saw this on the Atari ST for backgrounds too, and was, I believe the basis of Spectrum Uridium.
Further Complications
As an object starts to slide off the side of the screen either to the left or the right (fortunately nothing was ever big enough to clip off both sides), we may need to skip over the left section of the data, or stop before we get to the right-most vertical stripes. Additionally we may have to clip off the top or the bottom and skip data per vertical stripe or finish the stripe early and move to the next. Typically the plot routine splits up into different X clipping configurations early on, and then within each of those it has to set up the plot loops according to Y clipping. Being the most oft-called routine in the game it's important to make the plot routine as efficient as possible.
I'd probably not have been able to write such a big routine without an assembler, it likely took me a good 2 weeks to get it right with one. In order to reduce the plot overhead slightly I put a border of graphics round the game screen. That actually made it easier to see if the clipping was working because any incorrect plotting would show up as overwriting my border. You really don't want to be plotting accidentally above or below the screen, you might hit something important, like your own code.
Initially it's a good plan to put one object under joystick (or later mouse) control. You can drag it to the edges of the screen, and across the edges to see that the clipping is working. It was interesting to see what happens before we got the routine working properly. Then you have to push the object to and across the 4 corners as those cases are also all different. Sometimes the object would distort, or disappear early. This plog routine was the one that I spent the longest on, but it teaches you about how the screen works. We only logically ORed the data onto the screen because we were plotting onto a predominantly blank screen. We were also writing directly onto the display screen, not onto a back buffer. Hence you do see some flickering.
Unplotting
Once we got the plot routine written, we created its sibling un-plot routine. This removes a graphic from the screen. To reduce the flickering as much as possible we would go through the objects one by one and remove each one and re-plot it in its new position as quickly as possible. This differs from 16-bit strategies as we started later to have enough memory for back buffers and were plotting graphics cleanly over others with masks, behaving more like hardware sprites.
Space ships
We drew the enemy space ships from more or less front-on in about 8 different sizes from a couple of pixels across to about 24 x 24 pixels. This gives the illusion that the ships are coming towards you, though it was only an animation from smallest to largest. We also increased the X and Y speeds over time, and then if the ship goes off screen it "re-generates" itself at its smallest back in the middle-ish of the screen as if it's a new one coming in to attack. With the wraparound co-ordinate system simulating the player ship turning round in the X and Y directions it gives the illusion of flying through space.
Illusions
That's what our old games were doing a lot of the time, creating the illusion of reality, removing the mundane processes to create something that is fun. We weren't trying to create photo-realistic images, but it was an aim to add little semi-realistic touches like shadows, fragments, glowing lights, accelerations and gravity. Just beware that if you get it really wrong it will be noticed.
0 nhận xét:
Đăng nhận xét