pong collision from nerdy nights

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

User avatar
log in
Posts: 72
Joined: Tue Jun 24, 2008 1:06 pm
Location: neverland

Post by log in »

Uhm ... ill have to read this a couple of times again :oops: and search on google etc.
I think ill get the idea..but im not sure.

Somehow i already thought that the collision for the 2nd paddle didn't work because i used ball x and y again.
On the other hand i thought i saw some exsamples on the web that worked and used the ball x and y again without a problem?

The big picture is(i gues)That the variables i have like ballx and y are still set normal like variable ballx . rs 1 etc at the beginning.
There still are set on screen etc. by the rs variable BUT
I also set them as temp ram after ball x .rs in a different section like:
ballx=temp1 bally= temp 2 etc.
Then ill make a collision detection code somehow.
that will look different like this
lda ballx
clc
adc #$08
cmp paddle1
bcc no hit

but will do all the checks x with x and with y +height and width etc for all object that can collide with 1 subroutine code.
Something like the buttons ? all of them go threw ?
OR i look like a fool and it works completleY different :wink:
im a newbie,lets see how far i can get
User avatar
Kasumi
Posts: 1293
Joined: Wed Apr 02, 2008 2:09 pm

Post by Kasumi »

There is a programming term "scope". Scope is a very important/difficult programming concept, so I hope my simple explanation is good enough.

Each variable has a scope. A variable's scope is how long the variable is expected to refer to the same value. For example, how long ram location $00 is expected to contain ballx's position.

A variable's scope begins when it is used the first time after it was not being used, and ends when it is free to be used for something else.

Let's take a look at buttons1. Very early in each frame, you jsr to ReadController1. This starts storing which buttons are held in buttons1, and starts a scope where buttons1 is expected to hold which buttons are held.

I could write whatever I want to buttons1 BEFORE the jsr to ReadController1 and it would not affect anything, because at that point buttons1 isn't expected to hold anything.

The scope for buttons1 ends the last time it is expected to hold which buttons are held. So after you check for player 1 pressing up and down to move the paddle, the scope has ended.

I could write whatever I want to buttons1 AFTER checking up and down for the first paddle's movement, because at that point buttons1 is no longer expected to hold which buttons are held.

You cannot (safely) change a variable to an unrelated value while it is expected to hold a value, i.e. while it has scope. Imagine if we wrote something to buttons1 during its scope. It could possibly make the paddles move up and down without the user's input!

