It is currently Tue May 30, 2017 2:11 am

All times are UTC - 7 hours





Post new topic Reply to topic  [ 51 posts ]  Go to page 1, 2, 3, 4  Next
Author Message
PostPosted: Sun Dec 27, 2015 5:50 am 
Offline

Joined: Mon Dec 14, 2015 2:58 am
Posts: 18
I'm taking first steps in 6502 assembly using http://patater.com/gbaguy/nesasm.htm and Nerdy Nights and there's a lot of things that confuse me. I need someone experienced who'd look at my code and tell me what I should optimize, what I should and shouldn't do. What I attempted to do is basically a moving sprite program, resembling movement style from Dragon Quest series.
First question: how to force sprite to move on an 8x8 grid? This is crucial to games like these, when you have to search on a certain tile to obtain an item or something. I can make a sprite move by 8 px, but it'll just jump by this number of pixels instead of moving smoothly.
Second: where should I store variables like main characters current position on map, level, hp, status etc.? I'm currently starting at location $29, saw this in Metroid's source.
Third: http://nintendoage.com/forum/messagevie ... eadid=7974 - when changing sprite's position SBC or ADC is used, depending on the direction. Before every SBC carry flag is set using SEC and before every ADC carry flag is cleared using CLC. Why?
Fourth: What does RTI exactly do? Should all of my code be placed before it or just the code I want to occur during vblank?


Attachments:
wnk.rar [3.51 KiB]
Downloaded 137 times
Top
 Profile  
 
PostPosted: Sun Dec 27, 2015 7:15 am 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 1579
Location: DIGDUG
SEC, SBC...CLC, ADC...here's what they do...

Code:
lda #4
   clc
   adc #4 ;result A = 8

   lda #4
   sec
   adc #4 ;result A = 9

   lda #9
   sec
   sbc #4 ;result A = 5

   lda #9
   clc
   sbc #4 ;result A = 4


They have this ability, so you can do math in numbers larger than 255. Lets say you want to add two 16bit numbers (2 bytes each). You CLC, then add the LOW BYTES. Then (without CLC) add the HIGH BYTES. Any overflow will be stored temporarily in the CARRY FLAG, adding 1 more to the HIGH BYTE.

Now you want to subtract 2 16bit (2 byte) numbers...You SEC, subtract the LOW BYTES. Then, (without SEC) subtract the HIGH BYTES. If there was overflow on the first part, it will clear the CARRY FLAG, thus subtracting one more from the HIGH BYTE (It works like a borrow).

-------------------------------------------------------
Now, to your code...

Your startup code is putting #FE in the $300 page (as if that's going to be where sprites are going to be...it would put them off screen). But, you seem to be using the $200 page for sprites in the code.

Cut the #FE stuff off that startup loop. Make a seperate BLANKSPRITES subroutine which puts #FE at the $200 page. Why separate subroutine? So you can call this anytime you want to clear the sprites off the screen.

Second, you don't need to define every address the variables go to...
Code:
YACADIR = $29

could just as easily be...
Code:
.rsset $0
YACADIR .RS 1


And, the assembler itself will assign a location to the variable. But, that's up to you. If you prefer assigning them exact RAM locations, go ahead.

Controller reads should be one routine that reads all the buttons and stores them at a RAM address, here's an example...

Code:
GetInput:
   lda button
   sta oldbutton
   
   ldy #$01
   sty $4016
   dey
   sty $4016
   sty button
   ldy #$08
GetInput2:
   lda $4016
   and #$03
   cmp #$01
   rol button
   dey
   bne GetInput2


As to the moving like Dragon Warrior...that's a bit more complicated. I think the simple answer is...
1.Get Input for all buttons (as above)
2.If up is presses, start an automated sequence that will move the character up 1 pixel, repeat for 8 frames. (or 16 frames, really)
3.Etc. Down starts an auto-down sequence, L starts an auto-L sequence, R starts an auto-R sequence
4.Do NOT get new input until the auto-move is completed. (count down every frame, skip input if MoveCount not = 0)
5.Have a collision map, which has a byte for every 8x8 block on screen. (or really every 16x16 block, which is more standard on the NES.)
6.Everytime your auto-sequence wants to move, check the solidity of the block you are moving to, skip the move if solid

