Page 1 of 1

collision at higher speed then 50/60Hz

Posted: Sat Dec 29, 2012 4:49 am
by KennyB
Hello everybody,

many of you will know the Pong like game tutorial over at NA. After a long break, I am now continuing those lessons by creating my own pong game.
I created a nice playing field where my ball bounces around. I also managed to add the functionality of increasing the speed. But here is where the problem lies. I also asked this over at NA, but it doesn't hurt to also ask here.

I changed the ball speed to 2/3/4/5/6 to make it go faster. So instead of moving 1 pixel a time, I move 2/3/4/5/6 pixels a time.
But at these higher speeds, the ball will change direction before or after the "walls" I programmed.
They seem to have an offset. For the right and top wall it will change direction before it should hit the wall. And for the left and bottom wall it will change direction after it should hit the wall. (I think) I know why it does this ==> If the ball is, for example, 1 pixel before the wall ==> No collision ==> Increase the position of the ball with 5 (speed of the ball). This means that the ball will be bounced and shown 4 pixels beyond the wall. But I don't know why the ball will change direction before hitting the right and top wall, but it will most likely have something to do with the difference in subtracting (right/top wall) and adding (left and bottom wall) the speed ?
So to sum up ==> ball bounces on offset walls at higher speeds.

I used the BCC and BCS instructions after a CMP. So they will branch if greater/less than (and thus register a hit, although too late/soon)

CheckCollisionRight:
LDA boxRightX
CMP #RIGHTWALL
BCC CheckCollisionRightSkipper ; if A < RIGHTWALL ==> Carry is set to 0 ==>jump to label and skip over the direction change. If A >= RIGHTWALL ==> Carry = 1 ==> No jump to label and run change direction code
LDA #$00
STA ballright
LDA #$01
STA ballleft
CheckCollisionRightSkipper:
JMP CheckCollisionRightDone

CheckCollisionLeft:
LDA boxLeftX
CMP #LEFTWALL
BCS CheckCollisionLeftSkipper ; if A >= RIGHTWALL ==> Carry is set to 1 ==>jump to label and skip over the direction change. If A < RIGHTWALL ==> Carry = 0 ==> No jump to label and run change direction code
LDA #$01
STA ballright
LDA #$00
STA ballleft
CheckCollisionLeftSkipper:
JMP CheckCollisionLeftDone


Because I did the BCC and BCS the ball always bounces. But at the higher speeds, it often bounces to soon or to late. I'm thinking that it's impossible to eliminate this problem as it only updates at 50/60 Hz.
If the ball is 1 pixel before the wall in frame 1 ==> no hit ==> no change of direction.
In the next frame the ball will be 4 pixels behind the wall ==> it will branch ==> change direction. But it will still update the sprite for that frame and it will show it 4 pixels behind the wall.

I was thinking of creating a loop which will update the location of the ball one pixel a time. But this probably will slow down my ball :
==> frame 1 ==> loop 1 ==> 1 pixel moved
==> frame 2 ==> loop 2 ==> 1 pixel moved
==> frame 3 ==> loop 3 ==> 1 pixel moved
==> frame 4 ==> loop 4 ==> 1 pixel moved
==> frame 5 ==> loop 5 ==> 1 pixel moved

So in the end we still need 5 frames to perform 5 loops and we move only 5 pixels ==> which is exactly the same as moving 1 pixel a frame (or at a speed of 1 instead of 5).


Does anyone know a solution to this ? Or point me in the right direction ? Updating the sprite of the ball more often then 50/60 Hz is not possible.

Re: collision at higher speed then 50/60Hz

Posted: Sat Dec 29, 2012 6:12 am
by tepples
If you want to have perfectly elastic collision response, you'll need to reflect the ball's position through the wall. For example, if there's a wall at x=16, and the position has become x=14, you need to change the position to x=18, or 2 * wall position - ball position.

Re: collision at higher speed then 50/60Hz

Posted: Sat Dec 29, 2012 7:00 am
by Kasumi
I wrote a giant long post, but it was a waste because Tepples' solution is awesome and simple.

One thing I will say is that your line of thinking was pretty good.
I was thinking of creating a loop which will update the location of the ball one pixel a time. But this probably will slow down my ball.
It doesn't need to slow down your ball.

Code: Select all

Frame start:
Ball logic start:
     move ball one pixel
     check for wall.
     move ball one pixel
     check for wall.
     etc. 
Frame End:
Like that.
Maybe something like:

Code: Select all

loop:
    ldx speed;Number of pixels ball is supposed to move this frame.
    ;add or subtract 1 from ball's position
    ;Do check for wall
    dex
    bne loop
;The above moves the ball if speed is zero. To fix this
;put a beq instruction after ldx speed, and
;a label to beq to after that code, so it skips
;it entirely when speed is zero
Tepples' solution is a better method, but you're definitely showing great problem solving to think of something like the above.

Re: collision at higher speed then 50/60Hz

Posted: Sat Dec 29, 2012 7:42 am
by tepples
My own Pong clone runs physics at 120 Hz, but that's because objects can move up to 20 pixels per frame, fast enough to have an object move "through" another in a single frame. Individual half-frames still use the reflection method though.

Re: collision at higher speed then 50/60Hz

Posted: Sat Dec 29, 2012 9:23 am
by KennyB
Thanks guy's,

