Learning Action Game Basics

Are you new to 6502, NES, or even programming in general? Post any of your questions here. Remember - the only dumb question is the question that remains unasked.

Moderator: Moderators

Pokun
Posts: 2675
Joined: Tue May 28, 2013 5:49 am
Location: Hokkaido, Japan

Learning Action Game Basics

Post by Pokun »

Learning Action Game Basics, Acceleration-based Movement, Variable Height Jumping and Collision

Edit: The following code is apparently fine. I figured out the problem elsewhere and it fixed it. My other question is still relevant though.

So I decided to learn how to make an action game.
In order to make physics smooth and flexible like I want them, I understand that I need to make: A fixed-point subpixel system so I can move slower than 1 pixel/frame, acceleration-based movement for both running and jumping, and variable height jumping.

I already made subpixel coordinates using one byte for the fractional part and one for the integer part of the coordinate and they work fine.

Now I'm trying to make acceleration-based movement. I started making the controller handling for rightward movement. I learned to do comparisons with signed numbers from this article.

So to move rightwards, I'm using two bytes for velocity that are added to by a two-byte acceleration value every frame that the RIGHT d-pad button is pressed. Before updating the velocity I first compare (using 16-bit signed comparison from above link) the new velocity value with the max speed value and if it's more than the max speed, I set the velocity to the max speed value, else I proceed to update velocity with the new value.

Velocity is then added to the 16-bit X-coordinate which updates the sprite position.

My code works fine as long as my max speed isn't set to $00.7F or more. As soon as my sprite comes up to this speed he will start slowing down to zero again, then accelerate again and so on as long as I hold the button. I suspect something about the comparison routine makes it think that the lower velocity byte is signed. Shouldn't the sign only be in the most significant byte so that the range becomes $80.00 to $7F.FF?

Code: Select all

;Constants:
MOB_ACCEL_L = 4  ;walking/running acceleration value fractional part
MOB_ACCEL_H = 0  ;walking/running acceleration value integer part
MOB_MSPD_L = 128 ;maximum walking/running speed fractional part
MOB_MSPD_H = 0   ;maximum walking/running speed integer part

;Variables:
temp+0
temp+1      ;temporary storage
p1_x+0      ;horizontal coordinate fractional part
p1_x+1      ;horizontal coordinate integer part
p1_vx+0     ;horizontal velocity fractional part
p1_vx+1     ;horizontal velocity integer part

;Code:
@right1:                  ;controller I input handler for RIGHT button
  lda con_state+0
  and #CON_RIGHT
  beq @not_right1

  lda p1_vx+1
  bmi @not_right1         ;if velocity negative, not right

  lda p1_vx+0
  clc
  adc #MOB_ACCEL_L        ;add acceleration value to velocity, low byte
  sta temp+0
  lda p1_vx+1
  adc #MOB_ACCEL_H        ;add acceleration value to velocity, high byte
  sta temp+1              ;store new velocity temporary for comparison
  
  lda temp+0
  cmp #MOB_MSPD_L
  lda temp+1
  sbc #MOB_MSPD_H
  bvc @n_xor_v_vr
  eor #$80
@n_xor_v_vr:              ;16-bit signed comparison with max speed
  bmi @not_terminal_vr    ;if velocity < max speed, allow acceleration
  lda #MOB_MSPD_L
  ldx #MOB_MSPD_H
  jmp @terminal_vr        ;else, velocity = max speed
@not_terminal_vr:
  lda temp+0
  ldx temp+1
@terminal_vr:
  sta p1_vx+0
  stx p1_vx+1             ;set velocity  
  jmp @a1                 ;exit

@not_right1:              ;if !RIGHT, decelerate

;......

p1_move_x:                ;horizontal movement based on velocity
  lda p1_x+0
  clc
  adc p1_vx+0
  sta p1_x+0              ;add velocity to position to move, low byte

  lda p1_x+1
  adc p1_vx+1
  sta p1_x+1              ;add high byte plus carry
