Collision Detection

2007/07/22 – alpha release
The code needs some cleanup, but everything is here.
Suggestions welcome in the forum.

Ball Physics. Unit Tests and the Math Object.

Show the Ball

<div id="ball" class="sprite"></div>
#ball, .spare {
  position: absolute;
  background-image:url(../../sprites/ball.png);
  height:16px;
  width:16px;
  padding:0px;
  margin:0px;
}

#ball {
  top: 274px;
  left: 268px;
}
var Ball = Class.create();
Ball.prototype = Object.extend(new Sprite(), {
    initialize : function(id) {
        this.id = id;
        this.node = $(this.id);
        this.dx = 0;
        this.dy = 0;
        this.on_paddle = true;
    },
    
});

Stick to Paddle before Serve

 // this could be an array of sprites
        ball.tick();
var ball;
var serveSpeed = -4;
    ball = new Ball("ball");
    stickToPaddle : function (){
        // set x first, in case the ball is hidden in the lake
        // (otherwise, it would flash briefly)
        this.setX(paddle.getX()+(paddle.getW()/2) - (this.getW()/2));
        this.setY(paddle.getY()-this.getH());
        this.on_paddle = true; 
    },


    tick : function() {
        if (this.on_paddle) {
            this.stickToPaddle();
        } else {
        }
    }    

Serve Ball on Up Arrow

 case Event.KEY_UP:
            if (ball.on_paddle) {
                ball.serve(paddle.velocity+0.25, serveSpeed);
            }
            break;
    serve : function (dx, dy) {
        this.on_paddle = false;
        this.dy = dy;
        this.moveBy(0,-2);
        this.dx = dx;
    },

Maintain a List of Obstacles

This means anything the ball can hit.

@TODO: Define an Obstacle Interface

Basically just sprite plus the onHit method.

// consolidate these! 
Ball.prototype.smashBricks = function () {
    var bounce = null; // impact vector        
    for (var i=0; i < bricks.length; i++) {
        if (bricks[i].solid) {
            var vector = this.collide(bricks[i]);
            if (vector != null) {
                bricks[i].onHit();
                return vector; // can only hit one thing at a time
            }
        }
    }
    return bounce;
}

Ball.prototype.checkWalls = function () {
    // walls:
    if ((this.getX() + this.dx <= 0) 
    || (this.getX() + this.dx + this.getW() >= game.getW())){ 
        this.dx *= -1; 
        soundManager.play('bounce');
    }
    // ceiling:    
    if (this.getY() + this.dy <= 0) { 
        this.dy *= -1; 
        soundManager.play('bounce');
    }
}

Ball.prototype.hittingFloor = function () {
    return this.getY() + this.dy + this.getH() >= game.getH();
}

Ball.prototype.checkPaddle = function () {
    vector = this.collide(paddle);
    if (vector) {
        soundManager.play('bounce');
        this.bounce(vector);
        this.dx += parseInt(paddle.velocity * paddle.friction);
    }
}

Simulate Ball Physics

This as a very simplified physics with no friction, gravity, etc.

collide with walls, bricks

onHit : function() {
        if (this.shade-1 <= 0) {
            this.node.style['display'] = 'none';
            this.solid = false;
	   Brick.count -= 1;
            if (Brick.count <= 0) {
	       console.log('level clear');
	       level_init(current_level+1);
            }
        } else {
            this.setShade(this.shade-1);
        }
    }    

@TODO: unit test/cleanup collision code

// we have to do reaction separately from detection, 
            // because we might smash two bricks at once, and if 
            // we multiplied by -1 for both, the two bounces would 
            // cancel each other out!
            if (bounce = this.smashBricks()) {
                this.bounce(bounce);
            }
            this.checkPaddle();
            this.checkWalls();
            if (this.hittingFloor()) {
            } else {
                this.moveBy(this.dx, this.dy);
            }
// @TODO: move these to collision.js
// #### Ball Physics #############################

var BOUNCE = -1;
var NO_BOUNCE = 1;


