[4.5.9] Adding zapper support

kevin81

Well-known member
Here's a way to add NES Zapper support to your NESmaker game.
It's more or less a proof of concept which may or may not need to be improved, but at least it'll give you a working starting point.


1) Add a variable called zapped (1 byte) to Zero Page RAM.

2) Add a variable called palBackup (17 bytes) to Overflow RAM

3) Add a constant ZAP_WHITE_TILE with a value of 126* to User Constants.

4) Draw an 8x8 blue square in GameObjectTiles.bmp in the next-to-last tile*.
1699575226965.png

5) Download the attached txt script file, rename it to nm_zapper.asm, and put it somewhere in the GameEngineData/Routines folder.

6) In Script Settings, add a script define.
Name: Zapper handler (or whatever name you find suiting)
Define: SCR_ZAPPER
Script: Routines\nm_zapper.asm (or wherever you saved it after downloading)

7) In Project Labels, under Monster Bits, edit the value of Bit-7 and change it into Zappable (this is optional, but makes things more clear UI-wise).

8) In Routines\BASE_4_5\Game\MainGameLoop.asm, find the line that says dontSkipNormalGameLoop:, and directly below, add:
Code:
ifdef ZAP_WHITE_TILE
    .include SCR_ZAPPER
endif

9) In Routines\BASE_4_5\System\NMI.asm, find the lines that say...
Code:
doPaletteUpdates:
    .include SCR_LOAD_PALETTES
    JMP skipScreenUpdates
+
...and remove the JMP skipScreenUpdates line (or add a semicolon in front).

10) In Routines\BASE_4_5\Game\LoadAllSubroutines.asm, at the very end, add...
Code:
MonsterBits:
   .include "ScreenData\ObjectData\MonsterBits.dat"
ifdef ZAP_WHITE_TILE
    doHandleZap:
        JMP RESET
        RTS
endif
...and replace JMP RESET with whatever you want a successful zap to do. Destroy the object, add points to your score, warp to a winner screen... anything goes.

11) From the Monster Graphics Banks, open the monster you want to zap, click Object Details, click the Details tab, and select the checkbox that says Zappable (or Bit-7 if you skipped step 7).
1699575358236.png

12) Build and test the ROM. In Mesen, go to Options > Input and select Zapper for Player 2.

Happy zapping!


* 126 (or $7E) corresponds with the next-to-last tile in GameObjectTiles.bmp. If you cannot use this tile (e.g. because it is already in use), you can pick a different tile, and then count/calculate the corresponding tile number, or look it up in Mesen's PPU viewer.
 

Attachments

  • nm_zapper.asm.txt
    9.3 KB · Views: 33
just FYI for anyone wondering why the black shooting screen isn't working take a look at this tutorial:
 

baardbi

Well-known member
Here's a way to add NES Zapper support to your NESmaker game.
It's more or less a proof of concept which may or may not need to be improved, but at least it'll give you a working starting point.


1) Add a variable called zapped (1 byte) to Zero Page RAM.

2) Add a variable called palBackup (17 bytes) to Overflow RAM

3) Add a constant ZAP_WHITE_TILE with a value of 126* to User Constants.

4) Draw an 8x8 blue square in GameObjectTiles.bmp in the next-to-last tile*.
View attachment 7656

5) Download the attached txt script file, rename it to nm_zapper.asm, and put it somewhere in the GameEngineData/Routines folder.

6) In Script Settings, add a script define.
Name: Zapper handler (or whatever name you find suiting)
Define: SCR_ZAPPER
Script: Routines\nm_zapper.asm (or wherever you saved it after downloading)

7) In Project Labels, under Monster Bits, edit the value of Bit-7 and change it into Zappable (this is optional, but makes things more clear UI-wise).

8) In Routines\BASE_4_5\Game\MainGameLoop.asm, find the line that says dontSkipNormalGameLoop:, and directly below, add:
Code:
ifdef ZAP_WHITE_TILE
    .include SCR_ZAPPER
endif

9) In Routines\BASE_4_5\System\NMI.asm, find the lines that say...
Code:
doPaletteUpdates:
    .include SCR_LOAD_PALETTES
    JMP skipScreenUpdates
+
...and remove the JMP skipScreenUpdates line (or add a semicolon in front).

10) In Routines\BASE_4_5\Game\LoadAllSubroutines.asm, at the very end, add...
Code:
MonsterBits:
   .include "ScreenData\ObjectData\MonsterBits.dat"
ifdef ZAP_WHITE_TILE
    doHandleZap:
        JMP RESET
        RTS
endif
...and replace JMP RESET with whatever you want a successful zap to do. Destroy the object, add points to your score, warp to a winner screen... anything goes.

11) From the Monster Graphics Banks, open the monster you want to zap, click Object Details, click the Details tab, and select the checkbox that says Zappable (or Bit-7 if you skipped step 7).
View attachment 7657

12) Build and test the ROM. In Mesen, go to Options > Input and select Zapper for Player 2.

Happy zapping!


* 126 (or $7E) corresponds with the next-to-last tile in GameObjectTiles.bmp. If you cannot use this tile (e.g. because it is already in use), you can pick a different tile, and then count/calculate the corresponding tile number, or look it up in Mesen's PPU viewer.
Wow! This is insanely cool. I'll definitely try this out in the near future. Very very nice work!!!
 
working on making menus... is there a way if you shoot one menu white flash to destroy the rest before warping? because even after I warp the white boxes are still there
granted you only need to pull the zapper to go back to a screen in the credits & instructions menu... but for the main menu... again having trouble destroying the object before warping
 