Questions:
1) What's wrong with my code?
Edit: Of course soon after posting this, I found out the problem. It appears the code is fine, the problem was very simple and unrelated to this code. I guess the code can be used by others trying to do the same thing. My other question is still relevant though.

2) What is the Overflow Flag normally used for? From what I understand it indicates overflow/underflow between $7F and $80 to indicate that the value didn't fit in the signed number, much like Carry indicates that a number doesn't fit in a byte. But unlike Carry it isn't used in additions and subtractions. The only use I know of it is for signed comparisons, as a simulation of a branch always instruction and used in a special feature of the BIT instruction.

Any help is greatly appreciated!
Last edited by Pokun on Fri Aug 11, 2017 10:55 am, edited 1 time in total.
lidnariq
Posts: 11429
Joined: Sun Apr 13, 2008 11:12 am

Re: Learning Action Game Basics

Post by lidnariq »

Pokun wrote:

Code: Select all

  lda p1_vx+1
  bmi @not_right1         ;if velocity negative, not right
How is the user supposed to brake if they can't add acceleration against the current velocity vector?

Code: Select all

  lda temp+0
  cmp #MOB_MSPD_L
  lda temp+1
  sbc #MOB_MSPD_H
  bvc @n_xor_v_vr
  eor #$80
@n_xor_v_vr:              ;16-bit signed comparison with max speed
  bmi @not_terminal_vr    ;if velocity < max speed, allow acceleration
Because I'm terrible at math in asm, I tried checking against the output from cc65...

I think that's supposed to be bpl.
2) What is the Overflow Flag normally used for? [...] The only use I know of it is for signed comparisons, as a simulation of a branch always instruction and used in a special feature of the BIT instruction.
It's for any manual fixup of signed math. And I just found this article.

In practice, it's so rarely used that the bare 6502 has an external SET OVERFLOW pin, and I remember seeing a minimalist 6502 devkit that used it as one of the two dedicated inputs.
User avatar
rainwarrior
Posts: 8731
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Learning Action Game Basics

Post by rainwarrior »

I thought the BMI was correct. (reference)

As far as the code, it looks okay to me, but as always, it's very helpful to use a debugger and step through it to make sure it's going wrong where you think it is. Problems are often hard to read just on the page.

The overflow flag question... well you listed all the common uses, I think.
User avatar
gauauu
Posts: 779
Joined: Sat Jan 09, 2016 9:21 pm
Location: Central Illinois, USA
Contact:

Re: Learning Action Game Basics

Post by gauauu »

Pokun wrote:Learning Action Game Basics, Acceleration-based Movement, Variable Height Jumping and Collision
So this doesn't address your original question, but I find this article about side-scrollers to be incredibly useful when learning and planning how your game movement and collisions will work.
Pokun
Posts: 2675
Joined: Tue May 28, 2013 5:49 am
Location: Hokkaido, Japan

Re: Learning Action Game Basics

Post by Pokun »

I edited the post a bit too late. I think maybe there is no problem with the code. It appears an old stray routine was meddling with it. I added a jump over the stray routine and it fixed it. Thanks a lot everyone anyway!
lidnariq wrote:
Pokun wrote:

Code: Select all

  lda p1_vx+1
  bmi @not_right1         ;if velocity negative, not right
How is the user supposed to brake if they can't add acceleration against the current velocity vector?
You are right. I think I added that line after looking at other people's code to see if it would solve any problem, but it didn't. I left it in anyway because I wasn't sure if it was needed or not. I guess I'll need to remove it so the user can break faster by pressing the opposite direction.
2) What is the Overflow Flag normally used for? [...] The only use I know of it is for signed comparisons, as a simulation of a branch always instruction and used in a special feature of the BIT instruction.
It's for any manual fixup of signed math. And I just found this article.

