Showing posts with label collision. Show all posts
Showing posts with label collision. Show all posts

Wednesday, April 22, 2015

Simple DOM Collision Tracking

Back on the 15th, I had this awesome frog dodgeball game right in the third frame of my Amphibian.com comic. While today's Chaucer-inspired tale told in Middle English is amusing, it lacks the technical complexity of the dodgeball game. One of the many pieces that came together to enable a game inside a webcomic was simple collision detection between DOM nodes.

Frog Dodgeball
The comic frames, even in the dodgeball comic, are just "regular" DIV tags. I didn't use an HTML5 Canvas or anything like that. The frogs and balls are just IMG tags. To determine if a ball hits a frog, I had to come up with some reasonably efficient way of detecting the collision between two elements in the DOM and then taking some action based on a possible collision.

I used a class-based detection strategy similar to what I learned while using Crafty to make my entry for the GitHub Game Off 2015. Any element that should be checked for collisions with other objects should have the "collision" class applied to it. There isn't necessarily any special CSS associated with that class - it just has to be on the element. The element should also define a value for the field "collidesWith" that will be the name of another class. Only elements that have that class will be checked for collisions. I used a simple circle-based intersection formula for speed, so elements that can collide with other should define a value for the "collisionRadius" field as well. Finally, the elements should specify a function, hit, to be called if a collision is detected. The function will be passed an array of objects that are in collision.

Here is an example of how I set up the ball elements.

var ball = $('#ball');
var newBall = ball.clone()[0];

$(newBall).addClass("collision");

newBall.collisionRadius = $(newBall).width() / 2;
newBall.collidesWith = "frog";
newBall.hit = handleHit;

All balls are clones of a master ball element (line 2). I add the "collision" class, set the radius, specify what kinds of elements with which the balls collide, and specify the function to call if there is a hit. Here is an example of the handleHit function:

function handleHit(elemArray) {

    $(this).removeClass("collision"); // this ball won't hit anything else

    // make the element hit (assumes only 1 in this case)
    // semi-transparent for half a second
    $(elemArray[0]).css("opacity", "0.5");
    setTimeout(function() {
        $(elemArray[0]).css("opacity", "1");
    }, 500);

}

So that's the setup. But of course I had to add the collision-checking code to the comic framework. I already had a function that handled the animation, so I added a call to checkCollisions after each position update.

function checkCollisions() {

    $("img.collision").each(function(idx, elem) {

        if (elem.collidesWith) {

            var cx1 = Number($(elem).css('left').replace('px', ''));
            var ox1 = cx1 + ($(elem).width() / 2);

            var cy1 = Number($(elem).css('top').replace('px', ''));
            var oy1 = cy1 + ($(elem).height() / 2);

            var hits = [];
            $("img." + elem.collidesWith).each(function(idx2, elem2) {

                var cx2 = Number($(elem2).css('left').replace('px', ''));
                var ox2 = cx2 + ($(elem2).width() / 2);

                var cy2 = Number($(elem2).css('top').replace('px', ''));
                var oy2 = cy2 + ($(elem2).height() / 2);

                var distX = (ox2) - (ox1);
                var distY = (oy2) - (oy1);
                var magSq = distX * distX + distY * distY;
                if (magSq < (elem.collisionRadius + elem2.collisionRadius) * (elem.collisionRadius + elem2.collisionRadius)) {
                    hits.push(elem2);
                }

            });

            if (hits.length > 0 && elem.hit) {
                elem.hit(hits);
            }

        }

    });

}

My function iterates over all images that have the "collision" class. If those elements have a "collidesWith" defined, I get the X and Y coordinates for the center of the image. I'll need these for the collision check. Then I iterate over all the image elements that have the class specified by the "collidesWith" field and get their centers. The collision check starts on line 22. Basically, it makes two circles at the center-points of the images with the specified radii. A collision is detected if the circles overlap at all. If they do, I add that element to the hits array. At the end I call the hit function, passing the array, if there were any hits.

It all worked pretty well, and now I'll have it all ready for the next time I want to add a pointless game to a comic!

Amphibian.com comic for 22 April 2015

Wednesday, July 6, 2011

Your Polygons are Hitting Each Other

While working on HTML5 games, I sometimes need something in JavaScript that I'm not able to find. On one such occasion I found myself in need of a JavaScript polygon object that would support collision detection with other polygons. This algorithm, using the Separating Axis Theorem, is well-known and had many implementations in other languages. It wasn't too difficult to convert it to JavaScript. While I was at it, I added methods to support determining if the polygon contains a given point (to detect if I was clicking on it) and rotating the polygon.

You can see what I came up with here, and I have a test page for it here.

As you can see by viewing the source of the test page, it is fairly easy to use. It is designed to combine with the HTML5 canvas element.

To create a polygon centered at a given point and using center-relative coordinates for the vertices, you do something like this:
var poly = new Polygon( { x: 50, y: 50 }, "#00FF00");
poly.addPoint( { x: -20, y: -20 } );
poly.addPoint( { x: -20, y: 20 } );
poly.addPoint( { x: 20, y: 20 } );
If you want to use all absolute coordinates for the vertices, you can do that too:
var poly = new Polygon( { x: 50, y: 50 }, "#00FF00");
poly.addAbsolutePoint( { x: 130, y: 130 } );
poly.addAbsolutePoint( { x: 130, y: 170 } );
poly.addAbsolutePoint( { x: 170, y: 170 } );
To rotate, just call the rotate method with the number of radians you want to rotate. Remember, to convert degrees to radians, multiply by Pi/180.
poly.rotate(0.78539); // 45 degrees
So now you have no excuse for not making a fun HTML5 canvas game. I'd like to see a game about cheese-making. I think that would be awesome.