.segment "HEADER" INES_MAPPER = 0 INES_MIRROR = 1 INES_SRAM = 0 .byte 'N', 'E', 'S', $1A .byte $02 .byte $01 .byte INES_MIRROR .byte (INES_MAPPER & %11110000) .byte $0, $0, $0, $0, $0, $0, $0, $0 .segment "TILES" .incbin "background.chr" .incbin "sprite.chr" .segment "VECTORS" .word nmi .word reset .word irq .segment "CODE" reset: sei lda #0 sta $2000 lda #0 sta $2001 sta $4015 sta $4010 lda #40 sta $4017 cld ldx #$FF txs bit $2002 : bit $2002 bpl :- lda #0 ldx #0 : sta $0000, X sta $0100, X sta $0200, X sta $0300, X sta $0400, X sta $0500, X sta $0600, X sta $0700, X inx bne :- lda #255 ldx #0 : sta oam, X inx inx inx inx bne :- : bit $2002 bpl :- lda #%10101000 ; NMI enable, 8x16 sprites, ignored due to 8x16 mode sta $2000 jmp main .segment "ZEROPAGE" nmi_lock: .res 1 nmi_count: .res 1 nmi_ready: .res 1 nmt_update_len: .res 1 scroll_x: .res 1 scroll_y: .res 1 scroll_nmt: .res 1 temp: .res 1 .segment "BSS" nmt_update: .res 256 palette: .res 32 .segment "OAM" oam: .res 256 .segment "CODE" nmi: pha txa pha tya pha lda nmi_lock beq :+ jmp @nmi_end : lda #1 sta nmi_lock lda nmi_ready bne :+ jmp @ppu_update_end : cmp #2 bne :+ lda #%00000000 sta $2001 ldx #0 stx nmi_ready jmp @ppu_update_end : ldx #0 stx $2003 lda #>oam sta $4014 lda #%10101000 ; NMI enable, 8x16 sprites, ignored due to 8x16 mode sta $2000 lda $2002 lda #$3F sta $2006 stx $2006 ldx #0 : lda palette, X sta $2007 inx cpx #32 bcc :- ldx #0 cpx nmt_update_len bcs @scroll @nmt_update_loop: lda nmt_update, X sta $2006 inx lda nmt_update, X sta $2006 inx lda nmt_update, X sta $2007 cpx nmt_update_len bcc @nmt_update_loop lda #0 sta nmt_update_len @scroll: lda scroll_nmt and #%00000011 ora #%10101000 sta $2000 lda scroll_x sta $2005 lda scroll_y sta $2005 lda #%00011110 sta $2001 ldx #0 stx nmi_ready @ppu_update_end: lda #0 sta nmi_lock @nmi_end: pla tay pla tax pla rti .segment "CODE" irq: rti .segment "CODE" ppu_update: lda #1 sta nmi_ready : lda nmi_ready bne :- rts ppu_skip: lda nmi_count : cmp nmi_count beq :- rts ppu_off: lda #2 sta nmi_ready : lda nmi_ready bne :- rts ppu_address_tile: lda $2002 tya lsr lsr lsr ora #$20 sta $2006 tya asl asl asl asl asl sta temp txa ora temp sta $2006 rts ppu_update_tile: pha txa pha ldx nmt_update_len tya lsr lsr lsr ora #$20 sta nmt_update, X inx tya asl asl asl asl asl sta temp pla ora temp sta nmt_update, X inx pla sta nmt_update, X inx stx nmt_update_len rts ppu_update_byte: pha tya pha ldy nmt_update_len txa sta nmt_update, Y iny pla sta nmt_update, Y iny pla sta nmt_update, Y iny sty nmt_update_len rts PAD_A = $01 PAD_B = $02 PAD_SELECT = $04 PAD_START = $08 PAD_U = $10 PAD_D = $20 PAD_L = $40 PAD_R = $80 .segment "ZEROPAGE" gamepad: .res 1 .segment "CODE" ; gamepad_poll: this reads the gamepad state into the variable labelled "gamepad" ; This only reads the first gamepad, and also if DPCM samples are played they can ; conflict with gamepad reading, which may give incorrect results. gamepad_poll: ; strobe the gamepad to latch current button state lda #1 sta $4016 lda #0 sta $4016 ; read 8 bytes from the interface at $4016 ldx #8 : pha lda $4016 ; combine low two bits and store in carry bit and #%00000011 cmp #%00000001 pla ; rotate carry into gamepad variable ror dex bne :- sta gamepad rts .segment "RODATA" level: .byte 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 .byte 0,1,0,0,1,0,0,0,0,1,0,0,2,7,7,3 .byte 0,0,0,0,0,0,0,0,0,1,0,0,6,0,0,9 .byte 0,1,0,0,1,0,0,0,0,1,0,0,6,0,0,9 .byte 0,0,1,1,0,0,0,0,0,1,0,0,4,8,8,5 .byte 0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0 .byte 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 .byte 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 .byte 0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0 .byte 0,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0 .byte 0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0 .byte 0,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0 .byte 0,0,1,0,0,0,0,1,0,0,0,1,0,1,0,0 .byte 0,1,2,3,4,5,6,7,8,9,0,0,0,0,0,0 .byte 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 block_x: .res 1 block_y: .res 1 block_w: .res 1 block_h: .res 1 metatiles: .byte 0,0,0,0,0 .byte $14,$15,$16,$17,1 ; Full block .byte $14,$18,$1B,$0,1 ; up / left .byte $18,$15,$0,$19,1 ; up / right .byte $1B,$0,$16,$1A,1 ; down / left .byte $0,$19,$1A,$17,1 ; down / right .byte $1B,$0,$1B,$0,1 ; left .byte $18,$18,$0,$0,1 ; up .byte $0,$0,$1A,$1A,1 ; down .byte $0,$19,$0,$19,1 ; right .byte 3,3,3,3,0 .byte 3,4,5,6,0 example_palette: .byte $0F,$00,$3D,$20 ; greyscale .byte $0F,$09,$1A,$16 ; grass .byte $0F,$15,$26,$37 ; bg0 purple/pink .byte $0F,$09,$19,$29 ; bg1 green .byte $0F,$2D,$10,$3D ; reboot bottom .byte $0F,$2D,$10,$2C ; reboot eye .byte $0F,$01,$11,$21 ; bg2 blue .byte $0F,$00,$10,$30 ; bg3 greyscale ; .byte $0F,$18,$28,$38 ; sp0 yellow ; .byte $0F,$14,$24,$34 ; sp1 purple ; .byte $0F,$1B,$2B,$3B ; sp2 teal ; .byte $0F,$12,$22,$32 ; sp3 marine .segment "ZEROPAGE" buttons: .res 1 player_pos_x: .res 1 player_pos_y: .res 1 player_vel_x: .res 1 player_vel_y: .res 1 player_status: .res 1 ; 76543210 | 0: facing (0 right, 1 left) ; | 7: talking cursor_x: .res 1 cursor_y: .res 1 temp_x: .res 1 temp_y: .res 1 temp_mul: .res 1 var_m: .res 1 var_n: .res 1 var_o: .res 1 var_p: .res 1 jump_pressed_last_frame: .res 1 frame_counter: .res 1 last_frame_jumped: .res 1 .segment "CODE" main: ldx #0 : lda example_palette, X sta palette, X inx cpx #32 bcc :- jsr setup_background jsr ppu_update lda #$40 sta cursor_x sta cursor_y lda #0 sta frame_counter sta last_frame_jumped jsr init_objects @loop: lda frame_counter clc adc #1 sta frame_counter jsr controller jsr movement @draw: jsr update_background jsr draw_player jsr ppu_update jmp @loop draw_player: lda player_pos_y sec sbc #1 sta oam + (4 * 2) + 0 sta oam + (4 * 3) + 0 sec sbc #16 sta oam + (4 * 0) + 0 sta oam + (4 * 1) + 0 lda player_status and #%00000001 cmp #0 bne :+ lda player_pos_x sta oam + (4 * 1) + 3 sta oam + (4 * 3) + 3 sec sbc #8 sta oam + (4 * 0) + 3 sta oam + (4 * 2) + 3 jmp :++ : lda player_pos_x sta oam + (4 * 0) + 3 sta oam + (4 * 2) + 3 sec sbc #8 sta oam + (4 * 1) + 3 sta oam + (4 * 3) + 3 : lda player_status and #%10000000 cmp #0 bne :+ lda #$45 jmp :++ : lda #$49 : sta oam + (4 * 0) + 1 clc adc #2 sta oam + (4 * 1) + 1 lda #$65 sta oam + (4 * 2) + 1 lda #$67 sta oam + (4 * 3) + 1 lda player_status and #%00000001 cmp #0 beq :+ lda #%01000000 jmp :++ : lda #%00000000 : ora #%00000001 sta oam + (4 * 0) + 2 ora #%00000001 sta oam + (4 * 1) + 2 and #%11111100 sta oam + (4 * 2) + 2 and #%11111100 sta oam + (4 * 3) + 2 rts init_objects: lda #0 sta var_n ldx #0 : lda var_n clc adc #8 sta var_n sta player_pos_y, X ; Set Y position sta player_pos_x, X ; Set X position txa lsr lsr sta oam+1, X inx inx inx inx cpx #(8*4) bne :- lda #0 sta player_vel_x sta player_vel_y sta player_status lda #64 sta block_x sta block_y lda #16 sta block_w sta block_h rts draw: @end: rts movement: ; Talking ldx #0 jsr btn_b cmp #0 beq :+ lda player_status ora #%10000000 sta player_status jmp :++ : lda player_status and #%01111111 sta player_status : ; Horizontal velocity ldx #0 jsr btn_right cmp #0 beq :+ ldx #1 lda #2 sta player_vel_x lda player_status and #%11111110 sta player_status : jsr btn_left cmp #0 beq :+ ldx #1 lda #254 sta player_vel_x lda player_status ora #%00000001 sta player_status : cpx #1 beq @end lda frame_counter and #1 cmp #0 bne @end lda #0 cmp player_vel_x beq @end ; If player_vel_x = 0, skip decay ; If player_vel_x > 0, decrement bpl :+ ldx player_vel_x dex stx player_vel_x jmp @end : ; Else if player_vel_x < 0, increment ldx player_vel_x inx stx player_vel_x @end: ; Jump jsr btn_a cmp #0 beq @fail_jump ; If jump not pressed, forget it lda jump_pressed_last_frame cmp #1 beq @fail_jump ; If jump last frame, forget it ; Jump newly pressed this frame lda #249 sta player_vel_y lda frame_counter sta last_frame_jumped @fail_jump: jsr btn_a sta jump_pressed_last_frame ; Gravity lda frame_counter sec sbc last_frame_jumped and #%00000001 cmp #0 bne :+ lda player_vel_y clc adc #1 ; Add 2 to velocity sta player_vel_y : ; Apply Y velocity lda player_pos_y clc adc player_vel_y sta player_pos_y ; Apply X velocity lda player_pos_x clc adc player_vel_x sta player_pos_x ; load x pos, filter blocks, check up and down lda player_vel_y cmp #0 bmi :+ ; branch if moving up jsr downward_collision_check jsr left_collision_check jsr right_collision_check jsr upward_collision_check jmp :++ : jsr upward_collision_check jsr left_collision_check jsr right_collision_check jsr downward_collision_check : rts ; Keep grounded cmp #200 bcc :+ lda #0 sta player_vel_y lda #200 sta player_pos_y : ; Bonk lda #8 cmp player_pos_y bcc :+ lda #0 sta player_vel_y lda #8 sta player_pos_y : rts update_background: rts ;lda $2002 ;lda #$20 ;sta $2006 ;lda #$00 ;sta $2006 ldx #1 ldy #1 jsr ppu_update_tile lda #1 sta $2007 rts setup_background: lda $2002 ; reset latch lda #$20 sta $2006 lda #$00 sta $2006 ; empty nametable lda #0 ldy #30 ; 30 rows : ldx #32 ; 32 columns : sta $2007 dex bne :- dey bne :-- lda $2002 lda #$20 sta $2006 lda #$00 sta $2006 ldy #0 @row_start: ldx #0 : txa pha ; index is (Y * 16) + X stx temp_x lda #16 jsr mul_y clc adc temp_x tax lda level, X ldx #5 jsr mul_x tax lda metatiles+0, X sta $2007 lda metatiles+1, X sta $2007 pla tax inx cpx #16 bne :- ; again ldx #0 : txa pha ; index is (Y * 16) + X stx temp_x lda #16 jsr mul_y clc adc temp_x tax lda level, X ldx #5 jsr mul_x tax lda metatiles+2, X sta $2007 lda metatiles+3, X sta $2007 pla tax inx cpx #16 bne :- iny cpy #15 bne @row_start ; clear attributes lda #0 ldx #64 ; 64 bytes : lda #%11100100 sta $2007 dex bne :- rts lda $2002 ; reset latch lda #$20 sta $2006 lda #$00 sta $2006 ; empty nametable lda #0 ldy #30 ; 30 rows : ldx #32 ; 32 columns : sta $2007 dex bne :- dey bne :-- ; set all attributes to 0 lda #0 ldx #64 ; 64 bytes : txa and #%00000011 asl asl asl asl sta temp txa and #%00000011 ora temp lda #0 sta $2007 dex bne :- ; fill in an area in the middle with 1/2 checkerboard lda #1 ldy #0 ; start at row 8 : pha ; temporarily store A, it will be clobbered by ppu_address_tile routine ldx #8 ; start at column 8 jsr ppu_address_tile pla ; recover A ldx #8 : sta $2007 eor #$3 inx cpx #(32-8) bcc :- eor #$3 iny cpy #(30-8) bcc :-- lda #$24 sta $2006 lda #$00 sta $2006 lda #$00 ldy #30 : ldx #32 : sta $2007 clc adc #1 and #3 dex bne :- clc adc #1 and #3 dey bne :-- lda #0 ldy #4 : ldx #16 : sta $2007 dex bne :- clc adc #%01010101 dey bne :-- rts mul_x: cmp #0 beq @zero cpx #0 beq @zero sta temp_mul txa pha lda #0 : clc adc temp_mul dex bne :- sta temp_mul pla tax lda temp_mul rts @zero: lda #0 rts mul_y: cmp #0 beq @zero cpy #0 beq @zero sta temp_mul tya pha lda #0 : clc adc temp_mul dey bne :- sta temp_mul pla tay lda temp_mul rts @zero: lda #0 rts controller: lda #1 sta $4016 sta buttons lda #0 sta $4016 : lda $4016 lsr rol buttons bcc :- rts btn_right: lda buttons and #%00000001 rts btn_left: lda buttons and #%00000010 lsr rts btn_down: lda buttons and #%00000100 lsr lsr rts btn_up: lda buttons and #%00001000 lsr lsr lsr rts btn_start: lda buttons and #%00010000 lsr lsr lsr lsr rts btn_select: lda buttons and #%00100000 lsr lsr lsr lsr lsr rts btn_b: lda buttons and #%01000000 lsr lsr lsr lsr lsr lsr rts btn_a: lda buttons and #%10000000 lsr lsr lsr lsr lsr lsr lsr rts downward_collision_check: lda #0 sta var_n @begin_check: lda player_pos_x ldx var_n cpx #0 bne :+ sec sbc #6 jmp :++ : clc adc #5 : ; divide by 16 clc ror clc ror clc ror clc ror sta temp_x lda player_pos_y ; divide by 16 clc adc #8 clc ror clc ror clc ror clc ror tay lda #16 jsr mul_y adc temp_x tax lda level, X cmp #0 beq :+ tya asl asl asl asl sec sbc #8 cmp player_pos_y bcs :+ sta player_pos_y lda #0 sta player_vel_y : ldx var_n inx stx var_n cpx #1 beq @begin_check rts upward_collision_check: lda #0 sta var_n @begin_check: lda player_pos_x ldx var_n cpx #0 bne :+ sec sbc #6 jmp :++ : clc adc #5 : ; divide by 16 clc ror clc ror clc ror clc ror sta temp_x lda player_pos_y ; divide by 16 sec sbc #8 clc ror clc ror clc ror clc ror tay lda #16 jsr mul_y adc temp_x tax lda level, X cmp #0 beq :+ iny tya asl asl asl asl clc adc #8 cmp player_pos_y bcc :+ sta player_pos_y lda #0 sta player_vel_y : ldx var_n inx stx var_n cpx #1 beq @begin_check rts right_collision_check: lda #0 sta var_n @begin_check: lda player_pos_x ; divide by 16 clc adc #8 clc ror clc ror clc ror clc ror sta temp_x lda player_pos_y ldx var_n cpx #0 bne :+ sec sbc #7 jmp :++ : clc adc #6 : ; divide by 16 clc ror clc ror clc ror clc ror tay lda #16 jsr mul_y adc temp_x tax lda level, X cmp #0 beq :+ lda temp_x asl asl asl asl sec sbc #8 cmp player_pos_x bcs :+ sta player_pos_x lda #0 sta player_vel_x : ldx var_n inx stx var_n cpx #1 beq @begin_check rts left_collision_check: lda #0 sta var_n @begin_check: lda player_pos_x ; divide by 16 sec sbc #8 clc ror clc ror clc ror clc ror sta temp_x lda player_pos_y ldx var_n cpx #0 bne :+ sec sbc #7 jmp :++ : clc adc #6 : ; divide by 16 clc ror clc ror clc ror clc ror tay lda #16 jsr mul_y adc temp_x tax lda level, X cmp #0 beq :+ lda temp_x clc adc #1 asl asl asl asl clc adc #8 cmp player_pos_x bcc :+ sta player_pos_x lda #0 sta player_vel_x : ldx var_n inx stx var_n cpx #1 beq @begin_check rts