It is currently Tue May 21, 2019 12:21 am

All times are UTC - 7 hours





Post new topic Reply to topic  [ 16 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: Mon May 13, 2019 4:08 pm 
Offline

Joined: Mon May 13, 2019 11:52 am
Posts: 4
I'm looking for the correct way to create an array of values in Asm6.

I have my frames of animation stored as seperate section e.g:

Code:
run_right_frame_3:
    ;vert tile attr horiz
  .db $80, $32, $00, $80
  .db $80, $33, $00, $88
  .db $88, $34, $00, $80
  .db $88, $35, $00, $88


So I want to store all my looping frames in an array. And loop through them that way. Is this the correct way to do this?

How would I set up an array like this in Asm6?

Thanks,
Steve


Top
 Profile  
 
PostPosted: Mon May 13, 2019 4:35 pm 
Offline
User avatar

Joined: Sun Sep 19, 2004 9:28 pm
Posts: 4071
Location: A world gone mad
An "array" is just sequence of linear data in memory. What you have there is certainly "an array", if you choose to iterate over it as such. Example:

Code:
  ldx #0
loop:
  lda run_right_frame_3,x
  ; You probably want to do something here, like sta somewhere
  inx
  cpx #16
  bne loop
  ; We're done iterating over all the array members (16 bytes)

When X=0, the lda will first load byte $80, then the next loop iteration byte $32, then $00, then $80, then $80, then $33, then $00, then $88, etc... rinse lather repeat until the end of the "array".

There are multiple ways of detecting "the end" -- sometimes people use a special control byte (ex. a value of $FF meaning "end of data", i.e. cmp #$ff / bne loop), but if the value in question is legitimate data then that won't work. The choice there is to calculate the length of the array at assemble-time and use that, e.g.:

Code:
  ldx #0
loop:
  lda run_right_frame_3,x
  ...
  inx
  cpx #run_right_frame_3_len   ; important: note the use of immediate addressing!
  bne loop

...

run_right_frame_3:
  .db $80, $32, $00, $80
  .db $80, $33, $00, $88
  .db $88, $34, $00, $80
  .db $88, $35, $00, $88
run_right_frame_3_len = $-run_right_frame_3

What the latter line does is subtract the current PC address, i.e. where in ROM the assembler thinks the PC is at the time, which is represented by the special $ label, and subtract from that the address of the run_right_frame_3 label. For example, if run_right_frame_3 is at $8260, then run_right_frame_3_len would be $10 (16) because $8270-$8260=$10. Thus, cpx #run_right_frame_3_len gets assembled into cpx #$10.

If the amount of data you have exceeds 256 bytes in length then that's solved in a multitude of other ways (commonly a combination of 2 loops, one nested within the other).

All of this is pretty standard, e.g. assembler does not matter (though many use * instead of $ for their current PC character. If you aren't sure, refer to your assembler manual for details. If your assembler manual doesn't document such, then stop using that assembler and use one with good documentation. Reference for asm6's $ symbol)

HTH, good luck.


Top
 Profile  
 
PostPosted: Mon May 13, 2019 4:47 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 11345
Location: Rio de Janeiro - Brazil
There's no "correct" way to create arrays in ASM... You are free to decide how to store your data in the way that's most convenient for you. Some people prefer to arrange the data in a way that's easily readable by humans (easier to type by hand), others will prefer a format that's faster for the CPU to access... This is all up to the programmer.

What you have there is fine, you just have to consider *HOW* you're going to access that data. If the data is sequential like that, you'll only be able to access 64 entries using an 8-bit index (since each entry is 4 bytes). The alternative is to arrange the data as 4 parallel arrays: one for X, another for tile, another for attributes, and another for Y. That way you can access 256 entries with an 8-bit index. If you need access to more than 256 elements, you will need to manipulate pointers on the fly.

In your case, it's probably best to keep the data as is, and build the animations as sequences of pointers to the sprite definitions (e.g. run_right_frame_3), and every time you advance to a new frame, you copy a pointer to RAM and use it to access the sprite entries using indirect indexed addressing. 64 sprites (which's what you can access with an 8-bit index) is way more than enough for a single animation frame, considering that that's all the PPU can display anyway.


Top
 Profile  
 
PostPosted: Tue May 14, 2019 4:44 pm 
Offline

Joined: Mon May 13, 2019 11:52 am
Posts: 4
Hi Guys

Thanks for the replies. I guess I should have said how do I group arrays together? I already understand how to read values from the array into the sprite ram - now I need to do it for multiple arrays of values.

So am I right in thinking I would just need some sort of pointer the just skips to the next 'array' in the memory?

Initially I thought it would be possible to have something like in C

Code:
animation_frames:
   .db run_right_frame_1, run_right_frame_2, run_right_frame_3


But I guess that either isn't possible or isn't the right way to go about it? It didn't work when I tried it anyway. Okay I guess I should try implementing the pointer method suggested by tokumaru.

Cheers,
Steve


Top
 Profile  
 
PostPosted: Tue May 14, 2019 6:32 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 11345
Location: Rio de Janeiro - Brazil
You got it almost right, the only problem is that you used .db (define bytes) instead of .dw (define words). In 6502 assembly, addresses are 16-bit, so you need to use words when representing them.

You might need more data to implement a fully functional animation system though, since games usually have several animations, made from sequences of frames, each with a different delay. Looping is another thing to consider, seeing as different animations might have different looping points, and others might not loop at all. It might be a good idea to add a control byte before the pointer to each frame, containing timing and looping information. For example, values smaller than 128 (bit 7 clear) could mean the number of hardware frames to wait before advancing to the next animation frame, while values 128 and up could signal the end of the animation, with the lower 7 bits optionally indicating an animation frame to loop back to.

Things can get a little more complicated if you need to have animations with variable speeds, such as when a character slowly accelerates or if you need animations to run at the same perceivable rate in both PAL and NTSC, because then you wouldn't define frame durations in terms of hardware frames, but in "ticks" of some sort that you could clock at different rates from the main engine.


Top
 Profile  
 
PostPosted: Wed May 15, 2019 12:50 pm 
Offline

Joined: Mon May 13, 2019 11:52 am
Posts: 4
Wow, I never thought 'simple' NES games could get so complicated! lol.

I'm just testing my routines at the moment which is why I only have a few frames.

So here is my current load frame code (currently it just loads the new frame when A is pressed):

Code:
LoadFrame1:
  LDX #$00              ; start at 0
LoadFrame1Loop:
  LDA run_right_frame_1, x    ; load data from address (sprites + x)
  STA $0200, x                ; store into RAM address ($0200 + x)
  INX                         ; X = X + 1
  CPX #$10                    ; Compare X to hex $10, decimal 16 to load the complete mario sprite
  BNE LoadFrame1Loop          ; Branch to LoadSpritesLoop if compare was Not Equal to zero
  LDA #$02
  STA a_pressed               ; we have finished with A now and don't need to test it again


I simply replaced run_right_frame_1 with player_animation with the first value as the first frame. Well it didn't work, I'm clearly missing something. It loads a garbled random few tiles but it's not the correct frame I thought it would load. I must be missing something that's probably very obvious? If it was C++ I would have gave the value as player_animation[0]. Still too new to asm. :)


Top
 Profile  
 
PostPosted: Wed May 15, 2019 2:03 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 11345
Location: Rio de Janeiro - Brazil
reaktor24 wrote:
Wow, I never thought 'simple' NES games could get so complicated! lol.

In simple projects you can hardcode a lot of stuff and skip the complications of dealing with dynamic data, but that doesn't scale well as projects grow.

Quote:
I simply replaced run_right_frame_1 with player_animation with the first value as the first frame. Well it didn't work, I'm clearly missing something. It loads a garbled random few tiles but it's not the correct frame I thought it would load. I must be missing something that's probably very obvious? If it was C++ I would have gave the value as player_animation[0]. Still too new to asm. :)