In practice, it's so rarely used that the bare 6502 has an external SET OVERFLOW pin, and I remember seeing a minimalist 6502 devkit that used it as one of the two dedicated inputs.
Ah I see, that clears things up. I read that article too, but I thought maybe I misunderstood something and that maybe it was related to my problem messing with signed numbers. This experience has cleared up many question marks about 6502.
As far as the code, it looks okay to me, but as always, it's very helpful to use a debugger and step through it to make sure it's going wrong where you think it is. Problems are often hard to read just on the page.
Yeah thanks. I tried to use the debugger but I don't really understand how to use it properly, adding breakpoints and whatnot, so it didn't help me much. I should probably research how to use debuggers more.
So this doesn't address your original question, but I find this article about side-scrollers to be incredibly useful when learning and planning how your game movement and collisions will work.
Thanks for the link. Looks like it may be able to answer lots of questions I will have from now on making an action game.

Now I'm going to add in moving left, then we have jumping and collisions. I might post again if I run into any more problems.
User avatar
dougeff
Posts: 3078
Joined: Fri May 08, 2015 7:17 pm

Re: Learning Action Game Basics

Post by dougeff »

debugger but I don't really understand how to use it
If you can get your assembler to output a labels file, with all the addresses of the labels, you can set a breakpoint for execution from that address.

Or, you can insert a 'inc $ff' (assuming address ff is unused) just before the code you are looking at. Set a breakpoint for writes to $ff.

For movement code, you need correct input. Either set (with FCEUX) an auto-hold on a button press (I can't remember the default keys to do this)... or open the hex editor, locate your ram address for controller reads (probably also found on that label file)...press the direction on the controller, pause emulation, click on the ram address, and 'freeze' its value. Now set a breakpoint, and step through the code.
nesdoug.com -- blog/tutorial on programming for the NES
Pokun
Posts: 2675
Joined: Tue May 28, 2013 5:49 am
Location: Hokkaido, Japan

Re: Learning Action Game Basics

Post by Pokun »

Ah yeah Asm6 outputs a listing file with all addresses, I got a break point to work. I guess I could also use the BRK instruction and break on IRQ, I'm just not sure how to return to the game after a BRK though as the return address seems not to be preserved.
Can't find anything about using inputs in the debugger. I know Mesen recently added that feature though.

Yeah I've been using the hex editor in FCEUX a lot. It's very useful for RAM viewing and writing, I wish more emulators had such a thing. One thing FCEUX is missing though is an OAM viewer.


I added left movement by copying the right movement and changing to subtracting acceleration value instead of adding, and limiting velocity to the negated max speed.
The player sprite moves faster to the right than to the left however. It's strange because I can confirm that the velocity values in RAM goes up to the same max speed in both directions, just that the left max speed is negated.

I also started implementing gravity and jumping (basically copied left and right movement with some modifications). I haven't limited jump height or jump button presses so jumping works more like a jet-pack at the moment. Googling for variable height jumping it appears that jump height is often limited by a jump time counter.
Sour
Posts: 890
Joined: Sun Feb 07, 2016 6:16 pm

Re: Learning Action Game Basics

Post by Sour »

Pokun wrote:I guess I could also use the BRK instruction and break on IRQ, I'm just not sure how to return to the game after a BRK though as the return address seems not to be preserved.
Mesen has a "Break on BRK" option you can enable. Once the debugger breaks on a BRK instruction, you should be able to right-click and do "Set Next Statement" on the instruction below the BRK to skip executing the BRK instruction completely (so doing this means the BRK instruction will have no actual impact on the execution). "Set Next Statement" essentially changes the program counter and allows you to change the execution flow as you need (e.g force the code to skip a branch, etc.)

dougeff's idea of using write-specific breakpoints & writing to a specific address that you don't normally is probably simpler to use, though. If you're not using save/work ram, you can just write to the $6000-$7FFF range with no side effects and break on those writes.

I need to take a look at ASM6's listing output (couldn't find any example of them online) - might be something I can import into the debugger like I did for CC65's DBG files.
Also, out of sheer curiosity, is there anything FCEUX's hex editor does that you need that Mesen's doesn't?
User avatar
rainwarrior
Posts: 8731
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Learning Action Game Basics

