It is currently Tue Sep 18, 2018 7:50 pm

All times are UTC - 7 hours





Post new topic Reply to topic  [ 21 posts ]  Go to page 1, 2  Next
Author Message
 Post subject: 16 bit array iteration
PostPosted: Tue Feb 27, 2018 9:08 pm 
Offline
User avatar

Joined: Sat Sep 27, 2014 10:10 pm
Posts: 30
Location: Houston, TX
I'm working on a simple sprite animation system and I'm not sure the best way to accomplish it. I am storing my meta sprite frame data like this:

Code:
player_walking_1:
   .byte $00,$32,%00000000,$00
   .byte $00,$33,%00000000,$08
   .byte $08,$34,%00000000,$00
   .byte $08,$35,%00000000,$08

player_walking_2:
   .byte $00,$36,%00000000,$00
   .byte $00,$37,%00000000,$08
   .byte $08,$38,%00000000,$00
   .byte $08,$39,%00000000,$08

 etc...

player_walk_animation:
  .byte $04 ; number of frames
  .word player_walking_1, player_walking_2, player_walking_3, player_walking_4


And I'm using a macro that gets called on a timer set for every half second:

Code:
.macro play_animation animation, counter, sprite_ptr
    ; load sprite frame counter
   ldx counter
    ; increase x to put offset at the begginning of frame data
    inx
    ; set current sprite to current animation frame
    lda animation, x
    sta sprite_ptr + 0
    inx
    lda animation, x
    sta sprite_ptr + 1

    ; get number of frames and store it in temp1
    lda animation
    sta temp1

    ; check if frame counter is equal to number of frames
    ldx counter
    cpx temp1
    beq reset_counter
    ; advance frame counter if counter < number of frames
    inx
    inx
    stx counter
    jmp callback_done
reset_counter:
    lda #$00
    sta counter
callback_done:
.endmacro


My problem is that my array of metasprite frames is an array of 16 bit addresses. Because of that, indexing into it means that I have to increment my counter variable twice to get to the next slot. Because of that my animation data needs an increased frame count in order to work correctly. As I've defined it above, my walk animation won't work, I need to actually store the number of frames as $06 to make it work. How do people usually solve this type of problem?


Top
 Profile  
 
PostPosted: Tue Feb 27, 2018 9:30 pm 
Offline
User avatar

Joined: Wed Apr 02, 2008 2:09 pm
Posts: 1243
asl a essentially multiples by two.

Code:
lda counter;The current animation frame
clc
adc #1;Add one to it to advance the frame
cmp animation;compare to the total number of animation frames
bcc skipreset;If it's less than that, we don't need to set it to zero
lda #0;Otherwise we do
skipreset:
sta counter;Store either the new frame (old frame + 1) or zero if we looped
;the current frame number is in A. After all, we just stored it
asl a;now the current frame number *2 is in A
;Incidentally, the number of bytes after player_walk_animation-1 we need to skip
;Minus one because the frame number is the first byte
tax;Transfer that to X
inx;Because we also need to skip the frame count.
lda animation,x
sta sprite_ptr + 0
inx
lda animation,x
sta sprite_ptr + 1

jsr drawMetasprite;Using the address in the sprite_ptr RAM.

The way to think about this sort of thing is "What information needs to be kept across game frames?" The current frame of animation for the character needs to be stored. So that's counter. How far you index doesn't need to be stored or kept track of at all. The function to draw the metasprites don't even care, it just wants sprite_ptr + 0 and sprite_ptr + 1.

Just transform your long term RAM into short term RAM when you need to. Edit: I guess the primary difference between this approach and yours is this doesn't try to store where we are in the animation data. That's only needed short term. So this stores which frame we're on (needed long term) and we get where we are in the animation data from that every frame on an as needed basis.

_________________
https://kasumi.itch.io/indivisible


Last edited by Kasumi on Tue Feb 27, 2018 9:46 pm, edited 1 time in total.

Top
 Profile  
 
PostPosted: Tue Feb 27, 2018 9:43 pm 
Offline
User avatar

Joined: Sat Sep 27, 2014 10:10 pm
Posts: 30
Location: Houston, TX
I think I understand. Maybe this isn't important, but why increment the frame before loading the pointer? Won't that skip the first frame?


Top
 Profile  
 
PostPosted: Tue Feb 27, 2018 9:48 pm 
Offline
User avatar

Joined: Wed Apr 02, 2008 2:09 pm
Posts: 1243
Quote:
Maybe this isn't important, but why increment the frame before loading the pointer? Won't that skip the first frame?

