Page 4 of 109

Posted: Fri Mar 25, 2011 5:28 pm
by yesyesyall
unregistered wrote:^ yesyesyall, sorry, I'm uncomfortable to meet with you. :oops:
cool! totally understand!

Posted: Mon Mar 28, 2011 3:43 pm
by unregistered
unregistered wrote:
tokumaru wrote:It may seem strange if you are doing it in a hardcoded way (manually setting up all the sprites of a particular object), but usually programmers implement a sprite system. For each possible frame of a character there is a list of the necessary sprites to draw it. It's just a series of .db or .dw statements that represent things like "this frame needs 4 sprites; the first one is at coordinates (4, 5) and uses tile $78; the second is at coordinates (12, 8) and uses tile $79 (...)". The coordinates are usually relative to the position of the object/character being drawn.
How do you make the coordinates relative to the position of the object/character being drawn?

Code: Select all

aY .byte 10 
aX .byte 20 
.db aY, $80, $00, aX, aY, $81, $00, aX +1
would this work?

Code: Select all

aY .equ 10
aX .equ 20
.db aY,$80,$00, aX, aY, $81, $00, aX +1
this would work but I don't understand how the coordinates would stay relative. Aren't aY and aX constants here?
unregistered wrote:
tokumaru wrote:This means that the sprite system can draw any frame of any character if you point it to the correct list.
Really happy to have found this, thank you. :)

Posted: Mon Mar 28, 2011 3:46 pm
by qbradq
The positions you specify in the meta-sprite are relative to the object's origin. You make it relative to the object's position by adding the object's position to those values. If you game scrolls you will also need to subtract the scroll amounts.

Posted: Mon Mar 28, 2011 5:24 pm
by tokumaru
There is no automatic way to make the coordinates relative. This is not something the NES or the assembler will do for you, you'll have to code the logic for this yourself.

Every frame, instead of blindly copying the meta-sprite definitions directly to OAM, you'll do some processing with that data. Since you have the position of the object somewhere in RAM, you'll have to add the relative sprite coordinates to the object coordinates in order to find the final sprite coordinates, which you'll then write to OAM. Be careful with sprites going off screen, as you probably don't want pats of your characters wrapping to the opposite side of the screen.

Posted: Thu Mar 31, 2011 10:25 am
by unregistered
Thank you qbradq and tokumaru, that makes sense; I'm stumbling on one part... how do you, or what is the best way you can, write to OAM? Right now i have one meta-sprite to display and move around the screen. Am i susposed to use lda and sta to write all the sprites to $0200?
Each frame during v blank it sends all of $0200 - $02ff to OAM through the OAM_DMA register. That's all I can think of... lda and sta to page 02.... is there a better method?

Posted: Thu Mar 31, 2011 11:47 am
by qbradq
Nope. That's how most games (homebrew and commercial alike) do it.

Also, you do not usually allocate slots within the OAM page to specific objects. Rather you loop over all of your objects each frame and lay down thier OAM data in the OAM page. That way when you need to start cycling your sprites (and you will, trust me), you can just change your starting location in the OAM page each frame.

But we'll cover sprite cycling when you get there :D

Posted: Thu Mar 31, 2011 4:13 pm
by unregistered
unregistered wrote:
unregistered wrote:
tokumaru wrote:This means that the sprite system can draw any frame of any character if you point it to the correct list.
How do you point it to the correct list? So far I have this

Code: Select all

;80 81       
;90 91       
;a0 a1       
;b0 b1   
        .db aY, $80, $00, aX, aY, $81, $00, aX+8,
		    aY+8, $90, $00, aX, aY+8, $91, $00, aX+8,
		    aY+16, $a0, $00, aX, aY+16, $a1, $00, aX+8,
			aY+24, $b0, $00, aX, aY+24, $b1, $00, aX+8
