Copy-pasted from a hidden fortress deep under the sea
The NES is one of my favorite consoles; I grew up with it, and still have two clones and a boatload of cartridges sitting around on my shelves.Of course, now that I have knowledge of the arcane and whatnot, I decided to actually do a bit of digging into the process of creating games for the NES. My interest was initially piqued after I watched a video by the creator of Retro City Rampage, who ported his game to the NES (And managed to leave it more or less completely intact).For three years now, I've been making music for the NES hardware, playing games on it, and designing my games after the same 'style' as popular NES titles. Actually creating a game for the machine is fun (And challenging). I'll write down my discoveries here, and other general information.What I'm trying to accomplishBasically, it started with this:For this game, I used a portion of the NES palettes (More info on that below), and obviously began designing music in Famitracker.But then a friend saw the game, and wondered aloud if it would be possible to do this on actual NES hardware. As per usual, I couldn't resist a challenge, so I began my foray into the world of the 2A03.What I have accomplished so farMore or less, I have everything initialized, I have my CHR table loaded, and I'm displaying a large sprite that moves across the screen:The repeating pattern in the background is due to the nametable being zeroed out. More on that later.The 2A03The CPU at the core of the NES is not, as most people think, a 6502. Most people mistakenly think that the 2A03 is the "music chip". They're right, in a way. But the 2A03 is also the main processor.Of course, it is still a 6502, just modified in several ways.The 2A03 is a rare type of processor, pairing both the APU and CPU functionality on one die. It has one general purpose register, two index registers, and operates in 8-bit hexadecimal mode. The 2A03 has a clock speed of around 25mhz, and several dividers pulsing the PPU, IRQ lines and ROM/RAM. The processor is more or less the same as the Commodore 64's 6502, with the exception of the addition of the APU and the bank switching hardware. The PPUThe PPU or "Picture Processing Unit", was a Ricoh RP2C02/RP2C07 (Depending on region), and was really advanced for it's time.Before the NES, all graphics processing was performed by the CPU. Pixels were drawn manually, and thus VRAM was limited (A 320x240 screen at only 8 bits per pixel is already 640K of RAM, something that was impossibly expensive back then). The NES used a few tricks to not only speed up graphics processing, but increase the resolution beyond what the current consoles had available to them (Those being the Commodore series, the Atari machines and the ZX Spectrum).The 'native' resolution of a NES console is 256x240, a really nice 'square' aspect ratio (Nearly 1, actually 1.06).To handle this memory, the console used several concepts that became staples in the Nintendo product stable (Up to the DS). The first of these was the pattern table, an 8KB location in ROM where the game's graphics were stored. Two tables exist in a standard ROM layout, each one capable of holding 256 8x8x4 tiles. Often, one table would be used to store sprites, the other for storing backgrounds.The next piece of the puzzle is the nametable. This is basically an array of 34x32 bytes, each one containing a tile reference. It's usually used to draw anything that isn't a sprite.In my screenshot above, the nametable is filled with zeroes, and tile zero is the top-left of the player sprite. So the background is filled with that.Sprites are handled in a separate way, utilizing OAM (Object Attribute Memory). Each piece of "Object Memory" is basically a structure that looks like this in C:struct OAM_Entry
{
unsigned byte y;
unsigned byte tile;
unsigned byte attributes;
unsigned byte x;
}
; This is the 'iNES' header. Used by emulators to determine what 'virtual cartridge' to use.
; This would have absolutely no effect on an actual NES, where you'd have to use a compatible ROM with
; these settings (In this case, 16KB of PRG ROM, 8KB of CHR ROM, no mapper chip, and mirroring enabled)
.inesprg 1
.ineschr 1
.inesmap 0
.inesmir 1
; Memory bank 0, the first 8KB of PRG ROM
.bank 0
.org $C000
; Include my palette data here
data_palette:
.incbin "./palette_sprites.pal"
.incbin "./palette_bg.pal"
initialize:
.incude "./src/vectors.asm" ; Includes code for RESET, IRQ and NMI vectors.
; Initialize PPU
; Enable Sprite and Background rendering
lda #%00011000
sta $2001
load_palettes:
lda $2002
lda #$3F
sta $2006
lda #$10
sta $2006
ldx #$00
load_palette_loop:
lda data_palette, x
sta $2007
cpx #$20
bne load_palette_loop ; Jump back to loop beginning if we haven't processed all 32 palette entries.
; Enable PPU NMI
lda #%10000000
sta $2000
game_loop:
game_loop_vblank_1:
bit $2002
bpl game_loop_vblank_1
; Anything drawing related would go here
; Loop forever
jmp game_loop
; Memory bank 1, the upper 8KB of the ROM.
; Need to set up vectors here.
.bank 1
.org $FFFA
.dw NMI
.dw RESET
.dw IRQ
; Memory bank 2, CHR ROM
.bank 2
.org $0000
.incbin "./chr/tiles.chr"
I've programmed directly onto a Gameboy Advance and this post was still a bit much for me. This is dedication.
@SleepinJohnnyFish I did that a while back too. The GBA has a similar system for handling sprites (Using an OAM and DMA transfer, as well as tiles).
The main difference here is the jump from ARM architecture, and the potential use of a C compiler, to working with the 6502 code, and using plain assembly.Though I will say, using assembly is oddly refreshing…@Cyrus That's what I meant, really. I keep mixing these things up.I'd love to try some effects like PureSabe is pulling off. Make a bullet hell or something.Thanks for the doc link, missed that one :PI miss Assembly. I've used it at 0 of my actual jobs during my career, sadly.
It's been more or less phased out in mainstream programming. You'll rarely (If ever) see inline or linked assembly code in games, applications or anything really. It's a pity, since it's still the way to get the most speed out of the CPU.
Here we are, speaking of milking the NES for all it's worth; but what would happen if we tried the same with the modern PC? (Well, not accounting for the tons of demos out there already).Well, with certain technology coming out, high efficiency might become a big thing again. If something like the Oculus Rift or CastAR becomes massively popular in the future, people might start considering the difference some highly efficient code in certain areas of rendering could make…
One can only hope.Well, just think of the typical Demo. Some of the stuff that Demo coders pull off in real-time give Crytek a run for their money.
@mrpete Thanks.
With regards to the accessibility though, it's not as if it was a poorly documented circuit, and there were programming magazines by the dozen that had columns full of user code and tricks for all the 65XX series (And 68k series) devices, on both the professional and hobbyist levels.The main reason people didn't start really pushing the NES until later in it's lifetime (Like HAL did with Kirby's Adventure), was because earlier on the various developers were targeting multiple consoles, and trying to push games out at a frenetic pace (More games is More money, or so they thought).After a while, things slowed down and developers started really pushing their code to the limits.Neat! I love that there are people doing stuff like this.
I've always been curious about making games for NES.
Mega, you are the only person I know who pursues interests with great zeal. I wish I had the will to do something just because.