-------
RTI means Return from Interrupt. You need it at the end of the NMI code (and at the end of the IRQ code). It functions similar to the RTS at the end of a JSR subroutine. It means 'go back to where we were before the interrupt'.


-------
Edit---actually, Dragon Warrior doesn't move the main character sprites at all, it shifts the screen scroll for movement. You would have to write new background tiles continually from a large array of backgrounds -- as you move.

_________________
nesdoug.com -- blog/tutorial on programming for the NES


Top
 Profile  
 
PostPosted: Sun Dec 27, 2015 7:40 am 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 1579
Location: DIGDUG
And, you need to work on a metasprite movement system, which will move all your character's sprites as one contiguous block. The best would be to store the X and Y coordinates in the RAM, and recalculate the position of every sprite, every frame.

_________________
nesdoug.com -- blog/tutorial on programming for the NES


Top
 Profile  
 
PostPosted: Sun Dec 27, 2015 8:41 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 9657
Location: Rio de Janeiro - Brazil
Friendly_Noob wrote:
I'm taking first steps in 6502 assembly using http://patater.com/gbaguy/nesasm.htm

GBAGuy's tutorials are notoriously bad... Some of the things he says are just plain wrong. My advice would be to stop reading them.

Quote:
and Nerdy Nights and there's a lot of things that confuse me.

Nerdy Nights is much better. There are some oddities here and there, but nothing game-breaking IIRC.

Quote:
I need someone experienced who'd look at my code and tell me what I should optimize, what I should and shouldn't do.

Don't worry about optimization right know. Just make sure your code works correctly.

Quote:
First question: how to force sprite to move on an 8x8 grid? This is crucial to games like these, when you have to search on a certain tile to obtain an item or something. I can make a sprite move by 8 px, but it'll just jump by this number of pixels instead of moving smoothly.

Games are all about states and the small incremental updates that correspond to each state. Say, when the left button is pressed on the controller, instead of immediately changing the character's position, you change its state from "idle" to "moving left". Every frame you check what the character's status is: if it's "idle", don't do anything, if it's "moving left", subtract 1 from its X coordinate, and so on. after moving, you have to check if the character is aligned to the grid, in which case you change its status back to "idle". The pseudo-code goes something like this:

Code:
;handle any movement associated with the current state
if status = "moving up"
   playerY = playerY - 1
elseif status = "moving down"
   playerY = playerY + 1
elseif status = "moving left"
   playerX = playerX - 1
elseif status = "moving right"
   playerX = playerX + 1
end if

;stop moving if aligned to the 8x8 grid
if playerX AND %00000111 = 0
   if playerY AND %00000111 = 0
      status = "idle"
   end if
end if

;start moving only when idle
if status = "idle"
   if "up" is pressed
      status = "moving up"
   elseif "down" is pressed
      status = "moving down"
   elseif "left" is pressed
      status = "moving left"
   elseif "right" is pressed
      status = "moving right"
   end if
end if


Quote:
Second: where should I store variables like main characters current position on map, level, hp, status etc.?

Wherever there's space for them. I like to store most variables in zero page, because accessing them there is slightly faster (although if you're using NESASM, you have to prefix the addresses or variable names with "<" so that the faster addressing is used, otherwise there's no speed improvement at all), and the rest of the memory (pages 3 and up, normally) for larger data structures, such as palettes, level maps, object states, and so on. I said pages 3 and up because page 1 always contains the stack (you can steal part of page 1 for other uses though) and page 2 is usually the OAM buffer.

Quote:
I'm currently starting at location $29, saw this in Metroid's source.

Sounds pretty arbitrary. There's no reason for you to skip $00-$28... you can use whatever memory you want for your variables.

