How to have ball collide with bricks in Breakout (

2019-08-16 08:23发布

问题:

I've been trying to get Breakout to work in Racket, so far the ball bounces from the paddle (paddle is controlled by mouse) and the bricks are present

Here is the full on code:

(require 2htdp/image)
(require 2htdp/universe)

(define WIDTH 400)
(define HEIGHT 400)
(define BALL-RADIUS 10)
(define BALL-IMG (circle BALL-RADIUS "solid" "red"))

(define REC-WIDTH 50)
(define REC-HEIGHT 10)
(define REC-IMG (rectangle REC-WIDTH REC-HEIGHT "solid" "grey"))

(define BRICK-IMG0 (rectangle 60 30 "solid" "blue"))
(define BRICK-IMG1 (rectangle 60 30 "solid" "green"))

(define SCENE (empty-scene WIDTH HEIGHT))

(define STARTGAME
  (text "CLICK TO START" 60 "purple"))

(define GAMEOVER
 (text "GAMEOVER" 60 "red"))

(define-struct vel (delta-x delta-y))
; a Vel is a structure: (make-vel Number Number)
; interp. the velocity vector of a moving object

(define-struct ball (loc velocity))
; a Ball is a structure: (make-ball Posn Vel)
; interp. the position and velocity of a object

(define-struct rec (pos))
;
;

(define-struct brick (pos))
;
;

; Posn Vel -> Posn
; applies q to p and simulates the movement in one clock tick
(check-expect (posn+vel (make-posn 5 6) (make-vel 1 2))
              (make-posn 6 8))
(define (posn+vel p q)
  (make-posn (+ (posn-x p) (vel-delta-x q))
             (+ (posn-y p) (vel-delta-y q))))


; Ball -> Ball
; computes movement of ball in one clock tick
(check-expect (move-ball (make-ball (make-posn 20 30)
                                    (make-vel 5 10)))
              (make-ball (make-posn 25 40)
                         (make-vel 5 10)))
(define (move-ball ball)
  (make-ball (posn+vel (ball-loc ball)
                       (ball-velocity ball))
             (ball-velocity ball)))





; A Collision is either
; - "top"
; - "down"
; - "left"
; - "right"
; - "none"
; interp. the location where a ball collides with a wall

; Posn -> Collision
; detects with which of the bricks the ball collides
;(check-expect (collision (make-posn 0 12))  "left")
;(check-expect (collision (make-posn 15 HEIGHT)) "down")
;(check-expect (collision (make-posn WIDTH 12))  "right")
;(check-expect (collision (make-posn 15 0)) "top")
;(check-expect (collision (make-posn 55 55)) "none")

(define (collision ball rec)
  (cond
    [(<= (posn-x ball) BALL-RADIUS) "left"]
    [(<= (posn-y ball) BALL-RADIUS)  "top"]
    [(>= (posn-x ball) (- WIDTH BALL-RADIUS)) "right"]
    [(>= (posn-y ball) (- HEIGHT BALL-RADIUS)) "GAMEOVER"]
    [(and (>= (posn-x ball) (- (posn-x (rec-pos rec)) (/ REC-WIDTH 2)))
          (<= (posn-x ball) (+ (posn-x (rec-pos rec)) (/ REC-WIDTH 2)))
          (= (posn-y ball) (posn-y (rec-pos rec)))) "down"]
    [else "none"]))




; Vel Collision -> Vel  
; computes the velocity of an object after a collision
(check-expect (bounce (make-vel 3 4) "left")
              (make-vel -3 4))
(check-expect (bounce (make-vel 3 4) "top")
              (make-vel 3 -4))
(check-expect (bounce (make-vel 3 4) "none")
              (make-vel 3 4))

(define (bounce vel collision)
  (cond [(or (string=? collision "left")
             (string=? collision "right"))
         (make-vel (- (vel-delta-x vel))
                   (vel-delta-y vel))]
        [(or (string=? collision "down")
             (string=? collision "top"))
         (make-vel (vel-delta-x vel)
                   (- (vel-delta-y vel)))]
        [else vel]))


; WorldState is a Ball
; interp. the current state of the ball

; WorldState -> Image
; renders ball at its position
;(check-expect (image? (render INITIAL-BALL)) #true)
(define (render a-world)
  (draw-ball (world-ball a-world)
             (draw-bricklist (world-bricks a-world)
                             (draw-rect (world-rec a-world)))))


(define (draw-ball ball img)
  (place-image BALL-IMG
               (posn-x (ball-loc ball))
               (posn-y (ball-loc ball))
               img))

(define (draw-rect rec)
  (place-image REC-IMG
               (posn-x (rec-pos rec))
               (posn-y (rec-pos rec))
                SCENE))


(define (draw-bricklist list img)
  [cond ((empty? list) img)
        ((cons? list) (draw-bricklist (rest list) (draw-brick (first list) img)))])

(define (draw-brick brick image) 
  (place-image (choice BRICK-IMG0 BRICK-IMG1)
               (posn-x (brick-pos brick))
               (posn-y (brick-pos brick))
               image))


(define (choice a b )
  (if (zero? (random 1)) a b ))

; WorldState -> WorldState
; moves ball to its next location
;(check-expect (tick (make-ball (make-posn 20 12) (make-vel 1 2)))
;              (make-ball (make-posn 21 14) (make-vel 1 2)))
;(define (tick ball)
;  (move-ball (make-ball (ball-loc ball)
;                        (bounce (ball-velocity ball)
;                                (collision (ball-loc ball))))))

(define (tick world)
  (make-world (move-ball (make-ball (ball-loc (world-ball world))
                                    (bounce (ball-velocity (world-ball world))
                                            (collision (ball-loc (world-ball world))
                                                       (world-rec world)))))
  (world-rec world)
  (world-bricks world)))



(define (mouse world mouse-x mouse-y mouse-event)
  (cond
    [(string=? mouse-event "move") (make-world (world-ball world)
                                               (make-rec (make-posn mouse-x REC-Y-POS))
                                               (world-bricks world))]
    [else world]))



(define INITIAL-BALL (make-ball (make-posn 20 12)
                                (make-vel 1 2)))

(define REC-Y-POS (- HEIGHT (+ 20 REC-HEIGHT)))
(define INITIAL-REC (make-rec (make-posn 100 REC-Y-POS)))
(define INITIAL-BRICKS (list
                        (make-brick (make-posn 50 100))
                        (make-brick (make-posn 111 100))           
                        (make-brick (make-posn 172 100))
                        (make-brick (make-posn 233 100))
                        (make-brick (make-posn 294 100))
                        (make-brick (make-posn 355 100))
                        (make-brick (make-posn 50 131))
                        (make-brick (make-posn 111 131))           
                        (make-brick (make-posn 172 131))
                        (make-brick (make-posn 233 131))
                        (make-brick (make-posn 294 131))
                        (make-brick (make-posn 355 131))
                        (make-brick (make-posn 50 162))
                        (make-brick (make-posn 111 162))           
                        (make-brick (make-posn 172 162))
                        (make-brick (make-posn 233 162))
                        (make-brick (make-posn 294 162))
                        (make-brick (make-posn 355 162))))






(define-struct world (ball rec bricks))

(define INITIAL-WORLD-STATE (make-world INITIAL-BALL INITIAL-REC INITIAL-BRICKS))

(define (main state)
  (big-bang state (on-mouse mouse) (on-tick tick 0.01) (to-draw render)))

(main INITIAL-WORLD-STATE)

The Ball just flies straight through the bricks. I've added this following code to it:

;--------------------------------------------------------------------------------------------------
(define (overlap?
          brick-one-x brick-one-y brick-one-width brick-one-height
          brick-two-x brick-two-y brick-two-width brick-two-height)
          (cond
            [(and
                (and
                 (>= (+ brick-one-x brick-one-width) brick-two-x); within right bounds
                 (<= brick-one-x (+ brick-two-x brick-two-width)); within left bounds
                )
                (and
                 (>= (+ brick-one-y brick-one-height) brick-two-y) ;within top bounds
                 (<= brick-one-y (+ brick-two-y brick-two-height)) ; within bottom bounds
                ))
                #t]
             [else ;not overlapping
                #f]))



(define (brick-collide-ball? brick ball)
  (if
    (overlap?
       ;balls dimensions and location
        (posn-x (ball-loc ball))
        (posn-y (ball-loc ball))
        10
        10
     ;first brick in list
        (posn-x (brick-pos brick))
        (- (posn-y (brick-pos brick)) 10)
        60
        30)
         #t 
         #f))

(define (delete-bricks bricklist ball)
  (cond
    [(empty? bricklist) empty]
     ((cons? bricklist)
      (if (brick-collide-ball? (first bricklist) ball)
          (delete-bricks (rest bricklist) ball)
          (cons (first bricklist)(delete-bricks (rest bricklist) ball))))))

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

but it does not seem to make an impact on anything. I'm stuck now and would appreciate any tips to improve!

回答1:

I used the following in my version of the Atari classic:

(define (colliding? b1 b2)
  (match-define (body x1 y1 w1 h1) b1)
  (match-define (body x2 y2 w2 h2) b2)
  (not (or (eq? b1 b2)
           (< (+ x1 w1) x2) (> x1 (+ x2 w2))
           (< (+ y1 h1) y2) (> y1 (+ y2 h2)))))

Here both the paddle and the ball is a "body", which is an rectangle with and (x,y) position and a width, w, and a height h.

(struct body (x y w h) #:transparent)

Note: If you ball happens to move very fast, it can happen that it "jumps over" the paddle. This can be fixed by having a maximum velocity.



回答2:

One way you might be able to address this problem without falling into a velocity-jump in the other answer is to calculate the intersection of the line along which the ball is currently traveling and the bounding line of (presumably the front of) the paddle. Then you just determine if this point is actually on the line segment of the paddle and the line segment between the ball's previous position and it's hypothetical position to determine if a collision would have happened "between" frames and handle that reflection. A more thorough check would be to do the same thing with the edge of the paddle since that game did allow the paddle to hit the ball from the side.

Example calculation:

#lang racket/base
(require racket/struct)
(struct line (a b c) #:transparent) ; ax+by=c
(struct pos (x y) #:transparent)
(define (meet l1 l2)
  (let-values (((a b c) (apply values (struct->list l1)))
               ((d e f) (apply values (struct->list l2))))
    (let ((denominator (- (* a e) (* b d))))
      (if (zero? denominator)
          'parallel ;probably impossible in your game
          (let ((x* (- (* c e) (* b f)))
                (y* (- (* a f) (* c d))))
            (pos (/ x* denominator)
                 (/ y* denominator)))))))