You're using indexed addressing to read the contents of the run_right_frame_1 array, and that addressing mode works by adding a variable index (held in either X or Y) to a constant base address, which can't be changed, so you won't be reading data from anywhere else than run_right_frame_1 while using that addressing mode. To be able to change the base address, you need to use indirect indexed addressing, an addressing mode where the base address isn't constant, but held in a pointer in ZP, so you're free to change that base address to point to the base address of other animation frames.

So, here's how the data is set up:
Code:
animation_frames:
   .dw run_right_frame_1, run_right_frame_2, run_right_frame_3

run_right_frame_1:
   .db $80, $32, $00, $80
   .db $80, $32, $00, $80
   ;(...)
   .db $ff

run_right_frame_2:
   .db $80, $32, $00, $80
   .db $80, $32, $00, $80
   ;(...)
   .db $ff

run_right_frame_3:
   .db $80, $32, $00, $80
   .db $80, $32, $00, $80
   ;(...)
   .db $ff

I put an end marker after each frame's sprites, so that you can easily tell when to stop copying sprites.

And here's the code to select one of the pointers:
Code:
   lda frame_index ;get the frame index
   asl ;multiply it by 2 since each address is 2 bytes
   tax ;use it as an index
   lda animation_frames+0, x ;copy the low byte
   sta frame_pointer+0 ;to a position in ZP
   lda animation_frames+1, x ;copy the high byte
   sta frame_pointer+1 ;to the next position