Quote:
Before every SBC carry flag is set using SEC and before every ADC carry flag is cleared using CLC. Why?

The 6502 is an 8-bit CPU, meaning it can only do math 8-bits at a time. For it to be able to handle 16-bit (and larger) values, it needs the carry flag. The carry flag holds information about the result of the last mathematical operation you performed, which can be used to make decisions ("is this number bigger than that number" and things like that) or to propagate the operation to other bytes, allowing you to manipulate larger numbers.

Whenever you use ADC, the CPU will add whatever value you're adding PLUS the carry flag. It works exactly like when you learned to add in school:

Code:
  78
+ 15
----

First you add the units: 8 + 5 is 13, which is 2 digits, so you write the lower digit down and write the carry on top of the next space:

Code:
  1
  78
+ 15
----
   3

Now you add the tens, and add the carry as well: 7 + 1 + 1 = 9

Code:
  78
+ 15
----
  93

There you go. The 6502 always adds 2 values + the carry flag, allowing you to detect and propagate overflows to the next byte, much like you propagate them to the next digit adding on paper. The reason you need to clear the carry when starting an addition is because there isn't anything to propagate at the start, but the 6502 doesn't have an ADD (add without carry) instruction (some CPUs do), so you're forced to make sure the carry won't get in the way.

Subtractions work in a similar way, but the carry flag works more like an "imaginary value" that may or may not be borrowed during a subtraction. It's like you're saying to the CPU: "Here 6502, I'm leaving this flag set for you, just in case you need to borrow it". Then, once the subtraction is done, the value in the carry flag has the following meanings:

0: there was an underflow, and the CPU need to borrow 1;
1: the carry is intact, so there was no underflow and the result of the subtraction is positive;

And the 6502 interprets the carry flag like this on the next subtraction. If the carry is clear, it will know it has to subtract one extra unit, because of he borrow. To make sure there's no borrow at the beginning of a subtraction operation, you have to set the carry.

Quote:
Fourth: What does RTI exactly do? Should all of my code be placed before it or just the code I want to occur during vblank?

That depends on how your program is organized. Nerdy Nights goes for a program structure that places everything in the NMI handler. The vblank stuff comes first, and then the game logic, but it's all part of the NMI handler, so the RTI is at the very end of the game loop. The other approaches are:

Everything outside the NMI: The NMI doesn nothing more than set/increment a flag, and the main thread waits for this flag to change in order to detect the start of vblank. In this case, the only thing before the RTI is an instruction like INC vbcounter, everything else is in the main thread.

Game logic outside the NMI, video and audio updates inside the NMI: With this you actually have 2 threads running in parallel, with the main thread processing everything about the game and buffering video updates, while the NMI thread dumps these updates to the PPU.


Top
 Profile  
 
PostPosted: Mon Dec 28, 2015 3:08 am 
Offline

Joined: Mon Dec 14, 2015 2:58 am
Posts: 18
That's a lot of new info to digest, thanks. I'll try to fix the metasprite movement first. I'll just probably keep the character at the center of the screen and move background. This kind of movement is the reason why DQ characters are constantly moving their legs, right?
Quote:
GBAGuy's tutorials are notoriously bad... Some of the things he says are just plain wrong. My advice would be to stop reading them.

What about this http://nintendoage.com/forum/messagevie ... adid=33287 ? There is a lot of stuff to read about the 6502 itself, but not really that much about applying it to the NES.


Top
 Profile  
 
PostPosted: Mon Dec 28, 2015 4:11 pm 
Offline
User avatar

Joined: Sun Jun 05, 2005 2:04 pm
Posts: 2119
Location: Minneapolis, Minnesota, United States
Quote:
This kind of movement is the reason why DQ characters are constantly moving their legs, right?


Actually, that shouldn't be a reason for it. You can keep the character in the center of the screen, and update the player to be still while idle, and animated while moving. My guess as to why they are always moving is to keep animation as simple as possible, and reduce complexity with the code.


Top
 Profile  
 
