Anyone who was able to program a game, let alone a decent game, on the Atari 2600 deserves a hearty toast from all of us who enjoy the fruits of the industry it helped to establish.
Someday I may start a series of posts dissecting video game programming in general. It's an interesting challenge on any platform, because the illusion of things happening in real time is always just that. There's always something more methodical going on in the background, data and logic and numbers and inputs being routed and compared and acted upon by the hardware and its various subsystems. With everything getting done and updated in time for the next screen refresh, if the game is to run smoothly.
But the Atari 2600 is a real beast to deal with, and the gulf between early titles like Combat and late releases like H.E.R.O. is due not to any hardware or development tool advances, but to dedicated coders figuring out how to get the 2600 to do lots of stuff it was never designed to do. I toyed with the idea of writing a 2600 emulator years ago, before good ones became available for the downloading, but never really made much headway, primarily due to speed issues. Still, in the process I learned a lot about how the 2600 works.
The 2600 was designed in the mid-1970's to play Pong-style games. That was really about it. Titles like Pitfall! and Demon Attack were never part of the original vision. At the time, memory was VERY expensive, and CPUs weren't cheap or fast either. And of course, everything was programmed in assembly language -- there were no C++ compilers for the STELLA chip, no overhead available for tool libraries or off-the-shelf engines, no onboard ROM for letters/numbers or operating system functions. So all the memory management, variable handling, stacks, etc. had to be handled by the game code.
Cartridge memory size was a limitation too -- 2K and 4K were the original limits, but bank-switching allowed larger games, at the cost of doing coding and timing tricks to facilitate switching from one bank of ROM to another just when a new bit of code or data was needed. But with the 2600's limited graphics capability, this wasn't a huge stumbling block. Games were simple and focused on gameplay mechanics, not the visual splendors of the next level, so storage capacity didn't have a huge impact on the end result. But it was a constraint.
And then there's the big limitation: The Atari 2600 has almost NO video memory. There is never a complete screen image held in a buffer anywhere. What the Atari 2600 has is just enough memory to display a SINGLE SCAN LINE on the television.
This is a huge implication. As a programmer, I can't just draw my playfield once and then focus on running the game logic on top of it. I have to be constantly updating that one line, in perfect synchronization AS IT IS BEING DRAWN ON THE TELEVISION BY THE CATHODE RAY. Every time the screen refreshes, which happens 60 times per second, I am starting over and updating that single-line buffer 192 times to draw a part of my background and moving objects, at least if I'm at Activision or Imagic where single-line resolution was the goal. Other games, Atari's early efforts included, relied on half the resolution, updating the buffer 96 times per refresh on every other scan line, so the program isn't quite as busy but images are blockier in the vertical direction. That's still more than 500 refreshes per second.
In practical terms, this kills me. My program is kept very, very busy just drawing the screen most of the time. I have to implement MOST of my actual game logic -- looking at what the joystick is doing, detecting interactions of the player character and other objects in the game, updating my variables for score and character positions -- during the Vertical Blanking Interrupt. This is the very brief period of time when the cathode ray has finished one pass down the TV screen and is readying for another scan starting from the top. It gives my code a chance to breathe and handle the gameplay, before I get back to the work of painting the screen line by line.
The hardware does help out a bit. The 2600 has a background handler of sorts -- I have a few bytes I can use to draw VERY wide pixels in two colors, but there's only enough memory to cover one side of the screen (on a single scan line, again). So I can choose to make my playfield symmetrical (mirrored left to right) or matched (background on left half of screen is repeated on the right half of the screen.) Just about all 2600 games visibly demonstrate this characteristic, as it was very difficult to change the background map halfway through a scan line. But I can change the playfield bits on every scan line, so I can draw a background with some degree of vertical detail.
The 2600 also has sprites -- again, stored as single-line items, so if I want my sprite to have detail or multiple colors I'd better be changing its data on every scan line. Actually, it has two sprites, two missiles, and one ball. Missiles and balls are single pixels. I can change a missile/ball's position on each scan line and redisplay it to make it look like it's a vertical series of pixels. And I can reuse these objects as the ray moves vertically, so a sprite I drew at the top of the screen can be reused a few milliseconds later at a lower position on the screen, creating the illusion of multiple sprites. But if I have more than two moving, detailed characters and they're all on the same scan line, I can only draw two of them on each screen pass.
This is the source of the flickering common to 2600 games. For example, on one pass, I draw the player and enemy A, and on the next one, 1/60th of a second later, I draw the player and enemy B. This keeps the player sprite solid and lets the enemies be reasonably visible, each being shown 30 times per second. Of course, the infamous Atari 2600 Pac-Man took the laziest possible route, drawing M'sieur Man and only one of the four ghosts on each cycle for the entire screen. This caused the ghosts to be only faintly visible, showing up only 15 times per second out of a possible 60, and it looked awful. In fact, I don't recall the monsters ever being known as "ghost monsters" prior to this version; the name change was just Atari's transparent attempt to cover a woefully unsophisticated bit of code. The later Ms. Pac-Man cartridge for the 2600 rectified this, and the monsters only flicker when they're actually on the same scan line.
The Atari 2600 forced programmers to optimize every bit of their code to get decent results. But it was also a very flexible piece of hardware -- because developers were programming so close to the TV display, they could occasionally pull of a neat trick and make it do more than anyone thought it could. And Atari 2600 games by nature ran at a fast, locked-down 60 fps -- there was no other choice, as if the code fell behind the unstoppable march of the cathode ray, the screen display would start to roll crazily (a bug of this sort forced Apollo to recall Skeet Shoot back in the day.) As a result, the system survived long after it became technologically obsolete -- even as Mattel's Intellivision and the Colecovision brought much more horsepower to bear, the 2600 continued to host the biggest hits.
So here's to you, Atari 2600 programmers -- you guys and gals truly sweat blood to bring gamers a little bit of pleasure back in the day. Thanks for the insanely hard work.
Friday, August 14, 2009
Subscribe to:
Post Comments (Atom)
Yeah, Stella is a bitch to code.
ReplyDeleteThis is why the first thing to often get coded, is the display kernel (Joe Decuir labeled this as VOUT in his talks about Combat, which he programmed). During each line, once the horizontal sync has happened (either via the TIA resetting the line, or via programmer strobing WSYNC), you have 76 instruction cycles, which must be carefully timed. We often do little short hand notation like:
ReplyDeleteSTA WSYNC ; next line...
STA HMOVE ; 0 (+3) trigger horizontal move
LDA #$02 ; 3 (+2)
STA NUSIZ0 ; 5 (+3)
...
LDA (LOOKUP),Y ; 61 (+5)
STA HMP0 ; 66 (+3) get ready to nudge p0 for next line
STA WSYNC ; 69 (+3) some time left over, go to next line
STA HMOVE ; 0 (+3) ... and the next line ...
...
where the first # is the current cycle count, and the cycle count in the parens is the expected instruction count.
Given that an instruction can take up to 7 cycles, depending on its function, addressing mode, whether it crosses a page boundary, whether a branch is taken, a subroutine is called or returned, or even whether indirect or direct JMPs are used, these 76 cycles are gobbled up _VERY_ quickly, and you find yourself basically just trying to look stuff up in ram or rom tables, shove the values into the right registers, and do all of this just before the object is displayed on screen.
Why do we do it? because it provides a level of intimacy with hardware that is sorely lacking in today's modern software world. The 2600 world is very clean, austere, well defined, and completely lacking in anything legacy.
It helps keep me a ninja. ;)
-Thom
Oh boy... I've been programming games since 1996, starting with QBASIC. I've written 6502 assembly for the last few years and can honestly tell you that nothing has been more enjoyable than programming for the VCS. Even though the games WILL be clunky, you will have to work your butt off just to get a decent kernel. Also, after you do that, you won't have any reusable code because each game is 100% unique. Meaning, if you want to get this or that on the screen to do 'blank', the code requirements may completely change the outcome of your project. Some games, as you mention, were written in either a 1 line, or 2 line kernel. In one of my projects, I got stuck because my update code (during the 192 line drawing code) was "eating" into my graphics. I had to either dumb-down my logic, or dumb-down my graphics.
ReplyDeleteAll-in-all, I second your shout-out to 2600 programmers of yore. I would give ANYTHING to go back to the late 70s and work for Atari! Most folks don't get it. They just think video games are toys. But for the VCS, writing games was a true art form. Zen like if you will.
-Johnny
www.thestarrlab.com - lot's of retro junk and a bit of decent code