Ball.prototype.ticksToHLine = function (x1, x2, hline) {
    // there's a special case when dx = 0 (moving straight up)
    if (this.dx == 0) { 
        x = this.getX();
    } else {
        // in other cases, we need to use algebra to find
        // intersection of the line and the ball's path.
        //
        // start with the line of the ball's trajectory:
        //        y = (this.dy / this.dx) * (x-this.getX()) + this.getY()
        //
        // the two lines intersect where y == hline, so:
        //        hline = (this.dy / this.dx) * (x-this.getX()) + this.getY()
        // now solve for x:
        x = (hline - this.getY()) * (this.dx / this.dy) + this.getX();
    }

    // but we're dealing only with a line SEGMENT, so
    // we need to see if the point falls between the endpoints:
    if ((x < x1) || (x > x2)) return null;

    // if we're still here, we know the ball's path intersects
    // the line, but it only counts if the hit is in FRONT of the
    // ball, so we need to count the ticks until impact. 
    ticks = (hline - this.getY()) / this.dy;
    //alert(ticks);
    
    // negative ticks would put the line BEHIND the ball,
    // so we need to discard those results
    return (ticks >= 0) ? ticks : null;
}

Ball.prototype.ticksToVLine = function (vline, y1, y2){
    // the line for the ball's trajectory, where x = vline
    // again, there's a special case when moving straight 
    // up and down to avoid division by zero
    if (this.dx == 0) {
        // the other function can handle the corner just fine
        // and since there's no other possible 
        // way a ball moving straight up or down can
        // hit the side, we can just stop here.
        return null;
    } else {
        y = (this.dy / this.dx) * (vline-this.getX())  + this.getY()
        // the rest of the logic is similar to the horizontal check
        if ((y < y1) || (y > y2)) return null;
        ticks = (y - this.getY()) / this.dy;
        return (ticks>=0) ? ticks : null; 
    }
}

Ball.prototype.collide = function (rect) {
    
    ballLeft = this.getX();
    ballRight = ballLeft+this.getW();
    ballTop = this.getY();
    ballBottom = ballTop + this.getH();
    
    rectLeft = rect.getX();
    rectTop = rect.getY();
    rectRight = rectLeft+rect.getW();
    rectBottom = rectTop+rect.getH();    

    diameter = this.getW();
    radius = diameter /2;
    
    goingUp = this.dy < 0;
    goingLeft = this.dx <0;

    // the ball can not possibly hit both the top and bottom of the
    // rectangle at the same time. Further, if the ball is going down, it
    // can't possibly hit the bottom of the rectangle, and if it's going
    // up, it can't possibly hit the top. So we only have to check against
    // one horizontal line:
    
    yTicks = (goingUp) ? 
	this.ticksToHLine(rectLeft-diameter, rectRight+radius, rectBottom)
    // and note that when going down, the bottom of ball will hit first, 
    // so we need to subtract the ball's height
        :this.ticksToHLine(rectLeft-diameter, rectRight+radius,
			   rectTop-diameter);
    
    // the same principles apply for the left and right sides:
    xTicks = (goingLeft) ? 
	this.ticksToVLine(rectRight, rectTop-radius, rectBottom+radius)
        :this.ticksToVLine(rectLeft-diameter, rectTop-radius, 
			   rectBottom+radius);    

    //if (rect == paddle)
    //   $('debug').innerHTML = "(" + xTicks + ", " + yTicks + ")";
        
    hitX = (xTicks != null) && xTicks <= 1;
    hitY = (yTicks != null) && yTicks <= 1;
    
    // now. what if it would collide with BOTH?
    // in that case, it could hit both at the same time (direct hit on corner)
    // or it could hit one first, in which case we discard the other
    // (in real life, it could also glance off the corner, but our simplified
    // physics model ignores this possibility)
    
    if (! (hitX || hitY)) { return null };
    
    if (xTicks == yTicks) {
        // treat corners as hitting the top or bottom;
        hitX = false;
    } 
    return [(hitX ? BOUNCE : NO_BOUNCE),
            (hitY ? BOUNCE : NO_BOUNCE)];    
}

Ball.prototype.bounce = function (vector) {
    this.dx *= vector[0];
    this.dy *= vector[1];
}

React Only to First Collision

test first coding: collision detection

jsunit (already included in your zip file)

debugger can help , but tests are even better

like having a spell checker for your code

Scroll to Top