Friday, March 22, 2013

MetaSprite Code

My current metasprite code. This is not complete and needs library and macro code. This is a test only, still to be implemented as a library/unit. (CA65 assembler with heavy macro usage.)
DEBUG = 1 .include "..\header\nes_constants.h" .include "..\header\CA65HL.h" .include "..\header\memory.h" .include "..\header\ppu.h" .include "..\header\function_macros.h" .include "..\header\ndxdebug.h" .include "..\header\graphics.h" OAM_ROTATION_START = 4 * OAM_ENTRY OAM_ROTATION_AMOUNT = 160 METASPRITE_COUNT = 5 * 4 ; 4 bytes per ; -------------------------------------------------------------------------------------------- ; -------------------------------------------------------------------------------------------- .scope scope_spriteTestSTATE timerTitle 1, "metaspriteeval" ; setup dynamic memory: .include "..\MEMCONFIG\MEMORY_CONFIG_spriteTestSTATE.inc" IdentifyState "spriteTestSTATE" ; end memory declareBSS localJumpVector .addr endBSS declareBSS mSpriteMemory yPos .byte ::METASPRITE_COUNT att .byte ::METASPRITE_COUNT mTile .byte ::METASPRITE_COUNT xPos .byte ::METASPRITE_COUNT endBSS ; -------------------------------------------------------------------------------------------- ; EXPORTS: .export spriteTestSTATE ; -------------------------------------------------------------------------------------------- ; IMPORTS: .import waitNMI .import writeToVRAMbuffer ; .import split, yield, killThread .importzp NMIdoVRAMupdate .importzp NMIdoOAM .importzp FrameCounter .importZP PadPressed .importzp PadNewPressed .import VramBuffer1Offset .import VramBuffer1 ; -------------------------------------------------------------------------------------------- .pushseg .segment "RODATA" Metatiles: ; hepler constants: X_CR = %00001000 X_DEFINE_PLUS8 = %00000100 X_DEFINE_NEXT = %00000000 X_LASTSPR = %00001100 Y_DEFINE_SAME = %00100000 Y_DEFINE_NEXT = %00000000 TILE_PLUS1 = %00010000 TILE_DEFINE_NEXT = %00000000 V_FLIP = %10000000 H_FLIP = %01000000 metaspriteNumbersLo: .byte <metasprite0, <metasprite1 metaspriteNumbersHi: .byte >metasprite0, >metasprite1 ; first byte only has attribute, and tile byte metasprite0: ; .byte X, Y, TILE, ATTRIB ; boo: 9 bytes .byte $80, X_DEFINE_PLUS8 + Y_DEFINE_SAME + TILE_PLUS1 + 0 .byte X_CR + Y_DEFINE_SAME + TILE_PLUS1 + 0 .byte X_DEFINE_PLUS8 + Y_DEFINE_SAME + TILE_PLUS1 + 0 .byte X_CR + Y_DEFINE_SAME + TILE_PLUS1 + 0 .byte X_DEFINE_PLUS8 + Y_DEFINE_SAME + TILE_PLUS1 + 0 .byte X_CR + Y_DEFINE_SAME + TILE_PLUS1 + 0 .byte X_DEFINE_PLUS8 + Y_DEFINE_SAME + TILE_PLUS1 + 0 .byte X_LASTSPR metasprite1: ; megaman: 13 bytes .byte $90, X_DEFINE_PLUS8 + Y_DEFINE_SAME + TILE_PLUS1 + 1 .byte X_DEFINE_PLUS8 + Y_DEFINE_SAME + TILE_PLUS1 + 1 .byte X_CR + Y_DEFINE_SAME + TILE_PLUS1 + 1 .byte X_DEFINE_PLUS8 + Y_DEFINE_SAME + TILE_PLUS1 + 1 .byte X_DEFINE_PLUS8 + Y_DEFINE_SAME + TILE_PLUS1 + 1 .byte X_CR + Y_DEFINE_SAME + TILE_PLUS1 + 1 .byte X_DEFINE_PLUS8 + Y_DEFINE_SAME + TILE_PLUS1 + 1 .byte X_DEFINE_PLUS8 + Y_DEFINE_SAME + TILE_PLUS1 + 1 .byte X_DEFINE_NEXT + Y_DEFINE_NEXT + TILE_PLUS1 + 1 .byte <-6, <-10, X_LASTSPR + 2 .popseg ; -------------------------------------------------------------------------------------------- .proc spriteTestSTATE ; init state: setSubState M::localJumpVector := #welcomeSTATE nameTableInit 0 .pushseg .SEGMENT "RODATA" palette0: .byte $3f,$00, 32 .byte $16,$0F,$05,$16,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F .byte $16,$0F,$20,$16,$16,$0F,$2C,$11,$0F,$0F,$30,$38,$0F,$0F,$0F,$0F .popseg call writeToVRAMbuffer, _a(palette0), #(32 + 3) setFlag NMIdoVRAMupdate ; init sprite RAM ldx #METASPRITE_COUNT lda #$FF repeat sta mSpriteMemory::att, x until dex == zero ; ; some test data: mb mSpriteMemory[ 0 ]::yPos := #128 mb mSpriteMemory[ 0 ]::xPos := #128 mb mSpriteMemory[ 0 ]::att := #%00000000 mb mSpriteMemory[ 0 ]::mTile := #0 mb mSpriteMemory[ 1 ]::yPos := #(128+50) mb mSpriteMemory[ 1 ]::xPos := #(15) mb mSpriteMemory[ 1 ]::att := #%00000000 mb mSpriteMemory[ 1 ]::mTile := #1 mb mSpriteMemory[ 2 ]::yPos := #(128+50) mb mSpriteMemory[ 2 ]::xPos := #(100) mb mSpriteMemory[ 2 ]::att := #%00000000 mb mSpriteMemory[ 2 ]::mTile := #1 mb mSpriteMemory[ 3 ]::yPos := #(128+30) mb mSpriteMemory[ 3 ]::xPos := #(50) mb mSpriteMemory[ 3 ]::att := #%00000000 mb mSpriteMemory[ 3 ]::mTile := #1 PPU_ON ; end init ; -------------------------------------------------------------------------------------------- LOOP: jsr waitNMI jsri M::localJumpVector jmp LOOP .endproc ; ---------------------------------------------------------------------------------------------- .proc welcomeSTATE setFlag NMIdoVRAMupdate puts 8,12, "WELCOME!" puts 2,13, "PRESS A FOR PRIORITY" puts 2,14, "PRESS B AND SEL FOR FLIPPING" puts 2,15, "PRESS START TO CHANGE SPRITE" puts 2,16, "PRESS START TO CONTINUE" if PadNewPressed & #BUTTON_START setSubState M::localJumpVector := #subfunctionSTATE endif rts .endproc ; -------------------------------------------------------------------------------------------- .macro addMspriteCoord coord, adder, attbyte, attbit mb coord := coord + adder if carry set mb attbyte := attbyte ^ #(1 << attbit) endif .endmacro ; -------------------------------------------------------------------------------------------- .macro subMspriteCoord coord, adder, attbyte, attbit mb coord := coord - adder if carry clear mb attbyte := attbyte ^ #(1 << attbit) endif .endmacro ; -------------------------------------------------------------------------------------------- .proc subfunctionSTATE declareBSS toggleX .byte toggleY .byte width .byte height .byte endBSS if PadPressed & #BUTTON_UP dec mSpriteMemory::yPos if ldx mSpriteMemory::yPos : inx == zero ; if = #$FF mb mSpriteMemory::att := mSpriteMemory::att ^ #%00000100 endif endif if PadPressed & #BUTTON_DOWN inc mSpriteMemory::yPos if zero mb mSpriteMemory::att := mSpriteMemory::att ^ #%00000100 endif endif if PadPressed & #BUTTON_LEFT dec mSpriteMemory::xPos if ldx mSpriteMemory::xPos : inx == zero ; if = #$FF mb mSpriteMemory::att := mSpriteMemory::att ^ #%00001000 endif endif if PadPressed & #BUTTON_RIGHT inc mSpriteMemory::xPos if zero mb mSpriteMemory::att := mSpriteMemory::att ^ #%00001000 endif endif if PadNewPressed & #BUTTON_A mb mSpriteMemory::att := mSpriteMemory::att ^ #%00100000 endif if PadNewPressed & #BUTTON_B mb mSpriteMemory::att := mSpriteMemory::att ^ #%10000000 if M::toggleY dec M::toggleY subMspriteCoord mSpriteMemory::yPos, M::height, mSpriteMemory::att, 2 else inc M::toggleY addMspriteCoord mSpriteMemory::yPos, M::height, mSpriteMemory::att, 2 endif endif if PadNewPressed & #BUTTON_SELECT mb mSpriteMemory::att := mSpriteMemory::att ^ #%01000000 if M::toggleX dec M::toggleX subMspriteCoord mSpriteMemory::xPos, M::width, mSpriteMemory::att, 3 else inc M::toggleX addMspriteCoord mSpriteMemory::xPos, M::width, mSpriteMemory::att, 3 endif endif if PadNewPressed & #BUTTON_START if mSpriteMemory::mTile = #1 dec mSpriteMemory::mTile mb M::width := #( 16 - 8 ) mb M::height := #( 32 - 8 ) else inc mSpriteMemory::mTile mb M::width := #(24 - 8 ) mb M::height := #(24 - 8 ) endif endif ldaPPUmaskBits staPPUmaskBits #MA_MONOCHROME, now startTimer 1 jsr evalMetaSprites stopTimer 1 ldaPPUmaskBits #<~MA_MONOCHROME staPPUmaskBits , now setFlag NMIdoOAM rts .endproc ; ---------------------------------------------------------------------------- .proc evalMetaSprites ; metasprite Object in RAM format, one record: ; should be organized as one array of each element ; (example, if there are $10 records, the first $10 bytes are all Ypos ; Ypos : byte ; Attribute modifier : byte ; vhbcxypp --- Attribute modifier byte (always) ; |||||||| ; ||||||++---- Sprite Colour toggle: EOR with metatile attribs ; |||||+------ Y clipping bit ; ||||+------- X clipping bit ; |||+-------- Clipping or wrap : 0: clip sprites at screen edge, 1: allow wrap ; ||+--------- Background priority bit ; |+---------- Horizontal flipping toggle: EOR metatile attribs ; +----------- Vertical flipping toggle: EOR metatile attribs ; Metatile number : byte ; Xpos : byte ; -------------------------------------------------------------------------------------------- ; metasprite format to define metasprite in ROM data: ; xxxxxxxx --- Sprite X coordinate (if required) ; yyyyyyyy --- Sprite Y coordinate (if required) ; tttttttt --- Tile number (if required) ; vhytxxpp --- Attribute byte (always) ; |||||||| ; ||||||++---- Sprite Colour ; ||||++------ X position of next sprite (00 : required, 01 : X pos of this sprite + 8, ; |||| 10 : X pos of the first sprite, Y + 8 (CR/LF), 11 : Escape code for last sprite) ; |||+-------- Tile number of next sprite (0: required, 1 : Tile number of this sprite + 1 ; ||+--------- Y position of next sprite (0 : required, 0 : unchanged, , skip if X control was LF) ; |+---------- Horizontal flipping ; +----------- Vertical flipping ; static declareBSS OAMoffset .byte OAMusedLast .byte endBSS ; temp use zeropage: locals mSprModifier .byte sprAttrib .byte sprTile .byte sprXpos .byte sprYpos .byte sprXbit8 .byte sprYbit8 .byte metaspritePointerLo .byte metaspritePointerHi .byte sprAdder .byte sprXposStart .byte mSpriteOffset .byte endlocals metaspritePointer = local::metaspritePointerLo ; init: ldy #$FF ; - 1 sty local::mSpriteOffset ; skip reserved sprites if lda M::OAMoffset < #::OAM_ROTATION_START lda #::OAM_ROTATION_START endif sta M::OAMoffset sta local::sprAdder ; use as temp: save OAMoffset DoNextmetasprite: inc local::mSpriteOffset ldx local::mSpriteOffset mb a, local::mSprModifier := mSpriteMemory[ x ]::att ; load Attrib/meta data which is modifier for all ; if att is $FF then last entry, rotate and clear remaining shadow OAM if tay : iny == zero jmp clearRemainingOAM endif mb local::sprYbit8 := a >> 2 ; put Y 'bit8' into bit 0 local var (only bit0 matters) mb local::sprXbit8 := a >> 1 ; put X 'bit8' into bit 0 local var (only bit0 matters) mb local::sprYpos := mSpriteMemory[ x ]::yPos mb local::sprXpos := mSpriteMemory[ x ]::xPos sta local::sprXposStart mb a, x := mSpriteMemory[ x ]::mTile ; load metatile number ; get metatile pointer mb local::metaspritePointerLo := metaspriteNumbersLo[ x ] mb local::metaspritePointerHi := metaspriteNumbersHi[ x ] ; very first tile we read attrib and tile only from metatile ROM data ; Y counts index into ROM data ldy #0 mb local::sprTile := (metaspritePointer)[ y ] iny ldx local::sprAdder ; X now has OAMoffset DoNextTile: mb local::sprAttrib := (metaspritePointer)[ y ] ; read attrib and flags iny ; if both offscreen bits are clear OR No clipping mode is on if local::sprXbit8 | local::sprYbit8 >> 1 == carry clear || local::mSprModifier & #%00010000 mb OAM_SHADOW[ x + 2] := local::sprAttrib & #%11000011 ^ local::mSprModifier mb OAM_SHADOW[ x + 3] := local::sprXpos mb OAM_SHADOW[ x + 0] := local::sprYpos mb OAM_SHADOW[ x + 1] := local::sprTile if x := x + 4 == zero ldx #::OAM_ROTATION_START endif endif ; -------------- X pos mb local::sprAdder := #8 ; add 8 by default mb a := local::sprAttrib & #%00001100 ; process bit 3,2 as 0 to 3 value: if a < #(2 << 2) ; 0 or 1: ; if offset needed: if a = #0 mb local::sprAdder := (metaspritePointer)[ y ] iny endif mb a := local::sprAdder if { flagSet local::mSprModifier, 6 } ; if bit6 (h mirror) ; negate adder mb a := a ^ #$FF + #1 endif sta local::sprAdder mb local::sprXpos := a + local::sprXpos ror if a ^ local::sprAdder == N set ; if carry and adder sign are different: inc local::sprXbit8 ; toggle offscreen bit endif else if a = #( 2 << 2 ) ; Reload metasprite X and Y position: mb local::sprXpos := local::sprXposStart mb local::sprXbit8 := local::mSprModifier >> 3 lda #8 jmp Add8toYPos endif ; else: stx local::sprAdder ; save OAM offset here ;--------------- jmp DoNextmetasprite ; if = 0 then bail and do next RAm based metasprite endif ; -----------END X pos ; ----------- Y pos ; load Ypos? Skip all this if not 0 if ! local::sprAttrib & #%00100000 mb a := (metaspritePointer)[ y ] iny Add8toYPos: ; jump here if CR/LF code if flagSet local::mSprModifier ; if bit7 (v mirror) mb a := a ^ #$FF + #1 endif sta local::sprAdder clc adc local::sprYpos sta local::sprYpos ror if a ^ local::sprAdder == N set inc local::sprYbit8 endif endif ; -----------END Y pos ; -----------check tile: ; BIT4: check if 0: load tile, else use next tile inc local::sprTile ; inc by default if ! local::sprAttrib & #%00010000 mb local::sprTile := (metaspritePointer)[ y ] iny endif ; -------end tile processing jmp DoNextTile ; -------------------------------------------------------------------------------------------- clearRemainingOAM: OAMnext = local::sprAdder ; alias ldx OAMnext txa mb a := a - M::OAMoffset ; bytes used mb a := a >> 2 ; slots used sta OAMnext ; use as temp, since this value is in reg X mb y := #(64 - ( ::OAM_ROTATION_START / 4 ) ) - OAMnext ; total avaliable slots MINUS slots used lda #$FF repeat sta OAM_SHADOW, x mb x := x + 4 if x < #::OAM_ROTATION_START ldx #::OAM_ROTATION_START endif until dey == zero mb M::OAMoffset := M::OAMoffset + #::OAM_ROTATION_AMOUNT rts .endproc ; -------------------------------------------------------------------------------------------- .endscope

No comments:

Post a Comment