I'm going to try both solutions to learn the advantages/disadvantages of both.
tepples wrote:My own Pong clone runs physics at 120 Hz, but that's because objects can move up to 20 pixels per frame, fast enough to have an object move "through" another in a single frame. Individual half-frames still use the reflection method though.
The link on your site for the Zapruder code is not working.

Re: collision at higher speed then 50/60Hz

Posted: Sat Dec 29, 2012 6:11 pm
by tepples
KennyB wrote:The link on your site for the Zapruder code is not working.
Fixed. Pin Eight just moved to a different provider, and a few files didn't get copied correctly.

Re: collision at higher speed then 50/60Hz

Posted: Sun Dec 30, 2012 5:53 am
by KennyB
tepples wrote:
KennyB wrote:The link on your site for the Zapruder code is not working.
Fixed. Pin Eight just moved to a different provider, and a few files didn't get copied correctly.
Thanks, going to take a look to see if I can learn some extra stuff.

Thanks to everybody for the input ! I implemented the code and the balls aren't bouncing behind the walls anymore !
In case anybody has the same problem or if my code is not optimal (let me know !); here is how I worked it out:
First I had some trouble understanding where this formula came from.
tepples wrote:If you want to have perfectly elastic collision response, you'll need to reflect the ball's position through the wall. For example, if there's a wall at x=16, and the position has become x=14, you need to change the position to x=18, or 2 * wall position - ball position.
So I made a sketch of my playing field:
Image

In the left it shows the situation where the ball (blue dot) is beyond my wall (because of increasing with a speed higher then 1). The solution to this is to mirror the ball around the wall as seen on sketch on the right (purple dot). This mirrored position is where the actual ball would be in case the bouncing was done correctly.
So I wrote down the formula:

X_mirrored = Xwall - dx
X_mirrored = Xwall - (Xball-Xwall)
X_mirrored = Xwall - Xball + Xwall
X_mirrored = 2 x Xwall - Xball

For the left wall you need to add dx, but it still gives the same end formula because dx is now (Xwall -Xball) instead of (Xball -Xwall) :

X_mirrored = Xwall + dx
X_mirrored = Xwall + (Xwall -Xball)
X_mirrored = Xwall + Xwall - Xball
X_mirrored = 2 x Xwall - Xball

So I created some code which would mirror the position of the ball in case it has gone beyond the wall:

Code: Select all

CheckCollisionDown:                             ; check if the ball has hit the bottom wall
  LDA boxBottomY	                             ; Load A with the Bottom Y collison box value of the ball
  CMP #BOTTOMWALL	                            ; Compare it with BOTTOMWALL
  BCC CheckCollisionDownSkipper 		          ; if ball y < bottom wall, still on screen, skip next section
  LDA #$00						                    ; Load A with 0
  STA balldown					                   ; Store A in ball down ==> ball is not going down anymore
  LDA #$01						                    ; Load A with 1
  STA ballup         				               ; Store A in ball up ==> ball is now going up ( = bounce)
  LDA #BOTTOMWALL				                   ; load the value of the Botoom wall value
  CLC							                      ; clear the carry (so we are sure that if the carry flag is set, it will be from the ASL and not from something else
  ASL A						                       ; Arithmetic Shift Left ==> move all bit's one place to left = multiplying by two. Needed to calculate the Ymirrored position of the ball. 
		                  					         ; If we ASL the value 250 ==> 500 ==> bigger then 255 ==> this means the carry flag is set (carry flag = 255)
							                           ; So the value in A is: 500-255 = 245 (A = 2 x Previous A - 255)
							                           ; if the carry flag is not set then (A = 2 x Previous A )
  STA Multiplier					                 ; store the value of A in the multiplier so we can use it later on
  BCS MirroringBottomBiggerThen255	           ; If the carry is set (which means that A is bigger then 255) ==> jump to label
  
  SEC							                      ; set carry because: A = A - number - (1-carry)
  SBC boxTopY					                    ; subtract the box Top Y coodinate from A 
  STA boxTopY					                    ; storing it back in the box Top Y coordinate
  JMP MirroringDownDone			                ; Jump to label to skip over other code (for when carry was set)
  
  MirroringBottomBiggerThen255:		           ; label to skip to if the carry was set (and 2 x A > 255
  LDA #255						                    ; load 255 into A (if carry = set ==> X mirror = 2 x Xwall - Xball ; 2 x Ywall = 255 (because carry was set) + Ywall_remainder - Xball
  SEC							                      ; set carry because: A = A - number - (1-carry)
  SBC boxBottomY				                    ; subtract the Yball coordinates from A first (otherwise we can have a value bigger then 255)
  SBC #07						                     ; then subtract 07 to switch from boxBottom Y to boxTopY (which is main coordinate)
  CLC							                      ; clear the carry before adding
  ADC Multiplier					                 ; Add the value in the multiplier to A (= remainder of the Ywall)
  STA boxTopY					                    ; storing it in boxTopY
  MirroringDownDone:				                ; Mirroring is done, still need to update other coordinates
  LDA boxTopY					                    ; load the location of the Y-value of the collision box top
  CLC 						                        ; clear the carry before adding
  ADC #03 						                    ; Add 3
  STA boxInsideRightY				               ; store in boxInsideRightY
  ADC #01						                     ; Add 1
  STA boxInsideLeftY				                ; Store in boxInsideLeftY
  ADC #03						                     ; Add 3
  STA boxBottomY				                    ; Store in boxBottomY
  
  CheckCollisionDownSkipper:			           ; label used to skip over code
  JMP CheckCollisionDownDone		              ; label to jump back to after checking the collision