Compare commits
10 Commits
c5be4abf62
...
8c200beca4
| Author | SHA1 | Date | |
|---|---|---|---|
| 8c200beca4 | |||
| 0fc8b25714 | |||
| d05702f48d | |||
| d17de5971c | |||
| 38e8607572 | |||
| 637764ff28 | |||
| 7ea451361a | |||
| ff7579cdfe | |||
| 4c5b4719fa | |||
| 86c65ebe7b |
17
Makefile
17
Makefile
@@ -1,13 +1,20 @@
|
|||||||
|
SOURCE=nesgame.S \
|
||||||
|
include/defines.S \
|
||||||
|
include/math.S \
|
||||||
|
include/graphics.S
|
||||||
|
MAIN_SOURCE=nesgame.S
|
||||||
|
|
||||||
all: nesgame.nes
|
all: nesgame.nes
|
||||||
nesgame.nes : nesgame.S
|
nesgame.nes : $(SOURCE)
|
||||||
NESASM3 $<
|
ophis -o $@ $(MAIN_SOURCE)
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test:
|
test: nesgame.nes
|
||||||
fceux nesgame.nes
|
fceux nesgame.nes
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
rm *nes
|
rm *nes
|
||||||
rm *nes.deb
|
|
||||||
rm *fns
|
tags: $(SOURCE)
|
||||||
|
find . -name "*S" | xargs etags -a
|
||||||
|
|||||||
15
README
15
README
@@ -2,20 +2,25 @@ Yo, it's a NES game!
|
|||||||
|
|
||||||
Sort of. It will get better.
|
Sort of. It will get better.
|
||||||
|
|
||||||
|
Compiles with Ophis (http://michaelcmartin.github.com/Ophis/), tests with fceux (http://www.fceux.com/web/home.html). Put them both in your path, type "make", "make test", have fun staring at the pretty (?) pictures.
|
||||||
|
|
||||||
Milestone list:
|
Milestone list:
|
||||||
|
|
||||||
1- (DONE) Boot a ROM that does nothing
|
1- (DONE) Boot a ROM that does nothing
|
||||||
2- (DONE) Single sprite visible on the screen
|
2- (DONE) Single sprite visible on the screen
|
||||||
3- (DONE) Complete complex (multi-part) sprite visible on the screen
|
3- (DONE) Complete complex (multi-part) sprite visible on the screen
|
||||||
3.5 - single background tile visible on the screen
|
3.5 - (DONE) single background tile visible on the screen
|
||||||
4- Swap palette of sprite in response to gamepad
|
4- (INVALIDATED) Swap palette of sprite in response to gamepad
|
||||||
5- Move sprite on screen with no animation
|
This milestone got invalidated because my sprite data is in ROM, not in RAM
|
||||||
|
and besides, milestone 5 covers everything this was supposed to anyway
|
||||||
|
5- (DONE) Move sprite on screen with no animation
|
||||||
6- Display a full tile map on the screen
|
6- Display a full tile map on the screen
|
||||||
7- Collide a sprite with the world map
|
7- Collide a sprite with the world map
|
||||||
8- Apply gravity to a sprite and have him stand on the "ground"
|
8- Apply gravity to a sprite and have him stand on the "ground"
|
||||||
9- Jumping sprite in response to the gamepad
|
9- Jumping sprite in response to the gamepad
|
||||||
10- Scrolling tile map (moving camera)
|
10- Scrolling tile map (moving camera)
|
||||||
11- Add a second actor/sprite to the screen and make it stand
|
11- (DONE) Add a second actor/sprite to the screen
|
||||||
|
11.5 Make all visible actors stand
|
||||||
12- Collide two actors with each other
|
12- Collide two actors with each other
|
||||||
13- Create actors on screen from map data (e.g. some data in RAM)
|
13- Create actors on screen from map data (e.g. some data in RAM)
|
||||||
14- Change an actor's sprite based on its state
|
14- Change an actor's sprite based on its state
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
playerx=$0010
|
|
||||||
playery=$0011
|
|
||||||
curSpriteAttrOffset=$0012
|
|
||||||
curSpriteTileOffset=$0013
|
|
||||||
curSpriteXOffset=$0014
|
|
||||||
curSpriteYOffset=$0015
|
|
||||||
24
include/defines.S
Normal file
24
include/defines.S
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
.alias curSpriteDataRead curSpriteDataLo
|
||||||
|
.alias curSpriteDataWrite curSpriteDataHi
|
||||||
|
.alias pada %10000000
|
||||||
|
.alias padb %01000000
|
||||||
|
.alias padselect %00100000
|
||||||
|
.alias padstart %00010000
|
||||||
|
.alias padup %00001000
|
||||||
|
.alias paddown %00000100
|
||||||
|
.alias padleft %00000010
|
||||||
|
.alias padright %00000001
|
||||||
|
|
||||||
|
.macro storeStackReturn ; storeStackReturn
|
||||||
|
PLA
|
||||||
|
STA prevReturnAddrHi
|
||||||
|
PLA
|
||||||
|
STA prevReturnAddrLo
|
||||||
|
.macend
|
||||||
|
|
||||||
|
.macro restoreStackReturn
|
||||||
|
LDA prevReturnAddrLo
|
||||||
|
PHA
|
||||||
|
LDA prevReturnAddrhi
|
||||||
|
PHA
|
||||||
|
.macend
|
||||||
140
include/graphics.S
Normal file
140
include/graphics.S
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
.require "defines.S"
|
||||||
|
|
||||||
|
;; Function : paletteLoad
|
||||||
|
;;
|
||||||
|
;; Given the address of a palette in ROM or RAM, and the
|
||||||
|
;; index of which palette to load (0=3F00, 1=3F10), load a
|
||||||
|
;; palette into the PPU
|
||||||
|
;;
|
||||||
|
;; arguments are on the stack (in push order):
|
||||||
|
;; - 00 or 10, lyte byte of PPU palette address to load
|
||||||
|
;; - Low byte of palette address
|
||||||
|
;; - High byte of palette address
|
||||||
|
paletteLoad:
|
||||||
|
.invoke storeStackReturn
|
||||||
|
PLA
|
||||||
|
STA curPaletteHi
|
||||||
|
PLA
|
||||||
|
STA curPaletteLo
|
||||||
|
LDA $2002 ; The PPU Memory address at $2006 expects
|
||||||
|
; the high byte of the palette address first,
|
||||||
|
; then the low byte, but we can't know
|
||||||
|
; which one it's expecting right now, so we
|
||||||
|
; read the PPU status at $2002 to reset the
|
||||||
|
; high/low latch on $2006.
|
||||||
|
LDA #$3F ; palettes live at $3F00 and $3F10
|
||||||
|
STA $2006
|
||||||
|
PLA
|
||||||
|
STA $2006
|
||||||
|
LDX #$00
|
||||||
|
_loop:
|
||||||
|
LDA (curPaletteLo), y ; Loop over each index of the byte array at
|
||||||
|
STA $2007 ; 'palette', store each one into the accumulator
|
||||||
|
INY ; and then store the accumulator into the PPU
|
||||||
|
CPY #$20 ; .. compare X to 20 (size of 'palette'), and
|
||||||
|
BNE _loop ; loop as long as the Zero flag isn't set (NE)
|
||||||
|
.invoke restoreStackReturn
|
||||||
|
RTS
|
||||||
|
|
||||||
|
;; Function : backgroundLoad
|
||||||
|
;;
|
||||||
|
;; Given the address of a set of background tiles, and the
|
||||||
|
;; address of their attribute data, load the background into the PPU
|
||||||
|
;;
|
||||||
|
;; arguments on the stack (in push order):
|
||||||
|
;; - Length of background attribute data
|
||||||
|
;; - Hi byte of background attribute data address
|
||||||
|
;; - Lo byte of background attribute data address
|
||||||
|
;; - Length of background data
|
||||||
|
;; - Hi byte of background data address
|
||||||
|
;; - Lo byte of background data address
|
||||||
|
backgroundLoad:
|
||||||
|
.invoke storeStackReturn
|
||||||
|
PLA
|
||||||
|
STA curBackgroundLo
|
||||||
|
PLA
|
||||||
|
STA curBackgroundHi
|
||||||
|
PLA
|
||||||
|
STA curBackgroundLen
|
||||||
|
PLA
|
||||||
|
STA curBackgroundAttrLo
|
||||||
|
PLA
|
||||||
|
STA curBackgroundAttrHi
|
||||||
|
PLA
|
||||||
|
STA curBackgroundAttrLen
|
||||||
|
LDA $2002 ; reset the PPU hi/low latch
|
||||||
|
LDA #$20
|
||||||
|
STA $2006 ; PPU address data is written high then low
|
||||||
|
LDA #$00
|
||||||
|
STA $2006
|
||||||
|
LDY #$00
|
||||||
|
_loadbgloop:
|
||||||
|
LDA (curBackgroundLo), y
|
||||||
|
STA $2007
|
||||||
|
INY
|
||||||
|
CPY curBackgroundLen
|
||||||
|
BNE _loadbgloop
|
||||||
|
|
||||||
|
LDA $2002 ; reset the PPU latch again
|
||||||
|
LDA #$23
|
||||||
|
STA $2006
|
||||||
|
LDA #$C0
|
||||||
|
STA $2006
|
||||||
|
LDY #$00
|
||||||
|
_loadattrloop:
|
||||||
|
LDA (curBackgroundAttrLo), y
|
||||||
|
STA $2007
|
||||||
|
INY
|
||||||
|
CPY curBackgroundAttrLen
|
||||||
|
BNE _loadattrloop
|
||||||
|
.invoke restoreStackReturn
|
||||||
|
RTS
|
||||||
|
|
||||||
|
;; Function : oamInsertMultiSprite
|
||||||
|
;;
|
||||||
|
;; Given the address of a multisprite, and its length (number of
|
||||||
|
;; subsprites), load the multisprite into the OAM memory at
|
||||||
|
;; index 0.
|
||||||
|
;;
|
||||||
|
;; Arguments on the stack:
|
||||||
|
;; - Length of multisprite
|
||||||
|
;; - High byte of multisprite's address
|
||||||
|
;; - Low byte of multisprite's address
|
||||||
|
;;
|
||||||
|
;; FIXME: Need to keep a list of all multisprites so I can append
|
||||||
|
;; new ones to the list, and remove dead ones; right now this all
|
||||||
|
;; presumes $0200 is the root for the multisprite, which will stop
|
||||||
|
;; being true once I have more than one.
|
||||||
|
oamInsertMultiSprite:
|
||||||
|
.invoke storeStackReturn
|
||||||
|
LDX #$0
|
||||||
|
LDY #$0
|
||||||
|
PLA
|
||||||
|
STA curSpriteLen
|
||||||
|
PLA
|
||||||
|
STA curSpriteDataHi
|
||||||
|
PLA
|
||||||
|
STA curSpriteDataLo
|
||||||
|
;; ----
|
||||||
|
_looptop:
|
||||||
|
LDA playery ; set Y position
|
||||||
|
CLC
|
||||||
|
ADC (curSpriteDataLo), y
|
||||||
|
STA (curSpriteOAMIndexLo), y
|
||||||
|
INY
|
||||||
|
LDA (curSpriteDataLo), y ; set tile number
|
||||||
|
STA (curSpriteOAMIndexLo), y
|
||||||
|
INY
|
||||||
|
LDA (curSpriteDataLo), y
|
||||||
|
STA (curSpriteOAMIndexLo), y
|
||||||
|
INY
|
||||||
|
LDA playerx ; set X position
|
||||||
|
CLC
|
||||||
|
ADC (curSpriteDataLo), y
|
||||||
|
STA (curSpriteOAMIndexLo), y
|
||||||
|
INY
|
||||||
|
INX ; increment the sprite counter
|
||||||
|
CPX curSpriteLen ; any more sprites in the current multisprite?
|
||||||
|
BNE _looptop
|
||||||
|
.invoke restoreStackReturn
|
||||||
|
RTS
|
||||||
28
include/math.S
Normal file
28
include/math.S
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
;; Function : divide
|
||||||
|
;;
|
||||||
|
;; Divide the value in A by the value in Y
|
||||||
|
;; The dividend is returned in A
|
||||||
|
;; The modulus is returned in Y
|
||||||
|
divide:
|
||||||
|
.invoke storeStackReturn
|
||||||
|
sta tempdivident ;Stores divident
|
||||||
|
sty tempdivisor ;Stores divisor
|
||||||
|
lda #$00
|
||||||
|
sta tempdivresult ;Clear result
|
||||||
|
|
||||||
|
ldy #$10 ;The loop is for 16-bit result
|
||||||
|
_divide_loop:
|
||||||
|
asl tempdivident
|
||||||
|
rol ;Shift divisor in 1 bit
|
||||||
|
cmp tempdivisor ;Check if fractional dividend is greater than divisor
|
||||||
|
bcc _divide_subloop
|
||||||
|
sbc tempdivisor ;Substract (C is always set)
|
||||||
|
_divide_subloop:
|
||||||
|
rol tempdivresult ;Shift result (1 if substation was done, 0 otherwise)
|
||||||
|
rol tempdivmodulus
|
||||||
|
dey
|
||||||
|
bne _divide_loop
|
||||||
|
lda tempdivresult
|
||||||
|
ldy tempdivmodulus
|
||||||
|
.invoke restoreStackReturn
|
||||||
|
RTS
|
||||||
518
nesgame.S
518
nesgame.S
@@ -3,17 +3,66 @@
|
|||||||
;; how many 8kB banks of CHR data, which mapper to use for bank swapping
|
;; how many 8kB banks of CHR data, which mapper to use for bank swapping
|
||||||
;; and how to perform background mirroring
|
;; and how to perform background mirroring
|
||||||
|
|
||||||
.inesprg 1 ;; 1x 16kB bank of PRG (program) code
|
;; iNES header block
|
||||||
.ineschr 1 ;; 1x 8kB bank of CHR (tile/sprite) data
|
|
||||||
.inesmap 0 ;; use mapper 0; NROM, no bank swapping
|
|
||||||
.inesmir 1 ;; background mirroring (we don't care for now)
|
|
||||||
|
|
||||||
.include "defines.S"
|
.byte "NES",$1A
|
||||||
|
.byte $01 ;; 1 PRG ROM page (how big is an ophis page?)
|
||||||
;; For NESASM, we need to tell it where each bank begins.
|
.byte $02 ;; 1 CHR (tile/sprite) ROM page (how big is a page?)
|
||||||
|
.byte $00 ; Horizontal mirroring
|
||||||
|
.byte $00 ; Mapper 0 (NROM, no bank switching)
|
||||||
|
.byte $00,$00,$00,$00 ; Reserved bytes
|
||||||
|
.byte $00,$00,$00,$00 ; Reserved bytes
|
||||||
|
|
||||||
.bank 0
|
.require "include/defines.S"
|
||||||
.org $C000 ;; PRG bank 1 has 8kB at 0x0C000
|
|
||||||
|
.text zp
|
||||||
|
.org $0000
|
||||||
|
;; $0000 is reserved for things we use to extend PL/PH for call/return stacks
|
||||||
|
;; FIXME: Will need to expand this stuff once I start calling 2+
|
||||||
|
;; functions at once; e.g., JSR a -> JSR b -> JSR c...
|
||||||
|
;; will need to expand it to a real stack.
|
||||||
|
.space prevReturnAddrLo 1
|
||||||
|
.space prevReturnAddrHi 1
|
||||||
|
.org $0010
|
||||||
|
;; $0010 - 001F is reserved for global pointers
|
||||||
|
.space curPaletteLo 1
|
||||||
|
.space curPaletteHi 1
|
||||||
|
.space curSpriteOAMIndexLo 1
|
||||||
|
.space curSpriteOAMIndexHi 1
|
||||||
|
.space curBackgroundLo 1
|
||||||
|
.space curBackgroundHi 1
|
||||||
|
.space curBackgroundAttrLo 1
|
||||||
|
.space curBackgroundAttrHi 1
|
||||||
|
.space curSpriteDataLo 1
|
||||||
|
.space curSpriteDataHi 1
|
||||||
|
.org $0020
|
||||||
|
;; $0020 - 00FF is space for general purpose global variable
|
||||||
|
.space curBackgroundLen 1
|
||||||
|
.space curBackgroundAttrLen 1
|
||||||
|
.space curSpriteLen 1
|
||||||
|
.space playery 1
|
||||||
|
.space playerx 1
|
||||||
|
.space pad1state 1
|
||||||
|
.space pad2state 1
|
||||||
|
.space pad3state 1
|
||||||
|
.space pad4state 1
|
||||||
|
|
||||||
|
.space tempdivident 1
|
||||||
|
.space tempdivisor 1
|
||||||
|
.space tempdivresult 1
|
||||||
|
.space tempdivmodulus 1
|
||||||
|
|
||||||
|
.org $0040
|
||||||
|
.space sprMario2DataHook1 1
|
||||||
|
.space sprMario2DataHook2 1
|
||||||
|
.space sprMario2DataHook3 1
|
||||||
|
.space sprMario2DataHook4 1
|
||||||
|
|
||||||
|
.text
|
||||||
|
.org $C000 ;; PRG bank code starts at 0xC000
|
||||||
|
|
||||||
|
.require "include/math.S"
|
||||||
|
.require "include/graphics.S"
|
||||||
|
|
||||||
;; START will be called by the NES whenever the system boots
|
;; START will be called by the NES whenever the system boots
|
||||||
;; or when the reset button is pressed (think of _start in libc )
|
;; or when the reset button is pressed (think of _start in libc )
|
||||||
@@ -32,13 +81,13 @@ START:
|
|||||||
TXS ;; Move the contents of X to the stack pointer
|
TXS ;; Move the contents of X to the stack pointer
|
||||||
INX ;; increment X by 1, which causes overflow, so
|
INX ;; increment X by 1, which causes overflow, so
|
||||||
;; now X is 0
|
;; now X is 0
|
||||||
|
|
||||||
STX $2000 ;; set PPU flag to disable NMI (0x2000 = 0)
|
STX $2000 ;; set PPU flag to disable NMI (0x2000 = 0)
|
||||||
STX $2001 ;; set PPU flag to disable rendering (0x2001 = 0)
|
STX $2001 ;; set PPU flag to disable rendering (0x2001=0)
|
||||||
STX $4010 ;; disable APU IRQs, no audio
|
STX $4010 ;; disable APU IRQs, no audio
|
||||||
_START_vblankwait:
|
_START_vblankwait:
|
||||||
BIT $2002 ;; Bitwise AND the accumulator (LDA) with mem
|
BIT $2002 ;; Bitwise AND the accumulator (LDA) with mem
|
||||||
;; at 0x2002, and set the Zero, Sign and Overflow
|
;; at 0x2002, and set the Zero, Sign & Overflow
|
||||||
;; flags accordingly. 0x2002 is the PPU status
|
;; flags accordingly. 0x2002 is the PPU status
|
||||||
;; register; when 0x2002 has bit 7 set, we are
|
;; register; when 0x2002 has bit 7 set, we are
|
||||||
;; in vblank, so this is how we check for it.
|
;; in vblank, so this is how we check for it.
|
||||||
@@ -63,7 +112,7 @@ _START_clearmem:
|
|||||||
;; stack (0100-01FF), the entirety of main RAM
|
;; stack (0100-01FF), the entirety of main RAM
|
||||||
;; (0200-07FF)
|
;; (0200-07FF)
|
||||||
|
|
||||||
LDA #$FE ;; These two are clearing all of the sprite
|
LDA #$FE ;; These two are clearing all of the sprite
|
||||||
STA $0200, x ;; OAM; previous tutorial had this at 0300,
|
STA $0200, x ;; OAM; previous tutorial had this at 0300,
|
||||||
;; which may have been wrong. We don't HAVE
|
;; which may have been wrong. We don't HAVE
|
||||||
;; to reserve this range for OAM; we could
|
;; to reserve this range for OAM; we could
|
||||||
@@ -81,30 +130,272 @@ _START_vblankwait2:
|
|||||||
BPL _START_vblankwait2 ;; once we've gotten 1 vblank,
|
BPL _START_vblankwait2 ;; once we've gotten 1 vblank,
|
||||||
;; cleared mem, and gotten another vblank,
|
;; cleared mem, and gotten another vblank,
|
||||||
;; the PPU is ready. Wait for it.
|
;; the PPU is ready. Wait for it.
|
||||||
|
JMP main
|
||||||
|
|
||||||
MAIN:
|
|
||||||
|
main:
|
||||||
;; horray, here is main()
|
;; horray, here is main()
|
||||||
;; Let's hit milestone 2 and draw a sprite
|
|
||||||
|
|
||||||
_MAIN_LoadPalettes:
|
|
||||||
LDA $2002 ; The PPU Memory address at $2006 expects
|
|
||||||
; the high byte of the palette address first,
|
|
||||||
; then the low byte, but we can't know
|
|
||||||
; which one it's expecting right now, so we
|
|
||||||
; read the PPU status at $2002 to reset the
|
|
||||||
; high/low latch on $2006.
|
|
||||||
LDA #$3F ; we're populating the second palette, at $3F10
|
|
||||||
STA $2006
|
|
||||||
LDA #$00
|
LDA #$00
|
||||||
STA $2006
|
PHA
|
||||||
LDX #$00
|
LDA #<palette
|
||||||
_MAIN_LoadPaletteLoop:
|
PHA
|
||||||
LDA palette, x ; Loop over each index of the byte array at
|
LDA #>palette
|
||||||
STA $2007 ; 'palette', store each one into the accumulator
|
PHA
|
||||||
INX ; and then store the accumulator into the PPU
|
JSR paletteLoad
|
||||||
CPX #$20 ; .. compare X to 20 (size of 'palette'), and
|
;; $2000 is the PPU Control register, controlled by various bitflags.
|
||||||
BNE _MAIN_LoadPaletteLoop ; loop as long as the Zero flag isn't set (NE)
|
;;
|
||||||
|
;; 7654 3210
|
||||||
|
;; |||| ||||
|
||||||
|
;; |||| ||++- Base nametable address
|
||||||
|
;; |||| || (0 = $2000; 1 = $2400; 2 = $2800; 3 = $2C00)
|
||||||
|
;; |||| |+--- VRAM address increment per CPU read/write of PPUDATA
|
||||||
|
;; |||| | (0: increment by 1, going across;
|
||||||
|
;; |||| | 1: increment by 32, going down)
|
||||||
|
;; |||| +---- Sprite pattern table address for 8x8 sprites
|
||||||
|
;; |||| (0: $0000; 1: $1000; ignored in 8x16 mode)
|
||||||
|
;; |||+------ Background pattern table address (0: $0000; 1: $1000)
|
||||||
|
;; ||+------- Sprite size (0: 8x8; 1: 8x16)
|
||||||
|
;; |+-------- PPU master/slave select (has no effect on the NES)
|
||||||
|
;; +--------- Generate an NMI at the start of the
|
||||||
|
;; vertical blanking interval (0: off; 1: on)
|
||||||
|
|
||||||
|
|
||||||
|
;; The PPU mask is set at $2001, the 2nd PPU Control register, and it
|
||||||
|
;; sets one config option for every bit of the byte
|
||||||
|
;;
|
||||||
|
;; 76543210
|
||||||
|
;; ||||||||
|
||||||
|
;; |||||||+- Grayscale (0: normal color; 1: AND all palette entries
|
||||||
|
;; ||||||| with 0x30, effectively producing a monochrome display;
|
||||||
|
;; ||||||| note that colour emphasis STILL works when this is on!)
|
||||||
|
;; ||||||+-- Disable background clipping in leftmost 8 pixels of screen
|
||||||
|
;; |||||+--- Disable sprite clipping in leftmost 8 pixels of screen
|
||||||
|
;; ||||+---- Enable background rendering
|
||||||
|
;; |||+----- Enable sprite rendering
|
||||||
|
;; ||+------ Intensify reds (and darken other colors)
|
||||||
|
;; |+------- Intensify greens (and darken other colors)
|
||||||
|
;; +-------- Intensify blues (and darken other colors)
|
||||||
|
|
||||||
|
|
||||||
|
LDA #$80
|
||||||
|
STA playerx
|
||||||
|
STA playery
|
||||||
|
;; Put mario in as the first multisprite
|
||||||
|
LDA #<sprMarioData
|
||||||
|
PHA
|
||||||
|
LDA #>sprMarioData
|
||||||
|
PHA
|
||||||
|
LDA sprMario
|
||||||
|
PHA
|
||||||
|
;; DO IT
|
||||||
|
LDA #$00
|
||||||
|
STA curSpriteOAMIndexLo
|
||||||
|
LDA #$02
|
||||||
|
STA curSpriteOAMIndexHi
|
||||||
|
JSR oamInsertMultiSprite
|
||||||
|
;; ----------------------------------
|
||||||
|
LDA #$20
|
||||||
|
STA playerx
|
||||||
|
LDA #$20
|
||||||
|
STA playery
|
||||||
|
LDA #<sprMario2Data
|
||||||
|
PHA
|
||||||
|
LDA #>sprMario2Data
|
||||||
|
PHA
|
||||||
|
LDA sprMario2
|
||||||
|
PHA
|
||||||
|
LDA #$40
|
||||||
|
STA curSpriteOAMIndexLo
|
||||||
|
LDA #$02
|
||||||
|
STA curSpriteOAMIndexHi
|
||||||
|
JSR oamInsertMultiSprite
|
||||||
|
;; ---------------------------
|
||||||
|
_bgcall:
|
||||||
|
LDA bgattrsize
|
||||||
|
PHA
|
||||||
|
LDA #>backgroundattrs
|
||||||
|
PHA
|
||||||
|
LDA #<backgroundattrs
|
||||||
|
PHA
|
||||||
|
LDA bgsize
|
||||||
|
PHA
|
||||||
|
LDA #>background
|
||||||
|
PHA
|
||||||
|
LDA #<background
|
||||||
|
PHA
|
||||||
|
JSR backgroundLoad
|
||||||
|
|
||||||
|
;; Apparently you do NOT set sprite and tile data while these are turned on, bad things
|
||||||
|
;; will happen!
|
||||||
|
LDA #%10010000 ; enable NMI (so we get a function call every
|
||||||
|
; vblank), and draw sprites from table 0
|
||||||
|
STA $2000
|
||||||
|
LDA #%10011110 ;; turn on sprites, turn on background, don't clip on the left
|
||||||
|
STA $2001 ;; Write to PPU Control Register 2
|
||||||
|
LDA #$00 ; hey PPU, stop friggin scrolling!
|
||||||
|
STA $2005
|
||||||
|
STA $2005
|
||||||
|
LDX #$0
|
||||||
|
|
||||||
|
_MAIN_loop:
|
||||||
|
JMP _MAIN_loop ;; Loop forever
|
||||||
|
|
||||||
|
NMI:
|
||||||
|
;; We need to copy all our OAM data to put sprites on screen during
|
||||||
|
;; vblank. $2003 is the PPU OAM address, so we're going to tell it
|
||||||
|
;; to pull OAM from $0200, and do a DMA transfer.
|
||||||
|
LDA #$00
|
||||||
|
STA $2003
|
||||||
|
LDA #$02
|
||||||
|
STA $4014 ; 4014 is the OAM_DMA operation, which will
|
||||||
|
; do a DMA from the (LDA|$2003) address,
|
||||||
|
; for FF bytes (in our case $0200-$02FF),
|
||||||
|
; which takes ~513 cycles. An unrolled
|
||||||
|
; loop to do the same thing would take
|
||||||
|
; 3-4 times as long.
|
||||||
|
|
||||||
|
_latch1:
|
||||||
|
LDA #$01
|
||||||
|
STA $4016
|
||||||
|
LDA #$00
|
||||||
|
STA $4016
|
||||||
|
LDA #$00
|
||||||
|
STA pad1state
|
||||||
|
|
||||||
|
LDX #$08
|
||||||
|
CLC
|
||||||
|
_readpad1:
|
||||||
|
LDA $4016
|
||||||
|
AND #%00000001
|
||||||
|
ORA pad1state
|
||||||
|
DEX
|
||||||
|
BEQ _storepad1
|
||||||
|
CLC
|
||||||
|
ROL
|
||||||
|
STA pad1state
|
||||||
|
JMP _readpad1
|
||||||
|
_storepad1:
|
||||||
|
STA pad1state
|
||||||
|
|
||||||
|
_checkselect:
|
||||||
|
LDA #padselect
|
||||||
|
AND pad1state
|
||||||
|
BEQ _checkstart
|
||||||
|
LDA sprMario2DataHook1
|
||||||
|
CLC
|
||||||
|
ADC #$01
|
||||||
|
STA sprMario2DataHook1
|
||||||
|
_checkstart:
|
||||||
|
LDA #padstart
|
||||||
|
AND pad1state
|
||||||
|
BEQ _checka
|
||||||
|
LDA sprMario2DataHook2
|
||||||
|
CLC
|
||||||
|
ADC #$01
|
||||||
|
STA sprMario2DataHook2
|
||||||
|
_checka:
|
||||||
|
LDA #pada
|
||||||
|
AND pad1state
|
||||||
|
BEQ _checkb
|
||||||
|
LDA sprMario2DataHook3
|
||||||
|
CLC
|
||||||
|
ADC #$01
|
||||||
|
STA sprMario2DataHook3
|
||||||
|
_checkb:
|
||||||
|
LDA #padb
|
||||||
|
AND pad1state
|
||||||
|
BEQ _checkwalkright
|
||||||
|
LDA sprMario2DataHook4
|
||||||
|
CLC
|
||||||
|
ADC #$01
|
||||||
|
STA sprMario2DataHook4
|
||||||
|
|
||||||
|
_checkwalkright:
|
||||||
|
LDA #%00000001
|
||||||
|
AND pad1state
|
||||||
|
BEQ _checkwalkleft
|
||||||
|
LDA playerx
|
||||||
|
CLC
|
||||||
|
ADC #$01
|
||||||
|
STA playerx
|
||||||
|
_checkwalkleft:
|
||||||
|
LDA #padleft
|
||||||
|
AND pad1state
|
||||||
|
BEQ _checkwalkup
|
||||||
|
LDA playerx
|
||||||
|
SEC
|
||||||
|
SBC #$01
|
||||||
|
STA playerx
|
||||||
|
_checkwalkup:
|
||||||
|
LDA #padup
|
||||||
|
AND pad1state
|
||||||
|
BEQ _checkwalkdown
|
||||||
|
LDA playery
|
||||||
|
SEC
|
||||||
|
SBC #$01
|
||||||
|
STA playery
|
||||||
|
_checkwalkdown:
|
||||||
|
LDA #paddown
|
||||||
|
AND pad1state
|
||||||
|
BEQ _move_mario
|
||||||
|
LDA playery
|
||||||
|
CLC
|
||||||
|
ADC #$01
|
||||||
|
STA playery
|
||||||
|
_move_mario:
|
||||||
|
;; Put mario in as the first multisprite
|
||||||
|
LDA #<sprMarioData
|
||||||
|
PHA
|
||||||
|
LDA #>sprMarioData
|
||||||
|
PHA
|
||||||
|
LDA sprMario
|
||||||
|
PHA
|
||||||
|
;; DO IT
|
||||||
|
LDA #$00
|
||||||
|
STA curSpriteOAMIndexLo
|
||||||
|
LDA #$02
|
||||||
|
STA curSpriteOAMIndexHi
|
||||||
|
JSR oamInsertMultiSprite
|
||||||
|
|
||||||
|
|
||||||
|
_nmi_finish:
|
||||||
|
LDA #%10010000 ; enable NMI (so we get a function call every
|
||||||
|
; vblank), and draw sprites from table 0
|
||||||
|
STA $2000
|
||||||
|
LDA #%00011110 ;; turn on sprites, turn on background, don't clip on the left
|
||||||
|
STA $2001 ;; Write to PPU Control Register 2
|
||||||
|
LDA #$00 ; hey PPU, stop friggin scrolling!
|
||||||
|
STA $2005
|
||||||
|
STA $2005
|
||||||
|
RTI
|
||||||
|
|
||||||
|
palette:
|
||||||
|
.byte $22,$29,$1A,$0F,$22,$36,$17,$0F,$22,$30,$21,$0F,$22,$27,$17,$0F ;;background palette
|
||||||
|
.byte $22,$1C,$15,$14,$22,$02,$38,$3C,$22,$1C,$15,$14,$22,$02,$38,$3C ;;sprite palette
|
||||||
|
bgsize:
|
||||||
|
.byte $80
|
||||||
|
bgattrsize:
|
||||||
|
.byte $08
|
||||||
|
background:
|
||||||
|
.byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 ;;row 1
|
||||||
|
.byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 ;;all sky
|
||||||
|
.byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 ;;row 2
|
||||||
|
.byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 ;;all sky
|
||||||
|
.byte $24,$24,$24,$24,$45,$45,$24,$24,$45,$45,$45,$45,$45,$45,$24,$24 ;;row 3
|
||||||
|
.byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$53,$54,$24,$24 ;;some brick tops
|
||||||
|
.byte $24,$24,$24,$24,$47,$47,$24,$24,$47,$47,$47,$47,$47,$47,$24,$24 ;;row 4
|
||||||
|
.byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$55,$56,$24,$24 ;;brick bottoms
|
||||||
|
backgroundattrs:
|
||||||
|
.byte %00000000,%00010000,%01010000,%00010000
|
||||||
|
.byte %00000000,%00000000,%00000000,%00110000
|
||||||
|
.byte $24,$24,$24,$24
|
||||||
|
.byte $47,$47,$24,$24
|
||||||
|
.byte $47,$47,$47,$47
|
||||||
|
.byte $47,$47,$24,$24
|
||||||
|
.byte $24,$24,$24,$24
|
||||||
|
.byte $24,$24,$24,$24
|
||||||
|
.byte $24,$24,$24,$24
|
||||||
|
.byte $55,$56,$24,$24 ;;brick bottoms
|
||||||
|
|
||||||
;; All sprites live between 0200-02FF; there are a max of 64 sprites
|
;; All sprites live between 0200-02FF; there are a max of 64 sprites
|
||||||
;; on screen, and each one has a 4 byte struct describing it.
|
;; on screen, and each one has a 4 byte struct describing it.
|
||||||
@@ -125,141 +416,40 @@ _MAIN_LoadPaletteLoop:
|
|||||||
;; +-------- Flip sprite vertically
|
;; +-------- Flip sprite vertically
|
||||||
;; *(sprite + 3) = x position
|
;; *(sprite + 3) = x position
|
||||||
|
|
||||||
;; All the sprite OAM data is initialized at the bottom of bank 1
|
|
||||||
;; at .org $FF00
|
|
||||||
|
|
||||||
;; $2000 is the PPU Control register, controlled by various bitflags.
|
|
||||||
;;
|
|
||||||
;; 7654 3210
|
|
||||||
;; |||| ||||
|
|
||||||
;; |||| ||++- Base nametable address
|
|
||||||
;; |||| || (0 = $2000; 1 = $2400; 2 = $2800; 3 = $2C00)
|
|
||||||
;; |||| |+--- VRAM address increment per CPU read/write of PPUDATA
|
|
||||||
;; |||| | (0: increment by 1, going across;
|
|
||||||
;; |||| | 1: increment by 32, going down)
|
|
||||||
;; |||| +---- Sprite pattern table address for 8x8 sprites
|
|
||||||
;; |||| (0: $0000; 1: $1000; ignored in 8x16 mode)
|
|
||||||
;; |||+------ Background pattern table address (0: $0000; 1: $1000)
|
|
||||||
;; ||+------- Sprite size (0: 8x8; 1: 8x16)
|
|
||||||
;; |+-------- PPU master/slave select (has no effect on the NES)
|
|
||||||
;; +--------- Generate an NMI at the start of the
|
|
||||||
;; vertical blanking interval (0: off; 1: on)
|
|
||||||
|
|
||||||
LDA #%10000000 ; enable NMI (so we get a function call every
|
|
||||||
; vblank), and draw sprites from table 0
|
|
||||||
STA $2000
|
|
||||||
|
|
||||||
;; The PPU mask is set at $2001, the 2nd PPU Control register, and it
|
|
||||||
;; sets one config option for every bit of the byte
|
|
||||||
;;
|
|
||||||
;; 76543210
|
|
||||||
;; ||||||||
|
|
||||||
;; |||||||+- Grayscale (0: normal color; 1: AND all palette entries
|
|
||||||
;; ||||||| with 0x30, effectively producing a monochrome display;
|
|
||||||
;; ||||||| note that colour emphasis STILL works when this is on!)
|
|
||||||
;; ||||||+-- Disable background clipping in leftmost 8 pixels of screen
|
|
||||||
;; |||||+--- Disable sprite clipping in leftmost 8 pixels of screen
|
|
||||||
;; ||||+---- Enable background rendering
|
|
||||||
;; |||+----- Enable sprite rendering
|
|
||||||
;; ||+------ Intensify reds (and darken other colors)
|
|
||||||
;; |+------- Intensify greens (and darken other colors)
|
|
||||||
;; +-------- Intensify blues (and darken other colors)
|
|
||||||
|
|
||||||
LDA #%00010000 ;; turn on sprites, no more background color
|
|
||||||
STA $2001 ;; Write to PPU Control Register 2
|
|
||||||
LDX #$0
|
|
||||||
|
|
||||||
LDA #$80
|
|
||||||
STA playerx
|
|
||||||
STA playery
|
|
||||||
LDA #$0
|
|
||||||
STA curSpriteAttrOffset
|
|
||||||
STA curSpriteTileOffset
|
|
||||||
STA curSpriteXOffset
|
|
||||||
STA curSpriteYOffset
|
|
||||||
_MAIN_loop:
|
|
||||||
JMP _MAIN_loop ;; Loop forever
|
|
||||||
|
|
||||||
NMI:
|
|
||||||
LDX #$0
|
|
||||||
_NMI_CopyMarioToOAM:
|
|
||||||
LDA playery
|
|
||||||
LDY curSpriteYOffset
|
|
||||||
CLC
|
|
||||||
ADC sprMario_off_y, x
|
|
||||||
STA $0200, y
|
|
||||||
LDA #$4
|
|
||||||
ADC curSpriteYOffset
|
|
||||||
STA curSpriteYOffset
|
|
||||||
|
|
||||||
LDY curSpriteTileOffset
|
|
||||||
LDA sprMario, x
|
|
||||||
STA $0201, y
|
|
||||||
LDA #$4
|
|
||||||
CLC
|
|
||||||
ADC curSpriteTileOffset
|
|
||||||
STA curSpriteTileOffset
|
|
||||||
|
|
||||||
LDA #$00
|
|
||||||
LDY curSpriteAttrOffset
|
|
||||||
STA $0202, y
|
|
||||||
LDA #$4
|
|
||||||
CLC
|
|
||||||
ADC curSpriteAttrOffset
|
|
||||||
STA curSpriteAttrOffset
|
|
||||||
|
|
||||||
LDA playerx
|
|
||||||
LDY curSpriteXOffset
|
|
||||||
ADC sprMario_off_x, x
|
|
||||||
STA $0203, y
|
|
||||||
LDA #$4
|
|
||||||
CLC
|
|
||||||
ADC curSpriteXOffset
|
|
||||||
STA curSpriteXOffset
|
|
||||||
|
|
||||||
INX
|
|
||||||
CPX #08
|
|
||||||
BNE _MAIN_CopyMarioToOAM
|
|
||||||
;; We need to copy all our OAM data to put sprites on screen during
|
|
||||||
;; vblank. $2003 is the PPU OAM address, so we're going to tell it
|
|
||||||
;; to pull OAM from $0200, and do a DMA transfer.
|
|
||||||
LDA #$00
|
|
||||||
STA $2003
|
|
||||||
LDA #$02
|
|
||||||
STA $4014 ; 4014 is the OAM_DMA operation, which will
|
|
||||||
; do a DMA from the (LDA|$2003) address,
|
|
||||||
; for FF bytes (in our case $0200-$02FF),
|
|
||||||
; which takes ~513 cycles. An unrolled
|
|
||||||
; loop to do the same thing would take
|
|
||||||
; 3-4 times as long.
|
|
||||||
RTI ; just return
|
|
||||||
|
|
||||||
.bank 1 ;; NESASM sees our 16kB code banks as pairs of
|
|
||||||
.org $E000 ;; 8kB code banks, so we have to declare each
|
|
||||||
;; 8kB half-bank separately, and split code
|
|
||||||
;; between them.
|
|
||||||
;; .. How to know when we have written enough
|
|
||||||
;; code? ..
|
|
||||||
palette:
|
|
||||||
.db $0F,$31,$32,$33,$0F,$35,$36,$37,$0F,$39,$3A,$3B,$0F,$3D,$3E,$0F
|
|
||||||
.db $0F,$1C,$15,$14,$0F,$02,$38,$3C,$0F,$1C,$15,$14,$0F,$02,$38,$3C
|
|
||||||
sprMario:
|
sprMario:
|
||||||
.db $00,$01,$02,$03,$04,$05,$06,$07
|
.byte $08 ; Total number of subsprites in this metasprite
|
||||||
sprMario_off_x:
|
sprMarioData:
|
||||||
.db $00,$08,$00,$08,$00,$08,$00,$08
|
;; Y, Tile, Atr, X
|
||||||
sprMario_off_y:
|
.byte $00,$00,$00,$00
|
||||||
.db $00,$00,$08,$08,$10,$10,$18,$18
|
.byte $00,$01,$00,$08
|
||||||
|
.byte $08,$02,$00,$00
|
||||||
|
.byte $08,$03,$00,$08
|
||||||
|
.byte $10,$04,$00,$00
|
||||||
|
.byte $10,$05,$00,$08
|
||||||
|
.byte $18,$06,$00,$00
|
||||||
|
.byte $18,$07,$00,$08
|
||||||
|
|
||||||
.bank 1
|
sprMario2:
|
||||||
.org $FFFA
|
.byte $08 ; Total number of subsprites in this metasprite
|
||||||
.dw NMI ;; For Non-Maskable Interrupts, please jump to the location
|
sprMario2Data:
|
||||||
|
;; Y, Tile, Atr, X
|
||||||
|
.byte $00,$08,$00,$00
|
||||||
|
.byte $00,$09,$00,$08
|
||||||
|
.byte $08,$0A,$00,$00
|
||||||
|
.byte $08,$0B,$00,$08
|
||||||
|
.byte $10,$0C,$00,$00
|
||||||
|
.byte $10,$0D,$00,$08
|
||||||
|
.byte $18,$0E,$00,$00
|
||||||
|
.byte $18,$0F,$00,$08
|
||||||
|
|
||||||
|
.advance $FFFA
|
||||||
|
.word NMI ;; For Non-Maskable Interrupts, please jump to the location
|
||||||
;; of the NMI label
|
;; of the NMI label
|
||||||
.dw START ;; For the reset button or power-on, jump to the location
|
.word START ;; For the reset button or power-on, jump to the location
|
||||||
;; of the START label
|
;; of the START label
|
||||||
.dw 0 ;; If we used an external IRQ vector, we would put it here
|
.word 0 ;; If we used an external IRQ vector, we would put it here
|
||||||
|
|
||||||
;; --- graphics bank
|
;; --- graphics bank
|
||||||
|
|
||||||
.bank 2 ;; CHR bank 0 starts here for tile/sprite data
|
|
||||||
.org $0000 ;; CHR data is below PRG data in the memory
|
.org $0000 ;; CHR data is below PRG data in the memory
|
||||||
.incbin "mario.chr" ; include 8kB of graphics from SMB1
|
.incbin "mario.chr" ; include 8kB of graphics from SMB1
|
||||||
|
|||||||
Reference in New Issue
Block a user