PostPosted: Mon Dec 28, 2015 5:18 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 9657
Location: Rio de Janeiro - Brazil
Yeah, if they're always animated, that's one less state to keep track of. That shouldn't have anything to do with the movement.

Keeping the player always in the center doesn't really make the sprite code any simpler though... The sprite coordinates might be always the same (the player sprite, that is, because other sprites will still move around as the map scrolls), but the player object still has to move relative to the map.


Top
 Profile  
 
PostPosted: Tue Dec 29, 2015 12:47 pm 
Offline
User avatar

Joined: Sun Jun 05, 2005 2:04 pm
Posts: 2119
Location: Minneapolis, Minnesota, United States
Quote:
Keeping the player always in the center doesn't really make the sprite code any simpler though... The sprite coordinates might be always the same (the player sprite, that is, because other sprites will still move around as the map scrolls), but the player object still has to move relative to the map.


Yes, and it's a good practice to not think of the player sprite as being much different just because it's always in the center of the screen. However all of the other objects are drawn, that's how the player sprite should be drawn.

As dougeff mentioned earlier, a metasprite system is crucial. I don't think the concept of this is always made obvious. A lot of tutorials will teach you to move sprites around by manually changing the X/Y coordinates of a specific hardware sprite (for instance, INC $203 to move sprite #0 to the right). Believe me, this is not the way to go. When you start having objects moving partially off screen, more than 8 sprites on a scanline etc., things will get ugly really fast.

The way most games do it is to have 16-bit "map" coordinates for every object, including the player and the camera. If you take the map coordinates of an object and subtract the camera coordinates, you'll be left with where that object should be drawn on screen (at least with the top-left corner of the object). Then you would have a routine that would place the individual hardware sprites into RAM using that pair of coordinates as a starting point.

If you developed a system like this, the player's coordinates would change, but the game would be programmed so that the camera's coordinates change at the same time. The character's position relative to the camera would remain the same, and as a consequence, the player would always be redrawn in the middle of the screen.


Top
 Profile  
 
PostPosted: Tue Dec 29, 2015 1:10 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 9657
Location: Rio de Janeiro - Brazil
Celius is right. The player being always centered should be a side effect of the camera moving exactly as much as the player every frame, meaning that the relative distance between them is always the same, causing the player to always show at the same place on screen.

You will need a camera system because of the scrolling and all other objects that are not centered anyway, so it's actually less work to have the player use the same system as all other objects than to treat it differently (and write separate code for it) just because you know it will be centered all the time.


Top
 Profile  
 
PostPosted: Tue Dec 29, 2015 1:39 pm 
Offline
User avatar

Joined: Sun Jun 05, 2005 2:04 pm
Posts: 2119
Location: Minneapolis, Minnesota, United States
tokumaru wrote:
The player being always centered should be a side effect of the camera moving exactly as much as the player every frame, meaning that the relative distance between them is always the same, causing the player to always show at the same place on screen.


The great part of this is, if you develop a system like this and decide you don't want the player to always stay in the center, all you have to change is the camera movement (literally just change the code that updates the camera coordinates). Using a different system that just updates the sprites directly, you would have to do a considerable amount of re-working to get the player to be able to move around on the screen.


Top
 Profile  
 
PostPosted: Tue Dec 29, 2015 3:34 pm 
Offline

Joined: Mon Dec 14, 2015 2:58 am
Posts: 18
Quote:
Controller reads should be one routine that reads all the buttons and stores them at a RAM address

One ram address for all buttons (each byte representing pressed/not pressed state of the button) or each button - one ram address?
Also, since a lot of the code is copypasted, I'd like to ask about those two things:
Code:
NMI:
  LDA #$00
  STA $2003       ; set the low byte (00) of the RAM address
  LDA #$02
  STA $4014       ; set the high byte (02) of the RAM address, start the transfer


LatchController:
  LDA #$01
  STA $4016
  LDA #$00
  STA $4016       ; tell both the controllers to latch buttons