Post by rainwarrior »

Pokun wrote:Ah yeah Asm6 outputs a listing file with all addresses, I got a break point to work. I guess I could also use the BRK instruction and break on IRQ, I'm just not sure how to return to the game after a BRK though as the return address seems not to be preserved.
An RTI instruction in your IRQ handler would do the job. The BRK would take you to the IRQ handler, and one "step" should take you back to the instruction after the BRK (by executing the RTI).

BRK does preserve a return value, there's just an extra byte on the stack compared to a JSR (P/flags).
tepples
Posts: 22705
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Learning Action Game Basics

Post by tepples »

If you're using a breakpoint on an empty IRQ handler (that is, one that immediately executes RTI), make sure your assembler emits two bytes for BRK. Some emit one.
User avatar
thefox
Posts: 3134
Joined: Mon Jan 03, 2005 10:36 am
Location: 🇫🇮
Contact:

Re: Learning Action Game Basics

Post by thefox »

rainwarrior wrote:
Pokun wrote:Ah yeah Asm6 outputs a listing file with all addresses, I got a break point to work. I guess I could also use the BRK instruction and break on IRQ, I'm just not sure how to return to the game after a BRK though as the return address seems not to be preserved.
An RTI instruction in your IRQ handler would do the job. The BRK would take you to the IRQ handler, and one "step" should take you back to the instruction after the BRK (by executing the RTI).

BRK does preserve a return value, there's just an extra byte on the stack compared to a JSR (P/flags).
Also note that the return address pushed by BRK takes you one byte past the byte following the BRK opcode, i.e.:

Code: Select all

brk ; branches to irq
nop
nop ; rti returns here

irq:
rti
(In some assemblers BRK supports "immediate addressing", that is, you can write brk #$69. In ca65 an operand is not supported.)
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
Pokun
Posts: 2675
Joined: Tue May 28, 2013 5:49 am
Location: Hokkaido, Japan

Re: Learning Action Game Basics

Post by Pokun »

Ah yes it seems ASM6 assembles "BRK" as $00, and "BRK #$DB" becomes $00 $DB ("BRK $DB" also works). Passing a dummy argument like this works as the dummy byte, and now my IRQ handler returns properly on BRK. I could even detect the BRK IRQ by pulling P from the stack and checking the B flag.
Sour wrote:
Pokun wrote:I need to take a look at ASM6's listing output (couldn't find any example of them online) - might be something I can import into the debugger like I did for CC65's DBG files.
Also, out of sheer curiosity, is there anything FCEUX's hex editor does that you need that Mesen's doesn't?
That would be helpful, asm6 is one of the most popular assemblers after all.
Not anything I can think about, Mesen's hex editor seems to have about everything that the FCEUX one have, plus more. Only thing might be that Mesen has a slow interface on my laptop I'm currently programming on, and things like hex editor and PPU viewers requires first opening the debugger (which also opens slowly). But maybe that can't be helped.
FCEUX opens up lighting fast and has quick menus, which makes it ideal for quick testing.
Pokun
Posts: 2675
Joined: Tue May 28, 2013 5:49 am
Location: Hokkaido, Japan

Re: Learning Action Game Basics

Post by Pokun »

OK next problem.

I have got both moving sideways and jumping to work pretty well if given the right parameters, but there are two bugs that I can't figure out why they happen, and I'm not sure how the debugger could help me here either.

The first is that going left is a lot slower than going right. When going right, I come up to the speed $01.E0 pixel/frame according to the value in RAM, and when going left, I come up in $FF.20 pixel/frame. That's the negative of $01.E0 no? Yet I come up in this speed faster and the player character moves clearly much slower to the left than to the right.

Here is my leftward acceleration code (the rightward one is the same as the one in the first post). It is almost identical to the rightward code, except that acceleration is subtracted instead of added and the max speed is negated:

Code: Select all

Constants:
MOB_ACCEL_L = 8
MOB_ACCEL_H = 0         ;running acceleration value added to velocity each frame
MOB_DECEL_L = 12
MOB_DECEL_H = 0         ;breaking acceleration value subtracted from velocity each frame not pressing RIGHT/LEFT
MOB_MSPD_L  = 224
MOB_MSPD_H  = 1         ;maximum allowed running speed

@left1:
  lda con_state+0
  and #CON_LEFT
  beq @not_left1
  
  ;lda p1_ground           ;if in mid-air, skip acceleration (disallows
  ;beq @right1             ;horizontal acceleration control in mid-air)

  lda p1_vx+0
  sec
  sbc #MOB_ACCEL_L
  sta temp+0
  lda p1_vx+1
  sbc #MOB_ACCEL_H        ;subtract acceleration value from velocity
  sta temp+1              ;store new velocity temporarily for comparison
  
  lda temp+0
  cmp #(256-MOB_MSPD_L)
  lda temp+1
  sbc #(256-MOB_MSPD_H)
  bvc @n_xor_v_vl
  eor #$80
@n_xor_v_vl:              ;16-bit signed comparison with (-max speed)
  bpl @not_terminal_vl    ;if velocity < (-max speed), allow acceleration
  lda #(256-MOB_MSPD_L)
  ldx #(256-MOB_MSPD_H)
  jmp @terminal_vl        ;else, velocity = (-max speed)
@not_terminal_vl:
  lda temp+0
  ldx temp+1
@terminal_vl:
  sta p1_vx+0
  stx p1_vx+1             ;set velocity
  jmp @right1             ;exit
  
@not_left1:
  lda p1_vx+1
  bpl @right1             ;if velocity positive, no need to decelerate
  
  ;lda p1_ground           ;if in mid-air, skip acceleration (disallows
  ;beq @right1             ;horizontal acceleration control in mid-air)

  lda p1_vx+0
  clc
  adc #MOB_DECEL_L
  sta temp+0
  lda p1_vx+1
  adc #MOB_DECEL_H        ;add deceleration value to velocity
  sta temp+1              ;store new velocity temporarily for comparison
  
  lda temp+0
  cmp #$00
  lda temp+1
  sbc #$00
  bvc @n_xor_v_vls
  eor #$80
@n_xor_v_vls:             ;16-bit signed comparison with 0
  bpl @positive_vls       ;if velocity >= 0, velocity = 0
  lda temp+0
  sta p1_vx+0
  lda temp+1
  sta p1_vx+1             ;else, allow deceleration
  jmp @right1             ;exit
@positive_vls:
  lda #$00
  sta p1_vx+0
  sta p1_vx+1             ;stop

@right1:
;...
There are no meddling routines that run this time from what I can tell. Not anything that affects horizontal velocity or x-position anyway.
I attached the game to demonstrate the problem. That other problem is about jumping, but I'll get to that later.
Attachments
actiongame.zip
Problems: 1) Leftward acceleration is slower than rightward. 2) When jumping, double jumping is possible if done really quickly on the way up.
(3.57 KiB) Downloaded 138 times
tepples
Posts: 22705
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Learning Action Game Basics

Post by tepples »

The two's complement of $01E0 (+480) is $FE20 (-480). $FF20 is -224.
User avatar
thefox
Posts: 3134
Joined: Mon Jan 03, 2005 10:36 am
Location: 🇫🇮
Contact:

Re: Learning Action Game Basics

Post by thefox »

It's best to replace MOB_MSPD_L and MOB_MSPD_H with MOB_MSPD = $1E0, and use <MOB_MSPD, >MOB_MSPD, <(-MOB_MSPD) and >(-MOB_MSPD) in the code.

That kind of mistake is very common (at least I seemed to make them all the time when handling two's complement fixed point numbers, nowadays I try to be extra careful with them).
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
Post Reply