Just run this whenever frame_index changes (which in your test code should be when A is pressed, right?), in order to update the pointer to the current frame of animation.

Then you just need to update the copy loop to use this pointer and the end-of-sprite marker:
Code:
   ldy #$00 ;indirect indexed only works with Y
LoadFrame:
   lda (frame_pointer), y ;get Y coordinate
   cmp #$ff ;check for the end of the sprite
   beq Done
   sta $200, y ;copy the Y coordinate
   .rept 3 ;copy the remaining 3 bytes
   iny
   lda (frame_pointer), y
   sta $200, y
   .endr
   jmp LoadFrame
Done:

This should solve your simple case of just displaying different sprites when A is pressed, but an actual game will need a much more complex routine than that. First, you'll have to use separate indices for the source and destination (indirect indexed addressing only works with Y, so you can use X to index the destination), otherwise you won't be able to draw more than one character at a time. Second, you'll need to add the current position of the game object to the sprite coordinates in order to make the sprite movable (this may or may not affect the use of $ff as a end-of-sprite marker, but there will always be a value you can use). Third, you'll have to check for overflows and underflows in the resulting coordinates, in order to handle clipping when sprites are partially off-screen (unless your game mechanics do not allow sprites to be partially off-screen, ever). Fourth, you'll need to handle flipping, so your sprites can face left and right (and possibly up and down). Some people just use different metasprite definitions for left and right-facing sprites, but dynamic flipping isn't particularly hard to do.


Top
 Profile  
 
PostPosted: Wed May 15, 2019 3:16 pm 
Offline

Joined: Mon May 13, 2019 11:52 am
Posts: 4
Wow thanks for your help. Much appreciated!!

I can see now there is a lot more to NES development than I realised! lol.

Nevertheless I am not put off and enjoying it very much!

You're code worked a treat (albeit with a strange little glitch on the centre far left side of the screen when the frame changes - but I should be able to fix that).

My code already handles sprite positioning so he is moving around the screen smoothly and changes one frame fine using the pointer. 2/3 work fine but setting frame index to 2 causes some odd behaviour - a few extra tiles appearing - but the main sprite looks fine to me.

Okay I will work on improving the routine using your suggestions. You've been a great help! Many thanks Tokumaru!

Ps. Any hints where I should start with the timing between frames? Run some sort of counter in the draw code between frames?

Steve


Top
 Profile  
 
PostPosted: Wed May 15, 2019 4:09 pm 
Offline
User avatar

Joined: Sun Sep 19, 2004 9:28 pm
Posts: 4071
Location: A world gone mad
I'd imagine for delays between frames, in NMI, you could just increment a counter (maybe one per sprite -- depends on if you'd want all your sprites to have independent delays), and then in your main loop check the counter to see if it's been reached or not (if not, don't update/change the sprite. If so, update/change the sprite to use the next frame, and reset the counter).