There's something i'm missing. :(

Posted: Thu Mar 31, 2011 5:33 pm
by tepples
It'd go a little something like this (untested):

Code: Select all

hero_frame1:
  .db aY,    $80, $00, aX, aY,    $81, $00, aX+8
  .db aY+8,  $90, $00, aX, aY+8,  $91, $00, aX+8
  .db aY+16, $a0, $00, aX, aY+16, $a1, $00, aX+8
  .db aY+24, $b0, $00, aX, aY+24, $b1, $00, aX+8
hero_frame2:
  .db aY,    $82, $00, aX, aY,    $83, $00, aX+8
  .db aY+8,  $92, $00, aX, aY+8,  $93, $00, aX+8
  .db aY+16, $a2, $00, aX, aY+16, $a3, $00, aX+8
  .db aY+24, $b2, $00, aX, aY+24, $b3, $00, aX+8
hero_frame3:
  .db aY,    $84, $00, aX, aY,    $85, $00, aX+8
  .db aY+8,  $94, $00, aX, aY+8,  $95, $00, aX+8
  .db aY+16, $a4, $00, aX, aY+16, $a5, $00, aX+8
  .db aY+24, $b4, $00, aX, aY+24, $b5, $00, aX+8
hero_frame4:
  .db aY,    $86, $00, aX, aY,    $87, $00, aX+8
  .db aY+8,  $96, $00, aX, aY+8,  $97, $00, aX+8
  .db aY+16, $a6, $00, aX, aY+16, $a7, $00, aX+8
  .db aY+24, $b6, $00, aX, aY+24, $b7, $00, aX+8

; Operator < produces the low byte of an address
sprite_layouts_lo:
  .db <hero_frame1, <hero_frame2, <hero_frame3, <hero_frame4

; Operator > produces the high byte of an address
sprite_layouts_hi:
  .db >hero_frame1, >hero_frame2, >hero_frame3, >hero_frame4

; Number of hardware sprites in each layout
sprite_layouts_count:
  .db 8, 8, 8, 8

draw_sprite:
  ; t0-t2 are temporary zero page locations holding
  ; the address of a particular sprite layout and the
  ; number of sprites left to draw
  lda sprite_layouts_lo,x
  sta t0
  lda sprite_layouts_hi,x
  sta t0+1
  lda sprite_layouts_count,x
  sta t2
  ldy #0
  ; oamIndex is a variable tracking how far you've written
  ; into shadow OAM (customarily at $0200-$02FF)
  ldx oamIndex
@loop:
  ; If you have the address of an array in a zero page pointer,
  ; you use the (d),y addressing mode and increase Y to go
  ; to the next byte.
  lda (t0),y
  iny
  ; etc.
  dec t2
  bne @loop
  stx oamIndex
  rts

some_other_method:
  ldx #2
  jsr draw_sprite

EDIT: Fixed a typo.

Posted: Thu Mar 31, 2011 6:11 pm
by qbradq
Yea, what Tepples said :D

I have added a wiki document about using pointer tables. I wrote it prior to seeing Tepples's reply so it uses different code, but there is more explanation.

Dang Tepples, you code quick :D

Posted: Thu Mar 31, 2011 7:34 pm
by tokumaru
tepple's example is right. First you have to define the sprite data for all possible animation frames. Then you make a list of pointers to each of the frames, in order to give each of them an index. Finally, you make a function/subroutine that will receive a meta-sprite index and an object index and will render the requested frame for the requested object.

You can use registers X and Y to pass the indexes as parameters to the sprite drawing function. This function should add the meta-sprite coordinates to the object's coordinates (this is why the function needs to receive an object index) in order to find the final sprite coordinates, and, if those coordinates are within screen limits, write them to the OAM buffer (usually $0200-$02FF).

Posted: Thu Mar 31, 2011 9:41 pm
by unregistered
tepples wrote:It'd go a little something like this (untested):

Code: Select all

hero_frame1:
  .db aY,    $80, $00, aX, aY,    $81, $00, aX+8
  .db aY+8,  $90, $00, aX, aY+8,  $91, $00, aX+8
  .db aY+16, $a0, $00, aX, aY+16, $a1, $00, aX+8
  .db aY+24, $b0, $00, aX, aY+24, $b1, $00, aX+8
hero_frame2:
  .db aY,    $82, $00, aX, aY,    $83, $00, aX+8
  .db aY+8,  $92, $00, aX, aY+8,  $93, $00, aX+8
  .db aY+16, $a2, $00, aX, aY+16, $a3, $00, aX+8
  .db aY+24, $b2, $00, aX, aY+24, $b3, $00, aX+8
hero_frame3:
  .db aY,    $84, $00, aX, aY,    $85, $00, aX+8
  .db aY+8,  $94, $00, aX, aY+8,  $95, $00, aX+8
  .db aY+16, $a4, $00, aX, aY+16, $a5, $00, aX+8
  .db aY+24, $b4, $00, aX, aY+24, $b5, $00, aX+8
hero_frame4:
  .db aY,    $86, $00, aX, aY,    $87, $00, aX+8
  .db aY+8,  $96, $00, aX, aY+8,  $97, $00, aX+8
  .db aY+16, $a6, $00, aX, aY+16, $a7, $00, aX+8
  .db aY+24, $b6, $00, aX, aY+24, $b7, $00, aX+8

; Operator < produces the low byte of an address
sprite_layouts_lo:
  .db <hero_frame1, <hero_frame2, <hero_frame3, <hero_frame4

; Operator > produces the high byte of an address
sprite_layouts_hi:
  .db >hero_frame1, >hero_frame2, >hero_frame3, >hero_frame4

; Number of hardware sprites in each layout
sprite_layouts_count:
  .db 8, 8, 8, 8

draw_sprite:
  ; t0-t2 are temporary zero page locations holding
  ; the address of a particular sprite layout and the
  ; number of sprites left to draw
  lda sprite_layouts_lo,x
  sta t0
  lda sprite_layouts_hi,x
  sta t0+1
  lda sprite_layouts_count,x
  sta t2
  ldy #0
  ; oamIndex is a variable tracking how far you've written
  ; into shadow OAM (customarily at $0200-$02FF)
  ldx oamIndex
@loop:
  ; If you have the address of an array in a zero page pointer,
  ; you use the (d),y addressing mode and increase Y to go
  ; to the next byte.
  lda (t0),y
  iny
  ; etc.
  dec t2
  bne loop
  stx oamIndex
  rts

some_other_method:
  ldx #2
  jsr draw_sprite
:mrgreen: Thank you tepples! :D And thank you qbradq for your work... haven't really thought about it yet, sorry, tired. And thank you tokumaru! This is the second time, i think, you have explained step by step the first guy's help and that helps me so much too! :D Tomorrow is going to be so much fun! :D Good night.

Posted: Fri Apr 01, 2011 9:49 am
by unregistered
tepples, this code rocks the house!! Thank you so much! :)