It would assuming you started the animation at zero. (A way to avoid this would be to set counter to 255 rather than 0 to start an animation.) I thought yours did the same, but you were inx-ing to skip the frame count, I guess.

Edit: As for why, it avoids multiple loads of counter.

_________________
https://kasumi.itch.io/indivisible


Last edited by Kasumi on Tue Feb 27, 2018 9:58 pm, edited 1 time in total.

Top
 Profile  
 
PostPosted: Tue Feb 27, 2018 9:57 pm 
Offline
User avatar

Joined: Sat Sep 27, 2014 10:10 pm
Posts: 30
Location: Houston, TX
Kasumi wrote:
Quote:
Maybe this isn't important, but why increment the frame before loading the pointer? Won't that skip the first frame?

It would assuming you started the animation at zero. (A way to avoid this would be to set counter to 255 to start an animation.) I thought yours did the same, but you were inx-ing to skip the frame count, I guess.

Edit: As for why, it avoids multiple loads of counter.


I guess that's a fair assumption considering I had my frame count listed as $04 as opposed to $03. I actually had it at $06 before I pasted it in here because that is how I was able to make it work with my current code.


Top
 Profile  
 
PostPosted: Tue Feb 27, 2018 10:27 pm 
Offline
User avatar

Joined: Sat Sep 27, 2014 10:10 pm
Posts: 30
Location: Houston, TX
Ok I think I've got it worked out now. I did a couple of things differently. First of all I ditched the idea of keeping the number of frames at the beginning of the frame data. That wasn't a great idea to begin with and just made the code weird. Second, I used your approach and I now I'm understanding what you meant with the asl a comment, which was the actual answer to my question of how to iterate over a 16 bit array. Essentially, multiplying the iterator by 2 allows you keep it local and removes the need that I had for having the counter variable play double duty keeping track of the current frame as well as where I was in the data.

Code:
.macro play_animation animation, counter, sprite_ptr
    ; load sprite frame counter
    lda counter
    ; advance frame (initially $ff and rolls over to 0)
    clc
    adc #1
    ; if (current_frame == num frames) { reset() }
    cmp player_walk_num_frames
    bcc skip_reset
    lda #0
skip_reset:
    ; store frame + 1
    sta counter
    ; multiply a by 2 in order to get correct byte offset
    asl a
    ; transfer a to x for Absolute,X addressing mode
    tax
    ; set current sprite to current animation frame
    lda animation, x
    sta sprite_ptr + 0
    inx
    lda animation, x
    sta sprite_ptr + 1
.endmacro


I really appreciate you taking the time yet again to fix my noob code. Programming in assembly is really putting me at the outer limits of my coginitive abilities.


Top
 Profile  
 
PostPosted: Tue Feb 27, 2018 10:51 pm 
Offline
User avatar

Joined: Wed Apr 02, 2008 2:09 pm
Posts: 1243
No problem, and it seems you're really getting it!

One other suggestion: You can maybe add the frame count as a parameter to your macro which would make it more fully generic, I think.

_________________
https://kasumi.itch.io/indivisible


Top
 Profile  
 
PostPosted: Wed Feb 28, 2018 2:56 pm 
Offline
User avatar

Joined: Fri Nov 24, 2017 2:40 pm
Posts: 79
As a random aside. This:
Code:
    lda animation, x
    sta sprite_ptr + 0
    inx
    lda animation, x
    sta sprite_ptr + 1


Can become this:
Code:
    lda animation + 0, x
    sta sprite_ptr + 0
    lda animation + 1, x
    sta sprite_ptr + 1


Top
 Profile  
 
PostPosted: Wed Feb 28, 2018 3:23 pm 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 6801
Location: Canada
There's also striped arrays, where you put the two bytes of each entry in a separate 8-bit array. This skips the need to use ASL, and increases your maximum array size to 256, if you need it.
Code:
array0: .byte <ptr1, <ptr2, <ptr3
array1: .byte >ptr1, >ptr2, >ptr3

lda array0, X
sta temp+0
lda array1, X
sta temp+1

If you're typing this by hand it's a bit tedious to do everything twice with different prefixes, but if you're using an animation tool to generate your sprite information it's more or less trivial to output striped .byte arrays instead of .word arrays. You can also fudge it with macros.

Skipping the ASL also means you can do everything in X to begin with, and INX is faster and smaller than CLC, ADC #1, and there's a CPX instead of CMP, etc.


Top
 Profile  
 
PostPosted: Wed Feb 28, 2018 3:30 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10806
Location: Rio de Janeiro - Brazil
In ca65 you can easily create striped arrays using .define, .lobytes, and .hibytes:

Code:
  .define Things Thing0, Thing1, Thing2, Thing3

ThingsLow:
  .lobytes Things

ThingsHigh:
  .hibytes Things


Top
 Profile  
 
PostPosted: Wed Feb 28, 2018 8:26 pm 
Offline
User avatar

Joined: Sat Sep 27, 2014 10:10 pm
Posts: 30
Location: Houston, TX
Kasumi wrote:
No problem, and it seems you're really getting it!

One other suggestion: You can maybe add the frame count as a parameter to your macro which would make it more fully generic, I think.


It's coming along ok I think. Work has been busy so I only have relatively small windows of time to mess around with it.

I originally had the frame count being passed in as a parameter but I though it may be easier to use a more data driven approach. This way my player update can be consistently calling the play_animation macro without having to know any of the specifics of the animation. I may have a walk animation that's two frames, an attack animation thats 3 frames, etc... if I have to pass that in as a parameter I'll need to have special cases for each state transition.


Top
 Profile  
 
PostPosted: Wed Feb 28, 2018 8:28 pm 
Offline
User avatar

Joined: Sat Sep 27, 2014 10:10 pm
Posts: 30
Location: Houston, TX
slembcke wrote:
As a random aside. This:
Code:
    lda animation, x
    sta sprite_ptr + 0
    inx
    lda animation, x
    sta sprite_ptr + 1


Can become this:
Code:
    lda animation + 0, x
    sta sprite_ptr + 0
    lda animation + 1, x
    sta sprite_ptr + 1

Oh cool! I'm gonna use this.


Top
 Profile  
 
PostPosted: Wed Feb 28, 2018 8:37 pm 
Offline
User avatar

Joined: Sat Sep 27, 2014 10:10 pm
Posts: 30
Location: Houston, TX
rainwarrior wrote:
There's also striped arrays, where you put the two bytes of each entry in a separate 8-bit array. This skips the need to use ASL, and increases your maximum array size to 256, if you need it.
Code:
array0: .byte <ptr1, <ptr2, <ptr3
array1: .byte >ptr1, >ptr2, >ptr3

lda array0, X
sta temp+0
lda array1, X
sta temp+1

If you're typing this by hand it's a bit tedious to do everything twice with different prefixes, but if you're using an animation tool to generate your sprite information it's more or less trivial to output striped .byte arrays instead of .word arrays. You can also fudge it with macros.

Skipping the ASL also means you can do everything in X to begin with, and INX is faster and smaller than CLC, ADC #1, and there's a CPX instead of CMP, etc.


Interesting. I hadn't thought of doing it that way. That certainly seems like it would be annoying to do by hand but maybe not prohibitively so. I'm developing on a macbook pro so tools are a bit hard to come by as everything seems to be windows only. Even the tools that I can get to run in wine are just really buggy and more frustrating than doing it by hand. I keep thinking maybe I should invest some time in developing my own tools but honestly in the time it would take to do that, I'm pretty sure I would lose interest and move onto other things.


With that said I may make a couple of different macros and try out this approach as well now that I understand the term "striped arrays". I had read that in the nesdev wiki several times but was never really sure what it meant.


Top
 Profile  
 
PostPosted: Wed Feb 28, 2018 8:40 pm 
Offline
User avatar

Joined: Sat Sep 27, 2014 10:10 pm
Posts: 30
Location: Houston, TX
tokumaru wrote:
In ca65 you can easily create striped arrays using .define, .lobytes, and .hibytes:

Code:
  .define Things Thing0, Thing1, Thing2, Thing3

ThingsLow:
  .lobytes Things

ThingsHigh:
  .hibytes Things


Will this have the same effect? Will it expand out into an array of the low bytes and high bytes? If so that is awesome. I'm really liking ca65 so far. The documentation just leaves me scratching my head quite a bit. I saw the other day that it had structs and got really excited but I couldn't for the life of me get the assembler to stop complaining at me when I tried to use it.

I've noticed some people using some "dot" type notation in their code, is this just by convention or is there some syntactic sugar for that type of thing in ca65?


Top
 Profile  
 
PostPosted: Wed Feb 28, 2018 8:59 pm 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 6801
Location: Canada
The . begins all control commands in ca65. See here: http://cc65.github.io/doc/ca65.html

If you .define a comma separated list, .lobytes is like .byte with < before every element in that list, and .hibytes does the same but with >. I don't find it's very usable for longer lists, but YMMV, and there's a bunch of other alternatives.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 21 posts ]  Go to page 1, 2  Next

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 3 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group