Top
 Profile  
 
PostPosted: Wed May 15, 2019 6:31 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 11345
Location: Rio de Janeiro - Brazil
reaktor24 wrote:
I can see now there is a lot more to NES development than I realised! lol.

Yeah, but as long as you do things step by step and understand all the code you put into your project (i.e. don't blindly copy code or make random changes until it appears to work), you'll be fine.

Quote:
Nevertheless I am not put off and enjoying it very much!

I find it very rewarding to engineer these programs bit by bit and see them come to life!

Quote:
You're code worked a treat (albeit with a strange little glitch on the centre far left side of the screen when the frame changes - but I should be able to fix that).

I can't tell what's going on just from this description, but like I said before, be sure to track down the source of the bug and to understand WHY exactly it's happening.

Quote:
2/3 work fine but setting frame index to 2 causes some odd behaviour - a few extra tiles appearing - but the main sprite looks fine to me.

Again, hard to tell without seeing code or a ROM, but be sure to pinpoint the exact cause. Learn how to step through code in an emulator's debugger, if you're not doing this already. This is the best way to tell whether your code is doing what you want you want it to be doing.

Quote:
Ps. Any hints where I should start with the timing between frames? Run some sort of counter in the draw code between frames?

Yes, you need a counter, which will tell you how long to wait before advancing to the next animation frame. You don't use this in a time-wasting loop though, because if you did this you'd freeze the entire game. You're basically going to "clock" this counter once per frame, and when it expires, you can move on to the next frame.

Any versatile engine will let you specify a delay for each animation frame, so the animation_frames array seems like a good place to put that information:

Code:
animation_frames:
   .db $04
   .dw run_right_frame_1
   .db $03
   .dw run_right_frame_2
   .db $04
   .dw run_right_frame_3
   .db $80+0 ; code for "loop back to frame 0"

The bad part of doing it like this is that multiplying by 3 isn't as friendly as multiplying by 2 in assembly, but still not too bad. You can do this:

Code:
GetFramePointer:
   lda frame_index
   asl ;multiply by 2
   adc frame_index ;add one more to make x3
   tay
   lda animation_frames+0, y ;get the duration
   bpl SkipLoop
   and #%01111111 ;clear the "loop" bit
   sta frame_index ;set the frame as instructed
   jmp GetFramePointer ;start over
SkipLoop:
   sta frame_counter ;save the counter
   lda animation_frames+1, y ;get the low byte
   sta frame_pointer+0
   lda animation_frames+2, y ;get the high byte
   sta frame_pointer+1
   rts

One important thing to note is that when you add multiple animations for multiple characters, access to animation_frames will have to be made via indirect indexed addressing too, so you can have a pointer indicate which animation script is currently being used.

Another important thing is that each active object will need its own animation pointer, animation frame and animation counter, because each one has its own animation. We normally allocate chunks of RAM for all active objects dynamically, so they have a place to keep track of their state.

Anyway, to animate the sprite you can just decrement each object's frame_counter once per frame, and advance frame_index when it expires:

Code:
   dec frame_counter
   bne FrameReady
   inc frame_index
   jsr GetFramePointer
FrameReady:

This is the basic idea, but you should obviously arrange the code in the way that makes the most sense to you.


Top
 Profile  
 
PostPosted: Wed May 15, 2019 11:44 pm 
Offline
Formerly WheelInventor
User avatar

Joined: Thu Apr 14, 2016 2:55 am
Posts: 2018
Location: Gothenburg, Sweden
One thing to look out for a bit when doing arrays in asm6 particularily:

i got the "additional characters" error on this line:
Code:
.db $4b,$ff,$02,$00,$01,$09,$ff,$ff,$0f,$1b,$0a,$17,$14,$0e,$17,$10,$1b,$0a,$19,$11,$12,$0c,$1c


although there's positively nothing wrong with it.

i think it's a bug related to how it handles handles utf-8 text, maybe. Only vague indication i have is it appeared after i had used the delete key to remove a byte. I double checked for anything out of the ordinary, but no typos were present. The error vanished when i retyped the line, even though it was identical.

_________________
http://www.frankengraphics.com - personal NES blog


Top
 Profile  
 
PostPosted: Thu May 16, 2019 12:09 am 
Offline
User avatar

Joined: Sun Sep 19, 2004 9:28 pm
Posts: 4071
Location: A world gone mad
FrankenGraphics wrote:
One thing to look out for a bit when doing arrays in asm6 particularily:

i got the "additional characters" error on this line:
Code:
.db $4b,$ff,$02,$00,$01,$09,$ff,$ff,$0f,$1b,$0a,$17,$14,$0e,$17,$10,$1b,$0a,$19,$11,$12,$0c,$1c


although there's positively nothing wrong with it.

i think it's a bug related to how it handles handles utf-8 text, maybe. Only vague indication i have is it appeared after i had used the delete key to remove a byte. I double checked for anything out of the ordinary, but no typos were present. The error vanished when i retyped the line, even though it was identical.

There is no handling of UTF-8 in asm6f. Everything is treated effectively as what should be ASCII. More details:

Said .db line does not have any problem assembling for me, when the file is saved as ASCII ANSI, or UTF-8. However: it DOES fail (and rightfully so given what I just said) if encoded as UTF-8 with BOM -- just not with an "additional characters" error but rather "Illegal instruction". Likewise, UTF-16 fails with "nothing to do!" also rightfully so. In general, most text parsers -- even ones that support UTF-8 -- often do not support BOMs. If you're using one which does, turn off that feature to guarantee full compatibility.

TL;DR -- asm6/asm6f will work fine with UTF-8 files as long as the characters you're typing are within ASCII range (i.e. single bytes within range 0x20-0x7E). Values outside that range (0x00-0x1F and 0x7F-0xFF) will cause complications, regardless of character set or encoding.


Top
 Profile  
 
PostPosted: Thu May 16, 2019 1:29 am 
Offline
Formerly WheelInventor
User avatar

Joined: Thu Apr 14, 2016 2:55 am
Posts: 2018
Location: Gothenburg, Sweden
yeah it's the corrected line you copied, so it should definitely work. i didn't keep the faulty one, but i guarantee it was visually identical to the one posted, yet didn't assemble.

I used np++, "encode in utf-8". would this allow for invisible characters?

_________________
http://www.frankengraphics.com - personal NES blog


Top
 Profile  
 
PostPosted: Thu May 16, 2019 2:12 am 
Offline
User avatar

Joined: Sun Sep 19, 2004 9:28 pm
Posts: 4071
Location: A world gone mad
Notepad++ can be configured to save UTF-8 with or without BOM. BOM always shows up at the start of a file.

Unicode in general can allow all sorts of "invisible crap", such as full-width vs. half-width spaces, or alternate versions of alphanumeric characters. I don't think it's practical for me to try and cover all the cases here. Locale can also sometimes affect this.

If you can find the actual line that failed to assemble for you, you can look at it in a hex editor and find the bytes in question which are causing an issue (again: look for any bytes outside of the range of 0x20-0x7E). I can't do that for you because you didn't retain nor paste the faulty one. :(


Top
 Profile  
 
PostPosted: Thu May 16, 2019 7:56 am 
Offline
Formerly WheelInventor
User avatar

Joined: Thu Apr 14, 2016 2:55 am
Posts: 2018
Location: Gothenburg, Sweden
Knowing how to avoid the occurence from happening again is enough for me, so thanks for the explanation.
The incident happened a few days ago and i didn't care to document it as soon as i had it fixed..

More out of curiosity than anything, would it make sense for an assembler to filter out these character ranges and if so, warn that this has happened? Rather than throwing an error and aborting. If the data in question somehow is unusable from it, it seems to me it will be caught by appropriate error handling such as out of range, parameter missing, and so on. Like, if you as a questionable example happened to use ¡ (reverse exlamation mark) by mistake instead of i, the label would not exist either way.

_________________
http://www.frankengraphics.com - personal NES blog


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

All times are UTC - 7 hours


Who is online

Users browsing this forum: calima and 6 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