kevin81

Well-known member
working on making menus... is there a way if you shoot one menu white flash to destroy the rest before warping? because even after I warp the white boxes are still there
granted you only need to pull the zapper to go back to a screen in the credits & instructions menu... but for the main menu... again having trouble destroying the object before warping

My first guess would be that it has to do with the zapper code not accounting for objects being active yet. You could try to edit the script as follows.

Go to line #126, where the -zap_objLoop: starts, and add the following lines:

Code:
    LDA Object_status,x     ; Get the current object's status
    AND #%10000000          ; Check if it is active (i.e. Bit 7 = 1)
    BNE +                   ;
        JMP +zap_next       ; If it is inactive, go to step 6
    +

I'm not 100% sure, but I hope this solves the issue. At least it won't hurt anyway (and probably is a good idea even) to add in this snippet.
 
My first guess would be that it has to do with the zapper code not accounting for objects being active yet. You could try to edit the script as follows.

Go to line #126, where the -zap_objLoop: starts, and add the following lines:

Code:
    LDA Object_status,x     ; Get the current object's status
    AND #%10000000          ; Check if it is active (i.e. Bit 7 = 1)
    BNE +                   ;
        JMP +zap_next       ; If it is inactive, go to step 6
    +

I'm not 100% sure, but I hope this solves the issue. At least it won't hurt anyway (and probably is a good idea even) to add in this snippet.
Fixed it! Thanks my dude!
 

wallmasterr

Active member
This is realy cool. did u upload a rom anywhere?
I wana do some testing on pal and other stuff before jumping into a project with this method.
 

vanderblade

Active member
Thanks so much, @kevin81. I was able to add second-player support for the Zapper to my rhythm rail shooter Oratorio. And, from start to finish, it only took 15-20 minutes and works perfectly. The community owes you big time.
 

crazygrouptrio

Active member
Am I the only one who can't get this to work?? I've followed it exactly and done it all as described, but it doesn't register being hit and makes many, many white boxes all around where the "zappable" object is. Help please?
Also as is it gives many Illegal Instruction, not a number errors for the ZAPPER constant, which I just replaced with $4017 at every instance, assuming that's the same thing and wouldn't cause an issue.
 

dale_coop

Moderator
Staff member
Am I the only one who can't get this to work?? I've followed it exactly and done it all as described, but it doesn't register being hit and makes many, many white boxes all around where the "zappable" object is. Help please?
Also as is it gives many Illegal Instruction, not a number errors for the ZAPPER constant, which I just replaced with $4017 at every instance, assuming that's the same thing and wouldn't cause an issue.
Weird! That zapper script works well for me.
BUT I had to make a small modification to get the correct sizes for the white squares (bankswitching to 1C before the code that draws them), the rest works well.
 

RMiao

New member
10) In Routines\BASE_4_5\Game\LoadAllSubroutines.asm, at the very end, add...
Code:
MonsterBits:
   .include "ScreenData\ObjectData\MonsterBits.dat"
ifdef ZAP_WHITE_TILE
    doHandleZap:
        JMP RESET
        RTS
endif
...and replace JMP RESET with whatever you want a successful zap to do. Destroy the object, add points to your score, warp to a winner screen... anything goes.

This is a rookie question, but how would you alter the code to destroy the object, for instance, or even perhaps to set up a warp or add points or something?

I tried playing around with this, but got an error message when trying to use JMP DestroyObject, saying that I cannot call a macro here. Like I said, this is a rookie question I am sure, as everyone else seems to have gotten it, but any help would be appreciated. Thanks!
 

kevin81

Well-known member
You can't JMP to a macro, because a macro is a feature of the assembler, whereas JMP is an assembly instruction.
I think if you replace JMP DestroyObject with just DestroyObject, your code should work better (or at least compile).
 

RMiao

New member
You can't JMP to a macro, because a macro is a feature of the assembler, whereas JMP is an assembly instruction.
I think if you replace JMP DestroyObject with just DestroyObject, your code should work better (or at least compile).
Awesome! Thanks for the assistance and advice, it now works!
 

kevin81

Well-known member
Am I the only one who can't get this to work?? I've followed it exactly and done it all as described, but it doesn't register being hit and makes many, many white boxes all around where the "zappable" object is. Help please?
Also as is it gives many Illegal Instruction, not a number errors for the ZAPPER constant, which I just replaced with $4017 at every instance, assuming that's the same thing and wouldn't cause an issue.

I think I found out what the problem is. At lines 142-151 of nm_zapper.asm, the script loads the object's width and height to calculate the number of white sprites to draw. These dimensions are retrieved from bank $1C, but the script never switches to that bank. This causes the zap script to sometimes load the wrong dimensions, thereby drawing way too many sprites for way too many frames. The fix is to wrap lines 142-151 within a bank switch and return:

Code:
    SwitchBank #$1C
        LDA ObjectSize,y       ; Get the object's height in tiles
        AND #%00000111         ;
        STA tempC              ; and store it in a temp variable
        STA tempy              ; store it twice; we need to reuse this later
        LDA ObjectSize,y       ; Get the object's width in tiles
        LSR                    ;
        LSR                    ;
        LSR                    ;
        AND #%00000111         ;
        STA tempD              ; and store it in a temp variable
    ReturnBank

I think the Illegal Instruction, not a number error may be due to different assembler versions, but I'm not sure -- replacing those with $4017 is indeed the correct fix and should not be causing any issues.
 
Top Bottom