5 ms·
Ex-C64 games coder here: you are are correct - no, you couldn't relocate/page-flip the colour map, like you could the character map. So you had to update it all
by jimsmart 4y ago
Ex-C64 games coder here: you are are correct - no, you couldn't relocate/page-flip the colour map, like you could the character map. So you had to update it all somehow on the required frame.
The fastest technique I saw for updating the colour map in a single go, was to have the whole thing as a huge block of immediate mode load-stores, then one could 'scroll' the data across the LDA instructions within that code, in advance, over n-frames, then call this self-modified code block when one did the character screen flip (immediate load-stores was faster than load-stores from colour ram). e.g.
scroll_splat_colour:
LDA #$00 # colour data for char
STA $D800 # colour ram
LDA #$00
STA $D801
# etc., for every visible char onscreen in scrolling area
And one would be updating/scrolling those values loaded into the A register, in chunks over previous frames, similar to:
LDA scroll_splat_colour+6
STA scroll_splat_colour+1
LDA scroll_splat_colour+11
STA scroll_splat_colour+6
# etc., for every lda/sta in the above
Perhaps not the clearest explanation, but hopefully enough to communicate the idea.
FWIW, I didn't invent that technique, it was an improvement Jon Williams made to my code, whilst we both worked for Images Software (now Climax). Not sure where he got it from, maybe he invented it himself, maybe he cribbed it from elsewhere.
Related: I thought sprite multiplexing was awesome, and there were quite a few tricks there too to get it performant. But that's another far more complex topic.
- vardump 4y agoHow to handle the case when player changes direction to exact opposite immediately after the frame color data was transferred? Double buffering splatting code? Although one copy of it is 5001 bytes, ouch.
- jimsmart 4y agoYou generally don't handle that case! :) — Instead you let the player move within a rectangular area onscreen, and decide on which way you are going to scroll the screen in advance (or rather: after the fact, depending on how one looks at things), based upon where the player is inside that rectangle. So the screen catches up with where the player is moving/pushing. Eight-way scrolling like this was always a massive pain on the C64 (and other systems that used buffered scrolling with no h/w, e.g. Atari ST), but that way (a box the player moved around inside) was the only realistic way of handling it if you had to do a bunch of work in advance before doing the actual scrolling. Turns out that having the player in a loose rectangle is also easier on the eye too, which is perhaps why it's also used on systems that don't suffer the same h/w restrictions. Yeah, the colour RAM update was a lot of bytes to move. But dedicating a big chunk of code to it meant one could be a little freer to use slightly slower techniques elsewhere in the update cycle. Side note: the C64 actually only had 39 visible char across the screen when in 'scrolling' mode, because the borders where shrunk-in slightly (and slightly more than one expects). So one less char to worry about per line. That saved a tiny amount of code / memory / execution-time for the colour splat (and the scrolling of partial chunks - whether on back buffers, or the data within the colour splat code - over the other frames). Sure, it's only one less character. But it saved some cycles. And cycles mattered! Particularly when doing something with that much data to move / that took that much time.
- vardump 4y ago> C64 actually only had 39 visible char across the screen But 40 color cells were still visible, unless horizontal scroll register was 0.
- jimsmart 4y agoNo, but that's an easy enough mistake to make :) — It's called 38-column mode, and when enabled the VIC shrinks both borders in by 8 pixels, and then offsets the screen according to the x-scroll register bits. [Edit: another source says it's actually 7-pixels hidden on the left, and 9 on the right. But whatever: same principle, the screen is shrunk by 16 pixels in total horizontally] Which meant that at most only 39 characters were visible across the screen — with two of those, one at each end of the row, being partially visible — and that applies to both the character screen and its associated colour RAM. Only 38 characters were visible when the x scroll register was zero, and as soon as one shifted to a value of 1-7, the 39th column became visible (and the 1st one became partially offscreen). But the 40th column is never visible when in that mode. For more info see: http://www.devili.iki.fi/Computers/Commodore/C64/Programmers_Reference/Chapter_3/page_129.html http://www.devili.iki.fi/Computers/Commodore/C64/Programmers... "When scrolling in the X direction, it is necessary to place the VIC-II chip into 38 column mode. This gives new data a place to scroll from. When scrolling LEFT, the new data should be placed on the right. When scrolling RIGHT the new data should be placed on the left. Please note that there are still 40 columns to screen memory, but only 38 are visible." — But it's discussed on a handful of other pages too, if you google.
- vardump 4y agoOh damn... and I did a fair bit of coding on C64 back in the day. :-D Somehow I thought it hid 4 pixels both sides. Totally wrong. PS. Then it's so unfair bad line still takes 40 cycles!
- jimsmart 4y agoPlease stop giving the above comment downvotes because of this person's lack of knowledge: we all have to learn things — there was once a time I didn't know this either. It's not like vardump here was being a dick about anything in their comment, cut them some slack!
- Luc 4y agoI made an 8-way full-screen scroller. To avoid situations like this the player sprite at the center of the screen had momentum, i.e. the sprite had to rotate 180 degrees to change to the opposite direction, giving a few frames time to set everything up.
- jimsmart 4y agoThat's a nice little trick, cheers for sharing. (Not that I'll get a chance to use it these days, but still)
- vidarh 4y agoAnother "obvious" trick is to narrow the playing field but animate the rest, and then safe cycles by a combination of bands that requires less updates and sprites. E.g. Pole Position is a classic example, where the graphics covers most of the screen, but only about half actually has gameplay. The rest consists of a very narrow band of mountains, and a couple of bands of clouds. I haven't looked at what they did for Pole Position, but that pattern of the actual gameplay being constrained to a much smaller portion of the screen than what looks like the playing area is pretty common.
- 6510 4y ago> # etc., for every visible char onscreen in scrolling area For every changed char. (which is sometimes more and sometimes less) You could do them in order but if you're using only a few characters you need only 1 LDA for each char. (How to do this is left as a creative exercise for the reader)
- jimsmart 4y agoBut the overheads of tracking which characters might have been changed here completely outweighed simply scrolling / updating the whole thing. The code becomes too involved in tracking changes, and fudging about with- / rewriting- the splat code. You can leave it as 'a creative exercise for the reader', but that's because you can't solve this for the generic case (i.e. any map the graphics artists might give you) in less cycles than simply dealing with each and every character, which is the worst case. Processing that many bytes, and doing comparisons and extra branches, simply becomes overheads, and, very quickly, your code is slower than simply updating / scrolling everything simply. For the colour splat routine, having a giant, pre-assmebled block of immediate-mode load store pairs for every character is as optimal as it gets — and handles all cases — on the C64, you only have a frame to update the colour RAM (because it cannot be relocated/paged), and you are generally chasing the scan beam to move that much data before the next frame. You don't have the luxury of having extra cycles to re-write that block of code at runtime, and rewrite the code that scrolls the data within that code, nor do you have the luxury of having enough spare cycles to be comparing data, and branching conditionally depending on if it has changed or not. Perhaps you misunderstand the technique I describe, or perhaps you under-estimate the overheads required to perform what you describe. Or perhaps both.
- 6510 4y agofor example: say we use only 4 chars and repeat them in the same order then we only have 4 pre-baked transitions. You could then swap all 4 characters in all 4 positions for free. I confess I was a bit amused when you wanted to give the graphics artist complete freedom.
- 4y ago
- justinlloyd 4y agoYeah, compiled graphics and compiled colour tables, also, a routine that could self-modify code in regions of RAM to do the colour table writes. A slow set-up function at level start would build the code to be JSR'd later in the level. We did that on a few games on the C64 and the Speccy and Beeb and Atari. Later used the same techniques in DOS on PC. And of course, doing the same tricks but with D0 through D7 and A0 through A6 on Atari ST and Amiga. Also doing "stuff" in zero page because the address loads were shorter. And avoiding 256-byte page boundaries where possible because of the cycle penalty.
- jimsmart 4y ago> A slow set-up function at level start would build the code Interesting, and good thinking :) IIRC, when we used this technique on the C64, we didn't build the code during init at runtime, we actually built the code in the dev environment, using macros, so it got built at assembly/compile time. So we skipped the small time hit at runtime init, at the expense of a slightly longer load time for the user (and a tiny bit longer on our assembly/compile times, although that was fairly negligible cos we were building on PCs).
- justinlloyd 4y agoYeah, we did macro builds in the development environment too, but I also had access to doing some nifty and complex macro and pre-compiled graphics code by making use of BBC BASIC to do the manipulations. Like if you were doing soft sprites, pre-compiled functions to auto-shift the sprites on the X axis without having to do any kind of ROL/ROR and handling carry bits. One of my colleagues wrote really nice set of functions where you could specify that "this sprite should have eight shifts created during setup, go create the necessary loops, toot-sweet!" or "this fast moving bullet sprite can only appear on four pixel boundaries, only needs two shifts, and can be pre-compiled at build time." There were other functions that we all contributed to for doing horizontal and vertical flips and arbitrary rotations and a ton of stuff for collision detection boxes and bind points for weapon pickups and emission points for bullets being launched, muzzle flash, or shell casings ejected from the weapon. Tons of what was effectively dynamic macro code where you just set up a table of stuff you wanted included, and some data about how certain sprites should be handled. My development environment was a BBC Micro Model B with a macro assembler and double-sided/double-density dual 400KB floppy drives attached - 800KB of storage, which let me assemble the code from multiple pieces. Later I had a 20MB HDD. The BBC Micro could squirt the code over to the C64 via the Beeb's 1MHz Tube IO bus straight in to the C64's cartridge port. Instant load on the C64 effectively. Also had the same setup for the ZX Spectrum, though when I worked at a different company, we used RML 380Z machines IIRC, and everything ran over a shared network.
- egypturnash 4y agoThat's... that's horrible. Beautiful, but horrible. Which kinda describes any advanced c64 technique, really.
- jimsmart 4y agoIndeed, I totally agree on all points :)
- SyneRyder 4y agoI thought I remembered the name Jon Williams, so I looked it up on Lemon64... and it seems that you and Jon worked on one of my favorite games as a kid, C64 Back To The Future 2! Somewhere around here I still have my genuine boxed retail copy. I remember it as one of the few games I was absolutely determined to complete to the very end. I might have to break out an emulator and give it another play for nostalgia (still have my C64 too, but haven't turned it on in a long time). Thanks for working on that, it made the little-kid version of me very happy!
- jimsmart 4y agoThanks for the memory! And I'm glad to hear you liked the game :) IDK if it's a game that really stands the test of time, but having worked on it always kinda skews one's view: it's hard to be objective. Yeah, we both worked on C64 BTTF2. Although we occasionally talked and shared a bit of code when we were working on a few other separate projects too. He came to Images after I'd already been there for a while, so he got free rein to use any code I previously written for the company, so we talked quite a bit, and he'd always discuss how he was coding stuff, and share any improvements he'd made. Nice guy, and a good coder. — Funny thing is, I never received a boxed copy of BTTF2 myself, heh. That was actually a really common thing, on a lot of games. Most of them in fact :/