Jump to content

MRN

Member
  • Posts

    18
  • Joined

  • Last visited

  • Feedback

    0%

Posts posted by MRN

  1. I'm looking to purchase "in bulk" to take chunks out of my lists.  Please PM me with what you have, and hopefully we'll be able to work stuff out!  

     

    I'm still missing quite a few of each library, so you'll probably have something I don't.  Thanks for looking.  

  2. It kind of depends on the person, I guess.  If it's a game or something that I don't really care about, or something that would cost a bunch to upgrade, who cares if you have 1 or 2 or 3 that are crap.  You don't accumulate 800 games without some baddies.  I'd go crazy trying to find a perfect copy of everything.  

    I keep track of my stuff in excel.  I have it on Dropbox so I can look at it wherever I go and on my computer.  Apps (and sites, for that matter) come and go, so you should probably try to keep your stuff in something that won't disappear at some point....  Just sayin'.  

    But, at the end of the day, I liken it to 2:30 am, it's cold, no one is looking, and you don't want to go home alone.  Walk of shame doesn't matter, had sex.  🙂

    • Haha 1
  3. INTRO

    I had a request for MMC1 mapper information, so I thought everyone might like to read what I have to say.

    This will help you figure out MMC1 memory mapping.  It assumes that you have a working knowledge
    of NES programming and can understand my messed up little mind. Most of the information on this
    mapper can be found on:

    http://wiki.nesdev.com/w/index.php/MMC1

    MMC1 will allow you to use up to 256 kB of PRG ROM and 128 kB of CHR ROM.  I think that you can use SUROM to expand up to 4 MB of PRG ROM, but this is not covered here.


    First, you have to decide on what kind of configuration you want to use.  MMC1 will support PRG bank
    switching in either 16kB mode or 32kB mode.  And with the 16, you can choose whether you want
    the memory at $C000 or the memory at $8000 to be switched. 

    Second, you have to decide on the CHR ROM switching.  MMC1 supports switching of 4kB (background or sprite tiles separately) or 8kB (switching them together).


    After you decide this, you are ready to start.


    INES HEADER

    Notes:

    -You can only have even numbers of PRG banks.  NESASM uses 8kB "banks", I'm talking about 16kB banks. 
    i.e. 02 (32kB),04 (64kB),06 (96kB),08 (128kB),0A (160kB),0C (192kB),0E (224kB),10 (256kB)

    -CHR banks are in multiples of 8kB banks. (important if you are using 4kB swapping.)
    i.e. 01 (8kB),02 (16kB),03 (24kB),04 (32kB),05 (40kB), 06 (48kB), etc., 10 (128kB)

    -MMC1 mapper number is "1" ...creative!

    -Mirroring should match that used below in the initiation routine.

    In this exercise, we will use:

      .inesprg $10   ; 16x 16KB PRG code
      .ineschr $10   ; 16x  8KB CHR data
      .inesmap $01   ; mapper 1 = MMC1, 4KB CHR bank swapping
      .inesmir 0   ; background mirroring


    MAPPER CONTROL HEADER

    This is one bite that has all the information for the mapper.  Observe:

    76543210

    Bits 7,6,5 - Not sure what these do.
    Bit 4 - CHR ROM bank mode - (0 means switch 8kB at a time, 1 means switch the two separate 4kB banks independently)
    Bit 3 - PRG ROM bank mode - (0 means switch all 32kB at once, ignores bit 2)
                                (1 means switch ONLY the 16kB specified in bit 2)
    Bit 2 - PRG ROM location - (0 means switch 16kB at $C000, 1 means switch 16kB at $8000)
    Bits 1,0 - Mirroring - (0 means one screen, lower bank; 1 means one screen, upper bank
                           2 means vertical; 3 means horizontal)

    Here we will use LDX #%00011000

    BITCH WORK!  Look above and figure out what this means.


    INITIATE MAPPER

    Here we load the information required by the system to run the mapper as well as the initial banks.
    You have to do the 5 writes to make it work...for whatever reason.

    initMMC1Mapper:
      LDA #$80                     ;this locks the PRG ROM at $C000-$FFFF to the last bank.
      STA $8000
     
      TXA                          ;uses our header to initiate the mapper
      JSR setMMC1ControlMode
     
      LDA #$02                     ;sets the CHR information for the sprites
      JSR setCHRPage0000
     
      LDA #$01                     ;sets the CHR information for the background
      JSR setCHRPage1000
     
      LDA #$03                     ;sets the PRG information
      JSR setPRGBank
     
      RTS
     
    setMMC1ControlMode:
            STA $8000
            LSR A
            STA $8000
            LSR A
            STA $8000
            LSR A
            STA $8000
            LSR A
            STA $8000
      RTS
     
    setCHRPage0000:
            STA $A000
            LSR A
            STA $A000
            LSR A
            STA $A000
            LSR A
            STA $A000
            LSR A
            STA $A000 
      RTS

    setCHRPage1000:
            STA $C000
            LSR A
            STA $C000
            LSR A
            STA $C000
            LSR A
            STA $C000
            LSR A
            STA $C000
      RTS
     
    setPRGBank:
            STA $E000
            LSR A
            STA $E000
            LSR A
            STA $E000
            LSR A
            STA $E000
            LSR A
            STA $E000
      RTS

    Congrats....your program should work.

    USING THE MAPPER

    You can swap out banks whenever you want, even several times per NMI.  Just load the bank number you want to
    use into A and call the appropriate subroutine.  Just be sure that you don't switch away information that
    your program needs to run or it will die.

    Weird bank numbering notes:

    -CHR data is stored in 8kB for NESASM.  If you want to call the first 4kB of data from the 6th 8kB chunk,
    you would use bank #$0C.  Observe, call number for 4kB chunk vs. 8kB bank number:

    00-0
    01-0
    02-1
    03-1
    04-2
    05-2
    06-3
    07-3
    08-4
    09-4
    0A-5
    0B-5
    0C-6
    0D-6
    0E-7
    0F-7
    10-8
    11-8
    12-9
    13-9
    14-10
    15-10
    16-11
    17-11
    18-12
    19-12
    1A-13
    1B-13
    1C-14
    1D-14
    1E-15
    1F-15

    Clear?

    -PRG info is stored in 8kB chunks in NESASM, but you call and switch 16kB banks.  If you want to call bank 26, use call number #$0D.  Observe, call number vs. bank number:

    0-0,1
    1-2,3
    2-4,5
    3-6,7
    4-8,9
    5-10,11
    6-12,13
    7-14,15
    8-16,17
    9-18,19
    A-20,21
    B-22,23
    C-24,25
    D-26,27
    E-28,29
    F-30,31

    Clear?

    -At the end of each 16kB bank, you have to have vectors in place or it will die.

      .org $FFFA     ;first of the three vectors starts here
      .dw NMI          ;when an NMI happens (once per frame if enabled) the 
                              ;processor will jump to the label NMI:
      .dw RESET    ;when the processor first turns on or is reset, it will jump
                               ;to the label RESET:
      .dw 0               ;external interrupt IRQ is not used in this tutorial

    -Bank numbering is successive.  i.e. if you have PRG banks numbered 0-23, you would start numbering your CHR banks at 24.

    -If you have, for example, a 16kB CHR file, you only have to include the starting place and NESASM will
    split the banks properly. i.e. in 4kB mode:

      .bank 32
      .org $0000
      .incbin "MMC1.chr"   ;includes 16KB graphics file

    This will include 4 - 4kB (or 2-8kB) banks in the assembly process.  Be sure to account for the 2 banks
    in your numbering.  (see the attached ASM file.)


    PRACTICAL APPLICATION

    This is a little inefficient.  To use all this nonsense in something real, an example would be:

    LoadBackground:

    ;find room info

    ;switch to room info table bank

    ;load bankground pointer

    ;switch to bank where background information is stored

    ;load background

    ;switch back to room info table bank

    ;load attribute and palette pointers

    ;switch to attribute/palette information bank

    ;load attributes/palettes

    ;switch back to room info table bank

    ;load collision detection pointer

    ;switch to collision detection bank

      RTS


    WORKING EXAMPLE

    Here we use the controller to switch banks for both CHR banks and the PRG bank.

    A and B - swap out CHR information for the sprites
    Select and Start - nothing
    Up and Down - load a background located in different banks (the flashing is cause you are holding the button
    down for more than one frame.  Just tap it.  I was too lazy to add stuff in to fix this.)
    Left and Right - swap out the CHR information for the backgrounds

    Download the attached file and assemble the program.  Mess around with it and try to switch out the
    various buttons and banks.  Fix the flashing.  Add new backgrounds and characters...etc.

    The little DOS assembler file might not work, you may have to edit it to your drive.


    THE END!

    I'm sure I totally screwed this up, but don't worry!  Someone will help me out if there are any mistakes.

    MMC1.zip

    • Like 1
  4. ETA:  Forgot to set the clear memory to load $FE to the $0700 page.  Don't forget this.

    This tutorial is kind of a side note to the others. It just shows how to do sprite cycling and better attribute handling.

    This is fairly involved, so make sure you go slowly and think about stuff. If the position stuff doesn't make sense, it won't end well for you.



    First, when you have more than 8 sprites per scanline, some will dissapear. This is pretty simple to fix. You just flip-flop the order in which they are drawn every frame...keeping in mind that ones which are stored lower in RAM are drawn first (i.e. one at $0204 is drawn on top of $0208). So, by flipping the order they are stored in RAM, we can make them flicker so they won't dissapear completely.

    Start by reserving the set $0700 to be your working area for your sprites. Keep the set $0200 (or whatever) for your DMA memory. Then simply copy them "forward" one frame and then "backwards" the next...like so:

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

    Sprite_Cycling:

    LDA Sprite_Rotation
    BNE .Backwards

    .loop1
    LDA sprite_RAM,x ;copy forwards
    STA sprite_DMA,x
    INX
    BNE .loop1

    INC Sprite_Rotation
    RTS

    .Backwards
    LDY #$FC
    .loop2 ;copy backwards
    LDA sprite_RAM,x
    STA sprite_DMA,y
    INX
    INY
    LDA sprite_RAM,x
    STA sprite_DMA,y
    INX
    INY
    LDA sprite_RAM,x
    STA sprite_DMA,y
    INX
    INY
    LDA sprite_RAM,x
    STA sprite_DMA,y
    TYA
    SEC
    SBC #$08
    TAY
    INY
    INX
    BNE .loop2

    LDA #$00
    STA Sprite_Rotation

    RTS

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

    Pretty simple. You can cycle them all (like here) or just some (if you have a sprite zero hit, you don't want that bitch moving around). Or you can make it so your PC is always on top.



    Next, we will look at how to better handle attributes, at least in my opinion. If you figure this out, you'll never have to fuck with them again! It will be automatic.

    So, first we need to modify our metatiles.

    MetaTileSeta00
    .db $44,$49,$5F,$7A,%00000000,%00001111

    The first 4 bytes are the tiles, like always.
    The sixth byte is the collision data, like always.
    The fifth byte is the attribute data for that particular metatile. If you want it to draw from pallete "01" you'd use "%01010101", pallete "11", "%11111111", and so on.

    Do this for all of them. Note that if you have two tiles that use the same bg tiles but different colors, you'll have to make a duplicate.



    It's probably better that you look through the routine as you read. It's pretty simple once you figure it out.

    Next, we need to look at our position in the background data table. If you think in hex, you always have $10 meta tiles across the screen. So, row 0 would be $00-$0F, row 1 is $10-$1F, 2 is $20-$2F.

    So, the attribute bytes are 2 metatiles across. This means that for each row, we just divide by 2 to get the position in the table...we hope that the NES has a convinent command for this. Just use LSR A!!

    Next, there is the same thing with the vertical. Row 0 and 1 use the first 8 attribute bytes, 2 and 3 use the second, and so on. So, to figure out a row, we AND the room table counter with %11100000 and LSR A twice...I'll leave it to you to work this out.

    The combo of these two steps will produce your position within the attribute table.

    Next, you simply use the table position to work out which quadrant you are within in the attribute byte, and Bob's your uncle.



    I kinda glazed over this finding the position thing. So if you can't figure it out, I can delve into it further. But it is important that you work it out and understand what it's doing. Take a pen and paper, draw out a screen, label each metatile (in hex), and manually decode the routine to see what it's doing. It will click and life will be titties and rainbows.

    I hope you enjoyed this. Post questions!! 

    attributes.zip

    • Like 1
  5. INTRODUCTION

    This time we are going to add in the (in my opinion) hardest part so far.  This one takes some math skills, the ability to visualize exactly what the program is doing, and a complete understanding of how our pointers/data tables are set up.  If you understand everything up to this point, you should be okay.  If not, you're Fucked.  But don't dispair, if you get stuck or don't understand, just ask questions. 

    A couple things that I need to say before we get started:

    1.  The concept for this routine is 100% original, as is the actual code and the process.  I had hints from people on NA and nesdev (mostly it was them telling me that I was attempting to do it wrong at the start), but this I came up with pretty much on my own.

    2.  There are a couple things that could be corrected in the program.  Mainly that there are no walls in certain places and the routine doesn't exactly know what to do with the end of the screen, so it makes the NPCs jump to a random point when it hits the edge of the screen.  Easily corrected though.

    3.  This program is easily modified for use in a platformer.  If I get time in the near future, I'll go through that as well.

    4.  This program could also be modified to support scrolling.  I might do this too if I ever get around to it.

    5.  Other than the collision routine its self, which isn't all that complex, all the PRG space that this takes up is what we need in the way of collision data: one (1) byte per meta tile.  face-icon-small-happy.gif

    6.  This routine is based on the meta tiles and meta sprites that we've built up to this point.  You could modify it to support different ways of doing things, but then you'd loose the compression effects.

    7.  Ever thus to deadbeats.


    CONCEPTS

    First we need to lay out what we're aiming for.  We want some sort of routine that will detect when a sprite comes into contact with a piece of background that we define as "solid" and/or do something when it impacts a certain part of the screen.  Then we need to tell the program what to do when this impact(s) occur(s).  So, if we think about this, we need to somehow have the following steps.

    1.  Calculate the meta sprite's position on the screen.

    2.  Pull some sort of collision data based on this position and the current background.

    3.  Determine from this collision data if we need to move the character (or do something else).

    4.  Move the character.

    5.  Reset the graphics.

    6.  Repeat for each character/meta tile.

    7.  ???

    8.  Profit.

    Note that we are not going to cover anything besides "simple collision" here.  Once you have the program working that pulls the collision data, the various add ons (or op-codes) are fairly simple to input.


    COLLISION DATA

    I suppose that the best place to start would be for us to decide how we are going to define our collision data.  Well, this is pretty simple.  Remember our meta tiles??  Let's jump back to them.  Remember this picture:

    Graphics2.PNG.c4d22f743aa01ed2ca2d47beafd4a21c.PNG

    Here, we are going to keep everything simple.  "Solid" meta tile blocks and "hollow" meta tile blocks, however the process is exactly the same for something that only impacts in one direction.  (Think the bricks in Mario Bros. that you can jump up through, but can't fall down through.)  We are going to store all the data for each meta tile in one byte.  Basically:

    %0,0,0,0,0,0,0,0

    %Op-Code,Op-Code,Op-Code,Op-Code,Up,Down,Right,Left

    Where, for each bit:
    0 = hollow
    -and-
    1 = solid

    -or-

    0 = do nothing
    -and-
    1 = activate op-code (Note that this limits you to 4 op codes, you may add more pretty easily.)

    So, if we wanted something to only register an impact when we are:

    -going up,              %00001000
    -going down,         %00000100 (heh, going down, get it?)
    -going right,          %00000010
    -going left,             %00000001
    -going up/down,     %00001100
    -goint right/left,     %00000011
    -etc.

    Nice and simple.  And it keeps our data easy to read, which is always a plus. 


    META TILE COLLISION DATA

    Again, for our purposes, we are going to keep it simple.  While the program fully supports the various settings described in the previous section, we are going to stick with %00000000 and %00001111, hollow or solid.  Once you complete this, you should go back and change some of these settings to see what it actually does.

    So, if we make the only "hollow" meta tile the "sky" tile and the rest of them "solid", our meta tile bank turns into this:

    ;------------------------------------------------------------------------------------

    MetaTileSeta00
      .db $44,$49,$5F,$7A,%00001111

    MetaTileSeta01
      .db $AB,$AD,$AC,$AE,%00001111

    MetaTileSeta02
      .db $53,$54,$55,$56,%00001111

    MetaTileSeta03
      .db $B0,$B2,$B1,$B3,%00001111

    MetaTileSeta04
      .db $A5,$A6,$A7,$A8,%00001111

    MetaTileSeta05
      .db $7B,$7C,$7D,$7E,%00001111

    MetaTileSeta06
      .db $24,$24,$24,$24,%00000000

    MetaTileSeta07
      .db $45,$45,$47,$47,%00001111

    MetaTileSeta08
      .db $47,$47,$47,$47,%00001111

    MetaTileSeta09
      .db $CA,$CB,$CC,$CD,%00001111

    MetaTileSeta0A
      .db $C6,$C7,$C8,$C9,%00001111

    MetaTileSeta0B
      .db $6B,$6C,$70,$71,%00001111

    MetaTileSeta0C
      .db $6D,$6C,$72,$71,%00001111

    MetaTileSeta0D
      .db $6D,$6F,$72,$74,%00001111

    ;----------------------------------------------------------------------------------

    See, this is all we have to specify our collision data.  You could either have this monstorous set of "CMP"s for each direction (as in the sprite collisions), that takes on the order of 750 bytes per room, or one byte per meta tile.  You decide.


    THE "collideptr" POINTER

    One thing that we need to add that is kind of random is a pointer that supports the collision routine.  This needs to be set up in the "Room Loading" routine.  Note that technically here you could use the "ptr1" that we are using in the decompression routine, but as your program gets more complex...well, it's just better to have something as important as collision detection use its own variables.  So, in the "LoadbgBackground" routine:

    ;----------------------------------------------------------------------------------

    ;;;;;;;;;;;;;insert;;;;;;;;;;;;;

      ASL A                                ;indexing to a table of words

      STA room_index
     
      LDX room_index                       ;using the room index, load the pointer to the correct background data
      LDA backgroundpointer,X
      STA ptr1
      LDA backgroundpointer+1,X
      STA ptr1+1

    ;;;;;;;;;;;;;insert;;;;;;;;;;;;;

    -becomes-

    ;;;;;;;;;;;;;insert;;;;;;;;;;;;;

      ASL A                                ;indexing to a table of words

      STA room_index
     
      LDX room_index                       ;using the room index, load the pointer to the correct background data
      LDA backgroundpointer,X
      STA ptr1
      STA collideptr                       ;store the room data for collision detection
      LDA backgroundpointer+1,X
      STA ptr1+1
      STA collideptr+1

    ;;;;;;;;;;;;;insert;;;;;;;;;;;;;

    ;----------------------------------------------------------------------------------

    Notice that our collideptr is pointing to the background data table for the room that we are loading!  i.e. the table of meta tiles...see where we are going??


    COLLISION POSITION REQUIREMENTS

    If we take a step back and think about this for a moment, we have a bit of a problem.  Consider the following picture:

    CollisionExamples.PNG.2afcab257e810d18248819d501f1c185.PNG

    Assume that the red square represents our metasprite and that the block/sky is the space that it is currently occupying over the background.  You can see in each example picture, we are impacting the same 4 background meta tiles, but depending on EXACTLY where the 4 corners of our metasprite are, we would require completely different results from our collision routine. 

    Take the upper left hand example in the picture.  If we used the bottom of the meta sprite as the collision coordinates for moving to the right, we wouldn't register an impact, but the top would.  Same with the upper, 2nd from the left.  Or, if we were trying to move up, if we used the coordinates of the left side of the meta sprite, we wouldn't register a hit but the left side would.  Work your way through each of the 8 pictures to be sure you understand this point.  So what do we do about this little problem?

    Panic?  Sure.  Next take a breather and consider the following.  We just need our program to compute the location of each of the 4 corners.  Then if we were using the upper left picture and moving right, we would simply say:

    -Look at the bottom, right corner.  No impact. 
    -Look at the top, right corner.  Oh shit!  An impact!!

    Simple.  Understand?  Same with moving up:

    -Look at the top, left corner.  No impact.
    -Look at the top, right corner.  COLLISION! Slap ass crazy.

    So, now that we know that we have to compute the location of 4 different corners, we can define a few variables to help us along our way.  I'm just going to put these out there and define them better as we go along.

    ;----------------------------------------------------------------------------------

    collision_index  .rs 4        ;indexes for the look up of meta tile info in the main collision routine
    collide_metatile  .rs 4       ;place to store collision meta tile data
    collide_vertical  .rs 4       ;store the index data for the sprite positions in the main collision routine
    collide_horizontal  .rs 4

    collide_data  .rs 4           ;actual data from the meta tile streams
    collide_position  .rs 4       ;the location to set him to if he hits something

    ;----------------------------------------------------------------------------------


    DEFINING THE META SPRITE COLLISION BOUNDARIES

    First, we need to find the positions that we will be using to define our collision coordinates for these 4 corners.  Remember how before we referenced everything to the "sprite_vertical" and "sprite_horizontal"?  Well this is why.  When we loop our different characters through this routine, we will be using all of our registers in another fashion.  So, we need to simply transfer their coordinates to these variables before we call the collision routine, then, if there is a collision, modify these variables, then use these variables to transfer the data back into the RAM space. 

    In this routine, we are going to need to use these two variables to specify the location of each of the 4 corners.  Note that here we are assuming that all of our meta sprites are the same.  If you use different sizes, you'll have to add something based on enemy type for this.  Consider this picture and the variables that we introduced above:

    CollisionBoundaries.PNG.62bcb378ea6d0d5a048dfc36e477d688.PNG

    You can set your boundaries however you want, but if you don't set the bottom to the bottom of the feet, your character will "float" and if you don't leave a couple pixels at the top, your character will have trouble fitting between meta tiles.  Sameish for the right/left.  So, to load our values for our collide_horizontal and collide_vertical variables, we simply write:

    ;----------------------------------------------------------------------------------

    .sprite_transferBGCD:

      LDA sprite_vertical              ;transfer correct collision box coords
      CLC
      ADC #$02                         ;to change this, must change DEC below to keep him from flashing (comes later)
      STA collide_vertical             ;here we set the boundries of the collision detection for the metasprites
      STA collide_vertical+1           ;note that here we assume that all of the sprites are the same size

      LDA sprite_vertical              ;if your sprites are different, you'll have to add a subroutine here
      CLC
      ADC #$10
      STA collide_vertical+2
      STA collide_vertical+3

      LDA sprite_horizontal
      CLC
      ADC #$03
      STA collide_horizontal
      STA collide_horizontal+2

      LDA sprite_horizontal
      CLC
      ADC #$0C
      STA collide_horizontal+1
      STA collide_horizontal+3

    ;----------------------------------------------------------------------------------

    Note that in the following, some of this is not necessary.  However, I'm leaving it in here because if you expand the program, you will need all these variables later.


    FINDING META TILES

    Now that we have our dude's location on the background entered in temporary variables, we need to use them to calculate some sort of index to pull the meta tile information from our room data.  And after we have the meta tile info, we use that to pull the collision data we specified eariler...but one thing at a time. 

    Let's start with the top left corner of our meta sprite and a picture. 

    1170602942_RoomExample.PNG.ea2d04241835c876a03aac4bd1786129.PNG

    We see that a room is composed of 16x16 (or $10x$10) pixel meta tiles.  Consider this room and the data table that the room loading routine pulls data from.  We see that each $10 pixels we go down, we are going another $10 entries into this table (or 1 row down, hence formatting the room data talbes this way)...or, for the left most meta tile:

    -row 0 (top row) = $00
    -row 1 = $10
    -row 2 = $20
    -etc. 

    So, to specify which meta tile the collision coordinates are occupying in the vertical direction, we simply take the vertical position:

    -compare it to $10
          -if lower, we have found our meta tile
          -if higher
                 -subtract $10 from the vertical position
                 -add $10 to our collision_index variable then jump back to the start of the loop

    Now, we've specified which row our guy is in, so now we need to account for the horizontal portion of the position.  This is pretty much the same as the vertical part, except that for every $10 you go to the right, you only add $01 entry to our collision_index variable.  Make sense?

    Think about it.  If we are in:

    -column 0 = $X0
    -column 1 = $X1
    -column 2 = $X2
    -etc. 

    So, to add in the horizontal part of our position, we simply take the horizontal position and:

    -compare it to $10
          -if lower, we have found our meta tile
          -if higher
                 -subtract $10 from the horizontal position
                 -add $01 to our collision_index variable then jump back to the start of the loop

    So, now we've found the value for "collision_index".  Does this make sense?  This is basically the hardest part, so if you don't get it, stop and ask questions.  All we're doing is using the location of our meta sprite and translating that to an index to pull data from the specific room we are in's background data table. 

    Now, we have 4 corners that we need to consider when running collision detection.  So, this implies that we will need to run this little loop 4 times, 1 time for each corner.  However, this is resource intensive, it will basically take our program twice as long to run...so we are going to take a short cut.  Remember how our collision box above always keeps the right side a fixed distance from the left side?  Well, we can use that information to cut the 2nd and 4th loop out of the program.

    So, if we take the value for collision_index (upper left corner) and store it in collision_index+1, we can modify collision_index+1 to specify the upper right corner.  Think back to the last time through the loop for the horizontal portion.  We compare the value to $10 and if it is less than this, we just disregard this unused portion and jump down to the next step...however, if we do it correctly, the remainder should still be in the register, in this case "Y".  So, if we take this remainder, add the fixed horizontal distance that we specified in our meta tile collision boundaries, we will come up with the horizontal location of our right corner!!  So now, we simply take this value and:

    -compare it to $10
          -if lower, collide_index = collide_index+1 (both sides of the meta sprite are on top of the same meta tile)
          -if higher
                 -add $01 to our collision_index+1 variable
                 -jump to the next part (we see that collision_index+1 will always equal collision_index or be 1 greater than)

    Now if we've done this correctly, we should have an accurate location of each of the top corners with respect to the background data.  However, if we look back at our background data, we have one minor problem...the first entry in the table is always the meta tile bank number.  So we need to INC collision_index and collision_index+1 to account for this fact.

    NOW we have the correct location of the data we are seeking.

    The next step is simple.  Use these index numbers to pull the meta tiles from the room data.  Then store these meta tiles into the variables we set up, collision_metatile and collision_metatile+1. 

    Finally, we jump back to the start and repeat the process for the two bottom corners. 

    Simply stated in code, we do the following:

    ;----------------------------------------------------------------------------------

      LDX #$00

    .find_collision_meta_tiles

      LDA collide_vertical,x          ;find the index for the upper left corner of the sprite intersect
    ;  SEC
    ;  SBC #$40                       ;take off the HUD if you are using one to save loop time.  You'll have to change the vertical positions as well
    .loop
      CMP #$10                        ;Here we use the sprite vertical/horizontal positions to find the look up index in the room data table
      BCC .next                       ;use the vertical position to find the first part
      SEC
      SBC #$10                        ;take one row off of our vertical position
      TAY
      LDA collision_index,x
      CLC
      ADC #$10                        ;add $10 to our index or skip a row in the data table
      STA collision_index,x
      TYA
      INC collide_position,x          ;set up the value of the position marker (vertical_position)
      JMP .loop 

    .next                             ;include the horizontal portion of our position
      LDA collide_horizontal,x
    .loop2
      CMP #$10                        ;for each meta tile we are from the left, incriment our index
      BCC .next1
      SEC
      SBC #$10
      INC collision_index,x
      INC collide_position+1,x        ;set up the position marker
      JMP .loop2

    .next1
      TAY                             ;find the opposite corner, the right corner is always a FIXED distance from the left corner
      LDA collision_index,x
      STA collision_index+1,x        
      TYA
      CLC
      ADC #$09
      CMP #$10
      BCC .next2                      ;are the left and right corners in the same meta tile??
      INC collision_index+1,x

    .next2 
      INC collision_index,x           ;skip the meta tile bank entries in the table
      INC collision_index+1,x
                                      ;our index should be correct now!  PowerYay!!
     
    .load_meta_tile_data

      LDY collision_index,x           ;load the meta tile number from the room data block
      LDA [collideptr],y
      STA collide_metatile,x          ;store the meta tile info

      LDY collision_index+1,x         ;load the meta tile number from the room data block
      LDA [collideptr],y
      STA collide_metatile+1,x        ;store the meta tile info

      INX                             ;first loop is for the top of the metasprite
      INX                             ;second is for the bottom of the metasprite
      CPX #$04
      BNE .find_collision_meta_tiles

    ;----------------------------------------------------------------------------------


    SPECIFYING POSITIONS TO RESET META SPRITES

    You'll notice the variable set "collide_position" in this routine.  These are the variables that we use to specify where we want our meta sprite to be reset to given an impact in a certain direction.  You'll see that the values of collide_position and collide_position+1 are modified each time we go through our loops when we try to use the vertical and horizontal parts of our position.  So, we are counting meta tiles towards the bottom from the top of the screen and to the right of the left side of the screen.  Basically, we are setting up a grid that our sprites will snap to if they hit something. 

    Note that you only need the data acquired in the first time through the loop but you have to leave it in there cause we are using the "X" index. 

    Now, after we've set up a variable to track the position like this, we need some sort of data set that translates this info into pixel coordinates.  Like so:

    ;----------------------------------------------------------------------------------

    vertical_positions:
      .db $00,$0F,$1F,$2F,$3F,$4F,$5F,$6F,$7F,$8F,$9F,$AF,$BF,$CF,$DF        ;this is what we set the position data to when we hit something
    horizontal_positions:
      .db $00,$0D,$1D,$2D,$3D,$4D,$5D,$6D,$7D,$8D,$9D,$AD,$BD,$CD,$DD,$ED

    ;----------------------------------------------------------------------------------

    This is a somewhat iterative process that I did by trial and error after freaking error.  I'm sure if you tried hard enough, you could make this part cleaner, but at some point you just say "Fuck It" and move on.  By using the same data sets as described above for, say, up and down collision, you run into a slight "flashing" problem.  Basically, your dude runs through a wall before the program registers the impact, then the next frame the impact registers, then it moves the guy for that frame, then you move into the wall again, on and on, flashing one frame in each location, i.e.:

    1888474239_FrameFlash.PNG.c99919cf8bb66bfc8d3392deed1f9196.PNG

    So, we have to make some minor corrections to the vertical_positions and horizontal_positions data to make this flashing stop.  In the end, to save variable space, we overwrite these position variables that we calculated above with the actual values that we will set our meta sprite positions to.  This is a pretty simple part of the routine, suffice it to say that when it's done, you'll end up with this:

    collision_position = position to set the meta sprite to if moving DOWN
    collision_position+1 = position to set the meta sprite to if moving UP
    collision_position+2 = position to set the meta sprite to if moving RIGHT
    collision_position+3 = position to set the meta sprite to if moving LEFT

    -or, the actual code-

    ;----------------------------------------------------------------------------------

    .position_finder
      LDX collide_position            ;find the various stop places in the grid using our positions above
      LDA vertical_positions,x
      STA collide_position
      INX
      LDA vertical_positions,x
      LDX collide_position+1
      STA collide_position+1
      DEC collide_position+1          ;must change this if guy flashes
      LDA horizontal_positions,x
      CLC
      ADC #$06                        ;must change this if guy flashes
      STA collide_position+2
      INX
      LDA horizontal_positions,x
      STA collide_position+3

    ;----------------------------------------------------------------------------------


    PULLING COLLISION DATA FROM META TILE BANKS

    Now that we have everything pretty much set up, we need to do some simple looks ups to pull data from our meta tables.  i.e. the 1 byte per meta tile that we specified in the first part of this write up.  This part of the routine would run as follows:

    -load the meta tile bank number (first entry of the room data stream) and store the pointer info in our "meta_tile_sets_ptr" pointer
    -start a loop for X = 0, 1, 2, 3
    -load the collide_metatile+X that we found above and pull the value from our room data table
    -using our meta_tile_sets_ptr and the collide_metatile values, load the pointer meta_tile_ptr with the address of the meta tile we are checking collision with
    -pull the 4th entry from the meta tile data stream and store it in the variable collide_data+X for later use
    -jump back to the start of the loop unless X = 3

    -or-

    ;----------------------------------------------------------------------------------

      LDY #$00
      LDA [collideptr],y
      AND #%00111111                 ;load the correct meta tile bank address
      ASL A
      TAX

      LDA meta_tile_sets,x           ;load the meta tile set pointer
      STA meta_tile_sets_ptr
      LDA meta_tile_sets+1,x
      STA meta_tile_sets_ptr+1

      LDX #$00                       ;start out at X = zero

    .load_collision_data:

      LDA collide_metatile,x         ;load the meta tile found earlier
      ASL A
      TAY

      LDA [meta_tile_sets_ptr],y     ;load the pointer info for that metatile
      STA meta_tile_ptr
      INY
      LDA [meta_tile_sets_ptr],y
      STA meta_tile_ptr+1

      LDY #$04                       ;load the collision data from the meta tile string
      LDA [meta_tile_ptr],y
      STA collide_data,x             ;store it for later use

      INX
      CPX #$04                       ;repeat for each corner of the meta sprite
      BNE .load_collision_data

    ;----------------------------------------------------------------------------------

    Simply put, this uses the data that we've compiled throughout this exercise and finally pulls the collision data from our game's memory.  If we've made it this far, we are just about done!  Just need to tie up some loose ends and viola!  Success. 


    COLLISION DETECTION

    Now that we've got this, it is a simple matter of checking a couple positions and determining if we need to move our meta sprite or not. 

    Each time we run our Collision_Detection subroutine, the first thing we need to do is clear out some variables: the collision_index variable set and the collide_position set.  You'll notice on these variables we never actually store a value to them.  We either INC them or ADC to them.  So, if we don't reset them every time, we'd have some residual data that would fuck up our routine.

    ;----------------------------------------------------------------------------------

    Collision_Detection:

      LDA #$00                        ;clear out the indexes
      STA collision_index
      STA collision_index+1
      STA collision_index+2
      STA collision_index+3
      STA collide_position
      STA collide_position+1
      STA collide_position+2
      STA collide_position+3

    ;----------------------------------------------------------------------------------

    Next, we JSR to the "Find_Collision_Meta_Tiles" subroutine (i.e. find the collision data) that we just constructed above.  Note that you don't need to JSR here, but if you modify this for platformer use, you'll have to.

    Then, if you think about it, in this "top-down" type game, you only run in one direction at any given time...so you only need to check collision in the direction you're moving.  This saves us some effort.  (Again, with a platformer, you are moving up/down and right/left at the SAME time...keep that in mind.)  So, we need ot construct a simple routine that tells the program which direction we want to check:

    ;----------------------------------------------------------------------------------

    Collision_Detection:

      LDA #$00                        ;clear out the indexes
      STA collision_index
      STA collision_index+1
      STA collision_index+2
      STA collision_index+3
      STA collide_position
      STA collide_position+1
      STA collide_position+2
      STA collide_position+3

      JSR Find_Collision_Meta_Tiles   ;find the collision data from the meta tiles

      LDX enemy_number                ;we only need to check the direction we are currently traveling
      LDA enemy_direction,x
      BEQ .up
      CMP #$01
      BEQ .down
      CMP #$02
      BEQ .right
      JMP .left

    ;----------------------------------------------------------------------------------

    Then we will jump to the various directions.  Note that these directions are all pretty much the same, so we'll just go over one of them.  But first, remember the AND function that we learned about from MetalSlime?  We use it here.  You'll remember that we specified the solid/hollow collision data with one bit for each direction in a single byte to save space.  So, we'll have to use the AND command to pull the data for a single direction from this byte.  So, to check collision if we are moving UP:

    -load the variable "collide_data" into A (note that this is the upper LEFT corner)
    -AND #%00001000 (the UP bit)
          -if A=0, there is no collision, jump to "up1"
          -if A is not = 0, we have a collision!!! 
                 -load the correct "collide_position" value that we calculated into A (+1 in this case)
                 -store A in sprite_vertical (move the meta sprite to the collision boundary)
                 -jump to .continue_on
    .up1
    -load the variable "collide_data+1" into A (note that this is the upper RIGHT corner)
    -AND #%00001000 (the UP bit)
          -if A=0, there is no collision, jump to "up2" (a RTS command)
          -if A is not = 0, we have a collision!!! 
                 -load the correct "collide_position" value that we calculated into A (+1 in this case)
                 -store A in sprite_vertical (move the meta sprite to the collision boundary)
                 -jump to .continue_on
    .continue_on (this is just to make the meta sprites do something when they hit a wall, otherwise, they'd just sit there and keep running at the wall)
    -check to see if it is the PC (if it is, we don't want to mess with it because we control it)
          -yes, RTS
          -no, continue
    -JSR to our direction_change subroutine so that the meta sprite "bounces" off the wall.  However, you can put whatever you want here. 
    -RTS

    We do this for all four directions and we're done!!

    ;----------------------------------------------------------------------------------

    Collision_Detection:

      LDA #$00                        ;clear out the indexes
      STA collision_index
      STA collision_index+1
      STA collision_index+2
      STA collision_index+3
      STA collide_position
      STA collide_position+1
      STA collide_position+2
      STA collide_position+3

      JSR Find_Collision_Meta_Tiles   ;find the collision data from the meta tiles

      LDX enemy_number                ;we only need to check the direction we are currently traveling
      LDA enemy_direction,x
      BEQ .up
      CMP #$01
      BEQ .down
      CMP #$02
      BEQ .right
      JMP .left

    .right
      LDA collide_data+1              ;load the data we found
      AND #%00000010                  ;check only the right direction data
      BEQ .right1                     ;no impact, check the bottom of the sprite
      LDA collide_position+2          ;load the position setting we found
      STA sprite_horizontal           ;store it in the horizontal position of the sprite
      JMP .continue_on                ;jump to the next step
    .right1
      LDA collide_data+3              ;same thing on the bottom
      AND #%00000010
      BEQ .right2
      LDA collide_position+2
      STA sprite_horizontal
      JMP .continue_on
    .right2                           ;no impact?  Don't do shit!!
      RTS

    .left
      LDA collide_data                ;same as above.
      AND #%00000001
      BEQ .left1
      LDA collide_position+3
      STA sprite_horizontal
      JMP .continue_on
    .left1
      LDA collide_data+2
      AND #%00000001
      BEQ .left2
      LDA collide_position+3
      STA sprite_horizontal
      JMP .continue_on
    .left2
      RTS

    .up
      LDA collide_data
      AND #%00001000
      BEQ .up1
      LDA collide_position+1
      STA sprite_vertical
      JMP .continue_on
    .up1
      LDA collide_data+1
      AND #%00001000
      BEQ .up2
      LDA collide_position+1
      STA sprite_vertical
      JMP .continue_on
    .up2
      RTS
     
    .down
      LDA collide_data+2
      AND #%00000100
      BEQ .down1
      LDA collide_position
      STA sprite_vertical
      JMP .continue_on
    .down1
      LDA collide_data+3
      AND #%00000100
      BEQ .down2
      LDA collide_position
      STA sprite_vertical
      JMP .continue_on
    .down2
      RTS

    .continue_on
      LDA enemy_number
      BEQ .done
      JSR direction_change                          ;need to add this in here so that the enemies don't just sit there and run at the walls
    .done                                           ;errrr....helments!!  
      RTS

    ;----------------------------------------------------------------------------------


    CALLING THE COLLISION DETECTION ROUTINE

    Now that we've written the routine, we'll need to call it either in NMI or our Main Program.  Putting lenghtly stuff in NMI is bad, so we'll put it in the main program.  Think about where we should put it.  We'll have one place for our PC JSR group and on place in our NPC loop. 

    For the PC, if you think about it, the place that makes sense is right after we "handle_input".  That way we move our dude based on the controller input, check to see if he hits a wall, then run the graphics updates.  If you put it after the graphics updates, you'd have to run them again if you moved the meta sprite during collision which is a waste of time. 

    Note that these "transfer_location" routines are necessary to run the Collision_Detection routine we've set up, but you don't have to make them their own routine.  I do it this way because when I make my own programs, I reference EVERYTHING to the sprite_vertical and sprite_horizontal variables (rather than the Sprite_RAM+X stuff we use here).  I think that the Sprite_RAM stuff is easier to grasp and visualize when you are first starting.  It is a little more difficult to grasp when you use the same variables for everything.  But referencing everything to these two variables makes your routines faster and easier to loop through, IMHO.  But to each his own.

    Back on topic, we update our PC JSR group as follows:

    ;----------------------------------------------------------------------------------

    ;;;;;;;;;;;;;insert;;;;;;;;;;;;;

      LDA #$00
      STA enemy_number
      STA enemy_ptrnumber

      JSR handle_input

      JSR transfer_location_info

      JSR Collision_Detection

      JSR restore_location_info

      JSR Enemys_Animation

      JSR Enemys_Sprite_Loading

      JSR update_enemy_sprites

      JSR transfer_location_info

      JSR arrow

    ;;;;;;;;;;;;;insert;;;;;;;;;;;;;

    ;----------------------------------------------------------------------------------

    Then it's basically the same thing in the enemy update routine:

    ;----------------------------------------------------------------------------------

    ;;;;;;;;;;;;;insert;;;;;;;;;;;;;

    UpdateENEMIES:
      LDA #$01                         ;this loop updates the enemies one at a time in a loop
      STA enemy_number                 ;start with enemy one
      LDA #$02
      STA enemy_ptrnumber
    .loop
      JSR enemy_update                 ;move the sprites based on which direction they are headed

      JSR transfer_location_info

      JSR Collision_Detection

      JSR restore_location_info

      JSR Enemys_Animation             ;find out which frame the enemy animation is on
      JSR Enemys_Sprite_Loading        ;update the enemy meta tile graphics
      JSR update_enemy_sprites         ;update position

      LDY #$00            ;arrow       ;sprite collision detection routines
      JSR enemy_collision              ;note that 00, 02, and 04 are to specify the variables of each weapon
      LDY #$02            ;mace
      JSR enemy_collision
      LDY #$04            ;Playable Character
      JSR enemy_collision

      INC enemy_number                 ;incriment the enemy number for the next trip through the loop
      INC enemy_ptrnumber              ;these are addresses for the graphics data, so we need to keep it at 2x the enemy number
      INC enemy_ptrnumber
      LDA enemy_number
      CMP #$04                         ;if it is 4, we have updated enemies 0,1,2,3 so we are done
      BNE .loop
    UpdateENEMIESdone:

    ;;;;;;;;;;;;;insert;;;;;;;;;;;;;

    ;----------------------------------------------------------------------------------


    END GAME

    I think that's about it.  You should have a pretty good start to a background collision engine.  I would suggest that you take and modify the rooms to see that it infact updates the collision detection automatically.  Then change some of the collision properties of the various meta tiles.  Then add in some simple op-codes like infront/behind some hollow background stuff or something.  Play around and see what you can come up with.  Maybe expand the map to be sure you understand the room loading/switching routines.

    Modifying this for use in a platformer is pretty simple.  I'll try to sit down and write something up for that when I get time.  Scrolling is somewhat more complex.  Basically you'd just have to use your scroll values to modify your index that pulls data from the background tables and the collide_position variables will need changing.  If I do this one, it will be a while as scrolling and I don't really see eye to eye...YET! 

    Please enjoy and let me know if you have any questions.

    Until next time teabag.gif!!

    backgroundcollision.zip

    • Like 1
  6. INTRODUCTION

    This write up is pretty straight forward.  It basically merges our Sprite Collision program with our Background Compression program.  There are some changes here and there, but that's basically it.  I've added some extra stuff to give this part some content though.  It should be helpful to you. 


    MERGING THE PROGRAMS

    Well, the first thing that we need to do is merge the programs.  This is pretty much a cut/paste job that you can walk yourself through.  Just start at the top and work your way down.  I'll leave it to the user to figure this out as it really is simple.


    CHR_RAM PART 1

    First, you'll notice that the Sprite Collisions uses CHR_RAM and the Background Compression uses the standard CHR banks.  We'll we are going to use the CHR_RAM technique because it is much more interesting in my opinion.  First thing that we need to do is make sure that the .ineschr value is "$00" because we don't have any CHR banks.  All of our CHR data is stored in the program, not in seperate CHR banks.

    Next, we need to add this to our RESET routine:

    ;------------------------------------------------

      LDX #$00
      JSR LoadCompleteBank              ;load the sprite data

      LDX #$02
      JSR LoadCompleteBank              ;load the background data

    ;------------------------------------------------

    In the Sprite Collisions we were only using the sprite table, so we hardcoded the addresses.  Here we have 2 banks to mess with so we'll need some sort of pointer set up to reference the graphics we want to load.  So, we set up a address table as follows:

    ;------------------------------------------------

    graphicspointers:                 ;addresses of the CHR Data
      .word Sprite_Data,CHR_Data

    ;------------------------------------------------

    What does this reference?  Well, now we get into how to include the graphics files into your program without actually having to enter all the numbers in.  Thankfully, we can use the tools and CHR files that we've already made through our friend Tile Molestor.  face-icon-small-smile.gif  Now, you don't have to include entire banks, you can do partial banks, or even one tile using this method, but we'll get into that later.  Here, because we're lazy, we are just going to keep including the entire CHR data banks.  So, we need to do as follows in the various banks of our program data:

    ;------------------------------------------------

    ;;;;;;;;;;;;
    ;;;;;;;;;;;;

      .bank 2
      .org $C000

    ;;;;;;;;;;;;
    ;;;;;;;;;;;;

    CHR_Data:
      .db $10,$00                             ;background address in the PPU

      .incbin "CHR_Files/mario1.chr"

    ;;;;;;;;;;;;
    ;;;;;;;;;;;;
    ;;;;;;;;;;;;

      .bank 3
      .org $E000

    ;;;;;;;;;;;;
    ;;;;;;;;;;;;

    Sprite_Data:
      .db $00,$00                             ;sprite address in the PPU

      .incbin "CHR_Files/SpriteMovement.chr"  ;include the sprite graphics data

    ;;;;;;;;;;;;

      .org $FFFA     ;first of the three vectors starts here
      .dw NMI        ;when an NMI happens (once per frame if enabled) the
                       ;processor will jump to the label NMI:
      .dw RESET      ;when the processor first turns on or is reset, it will jump
                       ;to the label RESET:
      .dw 0          ;external interrupt IRQ is not used in this tutorial

    ;;;;;;;;;;;;
    ;;;;;;;;;;;;

    ;------------------------------------------------

    Before we used the .include command.  .include is for additional code that we want the assembler to incorperate into our program.  .incbin is "include binary file".  Basically, just include data. 

    You'll also note that there are 2 numbers before we say .incbin but after the address labels we used in our table above.  This is the address that we want the CHR data loader to start writing data to.  We have to tell it where to put the data or it will just put it wherever the hell it wants to.  You can use whatever you want, but here we are putting our sprite data at PPU address #$0000 and background data at #$1000.  Hence the labels.


    Well, what do we do with it?  This is pretty much exactly the same as writing tiles to get background as you would do when loading a room.  Just remember, you don't have to turn off NMI to write to the CHR space, but you MUST either do it during NMI or with the background off.  Otherwise your program will frac out. 

    Next, we need to introduce a new pointer.  This is the pointer to the current tile data to be loaded.  We will just use:

    ;------------------------------------------------

    tile_loader_ptr  .rs 2   ;pointer to the tile data for our CHR_RAM loading routine

    ;------------------------------------------------

    Next, we need to write a simple program to simply load the data to $2007.  Note that I haven't tried it, but in this form, it looks like without simple modification, this technique will only work if you start your CHR data at the beginning of a .bank XXXX.

    ;------------------------------------------------

    LoadCompleteBank:                 ;load in the graphics

      LDA graphicspointers,x          ;specify the address using the first two entries before the .incbin commands
      STA tile_loader_ptr             ;and the value we load into "X" before calling the LoadCompleteBank routine.
      LDA graphicspointers+1,x
      STA tile_loader_ptr+1

      LDY #$00                        ;start with zero
      LDA $2002                       ;read PPU status to reset the high/low latch
      LDA [tile_loader_ptr],y
      STA $2006                       ;write the high byte
      INC tile_loader_ptr             ;go to the next entry
      LDA [tile_loader_ptr],y
      STA $2006                       ;write the low byte
      INC tile_loader_ptr             ;next table entry 
      LDX #$00
      LDY #$00
    .LoadBank:
      LDA [tile_loader_ptr],y         ;load the value
      STA $2007                       ;store it to the PPU at the address we previously specified
      INY                             ;next Y value
      CPY #$00                       
      BNE .LoadBank                   ;if Y flips over back to zero, we need to change our pointer values
      INC tile_loader_ptr+1           ;next chunk of ROM loaded
      INX                             ;incriment our outer loop counter
      CPX #$10                        ;if X=$10, we are done, if not, jump back into the loop
      BNE .LoadBank

      RTS

    ;------------------------------------------------

    Simple.  Note again that this is for an entire bank of CHR information.  We'll see later how to change this for smaller data sets.


    ROOM SWITCHING

    Now that we've got all our program ready for input, we need to work out a way of switching rooms.  Before we were using the D-PAD to switch rooms.  Now the D-PAD controls character movement and the rest of the buttons are pretty much spoken for.  So....??  Well, how about making the PC sprite switch rooms when you hit the edge of a screen?  Let's do it. 

    First, we need to set some arbatrary constants that will trigger our room switching.  These can be whatever you want.  Here, we are going to use:

    ;------------------------------------------------

    top_exit = $0F        ;room switching locations
    bottom_exit = $CF
    left_exit = $0F
    right_exit = $E1

    ;------------------------------------------------

    Next, we need to define some sort of map for our rooms.  We only have Four Rooms like Tim Roth, so our map is pretty simple.  This can be expanded to up to 256 rooms without too much trouble.  Here is our map layout:

    X---0-----1---->
    Y
    0 Room0  Room2
    |
    1 Room1  Room3
    |

    Note that Y values increase as you go down and X values increase as you go to the right.  Now that we have this idea in our heads, we need to define variables to track our current location in the grid. 

    ;------------------------------------------------

    Y_coord  .rs 1                ;Y map coordinate
    X_coord  .rs 1                ;X map coordinate

    ;------------------------------------------------

    Again, Y is your vertical location in the map and X is your horizontal location in the map.  Make sure you understand this or you'll screw it up and get lost.  I speak from experience.  Now, we pull out our fancy pointer table that specifies the location of the room data that we had before.

    ;------------------------------------------------

    backgroundpointer:                     ;room data for our room_index to reference to
      .word Room1,Room2,Room3,Room4

    ;------------------------------------------------

    Now, remember the value "room_index" that we used in the background switching routine?  Well, not it's not set in our switching commands, so we need to find it using our Y_coord and X_coord values.  Let's write the following:

    ;------------------------------------------------

    .Find_Room_Index:
      LDA Y_coord                            ;load the coordinates we set in our movement routines
      LDX X_coord

      CPX #$00                               ;if we are in the first column, the Y coord is the room index
      BEQ .done

    .loop:  ;for each X, add 2 to the index  ;note that this would change depending on your map configuration
      CLC                                    ;there are only 2 values that Y can take on, so we use 2. 
      ADC #$02                               ;if there were values 0-5 that Y could be, we'd use 6, etc.
      DEX                                   
      CPX #$00
      BNE .loop

    .done

      ASL A                                  ;indexing to a table of words...careful here!!!  See **.

      STA room_index                         ;store it in the variable and we're done

    **If you have more than 128 rooms and you double it, you'll over run and flip back past zero.  It is a simple problem to get around, just keep it in mind. 

    ;------------------------------------------------

    This is a very simple routine, but it took me a while (and a lot of help from metalslime) to get it working properly.  Study it and make sure you understand what we're doing.  Add more rooms and modify it to fit.  It's powerful, but can be a tricky bastard. 

    The rest of the background loading is exactly the same as the Compression write up.

    Now we need to set up our program to call the routine when we want to switch rooms.  Again, nothing too complex.  First, note that all four directions are pretty much the same, so we'll just go over one to save time.  Modifying our "UpMovement" routine, we write:

    ;------------------------------------------------

    UpMovement:
      LDA #$00                        ;load the appropriate direction into the direction flag
      STA enemy_direction

      LDA sprite_RAM                  ;move the character
      SEC
      SBC #enemy_speed
      STA sprite_RAM

      INC Enemy_Animation             ;incriment the animation counter

    ;;;;;;;;;insert;;;;;;;;;;;;;

      LDA sprite_RAM                  ;check to see if we are at the top of the screen
      CMP #top_exit
      BCS .done                       ;if not, we are done.  If so, we need to switch rooms

      DEC Y_coord                     ;DEC the Y coordinate of the world map

      LDA #$BF                        ;move our character so that it looks like he is walking across the screen
      STA sprite_RAM                  ;this is another random value, set whatever you like

      JSR update_enemy_sprites        ;update the sprite meta tile
      JSR LoadbgBackground            ;using the new Y_coord, update the background to the new room

    .done

    ;;;;;;;;;insert;;;;;;;;;;;;;

      RTS

    ;------------------------------------------------

    Simple like NGD.  Hmmm...what else??  Oh, yes.  Remember that this routine is set up to switch rooms any time you impact an edge of the screen.  There is currently nothing stoping you from entering a room that doesn't exist.  Until we fix this little issue, just notice where the background is set up to have solid walls...and try not to walk through them. 


    CPU USAGE BAR REVISITED

    Well, given my status as a noob, I screwed up the placement of the call for the showCPUUsageBar command.  We need to put it at the end of our Forever loop to show the end of the program.  You can put in as many bars as you like, but I'm mostly interested in how long the total thing takes to run.  You'll notice that it starts to show up here.  Our program is getting some teabag.gif to it.  face-icon-small-smile.gif


    CHR_RAM PART 2 - BACKGROUND ANIMATION

    Now that we have the tools in place to mess with CHR_RAM, we can add some interesting effects.  Most of the CHR_RAM effects can be done using MMC3 CHR bank switching faster and easier than this, but I'm too lazy to figure out MMC3 and it won't work on the powerpak anyway.  The quickest example that comes to mind are the moving ? marks in Mario 3.  Open the ROM and look at the PPU viewer.  You'll see the bottom part of the background data change instantly every frame.  This is the bank switching that I'm talking about. 

    The technique that we are using doesn't happen instantly like bank switching does, so we have to be careful not to run out of NMI.  You can load something like 8-9 tiles during each NMI if you code it efficiently enough.  I've only been able to get 6 to work (on NTSC, PAL works a lot better for this stuff)  and I need time for other background updates, so I stick to 3 or 4 tiles per frame.  In this example, we are going to use 4 tiles/frame and attempt to duplicate the scrolling ? marks of Mario 3 with Mario 1 graphics.  face-icon-small-happy.gif  Awesomeness.

    The first step is simple.  We need to create CHR data for each frame of the animation that we want to use.  Tile Molestor to the rescue!!  Simply create a new file using the methods discussed in a previous write up and you'll be on your way.  Simply cut and paste the 4 tiles that make up the ? mark block out of our existing CHR file and paste it into your new one.  Save it.  Then use the handy button that Tile Molestor that shifts all the pixels in the area that you highlight one pixel right/left/up/down.  This is a handy button set, up to this write up, I've been entering every frame manually.  face-icon-small-tongue.gif  Then save the next frame as a new file, and so on till you get every frame saved in its own file.  I guess that I should point out that you could technically store all of this in one file and just specify the appropriate starting address, but I like it this way so blah.

    After we've created all of our files for each frame, we need to include them in our program with some sort of word table to pull addresses from.  Simply:

    ;------------------------------------------------

    TileUpdates:
      .word Question1,Question2,Question3,Question4,Question5,Question6,Question7,Question8
      .word Question9,Question10,Question11,Question12,Question13,Question14,Question15,Question16

    Question1:                                      ;this is all the data for the various ? block frames
      .incbin "CHR_Files/Question1.chr"

    Question2:
      .incbin "CHR_Files/Question2.chr"

    Question3:
      .incbin "CHR_Files/Question3.chr"

    Question4:
      .incbin "CHR_Files/Question4.chr"

    Question5:
      .incbin "CHR_Files/Question5.chr"

    Question6:
      .incbin "CHR_Files/Question6.chr"

    Question7:
      .incbin "CHR_Files/Question7.chr"

    Question8:
      .incbin "CHR_Files/Question8.chr"

    Question9:
      .incbin "CHR_Files/Question9.chr"

    Question10:
      .incbin "CHR_Files/Question10.chr"

    Question11:
      .incbin "CHR_Files/Question11.chr"

    Question12:
      .incbin "CHR_Files/Question12.chr"

    Question13:
      .incbin "CHR_Files/Question13.chr"

    Question14:
      .incbin "CHR_Files/Question14.chr"

    Question15:
      .incbin "CHR_Files/Question15.chr"

    Question16:
      .incbin "CHR_Files/Question16.chr"

    ;------------------------------------------------

    Note here that if you have more than one place to write the various tiles to, you'll need to include some sort of "write to" address table.  We're not going to cover this here as it's fairly easy to modify.

    Now that we have all of our files lined up like so, we need some sort of thingie to input them to the background.  Now we need to come up with the starting PPU address to write the tiles to.  Hmmm?  Break out the pencil and paper and do some math?  Nope.  Lazy people unite!!  If you open your ROM and open the PPU, there is a simple trick to finding PPU addresses in CHR space.  Remember that we are using $0000 as sprites and $1000 as background.  If you put your mouse over the tile where you want to write to, it will display the tile number at the bottom.  This is all you need:

    -Writing to tile $56 in the sprite space?  The starting address is $0560.
    -Writing to tile $F4 in the background space?  The starting address is $1F40.
    -Writing to tile $56 in the background space?  The starting address is $1560.

    See the pattern??

    Turns out that we want to start writing our ? mark data to tile $53, so we start with PPU address $1530.

    Now we have everything set up and all we need to do is write a quick code snippet and we're done.  Technically you could use the same code for the complete bank here, but we are going to do this because I like it.  Remember:  NMI time is special.  There's not much of it and more than likely, this CHR_RAM stuff is not the only thing happening during that time.  So, we need to get everything set and ready to go so all NMI has to do is send data to the PPU.  MetalSlime does this in his tutorials by buffering writes.  I'm going to do this in a little different way, but it's basically the same principal.  At the end of the main program, we will set up all the pointers and everything for out next frame of animation.  Then when NMI hits, we simply write the address to $2006 and dump the data into $2007.  Let's set up our pointers.  To do that we use the following routine:

    ;------------------------------------------------

    PartialBankSetUp:                  ;load the address that we want to write to.  See the tip in the write up.
      LDA #$15                        
      STA tile_loader_addy
      LDA #$30
      STA tile_loader_addy+1

      LDA #$40                         ;load the number of entries.  #$10 for each tile to load.
      STA tile_loader_stop

      LDA tile_loader_counter          ;load the pointer to the information
      ASL A
      TAX
      LDA TileUpdates,X
      STA tile_loader_ptr
      LDA TileUpdates+1,X
      STA tile_loader_ptr+1

      INC tile_loader_counter          ;handle the counters, so that the next frame loads next time
      INC tile_loader_counter+1        ;This variable is used when you are loading a different number of tiles
      INC tile_loader_counter+1        ;to different addresses.  Not really needed here.
      INC tile_loader_counter+1

      LDA tile_loader_counter          ;reset if we get to the last frame.
      CMP #$10
      BNE .done
      LDA #$00
      STA tile_loader_counter
      STA tile_loader_counter+1

    .done
      RTS

    ;------------------------------------------------

    Step by step, here's what we are doing here:

    1.  We need to specify the address that we want to send the CHR data to.  This is hard coded here because we are ONLY WRITING TO ONE PLACE!!  Normally, you'd set up a table of addresses for the program to write to.  This is the function of the currently useless "tile_loader_counter+1" variable, to pull data from this table. 

    2.  Specify the number of tiles that we want to send to the PPU.  Remember from previous lessons that writing one tile is $10, 2 is $20, etc.  Again, you'd have a table with the quantities of tiles to be loaded each frame.  Another function of the "tile_loader_counter+1" variable.

    Note:  You can see that tile_loader_counter+1 is INC 3 times per frame.  If you wanted to set this up a little more complicated using this other variable, you'd do this:

    ;------------------------------------------------

      LDX tile_loader_counter+1                  ;load the addresses
      LDA LoaderSpecs,X
      STA tile_loader_addy
      LDA LoaderSpecs+1,X
      STA tile_loader_addy+1

      LDA Specs+2,X               ;load the number of entries
      STA tile_loader_stop

    -with-

    LoaderSpecs:
      .db $15,$30,$40,etc for each frame

    ;------------------------------------------------

    3.  Using the frame counter, tile_loader_counter, you load the starting address for the next CHR data block into the data pointer using the "TileUpdates" table that we laid out before.  

    4.  Set up the frame counter and address/quantity data counter for the next animation frame.

    5.  Reset the counters if you are on the last frame of animation.

    We should now be set up to load our data into the PPU during the next NMI.  So, we write the shortest, most efficient sub-routine we can make and run it during NMI.  Simply:

    ;------------------------------------------------

    LoadPartialBank:

      LDA $2002
      LDA tile_loader_addy             ;input the address
      STA $2006
      LDA tile_loader_addy+1
      STA $2006
      LDY #$00
    .LoadBank:
      LDA [tile_loader_ptr],y          ;load the data to the CHR space DURING NMI
      STA $2007
      INY
      CPY tile_loader_stop
      BNE .LoadBank

      RTS

    ;------------------------------------------------

    That's it.  Now we should have a scrolling ? mark block everywhere we specify it in our background.

      Graphics2.thumb.PNG.85396bf38c66cc5a4e91770d0ff95882.PNG

    So, now all our room data should look the same.  A meta tile bank specification, a nice rectangle of meta tiles, then a $FF signifying the end of the data string.  Basically:

    ;------------------------------------------------

    Room1:
      .db %11000000
      .db $08,$03,$03,$04,$04,$04,$03,$03,$03,$03,$04,$04,$04,$03,$03,$03
      .db $08,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06
      .db $08,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06
      .db $08,$04,$04,$04,$04,$04,$04,$06,$06,$04,$04,$04,$04,$04,$04,$06
      .db $08,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06
      .db $08,$00,$06,$00,$06,$00,$06,$00,$00,$06,$00,$06,$00,$06,$00,$06
      .db $08,$02,$02,$02,$02,$02,$06,$02,$02,$06,$02,$02,$02,$02,$02,$06
      .db $08,$03,$03,$04,$04,$04,$06,$03,$03,$06,$04,$04,$04,$03,$03,$06
      .db $08,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06
      .db $08,$04,$04,$06,$04,$04,$04,$04,$04,$04,$04,$04,$06,$04,$04,$06
      .db $08,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06
      .db $08,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06
      .db $08,$05,$06,$05,$06,$06,$05,$06,$06,$05,$06,$06,$05,$06,$05,$06
      .db $08,$07,$07,$07,$07,$07,$07,$06,$06,$07,$07,$07,$07,$07,$07,$07
      .db $08,$08,$08,$08,$08,$08,$08,$06,$06,$08,$08,$08,$08,$08,$08,$08
      .db $FF

    ;------------------------------------------------

    See how it is nice and even and perfect for looking up data?  Sweet.  And it still takes us from 960 bytes per room to 240, a size reduction of 75% (not counting meta tiles). 

    Then we go back and make our room data look like this, then we're set for our next challenge.


    CONCLUSION

    Next time we will tackle simple collision detection with the background.  This will basically be the money write up, the place where most people get stuck.  Stay tuned and post comments/questions if you have them.  If you don't understand everything to date, the next stuff will be trouble for you. 

    Until next time...teabag.gif.  Thanks for reading.

    background_set_up.zip

    • Like 1
  7. BACKGROUND

    First off let me say that I am a noob programmer and I don't know what I'm doing. 
    However, this tutorial should give you a good start towards simple background compression.
    Also, there is a minor bug in the final program.  I'm tired of messing with it, but if someone
    finds it, I'd love to fix it.  This tutorial also assumes a working knowledge of NES programming.

    The purpose of background compression is just that:  to make your program smaller.  It works
    best with a large number of rooms, but can be beneficial to most anything.  For example,
    in my personal program, I did not design it to support this routine, and as a result, the
    compression took up a shit ton more space than it would have knowing what I know now, but
    it still shrank the stuff down to 25% of its original size.  In this example, we will make
    4 rooms.  Uncompressed, this would run you 960 bytes per room (or 3840 bytes total).  Compressed,
    the 4 rooms take up about 663 bytes, or 17.3% of their original size.  The point is obvious. 

    Compressing stuff is not very complicated, there are just a bunch of little steps.  In this
    example, we are going to steal and build on BunnyBoy's "background" program and "mario" CHR
    file. 

    Assuming that you are making your own game, you would want to make all of your tiles and put
    them into a CHR file like the mario one that we are using.  You want to be able to view them
    in a PPU viewer for use in construction of meta tiles that will come later.

    1215366606_MarioTiles.PNG.2223d53f6a79591f05cf18421e19d2b2.PNG

    We see that most of the 8*8 tiles in this tile set are used to construct 16*16 blocks.  We
    just take this natural tendency and use it to make stuff smaller.  Once you have your nice tile
    set, we need to make what are called "Meta Tiles". 

    258739539_MarioMetaTiles.PNG.8b60cbaae019b4ccfbe7228d9d8f0933.PNG

     

    Or, make rooms.

    Graphics.thumb.PNG.9bf37f6e3f84fd36df3d2b654f816ec5.PNG

    Note that you can have up to 128 meta tiles in each meta tile "bank".  But what if you needed
    more than 128, like 1000???  You will need multiple meta tile banks, this will come later.
    I just split them up this way to demonstrate the multiple bank function. 
    I'm not sure how to explain this better than the picture.  You can see how neatly the
    tiles fit into meta tiles and Walla!  Now you only have to specify one byte for
    every 4 bytes written to the PPU!  Savvy?

    After you have all of your meta tiles defined, start building rooms.  You can build as many
    rooms as you have the patience to build.  We will look at this example:

    620435153_MarioBackground.PNG.11c0560236f14f13de5aaa1152a63cfc.PNG

    You can see that you have this giant ass room made up of 8 meta tiles.  Easy, no?


    META TILE BANK SWITCHING

    First, let me explain the meta tile bank switching flag.  For this, we will use
    binary (only to set it apart and make it easy to see what we are doing).  For each
    meta tile bank, we need to assign a number.  We are going to call the top one, creatively
    enough, "MetaTileSeta" and the second "MetaTileSetb".  This will come later, but we need to
    use the following for these meta tiles sets: 

    MetaTileSeta = %11000000
    MetaTileSetb = %11000001

    The first two bytes specify stuff that will come later and the bottom ones specify the meta
    tile bank number, see that a is bank 0 and b is bank 1.


    REPEATING META TILES

    Okay, next we need to specify repeats of meta tiles.  If you look at the picture, there
    are a bunch of places where meta tiles are repeated.  So, for the top row, wouldn't it
    be easier to have only a repeat number and the tile number rather than having to repeat the
    tile number 16 times??  The thing to note is that the way I have my program set up, you need
    to specify the number of repeats - 1.  (i.e. if you want to repeat it 16 times, you have to use
    15, 3 times, use 2, etc.)  Here we are going to use "binary" as well, but with this one, we
    only need to have bit 7 = 1, bit 6 = 0, and the other 6 can be used to specify the repeat numbers.
    So, this makes it easier to use $8(repeat number).  Or:

    Repeat 6 times, $85
    Repeat 16 times, $8F

    When specifying meta tiles, you will only go up to $7F in any given bank, so the 8 stands out
    just like the binary.


    SPECIFYING META TILES

    Okay, now we know how to specify repeats and tile set switching sets, what about meta tiles? 
    Well, this is the easy part.  See the fancy picture?  The numbers down the side are the first
    digit in hex and the numbers across the top are the second.  EX:

    Tile in row 5 and column 9 would be $59
    Tile in row 0 and column 0 would be $00 and so on.


    COMPRESSING A ROOM

    We'll learn how to read this data later, but for now let's make a room and we'll hopefully understand
    it a little better.  Looking back at that picture, we see that there are rows of tiles.  I have
    found that it makes sense to me to leave the data string in these rows for later debugging and/or
    copying parts of rooms into other rooms.  So, first we need to specify the meta tile bank for the
    program to pull meta tiles from.  So, here we see that the first tile is $06 of the first bank and 
    it is repeated 16 times.  So, the first row would be:

      .db %11000000(bank 0),$8F(repeat 16 times),$06(the tile in row 0, column 6)

    Pretty simple, right?

    Second row:

      .db $06,$03,$03,$82,$04,$83,$03,$82,$04,$03,$03,$06

    See if you can decode what the data string is saying.  If you can't, the rest of this won't make sense.
    Please re-read up to this point until you get it, or ask me a question.  Note that you only need to
    specify meta tile bank switches when you switch banks, not every row.

    So, the completed room data string would look like this:

      .db %11000000,$8F,$06
      .db $06,$03,$03,$82,$04,$83,$03,$82,$04,$03,$03,$06
      .db $8F,$06
      .db $06,$8D,$04,$06
      .db $8F,$06
      .db $06,$00,$06,$00,$06,$00,$06,$00,$06,$00,$06,$00,$06,$00,$06,$06
      .db $06,$8D,$02,$06
      .db $06,$03,$03,$82,$04,$83,$03,$82,$04,$03,$03,$06
      .db $8F,$06
      .db $06,$8D,$04,$06
      .db $8F,$06
      .db $8F,$06
      .db $06,$05,$06,$05,$06,$06,$05,$06,$06,$05,$06,$06,$05,$06,$05,$06
      .db %11000001,$8F,$00
      .db $8F,$01,$FF

    Again, if you can't decode what this means, you should go back and study a little more, cause the rest
    of this won't make sense to you. 


    THE "$FF" FLAG

    Also, we see the first appearance of $FF.  This simply signifies the end of a data string and tells
    the subroutine that it is done.  Poweryay!!!  Our first room!


    CODING IN THE META TILES

    Okay, now we have this happy little string, what do we do with it?  Well, here we will learn how
    to tell the processor to decompress the stream and write to the PPU. 

    First, we will cover basic decompression.  If you didn't use any bank switches, or repeats, your
    program would look pretty simple.

    Here, you need to code in your meta tiles.  This is the part where having your plain tiles in the
    PPU will be most helpful (especially if you have the afore mentioned 1000.) Pretty simple, you simply
    take the top row then the bottom row in a series of bytes using the normal tile numbers from your
    original tile set (i.e. what you see in the PPU viewer).  For example, meta tile bank a:

    MetaTileSeta00:
      .db $44,$49,$5F,$7A

    MetaTileSeta01
      .db $AB,$AD,$AC,$AE

    MetaTileSeta02
      .db $53,$54,$55,$56

    ...and so on till you get all of them done.

    I usually keep them at the top of my meta tiles in ROM space for easy access, but you can put the
    following wherever.  You can see that you will have to use some sort of look up table to find
    the data for each meta tile, so we may as well do this now.

    ;-------------

    MetaTileSeta:
      .word MetaTileSeta00,MetaTileSeta01,MetaTileSeta02,MetaTileSeta03,MetaTileSeta04,MetaTileSeta05,MetaTileSeta06

    ;-------------

    MetaTileSetb:
      .word MetaTileSetb00,MetaTileSetb01,MetaTileSetb02,MetaTileSetb03,MetaTileSetb04,MetaTileSetb05,MetaTileSetb06

    ;-------------

    Note that this is a table of addresses!  This is why you can only put 128 meta tiles in each bank (even if
    we are using Deniz sized tile sets here, the full sets would be bigger....)

    And eventually, you will need a look up table for the meta tile bank you want the program to read from, so we
    may as well put that here as well:

    ;-------------

    meta_tile_sets:
      .word MetaTileSeta,MetaTileSetb

    ;-------------

    Make sense so far?  At this point, we have all the data we need to decode all the data streams.  Now, onto
    actual decoding.


    SIMPLE DECOMPRESSION

    For reasons of my sanity, I will skip the stuff that I think is pretty basic...like disabling NMI while
    writing to the background, turning off the PPU, specifying which room to load, and how to set up the PPU
    to receive a new room.  This will focus on the important decompression stuff.

    If we didn't have any repeats, or bank switches, we would be able to hard code the pointers and that would
    be that, let's assume for a moment that is the case.  When writing our meta tiles to the background, we
    run into the problem of trying to write two rows at one time, when the PPU accepts data only one row at a
    time....  What to do?  Here we should buffer our writes.  So, we need to reserve 32 variables in
    zerospace, one for each tile what we are writing to the background.

    background_row  .rs $20     ;the second row that each meta tile contains

    So, now we take our meta tile and write the first two entries to the PPU, then the second two entries
    to our buffer.  Now, to keep our positions in the meta tile and in the buffer straight, we need to use
    the registers X and Y.  We need to reserve Y for pointer use, so X will be used for the buffer.  To keep
    them straight, we will use simply:

    data_y  .rs 1               ;y counter
    data_x  .rs 1               ;x counter

    So, after our buffer fills up, we need to write the second row to the PPU, then jump to the next row of meta tiles.
    This is pretty easy, so I won't go into it.  (It should be noted here that the Y register is used for
    multiple things.  It uses the data_y for reading the room data stream, and here we see that it uses the register
    y to decode the meta tiles.)  Here is our code up to now:

    ;-------------

    decompress:
      LDY #$00
      LDX data_x
      LDA [meta_tile_ptr],y      ;write the first tile of the meta tile to 2007
      STA $2007
      INY
      LDA [meta_tile_ptr],y      ;write the second tile of the meta tile to 2007
      STA $2007
      INY
      LDA [meta_tile_ptr],y      ;write the third tile of the meta tile to memory for later use
      STA background_row,x
      INY
      INX
      LDA [meta_tile_ptr],y      ;write the forth tile of the meta tile to memory for later use
      STA background_row,x
      INX
      STX data_x
      CPX #$20                   ;see if we are at the end of a row.  If so, we need to use the tiles stored in memory, if not, jump to the top.
      BEQ .next
      JMP background_start
     
    .next
      LDY #$00
    row_start:                  ;write the tiles stored in memory to the background
      LDA background_row,y
      STA $2007
      INY
      CPY #$20
      BNE row_start
      LDX #$00
      STX data_x

      JMP background_start      ;jump back to the start of the routine and continue on

    ;-------------

    Not all that difficult, got it?  Okay, now onto bit testing.  Back when we specified our repeats, end of data
    string, and bank switches, we set up the data string to be bit tested. First, the end of the string.  Simply
    tell the routine that if it encounters:

    %11111111

    it is done and that it should "RTS".  Simple enough.  Next, we tell the program that if it encounters:

    %1xxxxxxx

    it needs to do something special. 

    %10xxxxxx means that it needs to repeat the meta tile
    %11xxxxxx means that it needs to switch meta tile banks

    I guess rather than my beating it to death, it would be easier for you to spend a couple minutes with this
    part of the program and either make a flow chart or just convince yourself that it is working.  It should
    be noted that there are 3 pointers at work here:

    ptr1  .rs 2                 ;pointer to the background information.  This is loaded in the part we skipped
                                ;about which room you want to load at a given time

    meta_tile_sets_ptr  .rs 2   ;pointer to which meta tile set you want to read from at any given time

    meta_tile_ptr  .rs 2        ;pointer to the meta tile/repeat number/bank number in the data stream

    Take a look through this bit of code and see if you understand what it is doing.  If not, please ask
    questions.  Here we need to define a variable for the number of meta tile repeats.

    repeat_meta_tile  .rs 1     ;times to repeat the current meta tile

    All this section of code does is:

    -test if you are in the middle of repeating meta tiles, if so repeat till it is done
    -test if you are at the end of a data stream, if so, skip to the end
    -test if it is a repeated meta tile or a bank switch
    -test if it is a switch, if so switch meta tile banks and jump back to the start and load the next entry in the stream
    -if it is not a switch, it is a repeat, load the number of repeats and load the next meta tile in the stream
    -then decompress the data as shown above

    ;-------------

    background_start:

      LDA repeat_meta_tile         ;see if you are repeating a tile
      BEQ .jump
      DEC repeat_meta_tile         ;decrement the repeated tile and jump back to the decompression routine
      JMP decompress

    .jump

      LDY data_y
      LDA [ptr1],y
      CMP #$FF           ;see if it is at the end of the data string
      BNE .next
      JMP background_complete
    .next
      LDA [ptr1],y
      AND #%10000000        ;see if this is a repeated tile or a switch of meta tile banks
      BNE .next1
      JMP .loadtile

    .next1
      LDA [ptr1],y
      AND #%01000000        ;see if this is a switch of the meta tile banks
      BNE .next2
      JMP .repeat

    .next2
      LDA [ptr1],y
      AND #%00111111         ;switch meta tile banks
      ASL A
      TAX

      LDA meta_tile_sets,x   ;load the new meta tile bank
      STA meta_tile_sets_ptr
      LDA meta_tile_sets+1,x
      STA meta_tile_sets_ptr+1
      INY
      STY data_y
      JMP background_start      ;jump to the next entry in the data table and return to the start of the routine

    .repeat
      LDA [ptr1],y
      AND #%00111111        ;load number of repeats for this meta tile
      STA repeat_meta_tile  ;note that this is set up so that you need to have the number of repeats - 1
      INY
      STY data_y

    .loadtile
      LDA [ptr1],y        ;load the meta tile number
      INY
      STY data_y
      ASL A               ;this is a table of addresses, so multiply by 2
      TAY

      LDA [meta_tile_sets_ptr],y    ;load the address of the meta tile in question
      STA meta_tile_ptr
      INY
      LDA [meta_tile_sets_ptr],y
      STA meta_tile_ptr+1
     
    decompress:

    ;-------------

    That's it.  There's not much to it.  For your convenience, you will find an attachment entitled "blankmetatiles".
    I made this when I was making my last backgrounds because I kept doing it over and over.  If you label your meta
    tiles as we did here, all you have to do is paste "MetaTileSetX" in front of all the freaking letters and you're done.

    For practice, the attached picture "Graphics" has the other three rooms that are compressed in the attached program.
    I would suggest that you practice with these and then make your own.  Using the D-PAD in the program will load
    each of the Four Rooms.  Like I said at the top, there is a weird bug, but I'm too lazy to look for it. 

    I guess that's it.  Post here or PM me if you have questions. 

    On a side note, I haven't got into scrolling too much, but a working example of compression and scrolling can
    be found:

    http://nintendoage.com/forum/messageview.cfm?catid=22&th...

    Thanks again!  Until next time...teabag.gif

    Background Compression.zip

    Mario Tiles.PNG

    Mario Tiles.PNG

    Mario Background.PNG

    • Like 1
  8. INTRODUCTION

    Last time we added weapons to our playable character, but they didn't do anything.  This time we are going to add some depth to our game by adding impacts between:

    1.  NPCs and PCs
    2.  Our projectiles and NPCs
    3.  Our whoop-ass stick and NPCs.


    RESULT OF A HIT

    First, we need to define what we do when we register a hit.  In practice, this would be a fairly complicated routine involving death checks, hit points, moving, etc., but here we are going to keep it simple and you can add stuff as you see fit.  This is the subroutine that we are going to jump to when we register a hit:

    ;-----------------------------------------------------------------------------------------------------

    direction_change:                 ;this is just a random routine to make something happen when a collision occurs
      LDX enemy_number
      LDA random_direction
      STA enemy_direction,x
      RTS

    ;-----------------------------------------------------------------------------------------------------

    All we do here is if there is a hit, make the enemy change direction.  That's it.  Just take the random direction we have stored in our random direction counter and store it in the active enemy direction.  It should be noted that in some cases the random direction you input into your character's direction could very well be the direction he has been traveling.  You could add code to fix this if you like. 


    COLLISION AREAS

    Next, we need to define the area that we will use when we are looking for hits (or collisions).  Consider the picture below:

    2084151063_Spritesbig.PNG.141c238c97ca8ad9db3fa4fd401750c5.PNG

    I highly recommend making a picture like this anytime you define collision coordinates.  It makes it easy to see and keeps you "organized". 

    First, draw out your playable character in all of its shooting poses.  It is pretty obvious here, but as your characters get bigger and more complex, this will be harder to visualize.  After we draw our pictures, we can see that there are 4 views for each weapon we need to consider.  In this case, the 4 directions of the D-PAD. 

    Then, draw an 8x8 pixel grid (or however big your weapon sprite is) as shown at the bottom of the picture.  Then draw the various views of the weapon sprite area THAT YOU WANT TO REGISTER A HIT on top of this grid.  Then draw a box around the sprites in the empty space of the grid as shown in this picture.  This is your "HIT" area. 

    Remember that the sprites on the NES are referenced from the upper left hand corner.  So, we need to figure out what the offsets are in all four directions for each view.  Sounds complicated, but it's really pretty simple.  We'll start with the arrow facing up. 

    Here, we see that our blue box extends all the way over the top of the last row of pixels.  So, since we are referencing our collision coordinates to the top of the sprite, we would basically have the vertical arrow sprite location + 0 = top collision coordinate of the arrow.  Then, we see that the box extends all the way past the bottom row of pixels, so we have vertical arrow sprite location + 8 = bottom collision coordinate of the arrow.  Then, we see that we skip one pixel from the left side of the arrow sprite, so we would have the horizontal arrow sprite location + 1 = left collision coordinate of the arrow.  The we skip 6 pixels before the right side of the box, or horizontal arrow sprite location + 6 = right collision coordinate of the arrow.

    That really makes it sound more difficult than it is.  Basically we just count the pixels above and to the left of the blue lines.  So, for each direction we would have:

    TOP,BOTTOM,LEFT,RIGHT
    Up View:  0,8,1,6
    Down View:  0,8,1,6
    Right View:  1,6,0,8
    Left View:  1,6,0,8


    STORING COLLISION INFO IN RAM

    Now, we've defined the offsets required for each direction that our little homie can shoot.  Now...what the hell do we do with it??

    Well, if we think back, we remember that we have a loop running for each of our enemies.  We can either re-compute these coordinates for each enemy (each loop), or we can compute it once and store it somewhere in RAM.  It's really up to you, but here we are going to store it in RAM.  To do this, we will need to define some new variables:

    ;-----------------------------------------------------------------------------------------------------

    arrow_vertical  .rs 2    ;the vertical collision coordinates we define for our arrow
    arrow_horizontal  .rs 2  ;horizontal coordinates

    ;-----------------------------------------------------------------------------------------------------

    Next, we basically just say that for each one of these new variables, we take the sprite location, add the appropriate offset we defined above, and store it in one of these variables.  Simple, no?  Well, I'm having a hard time putting it into words, so let's look at the subroutine:

    ;-----------------------------------------------------------------------------------------------------

    arrow_transfer:

      LDY arrow_direction
      LDA Weapons_RAM+4              ;transfer arrow collision coordinates
      CLC
      ADC arrow_a,y
      STA arrow_vertical
      LDA Weapons_RAM+4
      CLC
      ADC arrow_b,y
      STA arrow_vertical+1
      LDA Weapons_RAM+7
      CLC
      ADC arrow_c,y
      STA arrow_horizontal
      LDA Weapons_RAM+7
      CLC
      ADC arrow_d,y
      STA arrow_horizontal+1
      RTS

    ;-----------------------------------------------------------------------------------------------------

    All it is is load the sprite location, add a number, and dump it in the variables.  However, since we have 4 different views to work with, we can't just add a constant and make it work right.  We need to add some sort of look up table that stores the offset data that the program can access.  Here, arrow_a is the offset for the top of the blue box in the picture for the up, down, right, left directions, respectively.  arrow_b is the bottom, arrow_c is the left, and arrow_d is the right.  If you study the offsets we came up with and the following table, you'll see the relationship between the two.  Basically the transposition.  If we put this into a neat table, we get:

    ;-----------------------------------------------------------------------------------------------------

    arrow_a:                         ;outlines of the arrowhead
      .db $00,$00,$01,$01
    arrow_b:
      .db $08,$08,$06,$06
    arrow_c:
      .db $01,$01,$00,$00
    arrow_d:
      .db $06,$06,$08,$08

    ;-----------------------------------------------------------------------------------------------------

    Now, we've got a routine that we can put somewhere that will define the position of the arrow on the screen and give us the collision boundries to check against the bad guy locations. 

    The exercise is exactly the same for the hammer/mace.  I'll leave that to you to figure out.  It is good practice.  The resulting table is in the program.

    The exercise for the Playable Character sprites is exactly the same with one exception.  The playable character has the same "blue box" in every direction.  This saves us the trouble of messing with the look up table and allows us to hard code the location info.  However, if your character looks different in each direction, you may want to consider making a table for it as well. 


    CALLING THE TRANSFER ROUTINES

    Now that we've defined how we are going to transfer the sprite collision data into RAM, we need to call it at the appropriate time in the main program.  It's really up to you when you want to call it as it doesn't matter too much, but here we are going to call them after we finish handling the weapons routines, but right before we run the UpdateENEMIES loop.  This sets us up nicely for what comes next.


    CALLING THE SPRITE COLLISION DETECTION ROUTINES

    I guess I should explain this here to avoid confusion.  We need to put our collision data variables in a certian order in RAM to make the following collsion routine work correctly.  Observe:

    ;-----------------------------------------------------------------------------------------------------

    arrow_vertical  .rs 2         ;vertical position of the arrow for collision
    hammer_vertical  .rs 2        ;hammer
    PC_vertical  .rs 2            ;PC

    arrow_horizontal  .rs 2       ;horizontal position of the arrow for collision
    hammer_horizontal  .rs 2      ;hammer
    PC_horizontal  .rs 2          ;PC

    ;-----------------------------------------------------------------------------------------------------

    Messing up the order of these will cause you to waste time debugging.  You'll see why this is necessary in a second here.

    Next we need to update our "UpdateENEMIES" loop to call for sprite collision detection.  It will be simplest to just show you:

    ;-----------------------------------------------------------------------------------------------------

    UpdateENEMIES:
      LDA #$01                         ;this loop updates the enemies one at a time in a loop
      STA enemy_number                 ;start with enemy one
      LDA #$02
      STA enemy_ptrnumber
    .loop
      JSR enemy_update                 ;move the sprites based on which direction they are headed
      JSR Enemys_Animation             ;find out which frame the enemy animation is on
      JSR Enemys_Sprite_Loading        ;update the enemy meta tile graphics
      JSR update_enemy_sprites         ;update position

    ;;;;;;;insert;;;;;;;;;

      LDY #$00            ;arrow       ;sprite collision detection routines
      JSR enemy_collision              ;note that 00, 02, and 04 are to specify the variables of each weapon
      LDY #$02            ;mace
      JSR enemy_collision
      LDY #$04            ;Playable Character
      JSR enemy_collision

    ;;;;;;;insert;;;;;;;;;

      INC enemy_number                 ;incriment the enemy number for the next trip through the loop
      INC enemy_ptrnumber              ;these are addresses for the graphics data, so we need to keep it at 2x the enemy number
      INC enemy_ptrnumber
      LDA enemy_number
      CMP #$04                         ;if it is 4, we have updated enemies 0,1,2,3 so we are done
      BNE .loop
    UpdateENEMIESdone:

    ;-----------------------------------------------------------------------------------------------------
     
    Here we see why we need to put our variables in the correct order.  We run the enemy_collision routine 3 times, one for each weapon type.  First, we use index $00 for the arrow, index $02 for the mace, and index $04 for the playable character collision detection. 

    Now, we're all set up to write some sort of routine to check for collisions.


    SPRITE COLLISION DETECTION

    After we've defined all the stuff above, the actual detection routine, the "man meat" if you will, is pretty straightforward.  Remember the BCC and BCS commands we learned last time??  We need them here.  If you don't get it, go back and review or ask a search engine or something.  These are important.  Again, it's probably easiest if I just lay out the sub-routine and comment the hell out of it.  But, basically, all we are doing here is seeing if the "blue boxes" we defined above are intersecting with the "blue boxes" defined by the NPCs.  Note that this routine assumes that the NPCs are basically the same as the PC.  If you're enemies are of different styles, or have different looks depending on the direction they are traveling, it's not really any big deal, you just have to set up tables like we did before.  Just base them on the enemy_direction and/or define an "enemy_type" variable to guide your program to the correct "blue box" data.  Here, we are going to keep it simple.  Oh, and I lied above.  When we register a hit, we also have to tell the mace/arrow routines to dissapear the weapon sprites. 

    I think that the following picture will help illustrate my little routine here.  "Steps" refer to the comparisions in the following routine.  You can see examples of where you would branch and where you would RTS (green boxes mean branch, red mean RTS).  I think that this figure will make it a little clearer.

    1076228124_CollisionExample.PNG.94988cf2d279197c68b60cb22e2f0d55.PNG

    See the following:

    ;-----------------------------------------------------------------------------------------------------

    enemy_collision:              ;check collision with the arrows, mace, PC

      LDX enemy_number            ;load the enemy number for this time through the UpdateENEMIES loop
      LDA updateconstants,X       ;check the outline of the enemy vs. the weapon
      TAX                         ;change the constant to the X variable
      LDA sprite_RAM,X            ;load the vertical position of the dominate enemy sprite (refer to last week for "dominate")
      CLC                         ;make sure that the carry flag is clear
      ADC #$01                    ;add on the offset for the up direction for the NPC
      CMP arrow_vertical+1,y      ;compare the top of the NPC with the bottom of the weapon in question
      BCC .next                   ;if the top of the NPC box is above the bottom of the weapon box, branch to .next
      RTS                         ;otherwise, there is no collision, so RTS
    .next 
      LDA sprite_RAM,X            ;load the vertical sprite position
      CLC                        
      ADC #$0E                    ;add the offset for the bottom of the metasprite
      CMP arrow_vertical,y        ;compare with the top of the weapon blue box
      BCS .next1                  ;if the bottom of the NPC box is below the top of the weapons box, branch to .next1
      RTS                         ;otherwise, there is no collision, RTS

    ;inserting another comment here, so far, we have determined that at least 1 pixel of the defined collision boxes share the same vertical space on the screen.  Now, we need to check and see if they share the same horizontal space.

    .next1
      LDA sprite_RAM+3,X          ;load the sprite horizontal position
      CLC
      ADC #$03                    ;add the offset defined in our pictures with the blue box to get the left side of the metasprite
      CMP arrow_horizontal+1,y    ;compare this the the right side of the weapon sprite
      BCC .next2                  ;if the left side of the NPC box is to the left of the right side of the weapons box, branch to .next2
      RTS                         ;otherwise, there is no collision, RTS
    .next2
      LDA sprite_RAM+3,X          ;load the enemy sprite horizontal position
      CLC
      ADC #$0C                    ;add the defined offset to get the right side of the enemy metasprite
      CMP arrow_horizontal,y      ;compare to the left side of the weapons sprite
      BCS .next3                  ;if the right side of the NPC is to the right of the left side of the weapons box, branch to .next3
      RTS                         ;otherwise there is no hit, RTS

    ;inserting another comment here, in this previous snippet we determined that the weapons sprite and the enemy meta sprite share the same horizontal space on the screen. 

    ;However, the top part of this subroutine showed us that we were also occuping the same vertical space.  So, we know that if we are branching to .next3, the weapon sprite and the meta sprite are, in fact, occuping the same space on the screen!!

    .next3                        ;now that we've confirmed a hit, what do we do?
      CPY #$00                    ;we need to decide what weapon hit the duder, so remember $00 is for arrows
      BNE .nexts                  ;did we go over this?  Branch Not Equal (i.e. not an arrow) to .nexts
      JMP arrow_hit               ;do the arrow collision stuff
    .nexts
      CPY #$02                    ;is it the mace?
      BNE .nexts1                 ;again, Branch Not Equal to .nexts1
      JMP mace_hit
    .nexts1
      JMP PC_hit                  ;add more here if there are more weapons, or make a look up table, but all that's left is the PC impact, so jump to it!

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

    mace_hit:

      JSR direction_change

      LDA #$01                    ;set the mace timer to $01 to reset the mace graphics.
      STA B_timer

      RTS

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

    arrow_hit:
      LDA #$FE                    ;here we are just making the arrow inactive. 
      STA Weapons_RAM+4           ;see the arrow routine to see how this works.
      STA Weapons_RAM+7

      JSR direction_change
     
      RTS

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

    PC_hit:

      JSR direction_change       

      RTS

    ;-----------------------------------------------------------------------------------------------------

    So:

    1.  Determine if the sprites are overlapping in the vertical direction.
            a.  If not, RTS.
            b.  If so, go on to #2.
    2.  Determine if the sprites are overlapping in the horizontal direction.
            a.  If not, RTS.
            b.  If so, go on to #3.
    3.  Determine which weapon we are attacking with.
    4.  Do the collision shit for the various weapons. 

    Easy as cake.  If you have any questions, feel free to PM me or post.  If you don't understand what we did here, we need to figure it out.  This is important for stuff to come later. 


    DOWNLOAD THE PROGRAM

    Now we should have all the tools we need to make our program recognize sprite collisions.  Download the zip file and mess around with the ROM.  Change the offsets in the tables and see how it changes the collision areas.  Maybe even add some other thing for the program to do with each weapon hit, or add health, or add in your own weapons.  Whatever tickles your teabag.gif

    Next we'll start working with backgrounds.  face-icon-small-happy.gif  Please let me know if you have any questions.  Good luck.

    Sprite Collisions.zip

    Collision Example.PNG

    • Like 1
  9. This expands on #2 where we added a playable character to our game and some random content. 


    INTRODUCTION

    This time we will talk about adding two kinds of weapons to our playable character's um...playability.  You'll have to excuse the crappy graphics.  For me, building successful sprites takes the most time and effort out of all the skills required for programming.  As a result, I just halfassed these to get my point across.  If you want to, you can alter the program to include your own sprites.  In #1 we talked about building sprites and tiles.  However, it didn't even cross my mind that using Tile Molestor would not be something that most people would know how to do.  We'll correct that oversight and cover it briefly here.  But at the end of this write up we will have added a projectile type weapon and a sword type weapon to our character that will be activated using the A and B buttons.


    TILE MOLESTOR

    I do most of my actual graphics design in MS Paint.  It is familar and easy to use.  After I get everything the way I want it and split it into tiles, only then do I input everything into Tile Molestor.  So, let's think about what we'll need as far as sprites for our character. 

    1.  We need a shooting pose for 4 directions.  (We could technically make a different pose for projectile and sword, but the process is exactly the same and sloth wins out, so we are going to use the same sprites for both weapons.

    2.  We need a sword design.  Keep in mind that the weapons design HAS TO MATE UP with the shooting pose from point 1.  Also, with 4 directions, we'll need two sprites.  An up/down mirrored sprite as well as a left/right mirrored sprite. 

    3.  We need a projectile design.  See notes in point 2. 

    Following the procedeure laid out in write up 1, we construct the following:

    1388869274_WeaponsSprites.PNG.a2f06050a06093a061889914211b3b28.PNG

    Armed with our sprites, we head over to Tile Molestor.  Heh, molestor.  You don't need all this info for this write up, but we'll need it later, so shut up and listen.

    An NES pallette has 4 colors that can be applied to each pixel of each tile.  This jives with the attributes that we input for tiles in that we get two bits with which to specify info.  However, what you are specifying in the attributes is the sprite pallette, not the pixel colors.  To get into pixel colors, we have to go deeper.  To specify the colors for each pixel, we need two bits per pixel (i.e. color 1,2,3,4 of our pallette, or 00,01,10,11).  Make sense so far?

    Now, each tile is 8x8 pixels, or 64 total pixels.  If we multiply this out, 2 bits per pixel X 64 total pixels = 128 bits or 16 Bytes per tile.  When you click "NEW File" in Tile Molestor, this is the size it prompts you for.  So, if you want a file with 6 tiles, you would put in 6 tiles X 16 Bytes/tile = 96; or 28 tiles X 16 Bytes/tile = 448.  Easy as American Pie. 

    The defaults in TM are not set up for the NES.  You need to change the Codec to the code that the NES needs.  Go to View>Codec>2bpp planar, composite.  This is demonstrated in the following pic:

    Codec.thumb.PNG.637e1db37afbe2062c846ea4919a7c7b.PNG

    Now we should be set up to input NES graphics.  However, it just gives you a blank, black block where you input your tiles.  I like to select the "View Tile Grid" as shown in the above pic.  This keeps you from offsetting your graphics by 1 pixel or something retarted. 

    Next, you can use the default pallette shown at the bottom, or if you like, to make it easier, you can specify your own.  You can do one of two things.  Either click on the arrow buttons on the sides of the pallettes to use a standard one or double click on each color to specify your own pallette as shown here:

    ChangeColors.PNG.465b4a34959c674f91f050253bed53f3.PNG

    Now, if someone would add a cut from paint paste into Tile Molestor, I would gladly impregnate them.  But so far, I haven't figured that one out.  So, just go through pixel by pixel and transfer your stuff into the file.  I like to do the outlines and then fill in the solid colors.  Just try to keep your tiles easy to find and input into our sprite tables.  The rest of the controls work more or less like paint, so just let me know if you have any other questions.


    HANDLING INPUT

    First thing that we need to do is handle the input from the A and B buttons.  To do this, we are going to try to keep it as simple as possible.  All we are going to have the buttons do is set timers.  That's it.  We want our routines to be seperate entities so that we can reuse them without having to dig around for code.  So, we do this:


    ReadA:
      LDA joypad1_pressed       ; player 1 - A
      AND #%10000000            ; only look at bit 8
      BEQ ReadADone

      LDA Weapons_RAM+4         ;see if the arrow is ready to fire again
      CMP #$FE
      BNE ReadADone

      LDA #Atimer               ;reset the timer so that the arrow is fired next frame
      STA A_timer

    ReadADone:                  ; handling this button is done
     
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
     
    ReadB:
      LDA joypad1_pressed       ; player 1 - B
      AND #%01000000            ; only look at bit 7
      BEQ ReadBDone

      LDA B_timer               ;check and see if the mace is still out
      BNE ReadBDone

      LDA #Btimer               ;reset the timer if it was not pressed last time, but is this time
      STA B_timer
     
    ReadBDone:                  ;handling this button is done

    All these routines do is input a number (in this case a constant declared in our constants file) into the timer.  A checks to see if the arrow has hit something and has been reset before firing again and B checks to make sure that we don't try to hammer something again before the previous ass kicking has finished.  Note that from here on out I will assume that you know what I'm talking about with all the new variables and/or constants.  It feels redundant restating them all.

    When we input these constants into the timer, we need an independant way of counting down the timers.  So, we put a simple routine in our GameState0 routine AFTER the PC updates but BEFORE the NPC updates.  The order of operations is important here because if you put the weapons updates after the enemy updates, you'll have a delay before they kick in and it could mean the difference between life and death.  This way everything activates at the same time and your timing is right on.  So, we add the following:

      JSR update_enemy_sprites

    ~~~~~~~~~~~~~~~~~~~~~

      JSR transfer_location_info

      JSR arrow

    arrow_dec:
      LDA A_timer
      BEQ .done
      DEC A_timer
    .done

      JSR mace

    mace_dec:
      LDA B_timer
      BEQ .done
      DEC B_timer
    .done

    ~~~~~~~~~~~~~~~~~~~~~

    UpdateENEMIES:

    Pretty simple right? 


    TRANSFER LOCATION INFO

    Technically we don't need this yet, but we are going to put it in here to save us from having to hunt down and change a bunch of variables later.  You could use the ",x" indexing method for everything that this does, but with everything that we are going to use it for, it will save processor time and ROM space to use this quick transfer rouitine.  All it does is loads the position info for the "dominate" sprite into new variables called "sprite_vertical" and "sprite_horizontal".  Now that we have these we can write ALL of the routines that we would normally loop all of our enemies/PC/etc. through relative to these variables.  Just keeps it less confusing.  (Note that I refer to "dominate sprite".  I keep everything the same, meaning that all meta sprites are referenced to the upper left hand corner of the top left sprite tile.)

    transfer_location_info:
      LDX enemy_number
      LDA updateconstants,x
      TAX
      LDA sprite_RAM,x
      STA sprite_vertical
      LDA sprite_RAM+3,x
      STA sprite_horizontal
      RTS

    Later we'll add what amounts to the opposite of this routine.  Collision detection will operate on the variables then at the end we will transfer the variables back to Sprite_RAM.  In this write up, however, weapon positions are measured relative to "sprite_vertical" and "sprite_horizontal".


    BRANCH CARRY SET and BRANCH CARRY CLEAR COMMANDS

    I don't know if these are covered elsewhere, but we'll need them A LOT here, so be sure you understand these.

    Branch Carry Set or BCS:

    For this instruction, you would use something like this:

      LDA variable
      CMP #$08
      BCS .next

    On the CMP function (or CPX or CPY), the carry flag is set when A is greater than or equal to the value placed in the CMP line, in this case, #$08.  Or, if A is less than #$08, the flag will be cleared.  So, BCS is telling the thing to:

    -if variable is less than #$08, Carry is CLEAR, so do nothing
    -if variable is equal to #$08, Carry is SET, so jump to .next
    -if variable is greater than #$08, Carry is SET, so jump to .next

    Branch Carry Clear or BCC:

    This is basically the same as BCS, but branch when clear rather than set.  So, we'd write:

      LDA variable
      CMP #$08
      BCC .next

    On the CMP function (or CPX or CPY), the carry flag is set when A is greater than or equal to the value placed in the CMP line, in this case, #$08.  Or, if A is less than #$08, the flag will be cleared.  So, BCC is telling the thing to:

    -if variable is less than #$08, Carry is CLEAR, so jump to .next
    -if variable is equal to #$08, Carry is SET, so do nothing
    -if variable is greater than #$08, Carry is SET, so do nothing

    Simple, easy, and powerful.  Defenatly something you'll want to add to your toolbox.  There is also a BMI (Branch Minus) and BPL (Branch Plus), but I don't use them that much, if ever, so I'll leave that up to you to figure out.


    ARROW ROUTINE

    Now that we have everything set up, we can start working on our arrow routine.  Strive on.  Remember: control over the NES is won, not given.

    Basically we need to impliment the following steps in our program:

    1.  Check and see if the arrow is inactive (i.e. sitting with #$FE for its vertical and horizontal position)
     A.  If it's inactive
      a.  See if our A timer is equal to the value input by pressing A. Yes=load the starting position and graphics.  No=RTS
      b.  If it is NOT equal the the #Atimer and NOT active, the arrow is not being used.  Do nothing.
     B.  If it's active.
      a.  Check which direction the arrow is moving and move it in that direction the value that we have specified for our #arrow_speed.
    2.  Check the arrow collision with the walls.  (Just pick an arbatray position for the walls or our arrow will continue to loop around endlessly)
     A.  Reset if it hits a wall.
     B.  Do nothing.  (We'll add sprite collision here-ish later)
    3.  Is the A_timer is greater than or equal to zero?
     A.  We need to impliment our shiny new graphics that we just made for shooting poses.
     B.  The arrow is already away and we don't need to do anything else.

    Note:  We will use a constant similar to "Sprite_RAM" for our weapons sprite.  In this case, "Weapons_RAM".  Here, we use the sword weapon as Weapons_RAM+0,+1,+2,+3 and arrow as Weapons_RAM+4,+5,+6,+7.

    Let's assume that the arrow is inactive first.  We push the A button resetting our A_timer.  Our routine jumps to the loading of the arrow's initial position.  Note that there will be a similar routine for each of the 4 directions, but they are pretty much the same, so we'll just go through the one here:

      LDA enemy_direction        ;load the direction that our character is facing/traveling
      STA arrow_direction        ;store this direction in the variable for use in when the arrow is traveling
      CMP #$00                   ;our value for UP
      BNE .nexts                 ;if not facing UP, branch to the next direction

      LDA #$1E                   ;load the tile for the up/down arrow
      STA Weapons_RAM+5          ;store the tile in the appropriate place in RAM
      LDA #%00000010             ;load the attributes for the arrow
      STA Weapons_RAM+6          ;store in the appropriate place in RAM

      LDA sprite_vertical        ;load our dominate sprite's vertical position
      SEC
      SBC #$08                   ;here we are just making the arrow line up with our character
      STA Weapons_RAM+4          ;store the apprpriate vertical position in RAM
     
      LDA sprite_horizontal      ;load the dominate sprite's horizontal position
      SEC
      SBC #$01                   ;here we are just making the arrow line up with our character
      STA Weapons_RAM+7          ;store the apprpriate vertical position in RAM
      JMP .graphics              ;jump down to the graphics updates for our PC meta tile

    This is a pretty simple routine.  The only thing a little challenging is the position changes when specifying the initial arrow location.  You could technically work this out on paper, but I'm lazy and like to just adjust it until it lines up.  You know, hump it into submission and show it who's the bitch.

    Now that we have our loaded and fired, on the next frame it will need to travel.  How do we do this, you ask, the same as we do our other automatic movement.  Again, for up only, simply:

      LDA arrow_direction        ;load the arrow direction stored in the above routine
      BNE .down                  ;moving in the up direction (note that up is direction #$00)
      LDA Weapons_RAM+4          ;load the starting position
      SEC 
      SBC #arrow_speed           ;move up whatever constant we input for #arrow_speed
      STA Weapons_RAM+4          ;store the ending position

      JSR arrow_collision        ;check collsision with the walls

      JMP .loadarrow             ;jump to the next point in the routine

    Next, I guess we'll go over the arrow_collision routine.  All this says is that if the arrow hits a wall, reset it. 

    arrow_collision:             ;arrow collision detection with the walls
      LDA Weapons_RAM+4          ;load the arrow's vertical position, note: the updated position after we've moved it
      CMP #$08                   ;random value for the top wall
      BCC .next                  ;hey, look, our new command
      CMP #$E8                   ;random value for the bottom wall
      BCS .next                  ;holy shit, there's the other one
      LDA Weapons_RAM+7          ;load the arrow's horizontal position
      CMP #$02                   ;random value for the left wall
      BCC .next                  ;again!
      CMP #$FE                   ;random value for the right wall 
      BCS .next                  ;wow     
      JMP .end                   ;Well, we could change the line above to BCC, but I must have done this for some reason, so we'll leave it here.
    .next
      LDA #$FE                   ;reset the arrows by loading the idle position into the vertical and horizontal positions        
      STA Weapons_RAM+4
      STA Weapons_RAM+7
    .end
      RTS

    If we really didn't care, we could stop right there, but we want our game to not suck like NGD, so we will make our character pose when he is firing.  So, if we input the appropriate information into our subfile for "spritegraphics", we will be almost set up.  So, if the A_timer is not equal to zero, we jump to this routine.  Like so:

    shooting_graphics:

      LDA enemy_direction
      CMP #$00
      BNE .next
      LDY #$60                        ;up
      JSR enemyspritesupdate
      JSR update_enemy_sprites
      RTS
    .next
      CMP #$01
      BNE .next1
      LDY #$78                        ;down
      JSR enemyspritesupdate
      JSR update_enemy_sprites
      RTS
    .next1
      CMP #$02
      BNE .next2
      LDY #$68                        ;right
      JSR enemyspritesupdate
      JSR update_enemy_sprites
      RTS
    .next2
      LDY #$70                        ;left
      JSR enemyspritesupdate
      JSR update_enemy_sprites
      RTS

    You'll notice that this is pretty much EXACTLY the same as the updates that take place when we move our guy during "handle_input".  Granted, it is a little redundant, but this keeps our routines independent and seperate.  Meaning that we can call them in any order, any time.  All we are doing is over riding the values set previously.  That's it!  Now our guy will shoot arrows and look like he's having fun doing it!!


    SWORD TYPE WEAPONS

    Now that we have the PC sprite graphics set up, Swords are easy.  They don't move independent of the main character, so there's no moving routine to worry about.  This works exactly like the arrow loading routine.  First we need to just add a quick "skip the routine" if the B_timer is zero and, since the sword doesn't move, we need to add some sort of routine to move the sprite off screen as the B_timer approches zero.  Or:

    mace:                     ;graphics updates for the mace
      LDA B_timer
      BNE .down
      RTS

    .down
      JSR shooting_graphics   ;note that this JSR is very important

      LDA B_timer
      CMP #$01
      BNE .next3
      LDA #$FE
      STA Weapons_RAM
      STA Weapons_RAM+3
      RTS

    .next3

    Simple so far.  All we need to do now is load the position and orentation of the sword sprite and we're done!!  Like so:

    .next3
      LDA enemy_direction         ;check the UP direction
      CMP #$00
      BNE .next

      LDA #%00000010              ;update the UP graphics
      STA Weapons_RAM+2
      LDA #$1C
      STA Weapons_RAM+1

      LDA sprite_vertical         ;save sprite Y position
      SEC
      SBC #$07
      STA Weapons_RAM

      LDA sprite_horizontal       ;load sprite X position
      SEC
      SBC #$00
      STA Weapons_RAM+3
      RTS

    .next

    Viola!!  That's how it's done.  I believe that is everything in this everything that we've added this time.  PM or post if you have any questions.  Enjoy and we'll see you next time!

    Weapons Write Up.zip

    • Like 1
  10. Here we will continue where we left off with #1:  Sprite Animations.
     

    BETTER VARIABLE ORGANIZATION

    First, we need to reorganize our variables.  It's not really necessary here, but as our program gets bigger and stronger, we will run out of variables in zero space...so, this is how we will change it.

      .rsset $0000  ;only pointers here

      .rsset $0100  ;stack

      .rsset $0200  ;sprites

      .rsset $0300  ;sound

      .rsset $0400+  ;other variables

    This will keep it clean and we will avoid having to change stuff around later.


    NPC VARIABLE SET UP

    You'll remember from the last write up that we used strings of variables like:

    Enemy_Animation  .rs 4        ;Animation Counters

    Now, we get creative.  Currently all the graphics update routines for the enemies and our playable character are the same (as you'll see below).  So, what we're going to do is steal the variables for enemy 0 and use them for our playable character.  So, now we'll have our Playable Character as "Enemy 0" and our enemies as "Enemy 1,2,3".  Make sense?  This way we can reuse all of our code and not have to make seperate routines that do the same thing for our playable character.


    Now we will modify our random direction routine.  This will come in handy later when we have sprite/sprite and sprite/background collisions.  Basically, every frame we are putting a random number into the "random direction" that the sprite will take on when something causes it to change direction.  Not only that, but when the counter resets back to zero, we query our playable character's direction to further jumble up the direction routine.  Observe:

    random_table:    ;values entered at random for direction changes
      .db $01,$00,$03,$02,$02,$01,$00,$00,$01,$03

    random:
      INC random_direction1    ;random direction counter
      LDA random_direction1
      CMP #$0A
      BNE .next
      LDA enemy_direction      ;playable character's direction
      STA random_direction1
    .next
      LDX random_direction1
      LDA random_table,X
      STA random_direction     ;actual direction in the movement routine
      RTS


    PLAYABLE CHARACTER SET UP AND BIT TESTING

    This info  is pretty basic, so we'll breeze through it.  If we look at the main program, we can see that the playable character routine looks like this:

      LDA #$00
      STA enemy_number
      STA enemy_ptrnumber

      JSR strobe_controllers

      JSR handle_input

      JSR Enemys_Animation

      JSR Enemys_Sprite_Loading

      JSR update_enemy_sprites

    You see that this is almost exactly the same as the enemy update routine that we used last time.  The only changes are "strobe_controllers" and "handle_input".  First, "strobe_controllers".  I stole this routine from MetalSlime (I think) and he does an excellent job of going through it in his tutorials, so we'll skip it here and assume that you know what it is doing. 

    Next, "handle_input".  This is where the PC differs from the NPC.  All it does is tells the program where to move the sprites (rather than having the automatic random movement generator do it).  However, here we use a little "Bit Testing".  I can't remember if we covered this and I'm too lazy to look, so we'll go through it here.  There are three types of bit testing, AND, ORA, and EOR.  Again, I'll credit MetalSlime for explaining it to me a while ago, and I still haven't found a better explination of it, so we'll use it here.  (Let me know if you don't want me to use it and I'll remove it.)

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

    AND
    When you AND two values together, it compares them bit by bit.  If the two bits are both 1, the result will be one.  If either of the bits are 0, the result will be zero:

    0 AND 0 = 0
    0 AND 1 = 0
    1 AND 0 = 0
    1 AND 1 = 1

    Example:

    00111101  AND
    11100110
    --------
    00100100

    Notice that the 1's in the result are the bit positions where the two original values BOTH have a 1.  If either has a 0 bit, the result will have a zero bit.

    AND is often used to test individual bits in a bitflag.  You use 0's to clear all bits except the one you want to check.  If the result is 0, the bit you were checking was 0.  If the result is non-zero, the bit you were checking was 1.   For example, say we have a bitflag variable and we want to check bit 2.

        lda bitflag
        and #%00000100   ;clear all bits except bit 2.  If the result is 0, bit 2 was clear.
        beq .end
        ;if non-zero, the bit was set.
        ; the only way this could be non-zero is if bit 2 was 1.  Because the only way
        ; to get a 1 in the result is if you have 1 AND 1.  Since we only put a 1 in one
        ; position in our AND instruction, a non-zero result must mean that bitflag also has
        ; a 1in that position.  Make sense?
        ;
        ; ... bit set here, so do something
    ...
    .end:
        rts

    AND is also used to clear bits.  Most often used to turn off bits in a bitflag:

        lda bitflag
        and #%11110111  ;clear bit 3
        sta bitflag       ;save the result
        ;bit 3 of bitflag will be cleared.  All other bits will remain unchanged.
        ;  If they were 1 before, they will remain 1 (1 AND 1 = 1).  If they
        ;  were 0 before, they will remain 0 (0 AND 1 = 0).

    ORA
    When you ORA two values together, it compares them bit by bit.  If the two bits are 0, the result will be 0.  If either of the two bits are 1, the result will be 1.

    0 ORA 0 = 0
    0 ORA 1 = 1
    1 ORA 0 = 1
    1 ORA 1 = 1

    Example:
    11000110  ORA
    00110111
    --------
    11110111

    Notice we only get a 0 in the result in the bit positions where the original values BOTH have 0.  If either of the bits are 1, the result is 1.

    ORA is usually used to set bits in a bitflag:

        lda bitflag
        ora #%00001000   ;set bit 3
        sta bitflag          ;save
        ;here bit 3 will be set.  If it was 0 before, it will be 1 now.  If it was 1 before it will still be 1.
        ;   All other bits will remain unchanged.  If they were 0 before they will remain 0 (0 ORA 0 = 0).
        ;   If they were 1 before they will remain 1 (1 ORA 0 = 1)


    EOR
    EOR compares bits and results in a 0 if the bits are the same, or 1 if they are different:

    0 EOR 0 = 0
    0 EOR 1 = 1
    1 EOR 0 = 1
    1 EOR 1 = 0

    Example:

    11010011  EOR
    01110111
    --------
    10100100

    We get a 0 in the result in bit positions where the original values are either both 0 or both 1.

    Where AND is used to clear bits and ORA is used to set bits, EOR is used to toggle bits.  In the EOR chart above, pay close attention to what happens if you EOR something with 1:

    0 EOR 1 = 1
    1 EOR 1 = 0

    Notice the result is the opposite of the original value on the left,  EORing by 1 toggles the bit.  Off becomes on, on becomes off.

        lda bitflag
        eor #%00001000  ;toggle bit 3
        sta bitflag
        ;here bit 3 will be toggled.  If it was 1 before, it will be 0 now.  If it was 0 before it will be 1 now.
        ;    other bit will remain unchanged.  If they were 0 before, they will remain 0 (0 EOR 0 = 0).
        ;    If they were 1 before, they will remain 1 (1 EOR 0 = 1)

    I use EOR in my controller reading routines to determine if buttons are newly pressed (ie, pressed this frame, but were unpressed last frame).
       
        lda buttons_old  ;last frame's button states.  1 = pressed last frame, 0 = unpressed
        eor #%11111111 ;toggle all bits.  Now 1 = unpressed last frame, 0 = pressed
        and buttons     ;AND with *this* frame's buttons.  The result will only have 1's where the button was unpressed last frame, but pressed this frame. (1 AND 1 = 1)
        sta buttons_newly_pressed

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

    These instructions are VERY powerful.  You need to spend some time with them because we'll use them a lot.


    PLAYABLE CHARACTER MOVEMENT

    Okay, back to the program.  You'll notice that half of the button's don't do anything.  We'll use them later so I left them in for now.  You'll also notice the order of buttons is such that if UP is pressed, it will skip the other direction buttons.  You just have to pick the order that is best for you.  Since all the D-PAD buttons do the same thing, we'll just use this as an example:

    ReadRight:
      LDA joypad1 ;Right
      AND #%00000001
      BEQ ReadRightDone
      JSR RightMovement

    ReadRightDone:

      RTS

    Here we see the use of the AND function.  So, this means that we are only looking at bit 0, which is the RIGHT button as described in our strobe_controllers routine.  It tells the program to JSR the RightMovement routine. 

    RightMovement:

      LDA #$02
      STA enemy_direction    ;put "RIGHT" into the direction that this meta sprite is traveling

      LDA sprite_RAM+3       ;load sprite X position
      CLC
      ADC #enemy_speed       ;add the speed
      STA sprite_RAM+3       ;store the x position back in memory

      INC Enemy_Animation    ;incriment the animation counter

      RTS

    Pretty simple, right?  Then all we do is run the graphics and position updates, same as the NPCs, and you're done! 


    OTHER STUFF

    The following is kind of an afterthought.  I wasn't sure when or if I would add these tools in, but we may as well here because this is a pretty simple lesson. 

    First, I got this from someone, who got it from someone, who stole it from someone.  We all know that the NES is a computer.  Not just a computer, but a 25 year old computer.  It can only do so much per cycle before you over work the processor.  So...how do we know how hard we are working the processor??  Bunnyboy does a pretty good job of explaining NMI timing vs. Main Program timing, we well skip the details here (you can also look on nesdev wiki or the sound tutorials).  Basically, when NMI hits, you start a new "cycle".  The NTSC systems run at 60 Hz, meaning that you have 1/60th of a second to run your program without doing something special.  This is why we push/pop our registers in NMI and include the commands to skip the NMI routine if we are busy doing something.  Anyway, if you're interested in exactly how much the NES can do, nesdev wiki has a pretty good table. 

    So, when we start a new NMI, first we run the graphics updates (in NMI) and then we run our main program.  Then it goes back to sleep until NMI wakes it up again and it starts all over.  What say we stick something at the end of our main program that presents a visual indicator of how far close to the next NMI our program takes us?  The closer to the top of the screen, the shorter the program.  Or if the indicator is close to the bottom, you're close to over running your program and need to do something else.  How about a white line across the screen?  Easy to see and impliment:

    showCPUUsageBar:
      ldx #%00011111  ; sprites + background + monochrome (i.e. WHITE)
      stx $2001
      ldy #21  ; add about 23 for each additional line (leave it on WHITE for one scan line)
    .loop
        dey
        bne .loop
      dex    ; sprites + background + NO monochrome  (i.e. #%00011110)
      stx $2001
      rts

    We just call this at the end of our main program and it will work.  However, this will turn the graphics back on, so we have to put some simple commands in to skip it when we are updating backgrounds.  It should be noted that our program doesn't have any background that isn't monochrome and is pretty simple, so the usage bar won't show up yet.  But as we get more and more complex routines, it will start to bounce around the top of the screen. 


    GAME STATES

    This was covered briefly in another NA tutorial, but we'll impliment it here because I farted and it smells like tacos.  We are going to add "PAUSE"!!  How exciting. 

    First, to cover a few things.  Indirect jumps are pretty simple.  You can't "JSR" to a pointer address.  This necessitates the following pair of commands:

      JSR GameStateNMIIndirect

    -and-

    GameStateNMIIndirect:      ;indirect jump to NMI routine
      JMP [NMI_Pointer]

    We have to do this for both the NMI and Main Program.  This leaves a RTS in the stack for us to use when we get done with the code located at the pointer address.

    Next, we have to declare "Game States".  You can pick whatever you want, but we are going to use this:

    ;00=Main Top Down View
    ;01=Paused

    GameStates:
      .word GameState0,GameState1

    GameStateNMIs:
      .word GameStateNMI0,GameStateNMI1

    You can see where we're going with this.  We say load 00 or 01 into our game state, then JSR to a routine that loads the appropriate address into the "NMI_Pointer" or "Main_Pointer".  Then just use our indirect jump in the NMI/Main Program routines.  So, let's write the game state pointer update routine:

    GameStateUpdate:             ;load the game state and NMI pointers
      LDA GameState
      STA GameStateOld
      ASL A                      ;multiply by two
      TAX

      LDA GameStates,x           ;Load the Main Program Pointer
      STA Main_Pointer
      LDA GameStates+1,x
      STA Main_Pointer+1

      LDA GameStateNMIs,x
      STA NMI_Pointer
      LDA GameStateNMIs+1,x
      STA NMI_Pointer+1

      RTS

    The new variables:
      "GameState" - the current game state
      "GameStateOld" - if this is different than the current game state, trigger the Update routine
      Pointers - pointers as described above.

    Great!  So, now what?  Well, if we take our main program and cut all the meat out and put it in a "GameState0" routine and add a "JSR GameStateIndirect" in its place, we can use this technique.  Same thing in the NMI.  Now we will have a "Main_Program" and a "NMI" program AND we can have several "meat parts" of code split out into the GameState0,GameState1,etc. and GameStateNMI0,GameStateNMI1,etc. that we can call as needed any time we want to change how stuff is handled. 

    Next, we need to put a call for our GameStateUpdate routine into the program somewhere.  If we stick the following at the end of the Main_Program, we will have an automatic way of switching game states whenever we change the "GameState" variable.  i.e. the code is stand alone!  We don't have to call a routine at the time that we want to change the GS, we just wait until all of the main program is finished, then change.  This way we complete all of our JMP and JSR routines and don't leave open return to scripts which would be bad.  face-icon-small-smile.gif  So, now our NMI and main programs would look like this:

    ;----------------------------------------------------------------------
    ;-----------------------START MAIN PROGRAM-----------------------------
    ;----------------------------------------------------------------------

    Forever:
      INC sleeping                     ;wait for NMI

    .loop
      LDA sleeping
      BNE .loop                        ;wait for NMI to clear out the sleeping flag

      LDA #$01
      STA updating_background          ;this is for when you are changing rooms or something, not really needed here
                                       ;it will skip the NMI updates so as not to mess with your room loading routines

      JSR strobe_controllers

      JSR GameStateIndirect

      LDA GameState
      CMP GameStateOld
      BEQ .next

      JSR GameStateUpdate

    .next

      LDA #$00
      STA updating_background

      JMP Forever     ;jump back to Forever, and go back to sleep

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;--------THE FUCKIN' NMI ROUTINE, RECOGNIZE BITCH!------------;
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

    NMI:
      PHA                              ;protect the registers
      TXA
      PHA
      TYA
      PHA

    nmi_start:

      LDA updating_background          ;check to be sure that the main program isn't busy
      BNE skip_graphics_updates
     
      LDA #$02
      STA $4014                        ;set the high byte (02) of the RAM address, start the transfer

      JSR GameStateNMIIndirect

      LDA #$00                         ;tell the ppu there is no background scrolling
      STA $2005
      STA $2005

      LDA #%00011110                   ;enable sprites, enable background, no clipping on left side
      STA $2001

      LDA #$00
      STA sleeping                     ;wake up the main program
      STA updating_background+1


      LDA updating_background+1        ;we put this in because the usage bar TURNS ON THE BACKGROUND
      BNE skip_graphics_updates

      JSR showCPUUsageBar

    skip_graphics_updates:

      PLA                              ;restore the registers
      TAY
      PLA
      TAX
      PLA

      RTI                              ;return from interrupt

    Simple, easy, and clean.  Now all we have to do is put in code to change the variable "GameState" and we're set.  So, in our "handle_input" subroutine, let's change START to this:

    ReadStart:
      LDA joypad1_pressed       ; player 1 - start
      AND #%00010000  ; only look at bit 5
      BEQ ReadStartDone

      LDA GameState
      STA GameState+1
     
      LDA #PauseState
      STA GameState

    ReadStartDone:

    See, all it does is change the variable "GameState".  Now that it is different than "GameStateOld", at the end of the main program, it will automatically change to the PauseState.  (Note:  PauseState is just a Constant that we declare so that we can change it once and not have to hunt through the program for every use of that state.  This technique will save you a lot of time.  I reccomend using it a lot.)  We also see "GameState+1" for the first time.  This is not really needed when you only have 2 states, but when you have like 10 and want to use pause in all of them you need a way to tell the pause state which state to un-pause to.  Otherwise you could pause it on your top down view and when you un-pause, the program will switch to your map screen program and crash.  That said, we write our PAUSED state NMI and main routines:

    ;----------------------------------------------------------------------------------
    ;-----------------------MAIN PROGRAM GS #$01-PAUSED--------------------------------
    ;----------------------------------------------------------------------------------

    GameState1:

    ;ReadStart:
      LDA joypad1_pressed       ; player 1 - start
      AND #%00010000
      BEQ .ReadStartDone

      LDA GameState+1
      STA GameState
     
    .ReadStartDone:

      RTS

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;--------NMI ROUTINE, PAUSED GAME STATE #$01-PAUSED-----------;
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

    GameStateNMI1:

      RTS

    Now, it is just sitting there licking its nuts waiting for you to push START again.  When you do, it loads the saved state in to "GameState" and the pointers change back to the Main Top Down routines and game play continues.  If you really wanted to, you could add something creative here like a giant ball sack that pops up on the screen when it's paused, but we're just going to have the game freeze. 


    AND ANOTHER THING!!

    This is more of a personal preference than anything.  As you get a bigger and bigger program, you will have to use the ".include" and ".incbin" commands more and more.  This is all well and good, but what about when you have like 10 .include and 15 .incbin commands?  There's a different file associated with each command...that's a lot of crap to dig through.  If you are like me and like to organize your stuff into folders to keep it organized, just do this:

      .include "Subfiles/spritegraphics.asm"

    -or-

      .incbin "CHR_Files/SpriteMovement.chr"  ;include the sprite graphics data

    Bitchin! 


    END GAME

    That's about it!!  Attached are all the files that go with this program.  Open it, assemble it, take it apart, and learn well because we will build from here!

    Any questions, post or PM me.  Until next time...teabag.gif

    PlayableCharacter.zip

    • Like 1
  11. BACKGROUND

    Sorry, this one got kind of long.  

    1973409981_Sprites1.PNG.639f6efce7822e337332980a8833e4d8.PNG

    This is the "front view" of one of the sprites.  We'll go through the picture row by row.

    First row, we make the three views that we need to animate our sprite.  Notice that the second and third
    frame are mirrors of each other.  This will save space and save us the trouble of having to come up with
    more art.  And you can't really tell it is a mirror while playing the game.

    Second row, this puts the frames in order.  i.e. legs together, step forward with one leg, legs together,
    step forward with the other leg.  So, we only need to come up with 2 frames to make a smooth, four frame
    animation.

    Third row, here we simply split the sprites into the individual tiles that will be encoded into the
    PPU.

    Forth row, if we look back up to the "attributes" that we have to assign to each sprite tile, we notice
    that we can "mirror" them.  This allows us to not only use the mirror of each sprite for the various frames
    of our animation, but allows us to mirror each side of the sprite on its own.  Here, we just delete all
    the sprite tiles that are not unique and we see that the number of actual tiles condenses down quickly.

    Fifth, row.  Here we organize the tiles into a simple row.  This is what we program into a graphics editor
    such as Tile Molester.


    META SPRITES

    The above picture shows a plain example of a "meta sprite".  Here, we are going to keep it simple
    and use meta sprites that are 2 tiles x 2 tiles.  In the Building Sprites section, I may have incorrectly
    used "sprite" and "meta sprite" interchangeably, but from here on, a "sprite" will refer to a 8 pixel x
    8 pixel tile that is entered into the PPU and a "meta sprite" will be a 16 pixel x 16 pixel image
    made up of 4 sprites.  "Most" games use the 16x16 meta sprites, but you could really apply the technique
    that follows to whatever meta sprite you want. 

    First, the position of the sprites within the meta sprite.  For the sake of example, we will number our
    sprites within the meta sprite as follows:  1,2,3,4.  So as displayed on the screen, it would look like:

    1 2

    3 4

    When we move these sprites around, we want to ALWAYS keep them in the same relative position to each other.
    If we don't, then there are potential problems with collision detection, movement, animation, etc.  This
    technique should rule this problem out, but you never know.  Basically, this is bad:

    1 2  move right  2 1
       ------------->
    3 4              4 3
     |
     | move
     | down
     |
    3 4

    2 1

    This is good:

    1 2          1 2
       --------->
    3 4          3 4
     |
     |
     |
    1 2

    3 4

    You will notice in what follows that all position information is measured relative to the sprite #1.  So,
    we need to keep the order the same throughout the process.

    Next, we will introduce the concept of "constants" in sprite manipulation.  If we "hard code" in the addresses
    for sprite data, we would have to write a separate routine for each sprite and if we ever wanted to change
    the sprite address within the $0200 block of RAM, we would have to spend a large amount of time going back through
    our code and changing every place that we have an address.  This is a PITA and can lead to difficult bugs.  Let's
    fix this problem.  If we declare a constant for the beginning address of our sprites, we need only change this
    number and the assembler will automatically update all the subsequent addresses.  Observe:

    ;------------------------------------

    sprite_RAM = $0200

    ;------------------------------------

    That's it.  So, every where we want to put an address, we just put it relative to this constant.  For
    the first sprite:

    sprite_RAM   ;vertical position address
    sprite_RAM+1 ;tile number address
    sprite_RAM+2 ;attribute address
    sprite_RAM+3 ;horizontal position address

    Second sprite:

    sprite_RAM+4 ;vertical position address
    sprite_RAM+5 ;tile number address
    sprite_RAM+6 ;attribute address
    sprite_RAM+7 ;horizontal position address

    and so on.

    For example, sprite_RAM+7 means $0200+7 = $0207 to the assembler.

    If you have different sprite blocks you want to keep separate, like enemies, your hero,
    weapons, enemy weapons, etc., you can declare multiple constants and Bob's your uncle.

    Now that we have that figured out, we ask why we're doing it.  Well, as I said before, everything is
    measured relative to the sprite #1 and as our sprites will always be in the same relative position
    as each other, we can write:

    ;------------------------------------

    update_enemy_sprites:           ;this routine updates the position of the meta sprites relative to each other

      LDA sprite_RAM                ;vertical updates
      STA sprite_RAM+4
      CLC
      ADC #$08
      STA sprite_RAM+8
      STA sprite_RAM+12

      LDA sprite_RAM+3              ;horizontal updates
      STA sprite_RAM+11
      CLC
      ADC #$08
      STA sprite_RAM+7
      STA sprite_RAM+15

      RTS

    ;------------------------------------

    This routine will update our sprites 1,2,3 and 4 relative to sprite 1, i.e. moving as a meta sprite. 
    If we do this every frame, our sprite will move together and we will only have to specify
    the translation of sprite 1.  This keeps everything neat, orderly, and together. 


    MULTIPLE META SPRITES

    Now, say that we had multiple enemies going after us at once??  Do we have to write a separate
    routine for each meta sprite?  No.  We can use what is help from our friend, the "x" register.

    If we continue our sprite numbering we have:

    Meta Sprite 1 - sprites 1,2,3,4     - starting address $0200
    Meta Sprite 2 - sprites 5,6,7,8     - starting address $0210
    Meta Sprite 3 - sprites 9,10,11,12  - starting address $0220
    Meta Sprite 4 - sprites 13,14,15,16 - starting address $0230
    etc.

    Here, we'll construct a routine that will update 4 meta sprites with only a slight modification to the
    code block above.  Look at the starting addresses of the sprites in the meta sprite groups above. 
    Notice the pattern?

    Remember that in hex, things start at 0.  So, if we change the numbering system a little bit:

    Meta Sprite 0 - sprites 1,2,3,4     - starting address $0200
    Meta Sprite 1 - sprites 5,6,7,8     - starting address $0210
    Meta Sprite 2 - sprites 9,10,11,12  - starting address $0220
    Meta Sprite 3 - sprites 13,14,15,16 - starting address $0230

    Now, if we keep in mind everything that we have learned so far about meta sprites and apply
    them to the other 3 meta sprites, we notice that the addresses are exactly the
    same...only offset by $10.  So, naturally, if we want to keep our numbering scheme the same,
    we would declare some "updating constants" that would allow us to use loops for each meta sprite.

    ;------------------------------------

    updateconstants:                  ;constants for the use of the sprite_RAM constant           
      .db $00,$10,$20,$30             ;4 sprites for each meta sprite, so add $10 for each meta sprite we process

    ;------------------------------------

    Notice that if we use the meta sprite numbers to look up values in the table and add them to our
    constant, sprite_RAM, we get the starting addresses for each meta sprite.  It follows then that:

    -Load the meta sprite number, $03, into x
    -Look up meta sprite 3's update constant using "update constants" and x
    -Store this value, $30, in the x register
    -Add x to each value in the above "update_enemy_sprites" routine

    This allows us to update all 4 enemies using this one routine:

    ;------------------------------------

    update_enemy_sprites:             ;this routine updates the position of the meta sprites relative to each other
      LDX enemy_number                ;this is a variable used to specify our "meta sprite number" within the loop
      LDA updateconstants,x
      TAX

      LDA sprite_RAM,x                ;vertical updates
      STA sprite_RAM+4,x
      CLC
      ADC #$08
      STA sprite_RAM+8,x
      STA sprite_RAM+12,x

      LDA sprite_RAM+3,x              ;horizontal updates
      STA sprite_RAM+11,x
      CLC
      ADC #$08
      STA sprite_RAM+7,x
      STA sprite_RAM+15,x

      RTS

    ;------------------------------------

    Savvy?

    While this is a useful tool, it doesn't really do much if nothing moves and there is no animation.


    MOVING SPRITES

    Now that we know how to update sprites, and that we only need to move around the first sprite in
    our meta sprite, we can move onto more exciting things like moving the sprites around the screen.
    Again, in this write-up we will use the top down view, so the animation will have the front, back,
    left, and right side views. 

    First, we need some way to specify to the program which way the sprite is currently moving.  Here
    we will assign the following:

    Up    - $00
    Down  - $01
    Right - $02
    Left  - $03

    This allows us to write the necessary code for each of the 4 directions and makes it easy to figure out
    which way things are moving.  It follows then that we would need to reserve a variable for each
    meta sprite that specifies direction.  (We will include the enemy_number here for completeness.)

    ;------------------------------------

    enemy_number  .rs 1
    enemy_direction  .rs 4

    ;------------------------------------

    Note that when we reserve multiple variables like this, it will allow us to pull the directions of
    the various enemies using the variable "enemy_number" while in a loop. 

    Normally, a change in direction would be triggered by a collision or other random event, but here
    we don't have any of that.  So I have inserted a basic "random" direction change routine to make the
    example more interesting.  I think that you can figure out what it does, so I won't waste time on it. 

    Enemy animation, which we will get into later, is usually only active when your dudes are moving.  But
    as in this example they are always moving, we will just keep updating the "animation counter" all the
    time.  If you expand on this program, you need to expand when the animation counter is updated, and when
    it is not, or your dude will do the Moon Walk while standing still.  So, and this is a little redundant
    when the sprites are all moving all the time, but later we will need another variable for each enemy
    that specifies its position in the animation routine.  Since it appears in the sprite movement routine,
    we will introduce it now. 

    ;------------------------------------

    Enemy_Animation  .rs 4

    ;------------------------------------

    I assume that the reader has been through the tutorial on basic sprite movement, so I won't cover that.
    So, after we have all of this information, we write:

    ;-------------------------------------

    randomenemydirection:            ;these are random numbers used with the random_direction2 to make enemies switch direction
      .db $57,$CD,$AF,$05,$BC

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

    enemy_update:
      LDX enemy_number
      INC Enemy_Animation,x          ;increment the counter for the animation routine

      LDA random_direction2          ;check random_direction2 with the values stored in randomenemydirection
      CMP randomenemydirection,x     ;this is in place of the routine you would have in collision detection
      BEQ .down
      CMP randomenemydirection+1,x
      BNE .done
    .down

      LDA random_direction1          ;if the values match, switch direction the counter random_direction1
      STA enemy_direction,x

    .done
      LDA enemy_direction,x          ;for the various directions, move the sprites around the background
      BNE .next
      JMP enemy_up                   ;note JMP not JSR
    .next
      CMP #$01
      BNE .next1
      JMP enemy_down
    .next1
      CMP #$02
      BNE .next2
      JMP enemy_right
    .next2
      JMP enemy_left

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

    enemy_up:

      LDX enemy_number                ;move the sprite up
      LDY updateconstants,x
      LDA sprite_RAM,y
      SEC
      SBC #enemy_speed
      STA sprite_RAM,y

      RTS

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

    enemy_down:

      LDX enemy_number                ;move the sprite down
      LDY updateconstants,x
      LDA sprite_RAM,y
      CLC
      ADC #enemy_speed
      STA sprite_RAM,y

      RTS

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

    enemy_right:

      LDX enemy_number                ;move the sprite right
      LDY updateconstants,x
      LDA sprite_RAM+3,y
      CLC
      ADC #enemy_speed
      STA sprite_RAM+3,y

      RTS

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

    enemy_left:

      LDX enemy_number                ;move the sprite left
      LDY updateconstants,x
      LDA sprite_RAM+3,y
      SEC
      SBC #enemy_speed
      STA sprite_RAM+3,y

      RTS 

    ;-------------------------------------

    Notes:
    -The enemy direction is used to specify which update we want to use. 
    -Both the X and Y registers are used.
    -Normally for "enemy_speed" you wouldn't use a constant, you would have a variable for each meta sprite,
    but here we just keep it simple.  This would be another thing you should mess around with (and is the
    reason for the y register usage).  (If you had variables for the enemy_speed, you would need the
    "enemy_number", stored in x, to pull the values from RAM.  The y value holds our "updateconstants".)
    -The random direction routine and redundant calls of the enemy_number are included to help you
    avoid errors when expanding the program.


    META SPRITE ANIMATION SET UP

    Before we can completely jump into animation, we need to specify a few things.  Namely, how do we
    tell the program which animation frame to display given the specified direction and when to switch
    frames based on the afore mentioned "Enemy_Animation" counters.  You may want to specify separate
    "reset" points for each enemy’s counter, but I've never really found it necessary.  You can pick
    whatever resets you want, just use them so that the animation "looks right".  So, we will
    write the following:

    ;-------------------------------------

    Enemy_Frame  .rs 4            ;Animation Frame Number

    enemyFrames1 = $0C  ;enemy's counter resets
    enemyFrames2 = $18
    enemyFrames3 = $24
    enemyFrames4 = $30

    ;-------------------------------------

    Now that we have all this information in place, we use a simple series of compares, counter resets,
    and branches to keep our animation frames updated throughout the process.  Note that this routine
    does not update the animation frame number every frame as it is not really needed.  Go through the
    routine and figure out the animation sequence and how it relates to the picture above.  Assume here
    that frame $00 = "legs together".

    So, we simply write:

    ;-------------------------------------

    Enemys_Animation:                 ;this routine updates the frame that is displayed for each enemy 1,2,1,or 3

      LDX enemy_number

      LDA Enemy_Animation,x           ;compare to constants
      CMP #enemyFrames1               ;you can change these around to make the animation faster or slower on the constants page
      BEQ .Animation1
      CMP #enemyFrames2
      BEQ .Animation2
      CMP #enemyFrames3
      BEQ .Animation1
      CMP #enemyFrames4
      BEQ .Animation3
      JMP .AnimationDone

    .Animation1:                      ;load the various frames
      LDA #$00
      STA Enemy_Frame,x
      JMP .AnimationDone
    .Animation2:
      LDA #$01
      STA Enemy_Frame,x
      JMP .AnimationDone
    .Animation3:
      LDA #$02
      STA Enemy_Frame,x
    .AnimationDone

      LDA Enemy_Animation,x           ;reset the counter when it gets to the end
      CMP #enemyFrames4
      BNE .AnimationFinished
      SEC
      SBC #enemyFrames4
      STA Enemy_Animation,x
    .AnimationFinished
      RTS

    ;-------------------------------------

    You'll notice so far that this is simply a series of subroutines.  I write this way to allow myself
    to edit certain parts of the overall routine as needed.  i.e. if you had this all in one long ass
    sub-routine, you would have difficulty updating direction changes, variable movement, enemy weapons,
    hit/shoot poses, and on and on. 

    Okay, now that we have a basic engine in place to move our meta sprites around as one and an engine
    to drive the basic animation, we can move on to some more complex stuff. 


    FORMATTING GRAPHICS

    This is where it can get confusing as this is the "meat" of the whole process.  Here we are going to put
    our graphics data in a format that can be read by the program and displayed on screen.  You'll note that
    we have already been through movement, so we have covered the "vertical position" and "horizontal
    position" of the sprites.  Now it is time to focus on the sprite "attributes" and "tile number".

    First, when working with graphics, sprites or background, I recommend putting them into a "demo" program
    so that you can view your tiles in the PPU Viewer.  The reason for this is simple.  Well, the screen shot
    didn't capture the mouse, but you can still observe my point.  Reading tiles from the PPU viewer is
    quick and easy.  Simply put your mouse over the tile in question and the emulator displays the tile
    number at the bottom. 

    618892710_Sprites2.PNG.911e92c4722a5c23b86162bb34d0cadf.PNG

    Let's look at the picture above again and our sprite numbering inside of the meta sprite shown in the
    graphic above.  See how it goes 1,2,3,4?  This is how we are going to specify the data in our meta sprites.
    If you load the PPU viewer from the attached program, it will be a little easier to follow along here. 

    We are going to format our graphics as follows:

    tile 1, tile 2, tile 3, tile 4, attribute 1, attribute 2, attribute 3, attribute 4

    We will need to do this for each frame, for each direction, for each enemy type we want to use.

    Let's start with the example in the picture above.  This, combined with the PPU viewer, will give us:

    ;-------------------------------------

      .db $5C,$5C,$68,$68,%00000010,%01000010,%00000010,%01000010  ;Down, Frame 1 and 3
      .db $5A,$5B,$66,$67,%00000010,%00000010,%00000010,%00000010  ;Down, Frame 2
      .db $5B,$5A,$67,$66,%01000010,%01000010,%01000010,%01000010  ;Down, Frame 4

    ;-------------------------------------

    Personally, I keep the tiles stored in hex, since that is how they are displayed in the PPU viewer,
    and the attributes stored as binary just to make it easier to read.  To make this data format a
    little clearer, let's examine it a little closer. 

    We have our meta sprite format from above:

    1 2

    3 4

    If we look at frame 1 in the data stream above, we see that we are specifying the tiles to be
    displayed as:

    $5C $5C

    $68 $68

    and the attributes as:

    %00000010 %01000010

    %00000010 %01000010

    If we look at it like this, we can see the mirroring in effect.  Do this for the other
    two frames, and you will see an even better example of the mirroring in action!!

    If we do this for each of the 12 frames required to completely animate the meta sprite in all
    directions, we are left with a table that looks like this:

    ;-------------------------------------

    McBoobins_graphics:
      .db $5D,$5D,$5F,$5F,%00000010,%01000010,%00000010,%01000010  ;Up, Frame 1 and 3
      .db $5D,$5E,$69,$6A,%00000010,%00000010,%00000010,%00000010  ;Up, Frame 2
      .db $5E,$5D,$6A,$69,%01000010,%01000010,%01000010,%01000010  ;Up, Frame 4
      .db $5C,$5C,$68,$68,%00000010,%01000010,%00000010,%01000010  ;Down, Frame 1 and 3
      .db $5A,$5B,$66,$67,%00000010,%00000010,%00000010,%00000010  ;Down, Frame 2
      .db $5B,$5A,$67,$66,%01000010,%01000010,%01000010,%01000010  ;Down, Frame 4
      .db $57,$56,$63,$62,%01000010,%01000010,%01000010,%01000010  ;Right, Frame 1 and 3
      .db $55,$54,$61,$60,%01000010,%01000010,%01000010,%01000010  ;Right, Frame 2
      .db $59,$58,$65,$64,%01000010,%01000010,%01000010,%01000010  ;Right, Frame 4
      .db $56,$57,$62,$63,%00000010,%00000010,%00000010,%00000010  ;Left, Frame 1 and 3
      .db $54,$55,$60,$61,%00000010,%00000010,%00000010,%00000010  ;Left, Frame 2
      .db $58,$59,$64,$65,%00000010,%00000010,%00000010,%00000010  ;Left, Frame 4

    ;-------------------------------------

    On a personal note, and I suppose that I'll catch shit for doing this if I don't
    address it, the sprites used in this write-up are "dumbed down" versions of the sprites
    that Miss Clawful made for use in my personal game.  I used them mainly because I had
    the above tables already built and was too lazy to go and find new sprites, build
    the tables, and make it work.  I trust that everyone that actually uses this information
    will be original and make their own work.

    Okay, now that we have this built, and presumably, the tables for the other required
    enemies, we are ready to start the animation.  If construction of this table is not
    making sense to you, please review it and make sure you understand.  If not, please
    ask questions.

    Now, if we look at the above table we need to specify some way to pull graphic information
    from a certain point in the table....  Pretty simple, right?  We just specify the starting
    position of every frame in the array for each frame of animation.  We will use the variable
    defined in the "Enemys_Animation" routine to pick the required number from the table...but
    what about direction?  We simply use the "enemy_direction" variable to specify which look
    up table we want to look in?  Lost?  This might make it clearer:

    ;-------------------------------------

    NM_up:
      .db $00,$08,$10                ;the indexes to the various frames in the spritegraphics file
    NM_down:
      .db $18,$20,$28
    NM_right:
      .db $30,$38,$40
    NM_left:
      .db $48,$50,$58

    ;-------------------------------------

    For the moment assume that each of the numbers in that array is specifying the starting
    position of the frame in question and try to understand.  If it's not clear, you'll have to
    forgive me, I'm not the best teacher.  I'll assume you got it, so if not, ask questions.


    ANIMATING SPRITES

    Now that we have a fancy table set up with all of our graphics information and another array
    set up that holds the starting place in that table for each animation frame, we are ready to
    write a routine to use this stuff.

    You'll note that if we use the frame counter set up that we have above, it does not reset the
    counters when the direction changes...i.e. if your dude is taking a step when he changes directions,
    he will be taking a step when he starts in the other direction.  You can set it up to reset the
    counters and force him to start walking when he switches directions, but there's really no need.
    It would make the code much more complex and you won't be able to tell the difference. 

    Okay, for now, take it on faith that we need to load the appropriate value in that array up there
    into the Y register.  To do that, it is pretty simple.  Rather than explain it, I think that I will
    just post the code script, and you can go through it real quick:

    ;-------------------------------------

    Enemys_Sprite_Loading:           ;this routine updates the sprite graphics every frame based on data set by the other routines

      LDX enemy_number
      LDA enemy_direction,x
      BEQ .next  ;up                 ;find out which direction it is going
      CMP #$01
      BEQ .next1 ;down
      CMP #$02
      BEQ .next2 ;right
      JMP .next3 ;left

    .next                            ;UP
      LDA Enemy_Frame,x              ;load the spritegraphics index based on the frame number set in the animation routine
      TAX                            ;some of this is redundant because I removed some of the more complex code here
      LDA NM_up,x
      TAY
      JMP enemyspritesupdate         ;update graphics

    .next1                           ;DOWN
      LDA Enemy_Frame,x
      TAX
      LDA NM_down,x
      TAY
      JMP enemyspritesupdate

    .next2                           ;RIGHT
      LDA Enemy_Frame,x
      TAX
      LDA NM_right,x
      TAY
      JMP enemyspritesupdate

    .next3                           ;LEFT
      LDA Enemy_Frame,x
      TAX
      LDA NM_left,x
      TAY
      JMP enemyspritesupdate

    ;-------------------------------------

    Pretty simple right? 

    Well, here we have to go a little out of order.  In this simple example, we lack background, room loading,
    collision detection, starting positions for sprites, etc.  This is all beyond the scope of this document.
    So, what we need to do here is hard code some things you would normally load when entering a room. 
    If we are going to use the same code for multiple enemy types, we are going to need pointers.  If you don't
    know how to use pointers, there are several tutorials on this site that explain them. 

    This isn't really necessary, as if we don't do it, the sprites will just start off screen and walk on...but
    we'll do it anyway.  Specify starting positions for the sprites in the reset routine:

    ;-------------------------------------

      LDA #$80                         ;here we set up any start points for enemies
      STA sprite_RAM                   ;in practice, these locations would be determined by your background
      STA sprite_RAM+3
     
      LDA #$90
      STA sprite_RAM+16
      STA sprite_RAM+19
     
      LDA #$A0
      STA sprite_RAM+32
      STA sprite_RAM+35
     
      LDA #$B0
      STA sprite_RAM+48
      STA sprite_RAM+51

    ;-------------------------------------

    Next, for the various enemy types, we will need to set up pointers to the graphics in the "spritegraphics"
    file.  We need to define a pointer variable to compliment the enemy_number.  You can just "multiply by 2"
    every time you use it, but here we're going to do this:

    ;-------------------------------------

    enemygraphicspointer  .rs 02  ;pointer for the graphics updates
    enemy_pointer  .rs $08        ;pointer for the graphics data for Enemies

    ;-------------------------------------

    Basically, the enemygraphicspointer is the pointer that the routine will use each time through the loop
    to find the graphics data.  enemy_pointer is a series of numbers (or addresses) that we set up in our
    loading routine to make the various meta sprites look like we want them to.  We will need two bytes for
    each enemy, so that is a total of 8 bytes.  A little confusing, but it will clear up in a minute. 

    Like I said before, you would do this in your room loading routine in practice, here we will hard code
    these addresses in the reset routine:

    ;-------------------------------------

      LDA #LOW(crewman_graphics)       ;here we set up the pointer data for the different enemy types
      STA enemy_pointer                ;in practice, you wouldn't hard code these, they would be part of the loading routines
      LDA #HIGH(crewman_graphics)  ;note that this notation is probably WRONG in the actual program.
      STA enemy_pointer+1

      LDA #LOW(Punisher_graphics)
      STA enemy_pointer+2
      LDA #HIGH(Punisher_graphics)
      STA enemy_pointer+3

      LDA #LOW(McBoobins_graphics)
      STA enemy_pointer+4
      LDA #HIGH(McBoobins_graphics)
      STA enemy_pointer+5

      LDA #LOW(ArseFace_graphics)
      STA enemy_pointer+6
      LDA #HIGH(ArseFace_graphics)
      STA enemy_pointer+7

    ;-------------------------------------

    The last subroutine we need will load the graphics pointer for the current enemy, read the graphics tables
    we have set up, and store them to the appropriate place in RAM.  Then the next frame, the NMI routine will
    transfer them to the PPU and life will be grand.

    Again, this is pretty simple if you've been paying attention so far.  Note that the y register was
    set up in the "Enemys_Sprite_Loading" routine. 

    ;-------------------------------------

    enemyspritesupdate:               ;this routine updates the tiles and attributes for the enemies

      LDX enemy_ptrnumber             ;load in the pointer for the graphics data
      LDA enemy_pointer,x
      STA enemygraphicspointer
      INX
      LDA enemy_pointer,x
      STA enemygraphicspointer+1

    subenemyspritesupdate:            ;we put this here in case we want to have some sort of special update,
      LDX enemy_number                ;like shooting graphics, it is not used here
      LDA updateconstants,x
      TAX
     
      LDA [enemygraphicspointer],y    ;read the tile from the "spritegraphics" sub file and store it in memory
      STA sprite_RAM+1, x
      INY
      LDA [enemygraphicspointer],y
      STA sprite_RAM+5, x
      INY
      LDA [enemygraphicspointer],y
      STA sprite_RAM+9, x
      INY
      LDA [enemygraphicspointer],y
      STA sprite_RAM+13, x
      INY
      LDA [enemygraphicspointer],y
      STA sprite_RAM+2, x
      INY
      LDA [enemygraphicspointer],y
      STA sprite_RAM+6, x
      INY
      LDA [enemygraphicspointer],y
      STA sprite_RAM+10, x
      INY
      LDA [enemygraphicspointer],y
      STA sprite_RAM+14, x

      RTS

    ;-------------------------------------


    NMI AND THE MAIN PROGRAM

    So far, we have acquired all the tools we need to make our enemies run.  As I said up top, all you need
    for NMI is:

    ;-------------------------------------

      LDA #$02
      STA $4014                        ;set the high byte (02) of the RAM address, start the transfer

    ;-------------------------------------

    The main program is pretty simple as well.  Really, we just need to make a loop and it one time for
    each of our 4 enemies.

    -Set up the enemy number/enemy pointer
    -Loop it through our subroutines here
    -Run it once for each of our 4 (or however many you have) enemies

    Pretty simple:

    ;-------------------------------------

    UpdateENEMIES:
      LDA #$00                         ;this loop updates the enemies one at a time in a loop
      STA enemy_number                 ;start with enemy zero
      STA enemy_ptrnumber
    .loop
      JSR enemy_update                 ;move the sprites based on which direction they are headed
      JSR Enemys_Animation             ;find out which frame the enemy animation is on
      JSR Enemys_Sprite_Loading        ;update the enemy meta tile graphics
      JSR update_enemy_sprites         ;update position

      INC enemy_number                 ;increment the enemy number for the next trip through the loop
      INC enemy_ptrnumber              ;these are addresses for the graphics data, so we need to keep it at 2x the enemy number
      INC enemy_ptrnumber
      LDA enemy_number
      CMP #$04                         ;if it is 4, we have updated enemies 0,1,2,3 so we are done
      BNE .loop
    UpdateENEMIESdone:

    ;-------------------------------------


    ONE LAST THING

    I don't think that this has been introduced yet.  It is pretty simple, again, but the code for the
    Batch File that runs NESASM3 is as follows:

    ;-------------------------------------

    NESASM3 SpriteMovement.asm -s
    pause

    ;-------------------------------------

    As simple as run NESASM3 on the file SpriteMovement.asm.  -s tells the program that you want it to
    display the memory usage in each bank when the MS-DOS window pops up.  This is extremely useful
    in larger programs.  Then, pause simply tells the program that you are done. 


    CONCLUSIONS

    It is my personal style, but you can do it however you want.  I try to keep my playable character
    code separate from my NPC code.  This is generally because when you get something like this big enough
    to where it does something ... well, interesting, the playable character code will be so different
    than the NPC code, that you will have a shit ton of compares or branches and it will just be a mess. 
    However, the code is pretty similar. 

    For "homework", I would say that you should:
    1.  Make your own characters and input them into these routines
    2.  Modify the code to accept input from the controllers
    3.  Add code so that your enemy can do something special if it runs into a wall or gets hit, like flip
    people the bird.
    4.  Vary the enemy speed and/or animation frame speed. 
    5.  etc.

    Attached is a file showing the working example that I put together as well as various other things. 

    I hope you enjoy this and it wasn't too long.  'Till next time....

    teabag.gif

    SpriteMovement.zip

    • Like 2
×
×
  • Create New...