Skip navigation
Welcome, Guest! Please Login or Join

Loading...

Game Engine Building #1: Sprite Movement, Animation, and Meta Sprites NintendoAge Programming Resources

May 17, 2010 at 3:14:56 PM
Mario's Right Nut (350)
avatar
(Cunt Punch) < Bowser >
Posts: 6574 - Joined: 11/21/2008
Texas
Profile

BACKGROUND

Sorry, this one got kind of long. 

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. 


FORMATING 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. 

0

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....


-------------------------

This is my shiny thing, and if you try to take it off me, I may have to eat you.

Check out my dev blog.



Edited: 02/24/2012 at 09:31 AM by Mario's Right Nut

May 18, 2010 at 12:11:19 PM
bigjt_2 (17)
avatar
(Yeah, I'm a fat bastard.) < Meka Chicken >
Posts: 698 - Joined: 02/17/2010
Indiana
Profile
Damn, MRN, your churning these tuts out left and right! Thanks for the writeup. I can't wait to play around with McBoobins! :-)

-------------------------
Every thread on this forum is very offensive.  Please lock every last goddammed one of them!

"Tell him you'll break his A button pusher."
                                                  --Otto Hanson
"Unlike the Sega Genesis, which will give you cancer."
                                                  --Randy the Astonishing

Feb 23, 2012 at 7:17:21 AM
DoNotWant (1)

(Ham Sammich) < Eggplant Wizard >
Posts: 438 - Joined: 12/08/2011
Sweden
Profile
All these sprites moving around, and the random stuff is somewhat overwhelming, so I'm trying to create a moveable meta-sprite with CHR-RAM to dumb it down a little for me, but I don't quite get it. Is it something I'm missing, is something wrong, or is it both? http://pastebin.com/43nWS0SY...
I have been going back and forth between my code and MRN's, and in the process, have confused myself to the point where I don't know wheter to scrap the whole thing and start over, or go back to the basics. I have a somewhat functioning pong game done already tho, and that code is no problem to understand for me, so I don't know what I would go back to. -.-

Also:
LDA #LOW(crewman_graphics)
STA enemy_pointer
LDA #HIGH(crewman_graphics+1)
STA enemy_pointer+1



Why is there a +1 in LDA #HIGH(crewman_graphics+1) <--- that line?


Thanks!

-------------------------
 

Feb 23, 2012 at 12:25:38 PM
Mario's Right Nut (350)
avatar
(Cunt Punch) < Bowser >
Posts: 6574 - Joined: 11/21/2008
Texas
Profile
Originally posted by: DoNotWant

All these sprites moving around, and the random stuff is somewhat overwhelming, so I'm trying to create a moveable meta-sprite with CHR-RAM to dumb it down a little for me, but I don't quite get it. Is it something I'm missing, is something wrong, or is it both? http://pastebin.com/43nWS0SY
I have been going back and forth between my code and MRN's, and in the process, have confused myself to the point where I don't know wheter to scrap the whole thing and start over, or go back to the basics. I have a somewhat functioning pong game done already tho, and that code is no problem to understand for me, so I don't know what I would go back to. -.- Also:
LDA #LOW(crewman_graphics)
STA enemy_pointer
LDA #HIGH(crewman_graphics+1)
STA enemy_pointer+1

Why is there a +1 in LDA #HIGH(crewman_graphics+1) <--- that line?
Thanks!
Derp.  See bunny's response below.  I never use this notation, so I don't know what I'm talking about. 

Good catch.


-------------------------

This is my shiny thing, and if you try to take it off me, I may have to eat you.

Check out my dev blog.



Edited: 02/24/2012 at 09:30 AM by Mario's Right Nut

Feb 23, 2012 at 12:29:11 PM
removed04092017 (0)
This user has been banned -- click for more information.
< Bowser >
Posts: 7316 - Joined: 12/04/2010
Other
Profile
First off, recognize the interrupt. Second, I am not sure if you are trying to update CHR-RAM data and animate it like that, because if so you're doing something bad because you'll never be able to transfer enough data in VBlank for a sprite with more than a few tiles. Or, if you're not hopefully, you're using the wrong term. CHR-RAM is the 8KB of RAM on some carts for the tiles. Nametable RAM is the RAM for the screens, palette RAM is graphics, OAM is Sprite RAM in the PPU.

And what those #LOW does is takes a data word (2 bytes) and put it into RAM to point to it later via indirect indexed ((Pointer),Y) instructions. It does enemy_pointer+1 to put the high byte of the value to the right place (1 next to the piece of RAM enemy_pointer) for the (Pointer),Y addressing mode. It's 2 bytes of RAM reserved pointer by 1 tag basically. Below is the way it will probably look in his file.

enemy_pointer: .rs 2 ;Save 2 pieces of ram.

And I'd just save the source, start over again, and then try to figure it out. Do that until you get it working and understand it so you can go back on your old code and see what you did wrong and why and hopefully pay closer attention to it. Good luck man.

Feb 23, 2012 at 1:18:06 PM
DoNotWant (1)

(Ham Sammich) < Eggplant Wizard >
Posts: 438 - Joined: 12/08/2011
Sweden
Profile
@Mario's Right Nut
But wouldn't you want the low and high byte of the same adress?

@3GenGames
Yeah, I remember you mentioning recognizing the NMI with BIT $2002, but I haven't seen it anywhere else
and I don't know why I would do that, so an explanation would be nice.
And I just update the sprite RAM at $0200+ and transfer it with DMA so I don't see the problem with that.
With CHR-RAM I mean that I store the graphics in a PRG bank, not a CHR bank, which I haven't done before.
Seems more flexible than CHR-ROM, but damn harder to get to work the first time. X.x
The pointer stuff I already knew.

Thanks!

-------------------------
 

Feb 23, 2012 at 2:13:36 PM
Mario's Right Nut (350)
avatar
(Cunt Punch) < Bowser >
Posts: 6574 - Joined: 11/21/2008
Texas
Profile
Originally posted by: DoNotWant

@Mario's Right Nut
But wouldn't you want the low and high byte of the same adress?

 

Yes.  That's what it is doing. 

And I looked at your code and I'm too lazy (not smart enough) to find the issue.    However, you can load stuff directly into X and Y.  i.e.
LDX balls
rather than
LDA balls
TAX

and

LDY balls,x
rather than
LDA balls,x
TAY




-------------------------

This is my shiny thing, and if you try to take it off me, I may have to eat you.

Check out my dev blog.


Feb 23, 2012 at 3:00:10 PM
DoNotWant (1)

(Ham Sammich) < Eggplant Wizard >
Posts: 438 - Joined: 12/08/2011
Sweden
Profile
Originally posted by: Mario's Right Nut

Originally posted by: DoNotWant

@Mario's Right Nut
But wouldn't you want the low and high byte of the same adress?

 

Yes.  That's what it is doing. 

And I looked at your code and I'm too lazy (not smart enough) to find the issue.    However, you can load stuff directly into X and Y.  i.e.
LDX balls
rather than
LDA balls
TAX

and

LDY balls,x
rather than
LDA balls,x
TAY


 
Hey, sorry, but I'm also not smart enough, but wouldn't

LDA #LOW(crewman_graphics)
STA enemy_pointer
LDA #HIGH(crewman_graphics)
STA enemy_pointer+1

load the low and high byte? In bunnyboys tutorials when loading a background, you just do:
LDA #LOW(background)
STA pointerLo
LDA #HIGH(background)
STA pointerHi       
so is really LDA #HIGH(crewman_graphics+1) the plus one needed there? And if that's the case, what would be the difference between this and bunnyboy's stuff?

Thanks!

-------------------------
 

Feb 23, 2012 at 3:44:27 PM
bunnyboy (81)
avatar
(Funktastic B) < Bowser >
Posts: 7420 - Joined: 02/28/2007
California
Profile
You will have to be more specific about what is going wrong with your app.  In the emulator I see the 4KB chr getting loaded, and some sprites moving down the screen.  Which part is wrong?

Originally posted by: DoNotWant

so is really LDA #HIGH(crewman_graphics+1) the plus one needed there?
The +1 is wrong, but will almost never make a difference. Lets say:

crewman_graphics = $80FF

Correct way is:
#LOW(crewman_graphics) = low byte of $80FF = $FF which is correct.
#HIGH(crewman_graphics) = high byte of $80FF = $80 which is correct.

However when the +1 is there:
#HIGH(crewman_graphics+1) = high byte of $80FF+1 = high byte of $8100 = $81 which is wrong.

So it only matters when the +1 will change the high address byte, which is probably rare but would be a pain to debug!

Feb 23, 2012 at 3:56:37 PM
DoNotWant (1)

(Ham Sammich) < Eggplant Wizard >
Posts: 438 - Joined: 12/08/2011
Sweden
Profile
Originally posted by: bunnyboy

You will have to be more specific about what is going wrong with your app.  In the emulator I see the 4KB chr getting loaded, and some sprites moving down the screen.  Which part is wrong?

Originally posted by: DoNotWant

so is really LDA #HIGH(crewman_graphics+1) the plus one needed there?
The +1 is wrong, but will almost never make a difference. Lets say:

crewman_graphics = $80FF

Correct way is:
#LOW(crewman_graphics) = low byte of $80FF = $FF which is correct.
#HIGH(crewman_graphics) = high byte of $80FF = $80 which is correct.

However when the +1 is there:
#HIGH(crewman_graphics+1) = high byte of $80FF+1 = high byte of $8100 = $81 which is wrong.

So it only matters when the +1 will change the high address byte, which is probably rare but would be a pain to debug!
Wow, you actually see something? For me it's all black. When I open the PPU viewer in FCEUXD SP
I can see the sprites in the left window, but they seem to take colors from the background palette for some reason.
So I guess that is what's wrong. What I'm trying to do is get my character to move down when I press down, and only animate when I move. The reason for the down only movement is just because a retarded monkey could do better
sprites than I.

Thanks for clarifying that about pointers.

Sorry for not being very specific, but the language barrier makes it all a little harder. I will try to go into more detail
in future posts.

-------------------------
 

Feb 23, 2012 at 4:17:37 PM
bunnyboy (81)
avatar
(Funktastic B) < Bowser >
Posts: 7420 - Joined: 02/28/2007
California
Profile
The viewer might be assuming that pattern table 0 is background, so its showing those graphics using the background palette. You didn't set any background palette so they just appear as all black. You should still see the sprites on screen tho?

Feb 24, 2012 at 12:15:06 AM
DoNotWant (1)

(Ham Sammich) < Eggplant Wizard >
Posts: 438 - Joined: 12/08/2011
Sweden
Profile
Originally posted by: bunnyboy

The viewer might be assuming that pattern table 0 is background, so its showing those graphics using the background palette. You didn't set any background palette so they just appear as all black. You should still see the sprites on screen tho?
No, it's so strange, the whole BG palette is gray, and nothing shows up on screen.
http://i40.tinypic.com/3006syw.jp...



-------------------------
 

Feb 24, 2012 at 3:50:26 AM
bunnyboy (81)
avatar
(Funktastic B) < Bowser >
Posts: 7420 - Joined: 02/28/2007
California
Profile
Your sprite tile numbers are all $FE, which is all black in that screenshot. I am using the graphics from SMB so I see something

Looks like the real problem is in UpdateCHRRAM. The INX on line 363 shifts all the sprite writes on lines 368/371/374/377 so you are writing to the sprite attribute byte instead of the sprite tile number byte. The writes to attributes (lines 380/383/386/389) also get shifted so they are writing to the sprite X coord. Removing that INX and changing the next line to spritePointer+1 should make everything work as you want, or at least get closer!

Feb 24, 2012 at 6:11:26 AM
DoNotWant (1)

(Ham Sammich) < Eggplant Wizard >
Posts: 438 - Joined: 12/08/2011
Sweden
Profile
Thanks! I can see the sprite now, even tho it's messed up.
Going to check around a little more in that function, and the other updating routines and see if I can solve this now.

-------------------------
 

Mar 4, 2013 at 2:39:56 PM
drearyworlds (0)
avatar
(Jim C) < Cherub >
Posts: 15 - Joined: 12/05/2012
Alabama
Profile
I have a quick question about .rsset and .rs
Is there any difference between:

enemy_pointer .rs $08
and
enemy_pointer .rs 08

Is there a particular reason for using hex here, since the number is under 10? I'm currently using this guide to try and add animation to my game. I only have a single player at the moment, so I'm having to convert it to 1 object instead of 4 and take out the randomization since I have controller input working.

Mar 4, 2013 at 2:43:46 PM
removed04092017 (0)
This user has been banned -- click for more information.
< Bowser >
Posts: 7316 - Joined: 12/04/2010
Other
Profile
Didn't look at it, but I assume it's consistency. I use Hex for normal math all over my programs, except in the files that the user are supposed to edit.

Mar 4, 2013 at 3:00:46 PM
drearyworlds (0)
avatar
(Jim C) < Cherub >
Posts: 15 - Joined: 12/05/2012
Alabama
Profile
The one above it uses .rs 02 and others use .rs 4, so I thought there was a difference. Are all of these allocating in the same way (x number of bytes/addresses)?

Mar 4, 2013 at 3:04:17 PM
removed04092017 (0)
This user has been banned -- click for more information.
< Bowser >
Posts: 7316 - Joined: 12/04/2010
Other
Profile
Hex, decimal, octal, is all the same thing when processed. $80=%10000000=128. They all produce the same number, so yep it's all the same. And if they mix them, they may have had a reason when making it maybe. I believe most people declare RAM in decimal, I do. But still, do it in whatever base your heart desires in any place, basically.


Edited: 03/04/2013 at 03:05 PM by removed04092017

Apr 4, 2014 at 11:33:51 PM
Mega Mario Man (50)
avatar
< Kraid Killer >
Posts: 2144 - Joined: 02/13/2014
United States
Profile
Originally posted by: DoNotWant

All these sprites moving around, and the random stuff is somewhat overwhelming, so I'm trying to create a moveable meta-sprite with CHR-RAM to dumb it down a little for me, but I don't quite get it. Is it something I'm missing, is something wrong, or is it both? http://pastebin.com/43nWS0SY
I have been going back and forth between my code and MRN's, and in the process, have confused myself to the point where I don't know wheter to scrap the whole thing and start over, or go back to the basics. I have a somewhat functioning pong game done already tho, and that code is no problem to understand for me, so I don't know what I would go back to.
 

I think I am at the same point. This is by far the hardest concept to grasp after going through Nerdy Nights. What I could really use is someone standing over my shoulder and telling me "this does that and that does this". I just can't connect the dots.


-------------------------
Current Project
NONE

Homebrew Games For Sale
Tailgate Party

Homebrews Threads - Orab Games - On Facebook
 
Tailgate Party, Power Pad Demo, Happy Hour


Edited: 04/04/2014 at 11:34 PM by Mega Mario Man

Apr 5, 2014 at 1:14:33 AM
MODERATOR
KHAN Games (88)
avatar
(Kevin Hanley) < Master Higgins >
Posts: 7660 - Joined: 06/21/2007
Florida
Profile
Originally posted by: Mega Mario Man

Originally posted by: DoNotWant

All these sprites moving around, and the random stuff is somewhat overwhelming, so I'm trying to create a moveable meta-sprite with CHR-RAM to dumb it down a little for me, but I don't quite get it. Is it something I'm missing, is something wrong, or is it both? http://pastebin.com/43nWS0SY
I have been going back and forth between my code and MRN's, and in the process, have confused myself to the point where I don't know wheter to scrap the whole thing and start over, or go back to the basics. I have a somewhat functioning pong game done already tho, and that code is no problem to understand for me, so I don't know what I would go back to.
 

I think I am at the same point. This is by far the hardest concept to grasp after going through Nerdy Nights. What I could really use is someone standing over my shoulder and telling me "this does that and that does this". I just can't connect the dots.
 

Don't feel bad.  I still don't use meta sprites.


Jul 24, 2014 at 5:30:40 PM
Mega Mario Man (50)
avatar
< Kraid Killer >
Posts: 2144 - Joined: 02/13/2014
United States
Profile
So, I studied on this pretty hard the other day and understand the code pretty decent now. If interested, I have editted the original code to work with 2 wide x 4 tall sprites as well with a sprite that I drew. Good stuff, and I think I will try to use meta-sprites in my next game.

-------------------------
Current Project
NONE

Homebrew Games For Sale
Tailgate Party

Homebrews Threads - Orab Games - On Facebook
 
Tailgate Party, Power Pad Demo, Happy Hour

Sep 20, 2015 at 9:59:06 AM
Cockroachcharlie (0)
avatar
< Cherub >
Posts: 19 - Joined: 09/05/2015
Pennsylvania
Profile
Originally posted by: Mega Mario Man

So, I studied on this pretty hard the other day and understand the code pretty decent now. If interested, I have editted the original code to work with 2 wide x 4 tall sprites as well with a sprite that I drew. Good stuff, and I think I will try to use meta-sprites in my next game.
I would be very much interested myself in knowing how you did that.  I have been staring at the code and, while I have (i think) a fairly good understanding of what I am seeing, this is one spot where I am just missing.  Can't seem to figure out, where, exactly, he defines the "boundaries" for his meta-sprites.

I have managed to tweak a number of things, I have added more characters to the screen, changed their graphics, and removed some, but I can't get a 2x3 sized sprite (my goal before I move on).

 

Sep 21, 2015 at 9:40:01 AM
Mario's Right Nut (350)
avatar
(Cunt Punch) < Bowser >
Posts: 6574 - Joined: 11/21/2008
Texas
Profile
I think that Mega Mario Man is going to post his altered code for you, but it's pretty simple to re-define boundaries. Rather than my rambling, please review his code and see if that makes sense. We'll go from there!

-------------------------

This is my shiny thing, and if you try to take it off me, I may have to eat you.

Check out my dev blog.


Sep 21, 2015 at 12:26:34 PM
Cockroachcharlie (0)
avatar
< Cherub >
Posts: 19 - Joined: 09/05/2015
Pennsylvania
Profile
Lol. Between you and bunnyboy, it's fun comparing your different ramblings. You both make better sense on different topics (which is why I advocate soaking in all knowledge banks possible).

I'm sure it is ridiculously easyand i just keep missing it.

On another note, probably another easy question. Why do the offsets for each metasprite increase by ten when the metasprites themselves are four apiece. I assume this has to do with the total memory of each.

Sep 21, 2015 at 12:28:41 PM
user (6)

< El Ripper >
Posts: 1425 - Joined: 05/30/2014
Profile
Originally posted by: Cockroachcharlie

Lol. Between you and bunnyboy, it's fun comparing your different ramblings. You both make better sense on different topics (which is why I advocate soaking in all knowledge banks possible).

I'm sure it is ridiculously easyand i just keep missing it.

On another note, probably another easy question. Why do the offsets for each metasprite increase by ten when the metasprites themselves are four apiece. I assume this has to do with the total memory of each.
16px wide == 10 in HEX

Hope this helps.