Last edited by Friendly_Noob on Tue Dec 29, 2015 3:57 pm, edited 1 time in total.

Top
 Profile  
 
PostPosted: Tue Dec 29, 2015 3:52 pm 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 18368
Location: NE Indiana, USA (NTSC)
I'm pretty sure most games read eight bits from each controller into one 8-bit byte.


Top
 Profile  
 
PostPosted: Tue Dec 29, 2015 5:04 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 9657
Location: Rio de Janeiro - Brazil
Friendly_Noob wrote:
One ram address for all buttons (each byte representing pressed/not pressed state of the button) or each button - one ram address?

An NES controller has exactly 8 buttons, so it's very convenient to store the states of all of them in a single byte, 1 bit per button. Then you can use masks to check the buttons as needed, like this:

Code:
BUTTON_A = %00000001
BUTTON_B = %00000010
BUTTON_SELECT = %00000100
BUTTON_START = %00001000
BUTTON_UP = %00010000
BUTTON_DOWN = %00100000
BUTTON_LEFT = %01000000
BUTTON_RIGHT = %10000000

   lda Buttons
   and #BUTTON_A
   beq Skip
   ;A is pressed
Skip:

Quote:
Also, since a lot of the code is copypasted, I'd like to ask about those two things:

What exactly are you asking? Do you want a general explanation?

Quote:
Code:
  LDA #$00
  STA $2003       ; set the low byte (00) of the RAM address
  LDA #$02
  STA $4014       ; set the high byte (02) of the RAM address, start the transfer

This code is correct, but the comments are wrong. You can only DMA entire pages of memory, aligned to 256-byte boundaries, so you can't specify the low byte of the address. What the write to $2003 actually does is set the destination address in OAM (which is only 256 bytes). The NES periodically clears that address though, so it's somewhat redundant to clear it yourself. Nothing wrong with keeping things safe, though.

Quote:
Code:
LatchController:
  LDA #$01
  STA $4016
  LDA #$00
  STA $4016       ; tell both the controllers to latch buttons

This is just how the controller works. There's a chip inside each controller that receives the state of all the buttons. When you write 1 to $4016, this chip allows the button states to come in, and when you write 0, it "closes the gates" and keeps the last states that came in. Then, reading from $4016/7 will return the state that was "frozen" inside the chip, one bit/button at a time.


Top
 Profile  
 
PostPosted: Wed Dec 30, 2015 12:17 pm 
Offline

Joined: Mon Dec 14, 2015 2:58 am
Posts: 18
I feel like an idiot, I can't figure anything out myself. Dougeff, could you comment this code for me? From what I understand, GetInput2 is a loop checking $4016 8 times, because there are 8 buttons. Then depending on if the button is pressed or not 00000000 or 00000001 is ANDed with 00000011 ($03) so it's 00000000 or 00000001 again? Also, why should I store oldbutton?
Code:
GetInput:
   lda button
   sta oldbutton
   
   ldy #$01
   sty $4016
   dey
   sty $4016
   sty button
   ldy #$08
GetInput2:
   lda $4016
   and #$03
   cmp #$01
   rol button
   dey
   bne GetInput2


Top
 Profile  
 
PostPosted: Wed Dec 30, 2015 1:19 pm 
Offline
Site Admin
User avatar

Joined: Mon Sep 20, 2004 6:04 am
Posts: 3422
Location: Indianapolis
That controller reading code is coping with a bunch of things in addition to the controller, which is why it seems more complicated than you might first expect. $4016 also has "open bus" bits, Famicom microphone, Famicom expansion port controller, and Zapper bits. NES/Famicom controller data is D0, and the Famicom expansion controller is on bit D1. It's good to support Famicom fully, so that's why the AND #$03 is there. The CMP #$01 will set the carry flag if D0 and/or D1 is set. Then ROL button shifts the carry flag into the button variable.

You want to save the previous controller state so you can check if the button was newly pressed or held down.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 51 posts ]  Go to page 1, 2, 3, 4  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