buttons1 has a scope that is a little less than a frame. What about variables who have an "endless" scope? In your code right now, ballx has endless scope. After it is setup, it is always expected to have the same value. (The current value of the ball's x position.) Changing it to something unrelated would cause the ball to warp around!

A temporary variable is a variable that has very short scope. Here is an example that adds 1/2 of a number to itself. It does this by storing itself in a temporary variable.

Code: Select all

;Some number is in A. It doesn't matter what number specifically.
sta temp;A scope begins for temp here. It is expected to hold the number we want to add half to.
lsr a;A now holds half of itself.
clc
adc temp;We have just added temp (our number) to 1/2 of our number.
;We no longer need to know what temp (our number) was, because the operation is done. The scope for temp ends here.
We could use then temp for something else (start a new scope) IMMEDIATELY after this.

Now let's look at how this affects your collision detection routine:

Code: Select all

collisiondetection:
lda ballx
clc
adc #$08
cmp paddle1x
;etc
rts
This routine currently compares two variables with near endless scope!

If I wanted to use it to compare say... ballx and paddle2x. I would have to OVERWRITE paddle1x with paddle2x to use this routine. Overwriting a value in scope is BAD.

To get around this, we use temp RAM.

Code: Select all

collisiondetection:
lda temp1x
clc
adc #$08
cmp temp2x
;etc
rts;Scope for temp1x and temp2x ends
To use this, you write the x values you want to check (like ballx and paddle1x) to temp1x and temp2x before the routine starts. This starts a scope where they're expected to be those values. Then you jsr to the subroutine which checks them and ends their scope.

To check two different objects, you write the values you want to check (like ballx and paddle2x), and jsr to the same subroutine. This allows you to use the same routine for all collision checks, without overwriting any variables in scope.

The reason you want your collision routine as a subroutine is that, generally, you don't want to duplicate complicated bits of code.

Imagine you had collision routine duplicated for each collision you wanted. (paddle1, ball and paddle2, ball). If you found a mistake in the collision code, you'd have to update two different routines to make the same change.

edit: And the next day, I realize I'm describing a variable's "extent" not "scope"? At least I think...? Being self taught fills me with self doubt when I try to teach. I feel like I know how these things work but then call them the completely wrong things... Anyone else, please feel free to correct me.
User avatar
log in
Posts: 72
Joined: Tue Jun 24, 2008 1:06 pm
Location: neverland

Post by log in »

I think i got it :wink:

For my collision code i would only have 2 use 6 temps :roll: uhm make that 4.
Because my paddles are the same in size i would not have to edit height and width.
So ill get something like this


Checkcollision:
lda temp1
clc
adc #$08
cmp temp2
bcc no hit

hmmm now im typing this another thing pops up my collision code is

lda ballx
lda #$01
sta ballright
lda #$00
sta ballleft
RTS

So i would also have to get ballleft and ballright in the temp ram.What about ballspeed?i gues not?

and i would have to make checkcollision a subroutine
JSR Checkcollision

I understand the collision no problem BUT where do i put the temp ram and HOW?

My gues :roll:

1.It would have to be in the ENGINEPLAYING

2. It would have to be in programcode like this??

TempRam:

lda ballx
sta temp1
lda paddle1x
sta temp2
ETC .followed by
JSR checkcollision
Tempramdone

code goes further

Tempram2:

lda ballx
sta temp1
lda paddle2x

and so on and on for other collision checs if there where any..
im a newbie,lets see how far i can get
User avatar
Kasumi
Posts: 1293
Joined: Wed Apr 02, 2008 2:09 pm

Post by Kasumi »

log in wrote: So i would also have to get ballleft and ballright in the temp ram.
Nope. You don't need to put the speed or direction of the ball in temp RAM, because your collision routine doesn't care about any of that. Your collision routine should only return whether or not there is a collision. There are exactly two possible outcomes (either the objects are colliding or they are not), so you can just use the carry flag for the result instead of more temp RAM. Set the carry within the routine if there is a collision. Clear it if there is not. Then simply use a branch after the subroutine to do what you want with the result.

Code: Select all

;Collision check 1
lda ballx
sta temp1
lda paddlex
sta temp2
;etc
jsr Checkcollision
bcc skipbounceright
lda #$01
sta ballright
lda #$00
sta ballleft 
skipbounceright:
Having this part in the body of your actual subroutine:

Code: Select all

lda #$01
sta ballright
lda #$00
sta ballleft 
would mean that that subroutine could no longer be used to bounce the ball left. A similar problem to when you weren't using temp ram for paddle1x.
Where do i put the temp ram and HOW?
You make room for it with .rs like any other RAM. You store and load values to it like any other RAM. Its purpose is only special to you because you have to keep its extent and what it currently needs to hold in mind, but temp and ballx are not different types of variables to the assembler.

edit:
Because my paddles are the same in size i would not have to edit height and width.
Correct, but keep in mind if you work this way the ball's y position must be stored in the slot of temp RAM that adds 8 for height, and the paddle's y position must be in the slot of temp RAM that adds 16. You're right that for pong it is not that worth it/useful to have temp RAM for height and width.

Finally, a quick note about some code left over from the bunnyboy tutorial. The ball can't be traveling left and right at the same time or up and down at the same time, right? So you don't actually need four variables for the ball's direction (ballleft, ballright, ballup, balldown). You only need two. (balldirectionx and balldirectiony).


If balldirectionx is 0, the ball is traveling right. If it's nonzero, it's traveling left. Likewise for y. Less RAM, and serves the same purpose.
2. It would have to be in programcode like this??
*snip*
and so on and on for other collision checs if there where any..
That is correct. :D
User avatar
log in
Posts: 72
Joined: Tue Jun 24, 2008 1:06 pm
Location: neverland

Post by log in »

My code

temp1 .rs 1 ;
temp2 .rs 1 ;
temp3 .rs 1 ; added this to variables
temp4 .rs 1 ;

;---------------------------------------

JSR CheckCollision added this after the JMP readcontrollers

;----------------------------------------

Bounce:

LDA ballx
STA temp1
LDA bally
STA temp2
LDA PADDLE1X added this at the last part of the game
STA temp3 engine
LDA paddle1y
STA temp4
JSR CheckCollision
BCC SkipBounceRight



LDA #$01
STA ballright
LDA #$00
STA ballleft


SkipBounceRight:

JMP GameEngineDone

;----------------------------------------------------------------


CheckCollision:


LDA temp3
CLC
ADC #$08
CMP temp1 ; added this after the
controller loop
LDA temp1
CLC
ADC #$08
CMP temp3
.

LDA temp4
CLC
ADC #$32
CMP temp2


LDA temp2
CLC
ADC #$08
CMP temp4


RTS

;----------------------end of all editing
im a newbie,lets see how far i can get
User avatar
log in
Posts: 72
Joined: Tue Jun 24, 2008 1:06 pm
Location: neverland

Post by log in »

So when using this the ball goes ApeShit.
And i think i know why my collision sucks.

I work this all out on paper first (like the 6502 book says,yeah got a translated version :D of rodney)

The problem is the split up off my code.

The collision is ok as far as i can see,but the problem is the split of the collision code and the code for the ball bouncing.

Ok ,how do i say this :roll:

In my old code i would bcc if the collision could not happen to NoHit and if everything was ok the program would work his way to the collision code.
But now that won't work..
Because the collision code is somewhere else and if i try to get there its out of reach so..

i let the code run and tried bcc and the ball goes bananas,proberly because it checks only 1 bcc of my 4 checks :?:
im a newbie,lets see how far i can get
User avatar
Kasumi
Posts: 1293
Joined: Wed Apr 02, 2008 2:09 pm

Post by Kasumi »

If this is your code it can't work.

Code: Select all

CheckCollision:


LDA temp3
CLC
ADC #$08
CMP temp1 ; added this after the
controller loop
LDA temp1
CLC
ADC #$08
CMP temp3
.

LDA temp4
CLC
ADC #$32
CMP temp2


LDA temp2
CLC
ADC #$08
CMP temp4


RTS
There's not a single branch there. That means that only the very last cmp determines whether or not there is a collision.

When your old code worked, it did this when there was a collision:

Code: Select all

LDA #$01
STA ballright
LDA #$00
STA ballleft
And nothing when there was not.

You want the same collision checks in your subroutine (except with temp RAM) except you want to SEC instead of the above when there is a collision (then RTS), and CLC instead of nothing when there is not a collision. (then RTS).

Do all four collision checks:

Code: Select all

;If a collision fails, you branch to nohit.
;If all the collision checks are true it continues down to the following without branching.
sec
rts
nohit:
clc
rts
That way if it returns after branching from nohit, the carry is guaranteed to be clear when it returns. If it never branches to nohit, the carry is guaranteed to be set when it returns. Then, after the subroutine returns you can use bcc to skip the code that shouldn't happen if there was no collision.
JSR CheckCollision added this after the JMP readcontrollers
You mean like this?

Code: Select all

JSR ReadController1 ;;get the current button data for player 1
JSR ReadController2 ;;get the current button data for player 2
JSR CheckCollision 
Why? You should only jsr to the check collision if you're checking a collision.
User avatar
log in
Posts: 72
Joined: Tue Jun 24, 2008 1:06 pm
Location: neverland

Post by log in »

Bounce2:

LDA ballx
STA temp1
LDA bally
STA temp2
LDA PADDLE2X
STA temp3
LDA paddle2y
STA temp4
JSR CheckCollision
BCC SkipBounceRight2



LDA #$00
STA ballright
LDA #$01
STA ballleft


SkipBounceRight2:

;-----------------------------------------------------------------



1. What am i missing or doing wrong i read it a couple of times again and it must work.The JSR and the code is ok.
Because paddle 1 works.
So what is wrong with this 1? the temps have been replaced,the ball bounce code has been reversed.
Are the temps not clear again?

2.The JSR in the last post was stupid and made no sence at all.

3.Im curius about collision some more.Are there limitations? Like the number of collisions on screen(something like 8 sprites on a line).
And can it happen that you get in trouble with 2 collisions at the same time?
im a newbie,lets see how far i can get
User avatar
Kasumi
Posts: 1293
Joined: Wed Apr 02, 2008 2:09 pm

Post by Kasumi »

Are the temps not clear again?
You just have to set the temps again with the new values before you call the subroutine again. It looks like you're doing that, so no problem there.
3.Im curius about collision some more.Are there limitations? Like the number of collisions on screen(something like 8 sprites on a line).
And can it happen that you get in trouble with 2 collisions at the same time?
There aren't any limitations like that, no. You should be able to check any number of collisions, even 1,000 if you wanted to. One limitation is that the current routine may return false if both objects are colliding very close to the bottom or right of the screen, but I don't think that would be the issue here.

So the problem must be with your collision code. It's possible the way it is written would make it work for one case and not another. I haven't seen it since you said it was working, so please post it.
User avatar
log in
Posts: 72
Joined: Tue Jun 24, 2008 1:06 pm
Location: neverland

Post by log in »

time to test my pasta bin skills :wink:
http://pastebin.com/PVhcBvdh
im a newbie,lets see how far i can get
User avatar
Kasumi
Posts: 1293
Joined: Wed Apr 02, 2008 2:09 pm

Post by Kasumi »

Code: Select all

PADDLE1X       = $08  ; horizontal position for paddles, doesnt move
PADDLE2X       = $F0

Code: Select all

LDA PADDLE1X; = lda $08
;Which is NOT the same as lda #$08.
LDA PADDLE2X; = lda $F0
;Which is NOT the same as lda #$F0.
I said that already and went into detail in the very beginning of this post.

Edit: Though, I guess I understand why you did this. It's what I did in my example. I did this I'd actually just use RAM for the two paddlex values. It makes it much easier. In any case, that's the issue.

Code: Select all

ADC #$32; #$32 = lda #50.
;You want adc #$20 if the paddle is only 32 pixels tall.
I said that already in this post.

Code: Select all

  JSR ReadController1  ;;get the current button data for player 1
  JSR ReadController2  ;;get the current button data for player 2
  JSR CheckCollision;Still there.
But the collision code is fine, and should work when you make those changes.
User avatar
log in
Posts: 72
Joined: Tue Jun 24, 2008 1:06 pm
Location: neverland

Post by log in »

fixed everything and got the paddles working,it all worked perfect on the powerpak.
Wrote everything down in my map and wrote constants and # down in BIG letters :wink:

I think i can close the capter collisions now(sprite vs sprite)
More tips or pointers would be very welcome :wink:
Many MANY thank you's
im a newbie,lets see how far i can get
User avatar
Kasumi
Posts: 1293
Joined: Wed Apr 02, 2008 2:09 pm

Post by Kasumi »

Congrats!

The first thing you should do is start naming your labels and variables more meaningful things.

SkipBounceRight2? Why not SkipBounceLeft? It is not a good habit to copy and paste things and change just enough so it works right. If you read your code later, you'll have to think twice as hard about what the code is doing.

MovePaddle1y
DownPaddle1y

Why not UpPaddle1y? The labels should be immediately clear. Related code should follow a format.

Code: Select all

paddle1y   .rs 1;This one's labeling doesn't match the others.
paddle12  .rs 1;These could be read as PaddleTwelve
paddle13  .rs 1;PaddleThirteen
paddle14  .rs 1;PaddleFourteen
;Which hides their purpose a little bit.
You actually don't need paddle12, paddle13, and paddle14 at all.

Right now you are using 4 zero page RAM locations for each paddle's y position.

This has two problems:
1. You have to update all 4 every time the paddle moves.
2. You're using 3 more bytes of zero page RAM than you need.

You can update only one byte of RAM that contains the actual Y position of the paddle:

Code: Select all

MovePaddle1y:
  LDA buttons1
  AND #%00001000
  BEQ MovePaddle1yDone

  LDA paddle1y
  SEC 
  SBC #$01
  STA paddle1y
MovePaddle1yDone:
Then do this when you update the sprites:

Code: Select all

  LDA paddle1y;Paddle1y is in A.
  STA $0204
  
  LDY #$37;We're using Y instead of A
  STY $0205;Because A holds a value we'll add to
;For the other sprites in the paddle.
  LDY #$00
  STY $0206

  LDY #$08
  STY $0207

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  CLC
  ADC #$08;We've added 8 to the top y position. 
  STA $0208;Paddle1y+8 is in A.
  
  LDY #$47
  STY $0209

  LDY #$00
  STY $020A

  LDY #$08
  STY $020B

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

  CLC
  ADC #$08;Paddle1y+16 is in A.
  STA $020C
  
  LDY #$57
  STY $020D

  LDY #$00
  STY $020E

  LDY #$08
  STY $020F

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

  CLC
  ADC #$08;Paddle1y+24 is in A.
  STA $0210
  
  LDY #$67
  STY $0211

  LDY #$00
  STY $0212

  LDY #$08
  STY $0213
That updates all the sprites properly without the extra RAM.

In fact, you can take it a little further. I said earlier that anytime you need to duplicate complicated code, it's probably worth turning it into a subroutine. Right now you have the "update paddle sprites" code duplicated for each paddle.

You can make a subroutine using the ,x addressing mode.

lda $0200,x loads the value at the location $02XX. If X is FF it loads from $02FF.
If X is 8, it loads $0208.

Remember how we had to put things in temp RAM to set up our checkcollision routine? Here we need to do some setup, but we'll just use A, X, and Y.

Code: Select all

;load the y position of the paddle in A.
;Load the X position of the paddle in Y.
;Then load the location of the first byte of the paddle you want to draw in X.
;JSR to this subroutine and it will draw the paddle.

DrawPaddle:
  STA $0200,x;This works because before we jsr'd to DrawPaddle, we loaded the y position in A.


  STA temp1;We need to store it in temp RAM instead of keeping it in A
;For the subroutine. This is because you can't STY $0200,x. ST ,x is only available for A.

  LDA #$37
  STA $0201,x

  LDA #$00
  STA $0202,x

  TYA;Move the value in Y(the X position of the paddle) into A.
  STA $0203,x 
  inx
  inx
  inx
  inx;We want to work with a new sprite now, so we advance four bytes.

  LDA temp1
  CLC
  ADC #$08
  STA temp1
  STA $0200,x
  
  LDA #$47
  STA $0201,x

  LDA #$00
  STA $0202,xx

  TYA
  STA $0203,x

  inx
  inx
  inx
  inx
;*insert the same sort of thing for the final two sprites*
  rts
Then, you can use it like this:

Code: Select all

  lda paddle1y
  ldy paddle1x
  ldx #$04;The ball occupies $0200, $0201, $0202, and $0203, so we start with $0204.
  jsr DrawPaddle
;The first paddle's sprites are updated! You don't even have to update X to do more, since the DrawPaddle routine automatically moves it to the next free sprite.

  lda paddle2y
  ldy paddle2x
  jsr DrawPaddle
;The second paddle's sprites are updated!

;You can even draw more paddles in other locations!

  lda #$80
  ldy #$80
  jsr DrawPaddle
If you use subroutines instead of hard coding everything it makes it very easy to add new objects to the game.

One last thing. The CURSE of nesasm.

Whenever you are using a zero page variable, put < before it.

Code: Select all

lda temp1
;becomes
lda <temp1

lda paddle1y
;becomes
lda <paddle1y
This makes your code faster.

lda temp1 is like typing lda $XXXX which takes 4 cycles.
lda <temp1 is like typing lda $XX which takes 3 cycles.

It's faster, and there's no drawback to doing it 99% of the time.

The one percent is when you want to do lda temp1,x. In that case lda <temp1,x and lda temp1,x can possibly do different things.
Post Reply