aboutsummaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
Diffstat (limited to 'examples')
-rw-r--r--examples/input.s170
-rw-r--r--examples/sprite.s577
2 files changed, 0 insertions, 747 deletions
diff --git a/examples/input.s b/examples/input.s
deleted file mode 100644
index 25b17b0..0000000
--- a/examples/input.s
+++ /dev/null
@@ -1,170 +0,0 @@
-;;
-;; This example shows how to read from one controller and set it into the $20
-;; memory address. The `Main` subroutine will call the `ReadController`
-;; subroutine and then increment the value on $42 if the right arrow was
-;; pressed. When running this ROM, watch for the following RAM addresses:
-;;
-;; - $20: the bitmap of the current status of the controller (notice that since
-;; we are constantly polling it and filling it, the value will move constantly).
-;; - $21: the previous status of the right arrow.
-;; - $42: the counter which is incremented on each press of the right arrow button.
-;;;
-
-;;;
-;; You can safely ignore all of this up until the `ReadController` subroutine.
-;; This is boilerplate that is explained on the `sprite.s` example.
-;;;
-
-.segment "HEADER"
- .byte 'N', 'E', 'S', $1A
- .byte $02
- .byte $01
- .byte $00
- .byte $00
-
-.segment "VECTORS"
- .addr nmi
- .addr reset
- .addr irq
-
-.segment "STARTUP"
-
-.segment "CODE"
-
-nmi:
-irq:
- rti
-
-reset:
- sei
- cld
- ldx #$40
- stx $4017
-
- ldx #$ff
- txs
-
- inx
- stx $2000
- stx $2001
- stx $4010
-
-@vblankwait1:
- bit $2002
- bpl @vblankwait1
-
- ldx #0
- lda #0
-@ram_reset_loop:
- sta $000, x
- sta $100, x
- sta $200, x
- sta $300, x
- sta $400, x
- sta $500, x
- sta $600, x
- sta $700, x
- inx
- bne @ram_reset_loop
-
-@vblankwait2:
- bit $2002
- bpl @vblankwait2
-
- jmp main
-
-.proc ReadController
- ;; The status of the eight buttons fits into a single byte. We start the whole
- ;; dance by setting the first bit of the position we are storing this info
- ;; ($20). This bit will act as a guard in the following code.
- lda #1
- sta $20
-
- ;; The 4021 chip is the one responsible to bring the input from the controller
- ;; into the NES. The console reserves two addresses on the memory for the
- ;; controllers: $4016 and $4017 (see
- ;; https://www.nesdev.org/wiki/Input_devices). If you write into one of them
- ;; first with a #1 and then with a #0, we activate the latch for the
- ;; controller, and it will start to send a bit representing the state for each
- ;; button upon each read.
- ;;
- ;; Thus, since we conveniently now have #1 into the 'a' register, we send this
- ;; value to the 4021 chip, and we follow it by sending #0. This way we tell
- ;; the controller to start to deliver the data.
- sta $4016
- lda #0
- sta $4016
-
- ;; The status of the buttons will be provided one by one following a specific
- ;; order. The algorithm goes as follows:
- ;;
- ;; 1. Load the bit you get from the 4021 chip into `a`. After performing
- ;; this read the controller will send the next one so it's ready for the
- ;; next iteration.
- ;; 2. Shift the value right so to set the carry flag as its comes (note:
- ;; overflowing from the right also sets the carry flag on!).
- ;; 3. Rotate one bit left from $20: C <- [$20] <- C. This way, we always get
- ;; the result we put on the carry register at the right-most part of the
- ;; byte on $20, and we clear the carry flag (the previous left-most bit
- ;; moves into the carry register, which is 0 until we reach the one we
- ;; planted as a guard).
- ;; 4. We jump back into `read_loop` if the carry flag is clear. This is the
- ;; case for most of the time until the #1 that we set at the very
- ;; beginning as a guard flows into the carry flag as expected from the
- ;; `rol` instruction. At this point, we have already read the full byte.
-read_loop:
- lda $4016
- lsr a
- rol $20
- bcc read_loop
-
- rts
-.endproc
-
-;; The main function will run indefinitely and it will continuously poll from
-;; the controller and increment the value on $42 each time the user performs a
-;; new press on the right arrow (that is, we want to count new presses on this
-;; button, and we don't want to increment this value while the right arrow is
-;; being pressed).
-.proc main
- ;; Initialize the value on $21 (previous state) and on $42 (counter).
- lda #0
- sta $42
- sta $21
-
-loop:
- jsr ReadController
-
- ;; Was the right arrow being pressed? If that's the case, then jump into the
- ;; `pressed` label to compare it with the previous state.
- lda #1
- and $20
- bne pressed
-
- ;; The right arrow was not being pressed. Thus, we need to update the previous
- ;; state to #0 before we read the controller again.
- lda #0
- sta $21
- jmp loop
-
-pressed:
- ;; Now the right arrow is being pressed, and we have the guarantee that `a =
- ;; 1` (because of the `and $20` instruction returning a non-zero result). Now
- ;; do the same with the previous state. If it's a non-zero result, then it
- ;; means that the previous state was already of pressed. Hence, at this point
- ;; we can return into the main loop. If this was not the case, then it's a new
- ;; press.
- and $21
- bne loop
-
- ;; It's a new press, set $21 to #1 accordinly and increment the counter on $42.
- inc $21
- inc $42
-
- ;; There and back again.
- jmp loop
-
- rts
-.endproc
-
-.segment "CHARS"
diff --git a/examples/sprite.s b/examples/sprite.s
deleted file mode 100644
index f429bb1..0000000
--- a/examples/sprite.s
+++ /dev/null
@@ -1,577 +0,0 @@
-;;;
-;; The iNES is the de facto standard for the distribution of NES binary programs
-;; and it's compatible with the format used by NES cartridges themselves (used,
-;; even, by the Wii Virtual Console). The layout is composed by segments in
-;; memory, which are defined with the `.segment` macro and known by the compiler
-;; through a linker configuration. You can provide a configuration of your own,
-;; but bear in mind that compilers like `cc65` (the one used here, which is the
-;; most common) already provide a default configuration for the linker that
-;; glues a set of pretty common defined named segments. You can read about this
-;; in `cfg/nes.cfg` from inside your cc65 installation.
-;;;
-
-;;;
-;; The "HEADER" is the first segment of any iNES binary and it contains basic
-;; information about what this "cartridge" requires in order to work. Besides
-;; basic identification, it defines some relevant things like PRG and CHR sizes,
-;; plus mapping if desired. See https://www.nesdev.org/wiki/NES_2.0#Header for
-;; documentation on this, or also: https://www.nesdev.org/neshdr20.txt.
-;;;
-.segment "HEADER"
- ;; The first thing to do is to define the magic "NES\0" string identifier
- ;; ($1A is the ASCII that MS-DOS wanted as end of string). Some people write
- ;; it in hexadecimal through and through, but cc65 already compiles
- ;; characters into their proper hexadecimal values, so there's no need to be
- ;; extra cryptic here.
- .byte 'N', 'E', 'S', $1A
-
- ;; The next two bytes define the size of the PRG and CHR ROMs in this order.
- ;; More specifically, they define how many 8KB banks are available for PRG
- ;; and CHR. Hence, the next two bytes define a 16KB (2x 8KB) of PRG-ROM, and
- ;; 8KB of CHR-ROM.
- .byte $02
- .byte $01
-
- ;; Next we have two bytes for selecting a mapper. This is a huge topic (see
- ;; the NesDev wiki for this), but it basically refers to the fact that some
- ;; cartridges had specific requirements on how to place their data, or the
- ;; amount of it they required, or how they expected their data to be
- ;; mirrored in the memory, etc. During the life-time of the NES, and as
- ;; developers pushed the boundaries of the NES hardware, more intricate
- ;; mappers were used on the hardware of cartridges themselves. Thus, in
- ;; these two bytes we are telling the emulator: "hey, act as if this was a
- ;; cartridge that used this kind of mapper".
- .byte $00 ; Horizontal mirroring (good for vertical scrollers)
- .byte $00 ; No mapper nor special-case flags.
-
- ;; The previous are the mandatory bytes in order to get a "cartridge" going.
- ;; After this there are some other bytes you can put into the header, like
- ;; if the "cartridge" counts on a battery-backed RAM-mapped section to save
- ;; states (e.g. The Legend of Zelda) or rare flags like specifying the
- ;; region (NTSC vs PAL), but for now this falls out of my radar :-)
-
-;;;
-;; This is the segment where we tell the processor where to find the code for
-;; three important topics: the Non-Maskable Interrupts handler, the Reset
-;; handler, and the IRQ handler. If you look at the configuration from `cc65`
-;; that was provided on your installation (or if you are pesky enough to create
-;; one yourself), you will notice that these vector addresses are placed at the
-;; very end of memory ($fffa-ffff). The NES (and emulators) will look at these
-;; three last positions in memory to know where to jump for each case.
-;;;
-.segment "VECTORS"
- .addr nmi
- .addr reset
- .addr irq
-
-;;;
-;; Required by the default linker configuration. Theoretically there should be a
-;; semantical difference between this section and "CODE", but as for the linker
-;; goes, there is no difference and everything will be put sequentially on the
-;; resulting binary. Hence, if you want (and as I do here), you can leave this
-;; empty (so to make the default configuration of the linker happy), and put
-;; everything into the "CODE" segment. In fact, according to the Famicom Party
-;; Book (https://famicom.party/book/04-hardwareoverview/), the "STARTUP" section
-;; is only used by C programs compiled down into 6502 assembly, so it might not
-;; be even relevant for us (and in fact said book actually removes this segment
-;; in its linker configuration down the road).
-;;;
-.segment "STARTUP"
-
-.segment "CODE"
-
-;;;
-;; The Reset handler is executed when the whole thing starts (i.e. the user has
-;; pressed either the reset or the power on buttons on the NES). Thus, this
-;; piece of code is pretty standard and an implementation is even given in the
-;; NesDev wiki which is pretty much followed by everyone as I could see. The
-;; main idea is to leave the hardware in a known state and then jump into the
-;; main game subroutine.
-;;;
-reset:
- ;; We first instruct the NES to disable everything. That is, we don't want
- ;; any pesky interrupt to make us jump into the `nmi` section, for example,
- ;; before we have configured everything.
-
- ;; Ignore IRQs and disable decimal mode (the NES 6502 chip, for copyright
- ;; issues or legal reasons against MOS that I don't fully know nor care,
- ;; does not have decimal mode anyway, but it's considered good practice).
- sei
- cld
-
- ;; Disable APU frame IRQ. This is the first instance we see of Memory-Mapped
- ;; I/O. This is a core concept in NES programming and, to sum things up, the
- ;; memory range $2000-$6000 is reserved to I/O operations, and each address
- ;; is reserved to a specific hardware operation. This is because the NES CPU
- ;; doesn't directly control the PPU nor other chips. In this case, ranges
- ;; $4000-$4017 control the APU (Audio Processing Unit). More precisely, the
- ;; $4017 address controls what is called the "Frame counter" from the APU
- ;; (https://www.nesdev.org/wiki/APU#Frame_Counter_($4017)). Setting #$40 to
- ;; it disables it completely, so we are in a known state. If we were to use
- ;; sound, at the end of the reset code we should enable it back. We do *not*
- ;; do it here because we don't need it.
- ldx #$40
- stx $4017 ; APU Frame Counter
-
- ;; Set up the stack register with the proper value (the stack will grow in
- ;; decreasing order from $01FF -> $0100).
- ldx #$ff
- txs
-
- ;; And now disable, in this order, NMI, rendering and DMC IRQs. Note that
- ;; `x` was set to $ff, so increasing it by one results in a zero, which is
- ;; the value then stored in the aforementioned memory locations.
- ;;
- ;; The one on $4010 refers again to the APU (as described before), and it
- ;; directly controls the DMC. Again, if you wanted sound, you should enable
- ;; this back after the whole reset block.
- ;;
- ;; On the other side, the low addresses of $2000 control the PPU. In
- ;; particular, we disable NMIs from the PPU by setting the PPUCTRL address
- ;; ($2000) to zero, and we do the same for the PPUMASK ($2001). Don't worry
- ;; about them just now, we will go deeper down below.
- inx
- stx $2000 ; PPUCTRL
- stx $2001 ; PPUMASK
- stx $4010 ; APU DMC
-
- ;;;
- ;; NOTE: If you are using a mapper which needs some special configuration,
- ;; now it would be a good time set it up. I am not using a special mapper,
- ;; so there's nothing from me to do here.
- ;;;
-
- ;; At this point, we have to wait for the PPU to stabilize. This is
- ;; typically done by checking a flag from the PPUSTATUS address ($2002) and
- ;; waiting until the proper value is set by the PPU. Since this wait can
- ;; take a while, programmers typically put other initialization code here,
- ;; like sprite resetting and such.
- ;;
- ;; The PPUSTATUS memory address contains general information on the status
- ;; of the PPU, and is read-only. Moreover, reading from the PPUSTATUS has a
- ;; convenient side-effect: it resets the "address latch" for PPUADDR. We
- ;; will see how we can take advantage of this when we use the PPU properly
- ;; down the road.
-
- ;; First of the two waits.
-@vblankwait1:
- bit $2002 ; PPUSTATUS
- bpl @vblankwait1
-
- ;; The PPU has at least started, now we have a bunch of cycles for it to
- ;; stabilize, which will be properly announced through the PPUSTATUS memory
- ;; address. Instead of just waiting for it again, we will take the chance to
- ;; initialize more stuff.
-
- ;; One typical thing to do is to leave the RAM in a known state. That is, we
- ;; will set to 0 addresses $0000 - $07FF. Apparently there are some people
- ;; who say that doing this is bad because it will hide programming mistakes
- ;; (e.g. bad initialization code). So, if you are one of these people, you
- ;; can safely remove this loop. Otherwise let's get the RAM clean. That
- ;; being said, notice that we are skipping $200-$2ff. This is no mistake as
- ;; you will see below.
- ldx #0
- lda #0
-@ram_reset_loop:
- sta $000, x
- sta $100, x
- sta $300, x
- sta $400, x
- sta $500, x
- sta $600, x
- sta $700, x
- inx
- bne @ram_reset_loop ; if x overflows back to #00, then we are done.
-
- ;; And now another thing we can do is to reset the sprites. In NES programs
- ;; sprites live in a special range of memory addresses from the RAM called
- ;; OAM (Object Attribute Memory). This range is located in $0200-02ff (the
- ;; range we did not initialize when cleaning up RAM).
- ;;
- ;; "Resetting sprites" is just a matter of giving them a value which will
- ;; not bother us in the future. One way to do this is to set each value to
- ;; $ef, which will give each "sprite" off-screen Y-coordinates. How any of
- ;; this is the case will be shown whenever we deal with loading proper
- ;; sprites below.
- lda #$ef
-@sprite_reset_loop:
- sta $200, x
- inx
- bne @sprite_reset_loop
-
- ;; And write these resetted "sprites" into the PPU. How does this work?
- ;; Well, address $2003 has the OAMADDR. That is, from which position the PPU
- ;; should start the DMA process. In our case we set it to the very
- ;; beginning. Then, if you write to the OAMDMA memory address ($4014), you
- ;; will instruct the PPU to start a DMA process starting at $1XX*N. XX is
- ;; the byte given at the OAMADDR, thus XX = #$00; and N = 2 (see `lda
- ;; #$02`). Therefore, we are telling the PPU to start the DMA process from
- ;; $200. The PPU will assume that the following 256 bytes of memory are the
- ;; ones to be copied, resulting in a DMA copy of $200-$2ff, right where the
- ;; sprite data is located in memory.
- lda #$00
- sta $2003 ; OAMADDR
- lda #$02
- sta $4014 ; OAMDMA
-
- ;; As advertised, the other wait. Once we are out of this loop, we are 100%
- ;; guaranteed to have a properly working PPU which is ready to render stuff
- ;; into the screen.
-@vblankwait2:
- bit $2002 ; PPUSTATUS
- bpl @vblankwait2
-
- ;; And we also reset the palettes. How is this done? Well, the $2006 memory
- ;; address is the PPU address (PPUADDR). This address is given byte by byte,
- ;; the most significat byte first. Thus, the four lines below store into
- ;; $2006 the value $3F00. This is the first address where palettes are
- ;; stored. Palettes are the answer from old systems like the NES to: how can
- ;; you display this amount of colors on screen without taking too much
- ;; memory? The NES allows developers to store eight palettes (four
- ;; background, four foreground), and each palette group four colors. This
- ;; way, whenever we want to draw a sprite or a piece of background, we don't
- ;; specify which colors to pick for each pixels, but we rather apply a
- ;; palette to a srite or background tile definition.
- lda #$3F
- sta $2006 ; PPUADDR
- lda #$00
- sta $2006 ; PPUADDR
-
- ;; After setting the address for the first palette, now we loop #$20 times:
- ;; 8 palettes * 4 bytes per palette (1 byte per color) = 32 (#$20
- ;; hexadecimal). At each iteration we will write into $2007 the value #$0F.
- ;; This memory address is the PPUDATA, and #$0F corresponds to a black
- ;; color. In each iteration, therefore, we are telling the PPU that we want
- ;; a black color for all the colors from all palettes. Moreover, whenever
- ;; you write into PPUDATA, the memory address for the next store is
- ;; incremented by one automatically. This is why we don't have to index the
- ;; write or anything like that, but we can simply write to the same memory
- ;; address 32 times and it will be performed onto 32 consecutive positions.
- lda #$0F
- ldx #$20
-@palettes_reset_loop:
- sta $2007 ; PPUDATA
- dex
- bne @palettes_reset_loop
-
-
- ;; At this point everything is clear and with a state we know, now we can
- ;; jump into our main subroutine and start loading sprites, palettes, etc.;
- ;; and start the game proper.
- jmp main
-
-;;;
-;; This is our main subroutine. At this point we can assume that the hardware
-;; has already been set to a proper and defined state. So now we can load all
-;; the data we need for our game and enter the main game loop.
-;;;
-.proc main
- ;; Before starting the game loop proper we initialize all our assets: load
- ;; the palettes, nametables and sprites for this game.
- jsr init_palettes
- jsr init_nametable
- jsr init_sprites
-
- ;; Reset scroll. This could have been done in the reset process, but
- ;; scrolling feels like something that should be in control entirely by the
- ;; game itself. Any ways, the scroll is handled through the $2005
- ;; (PPUSCROLL) memory address. We first need to write the X coordinate and
- ;; then the Y coordinate for the camera. Since we are not planning on doing
- ;; anything fancy with scrolling, we just initialize it to a zero position.
- bit $2002 ; PPUSTATUS
- lda #$00
- sta $2005 ; PPUSCROLL
- sta $2005 ; PPUSCROLL
-
- ;; Remember when we disabled rendering and NMI on the reset code? Now it's
- ;; time to enable them back. Things to note:
- ;; - PPUCTRL: we enable three bits:
- ;; - 4: background pattern table starts at $1000. The CHR is divided by
- ;; two pattern tables, and each table contains 256 8x8 tiles. In my
- ;; case, my sprite data is located at the first pattern table (thus
- ;; $0000, and that's why the 3rd bit from PPUCTRL is set to zero),
- ;; and the background data is on the second pattern table (that
- ;; starting at $1000). In the end this all depends on how you want
- ;; to structure your CHR file.
- ;; - 5: sprite size is 8x16. Set this to 0 if your sprites in the
- ;; character file are 8x8.
- ;; - 7: allow a NMI at the start of the vertical blanking interval.
- ;; That is, whenever the screen has displayed one frame, send us
- ;; an interrupt so we can generate the next one (see the code on
- ;; `nmi`).
- ;; - PPUMASK: we enable 4 bits:
- ;; - 1: Show background in leftmost pixels on the screen.
- ;; - 2: Show sprites in leftmost pixels on the screen.
- ;; - 3: Show background.
- ;; - 4: Show sprites.
- ;;
- ;; As for showing on the leftmost pixels, this might not be a good idea if
- ;; the game is a scroller, since the game might flicker depending on how you
- ;; manage the camera. This is out of scope.
- cli
- lda #%10110000
- sta $2000 ; PPUCTRL
- lda #%00011110
- sta $2001 ; PPUMASK
-
-@main_game_loop:
- ;;;
- ;; NOTE: enter here your game loop logic.
- ;;;
-
- ;; This is a hand-shake between the code on `main` and the code on the
- ;; `nmi`. That is, whenever we are done with the game loop logic, we set
- ;; this flag to indicate that the rendering is doable. After this, the code
- ;; on `main` will be blocked until this flag is unset by the `nmi` code,
- ;; which will indicate that it can proceed with another iteration of the
- ;; game loop. On the contrary, the `nmi` code will be blocked until this
- ;; flag is set, at which point it will start rendering and unset the flag
- ;; whenever that is done.
- lda #%10000000
- ora $20
- sta $20
-@wait_for_render:
- bit $20
- bmi @wait_for_render
-
- ;; Rendering is done, we can perform another iteration of the loop!
- jmp @main_game_loop
-.endproc
-
-;; init_palettes copies all the palettes for our game into the proper PPU
-;; address.
-.proc init_palettes
- ;; Remember these four instructions? That's what we also did when resetting
- ;; palettes on the reset code. That is, we are preparing the PPU to write
- ;; data starting from $3f00.
- lda #$3F
- sta $2006 ; PPUADDR
- lda #$00
- sta $2006 ; PPUADDR
-
- ;; We are going to copy the eight palettes stored in the `palettes` tag into
- ;; PPU. This is done by indexing with the `x` register, which is incremented
- ;; until it reaches #$20 (32 in hexadecimal), which corresponds to 8
- ;; palettes * 4 bytes each (just like we did in the reset code).
- ldx #0
-@load_palettes_loop:
- lda palettes, x
- sta $2007 ; PPUDATA
- inx
- cpx #$20
- bne @load_palettes_loop
- rts
-palettes:
- ;; Let there be palettes! This is the data for all the palettes we have for
- ;; our game. Each hexadecimal value is a byte that represents a color (you
- ;; can use YY-CHR to quickly take a look at which color represents each
- ;; hexadecimal value, or use any of the tables that can be found online, or
- ;; even fceux has this with its "Palette editor" under "Tools"). There is,
- ;; though, one big catch: the first color from all palettes *must* be the
- ;; same. To be more precise, it's not like the NES will explode if you pick
- ;; other colors, but the NES *will* assume that the first color from all
- ;; palettes is the one found in $3F00 (that is, the first color from the
- ;; first palette). In this case, $0F will be this "default color". Moreover,
- ;; for this simple game we are not using all palettes, and that's why for
- ;; some of them all four bytes are simply zero'ed.
-
- ;; Background
- .byte $0F, $12, $22, $32
- .byte $0F, $00, $28, $30
- .byte $0F, $28, $16, $2D
- .byte $0F, $28, $16, $2D
-
- ;; Foreground
- .byte $0F, $00, $05, $30
- .byte $0F, $00, $00, $00
- .byte $0F, $00, $00, $00
- .byte $0F, $00, $00, $00
-.endproc
-
-;; WRITE_PPU_DATA is a macro that will write into PPUADDR the given address and
-;; into PPUDATA the given byte value.
-.macro WRITE_PPU_DATA address, value
- bit $2002 ; PPUSTATUS
- lda #.HIBYTE(address)
- sta $2006 ; PPUADDR
- lda #.LOBYTE(address)
- sta $2006 ; PPUADDR
- lda #value
- sta $2007 ; PPUDATA
-.endmacro
-
-;; init_nametable loads the relevant data to the nametable that is then going to
-;; be used in order to build up the background.
-.proc init_nametable
- ;; The general idea here is that the background defaults to the
- ;; "transparent" color, which is the first color on the palette. Then for
- ;; the background we only need to store into the PPU those elements which
- ;; are not the default thing. Hence, if we want to draw a couple of
- ;; background elements in our game in some positions, we have to instruct
- ;; the PPU where to put each item and where to find it on the pattern table.
- ;; After that, we can change/toggle the palette for a background tile if we
- ;; so want it, and we are done with it.
-
- ;; Anyways, before loading data into the PPU, we always have to check the
- ;; PPUSTATUS, that's why the first instruction of each block is a `bit
- ;; $2002`. This is a safe-guard so to reset the "address latch". That is, if
- ;; instead of writing two consecutive bytes we only wrote one by mistake (or
- ;; because we only wanted to write the high byte), it resets back its
- ;; internal index.
-
- bit $2002 ; PPUSTATUS
-
- ;; Load the first item for the background! Let's make sense of the values.
- ;; We first need to set the address that the PPU will use (that is, which
- ;; couple of bytes we need to pass to PPUADDR). This can be tricky, but
- ;; luckily some tools makes things easier for us. For example, the NEXXT
- ;; tool allows users to draw a NES screen and then it gives the offset
- ;; address that you can use for each drawn element. This way I got that the
- ;; offset for the first element was $00C8, which added to the base address
- ;; of $2000 (start of the first nametable), gives us the address $20C8.
- ;; Therefore, if I want this background element to be rendered in the
- ;; position I envisioned on this tool, I need to write $20C8 into PPUADDR.
- ;; As for the PPUDATA address, I need to pass #$02 because that's the index
- ;; inside of the CHR file of the star in the second pattern table (check the
- ;; PPUCTRL setting at the end of the `main` function on why it's the second
- ;; pattern table).
- lda #$20
- sta $2006 ; PPUADDR
- lda #$C8
- sta $2006 ; PPUADDR
- lda #$02
- sta $2007 ; PPUDATA
-
- ;; Now we have to do the same for the rest of the elements. I have gone
- ;; through the same process of fetching the offset from a canvas I drew on
- ;; NEXXT and here's the result. Instead of repeating te same code over and
- ;; over but with slightly different values, I am using this macro I created
- ;; to do the same.
- WRITE_PPU_DATA $20B9, $04
- WRITE_PPU_DATA $21CE, $04
- WRITE_PPU_DATA $21BA, $04
- WRITE_PPU_DATA $22B8, $04
- WRITE_PPU_DATA $22E7, $04
- WRITE_PPU_DATA $227A, $02
-
- ;; Now everything we wanted to write for the background is there, but we
- ;; might want to toggle the colors for some of the elements. You can do this
- ;; with palettes, which for background tiles is done via attribute tables.
- ;; Each attribute table lives right after each nametable, and it consists of
- ;; 1KB of memory in which you can establish the palettes to be used for the
- ;; tiles appearing on the background screen as defined on that specific
- ;; nametable. Information on this is also given to us by tools like NEXXT.
- ;; Shortly, for one of the elements I want to change their palette I was
- ;; told that the "attribute offset" was $03CE. Again, added to the base
- ;; address, it means that we need to pass $23CE to the PPUADDR. The value
- ;; for PPUDATA is a bit more tricky: on the attribute table each 8x8 tile is
- ;; broken down into 4 2-sized squares. Each of these 4 squares of the tile
- ;; has a palette assigned to it ($00 by default). Thanks to this setup, a
- ;; single byte can encode four palettes by addressing each square:
- ;; %44332211. The tool also tells us in which of these smaller squares our
- ;; element resides. With this info, and since we have two bits available for
- ;; each of these smaller squares and, therefore, can encode up to 4
- ;; palettes, it means that we just have to assign either on the range of
- ;; 00-11 to the pair of bits representing the smaller square that NEXXT is
- ;; telling us that our background element resides in. In this case it's the
- ;; first square, so we just need to assign $01 to the bits reserved to this
- ;; square if I want to change the palette to 1. This is what we pass as a
- ;; value.
- WRITE_PPU_DATA $23CE, %00000001
-
- rts
-.endproc
-
-;; init_sprites loads all the sprites we want from our game.
-.proc init_sprites
- ;; This is tied to the number of sprites stored in the `initial_sprite_data`
- ;; section. Remember that this can be 64 *maximum*: 64 sprites * 4 bytes per
- ;; sprite = 256 bytes; which is the reserved space in memory we have for
- ;; sprites: $0200-$02ff.
- NUM_SPRITES = 2
-
- ;; The loading is quite straight-forward. We just store whatever is on
- ;; initial_sprite_data + x into $0200 + x, where x is our index that gets
- ;; increased on each iteration. We will stop the loop whenever x reaches
- ;; NUM_SPRITES * 4 (each sprite takes 4 bytes, more below).
- ldx #$00
-@load_sprites_loop:
- lda initial_sprite_data, x
- sta $0200, x
- inx
- cpx #(4 * NUM_SPRITES)
- bne @load_sprites_loop
- rts
-initial_sprite_data:
- ;; Each sprite consists of 4 bytes:
- ;; 1. The Y position.
- ;; 2. The sprite position on the pattern table in hexadecimal. Make sure to
- ;; get this position right by using YY-CHR (or whatever program you are
- ;; using to manipulate the CHR file). You can get this wrong also
- ;; depending if you are using 8x8 or 8x16 tiles mode (check the bit set
- ;; on PPUCTRL on the `main` code).
- ;; 3. The attributes for the sprite. The first two bits control which
- ;; palette is used to draw the sprite (in our case palette $00). Bits 2
- ;; to 4 are not used. Bit 5 sets the sprite behind the background if set
- ;; to 1; bit 6 flips the sprite horizontally; and bit 7 flips the sprite
- ;; vertically.
- ;; 4. The X position.
- ;;
- ;; In our case, notice that we are using the same sprite (located at $00),
- ;; but the second sprite is moved eight pixels right and flipped
- ;; horizontally. This is because the sprite located at $00 is just half of
- ;; the real "meta-sprite", and so we can build the entire "meta-sprite" by
- ;; simply flipping the same part horizontally. This is one of the many
- ;; tricks we can use to save space on CHR-ROM.
- .byte $B0, $00, %00000000, $7A
- .byte $B0, $00, %01000000, $82
-.endproc
-
-;;;
-;; Non-Maskable Interrupts handler. After being enabled by the initialization
-;; code, you can count on this code being called at the end of each PPU
-;; rendering frame during the Vertical Blanking Interval (VBLANK). This time
-;; span is pretty tight (around 2273 CPU cycles), so you better update
-;; everything to be rendered before that if you don't want graphical glitches.
-;;;
-nmi:
- ;; As mentioned on the `main` subroutine, rendering will be skipped until
- ;; the proper flag is set.
- bit $20
- bpl @next
-
- ;; We are instructed that we can start rendering stuff. Transfer the sprites
- ;; via OAM. This is the same we did when we resetted sprites in our `reset`
- ;; code.
- lda #$00
- sta $2003 ; OAMADDR
- lda #$02
- sta $4014 ; OAMDMA
-
- ;; And unset the render flag so the `main` code is unblocked.
- lda #%01111111
- and $20
- sta $20
-@next:
- rti
-
-;;;
-;; Interrupt Requests handler. This is triggered by the NES' sound processor
-;; (APU) or by some specific cartridge hardware (e.g. something specific to a
-;; mapper). In our case we don't have to do anything here, so we just return
-;; from the interrupt.
-;;;
-irq:
- rti
-
-;;;
-;; Include into this all the data that needs to go into the CHR ROM. One typical
-;; implementation for this is by using the `.incbin` macro, which will blindly
-;; copy the bitmap that you have generated through a program such as YY-CHR into
-;; the CHR ROM. Note that not all games used the CHR-ROM for storing their
-;; assets and instead used the PRG-ROM for that as well (e.g. The Legend of
-;; Zelda). There are multiple technical reasons to do this, but this falls out
-;; of the scope of this file and my expertise, to be honest.
-;;;
-.segment "CHARS"
- .incbin "../assets/basic.chr"