Posted: Mon Apr 04, 2011 9:20 am
by unregistered
I get the error "daprg.asm(250): Branch out of range."

frome here:

Code: Select all

		draw_sprite:
		  ; t0-t2 are temporary zero page locations holding
		  ; the address of a particular sprite layout and the
		  ; number of sprites left to draw
		  lda sprite_layouts_lo, x
		  sta t0
		  lda sprite_layouts_hi, x
		  sta t0+1
		  lda sprite_layouts_count, x
		  sta t2
		  ldy #0
		  ; oamIndex is a variable tracking how far you've written
		  ; into shadow OAM (customarily at $0200-$02FF)
		  ldx oamIndex
		@loop:
		  ; If you have the address of an array in a zero page pointer,
		  ; you use the (d),y addressing mode and increase Y to go
		  ; to the next byte.
		  lda (t0), y
		  iny
		  ;ect.
		  dec t2
		  bne loop ;########(LINE NUMBER 250)
		  stx oamIndex
		  rts
		  
		  
		  
		  
        ; Set initial value of dx
        lda #$01
        sta dx
        rts

; Load palette into $3F00
load_palette:
So why is it out of range? The only help i could find was
in this thread
koitsu wrote:Branch instructions range from +127 to -128. It's just a signed 8-bit number.
I don't understand though. :(

Posted: Mon Apr 04, 2011 9:36 am
by tepples
The problem is my fault. I partially rewrote the code to reflect an apparent coding style of not using the .proc statement of ca65. So I changed loop: to @loop: but not bne loop to bne @loop. I have edited the sample code.

Posted: Mon Apr 04, 2011 11:53 am
by unregistered
Ok, I didn't know if the local link needed an @ in

Code: Select all

bne @loop
because with the @ asm6 builds the rom with out error; but, now, when running it, Nintendulator stops and says "Bad opcode, CPU locked" and VirtuaNES stops and says "Executed an undefined order." I thought this was brought on by adding the @ to loop up there... the asm6 README.TXT doesn't have an example of that code. So, thank you for helping me learn that it's proper to use an @ in that code. :) It's ok tepples :)
I'm still lost. It confuses me that asm6 would assemble a "bad opcode"? :?