1
\$\begingroup\$

One of the Cocos2D games I am working on has circular explosion effects. These explosion effects need to deal a percentage of their set maximum damage to all game characters (represented by rectangular bounding boxes as the objects in question are tanks) within the explosion radius. So this boils down to circle to rectangle collision and how far away the circle's radius is from the closest rectangle edge. I took a stab at figuring this out last night, but I believe there may be a better way. In particular, I don't know the best way to determine what percentage of damage to apply based on the distance calculated.

Note : All tank objects have an anchor point of (0,0) so position is according to bottom left corner of bounding box. Explosion point is the center point of the circular explosion.

        TankObject * tank = (TankObject*) gameSprite;

        float distanceFromExplosionCenter;
        // IMPORTANT :: All GameCharacter have an assumed (0,0) anchor
        if (explosionPoint.x < tank.position.x) {
            // Explosion to WEST of tank
            if (explosionPoint.y <= tank.position.y) {
                //Explosion SOUTHWEST
                distanceFromExplosionCenter = ccpDistance(explosionPoint, tank.position);

            } else if (explosionPoint.y >= (tank.position.y + tank.contentSize.height)) {
                // Explosion NORTHWEST
                distanceFromExplosionCenter = ccpDistance(explosionPoint,
                                                          ccp(tank.position.x, tank.position.y + tank.contentSize.height));

            } else {
                // Exp center's y is between bottom and top corner of rect
                distanceFromExplosionCenter = tank.position.x - explosionPoint.x;

            } // end if
        } else if (explosionPoint.x > (tank.position.x + tank.contentSize.width)) {
            // Explosion to EAST of tank
            if (explosionPoint.y <= tank.position.y) {
                //Explosion SOUTHEAST
                distanceFromExplosionCenter = ccpDistance(explosionPoint,
                                                          ccp(tank.position.x + tank.contentSize.width,
                                                                              tank.position.y));

            } else if (explosionPoint.y >= (tank.position.y + tank.contentSize.height)) {
                // Explosion NORTHEAST
                distanceFromExplosionCenter = ccpDistance(explosionPoint,
                                                          ccp(tank.position.x + tank.contentSize.width,
                                                              tank.position.y + tank.contentSize.height));

            } else {
                // Exp center's y is between bottom and top corner of rect
                distanceFromExplosionCenter = explosionPoint.x - (tank.position.x + tank.contentSize.width);

            } // end if
        } else  {
            // Tank is either north or south and is inbetween left and right corner of rect
            if (explosionPoint.y < tank.position.y) {
                // Explosion is South
                distanceFromExplosionCenter = tank.position.y - explosionPoint.y;

            } else {
                // Explosion is North
                distanceFromExplosionCenter = explosionPoint.y - (tank.position.y + tank.contentSize.height);

            } // end if
        } // end outer if
        if (distanceFromExplosionCenter < explosionRadius) {
            /* 
             Collision :: Smaller distance larger the damage
             */
            int damageToApply;
            if (self.directHit) {
                damageToApply = self.explosionMaxDamage + self.directHitBonusDamage;
                [tank takeDamageAndAdjustHealthBar:damageToApply];
                CCLOG(@"Explsoion-> DIRECT HIT with total damage %d", damageToApply);
            } else {
                // TODO adjust this... turning out negative for some reason...
                damageToApply = (1 - (distanceFromExplosionCenter/explosionRadius) * explosionMaxDamage);
                [tank takeDamageAndAdjustHealthBar:damageToApply];
                CCLOG(@"Explosion-> Non direct hit collision with tank");
                CCLOG(@"Damage to apply is %d", damageToApply);
            } // end if

        } else {
            CCLOG(@"Explosion-> Explosion distance is larger than explosion radius");
        } // end if
    } // end if

Questions:

1) Can this circle to rect collision algorithm be done better? Do I have too many checks?

2) How to calculate the percentage based damage? My current method generates negative numbers occasionally and I don't understand why (Maybe I need more sleep!). But, in my if statement, I ask if distance < explosion radius. When control goes through, distance/radius must be < 1 right? So 1 - that intermediate calculation should not be negative.

Appreciate any help/advice!

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

You could simplify the checks by calculating the squared distance from the center of the explosion to each corner, then comparing the minimum squared value to the square of the radius to see if there is an overlap. If so, then calculate the square root of the minimum distance to calculate the percentage of damage. However, your code looks efficient enough, so I'd recommend profiling both options to see which is better. What you have is well documented and readable and I don't think the performance needs optimization.

The reason for the occasional negative values for damage is the order of operations. You are performing this calculation:

(distanceFromExplosionCenter/explosionRadius) * explosionMaxDamage

before subtracting the result from 1. If the result of that multiplication is greater than 1, you'll get a negative damage value.

Change your existing code to:

damageToApply = (1 - (distanceFromExplosionCenter/explosionRadius)) * explosionMaxDamage;

This will calculate the percentage first, then multiply by maximum damage so that you don't end up with negative values.

\$\endgroup\$
2
  • \$\begingroup\$ Good catch with the parenthesis. Changed it and it works just fine. I will try your suggestion tonight and compare the results/efficiency. \$\endgroup\$ Jul 3, 2013 at 14:21
  • \$\begingroup\$ Accepted.. Would have liked to see some alternatives for calculating damageToApply. The catch on the parenthesis saved me many a headache. \$\endgroup\$ Jul 9, 2013 at 1:53

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .