Search code examples
javascripthtmlcssphysicsgame-physics

How to throw a ball into a cup?


I have an image of a ball, a cup, and an inner and outer div that represent a throw power bar.
When the user clicks the ball, the power bar starts to increment and then decrements. When the user clicks the ball a 2nd time, the throw power bar stops and the ball is thrown.

As I have began coding this, I realized certain things are going to be extremely complicated, even though the idea itself is rather simple.

For example, I want the ball to be able to "bounce", meaning I will need to not only keep track of the balls x and y coordinates, but also a z coordinate representing depth.

When the ball falls to bounce, the z coordinate with be decremented, and the image of the ball should be scaled down in size, and when it begins bouncing back up, it should scale up in size, again based on the z coordinate. I also want the ball to bounce off the cup if the z coordinate is below a certain value by the time it reaches the cup, go into the cup if it is a certain sweet spot, or go over the cup if it's z value is over that sweet spot.

In the interest of keeping this somewhere short, I'll just post what I have so far. This example is lacking certain things that I was hoping people here could help me with.

http://jsfiddle.net/7Lsh78nw/

<html>
    <head>
    <style>
        #ball {
            position:absolute;
            width:75px;
            height:75px;
        }

        #cup1 {
            position:absolute;
            left:375px;
        }

        #outerPowerMeter {
            position:absolute;
            width:25px;
            height:100px;
            background-color:red;
        }

        #innerPowerMeter {
            position:absolute;
            width:25px;
            height:100px;
            background-color:black;
        }
    </style>

    <script>
        window.onload = function() {
            var ball = document.getElementById("ball");
            var yPos = 500;
            var xPos = 400;
            var zPos = 100;
            var ballWidth = 75;
            var ballHeight = 75;
            var throwBallInterval;
            var changeBallSizeInterval;

            ball.style.top = yPos + "px";
            ball.style.left = xPos + "px";

            var cup1 = document.getElementById("cup1");

            var powerMeter = document.getElementById("innerPowerMeter");
            var powerMeterValue = 0;
            var powerMeterHeight = 100;
            var powerMeterActive = false;
            var powerMeterInterval;

            powerMeter.style.height = powerMeterHeight + "px";

            ball.onclick = function() {
                if (powerMeterActive == false) {
                    powerMeterActive = true;
                    startPowerMeter();
                } else {
                    powerMeterActive = false;
                    stopPowerMeter();
                    throwBall();
                }
            }

            function throwBall() {
                throwBallInterval = setInterval(function() {
                    yPos = yPos - 1;
                    ball.style.top = yPos + "px";
                }, 1);

                changeBallSizeInterval = setInterval(function() {
                    zPos = zPos - 1;
                    ballWidth = ballWidth - 1;
                    ballHeight = ballHeight - 1;
                    ball.style.width = ballWidth;
                    ball.style.height = ballHeight;
                }, 100);
            }

            function startPowerMeter() {
                var increment = true;
                powerMeterInterval = setInterval(function() {
                    if (increment == true) {
                        powerMeterValue = powerMeterValue + 1;
                        powerMeter.style.height = (powerMeterHeight - powerMeterValue) + "px";
                        if (powerMeterValue == 100) {
                            increment = false;
                        }
                    } else {
                        powerMeterValue = powerMeterValue - 1;
                        powerMeter.style.height = (powerMeterHeight - powerMeterValue) + "px";
                        if (powerMeterValue == 0) {
                            increment = true;
                        }
                    }
                },1);
            }

            function stopPowerMeter() {
                clearInterval(powerMeterInterval);
            }

            function detectCollision() { }

            function detectGoal() { }
        }
    </script>

    </head>
    <body>
        <img id="cup1" src="http://beerwar.com/game/images/cup.png">
        <img id="ball" src="http://beerwar.com/game/images/ball.png">
        <div id="outerPowerMeter">
            <div id="innerPowerMeter"></div>
        </div>
    </body>
</html>

Solution

  • Since you posted such a detailed case, i thought i give you some pointers. Mind you: this is mostly vector math. I'm not a physicist either, but vector math isn't that complicated luckily! Some pythagoras here and there and you are set.

    A good an fast library for that is glMatrix

    A couple of things to get you going. Please note: it is pseudo code, but it does explain the concept of it.

    • Keep a vector for the position of the ball
    • Keep a vector for the position of the cup (where the ball should hit)
    • Keep a vector for the position of 'the camera' (since you want to scale the ball based on distance from the camera. Doesn't have to be accurate, just get the idea across)
    • Keep a vector for the 'direction of the force' you are going to apply to the ball. (this can be multiplied with the force from your force meter)
    • Keep a vector for the 'velocity of the ball'
    • Keep a vector for the 'gravity'

    Your 'throw' function would become something along the lines of:

    ball.velocity = throw.direction * throw.power
    setInterval(tick,50);
    

    Basicly, your 'tick' function (the function you apply every x-time)

    ball.velocity += gravity; // we apply gravity to the speed of the ball. Pulling it down
    ball.position = ball.position + ball.velocity // we add the velocity to the position every tick
    
    if (ball.position.y < ball.radius) // if the ball is below its radius, it is colliding with the ground
    {
        ball.position.y = 0 - ball.position.y; // just invert its 'up' position, to make it not collide with the ground anymore
        // to make the ball go back up again, invert its 'up' velocity. Gravity will get it down eventually
        // dampening applied so it will bounce up less every time. For instance make that 0.9.
        ball.velocity.y = 0 - (ball.velocity.y * dampening);
    }
    // the scale of the ball is not determined by its height, but by its distance from the camera
    distanceFromCamera = (camera.position - ball.position).length()
    ball.scale = 100 - (distanceFromCamera / scaleFactor);
    
    // to make a simply guess if we are hitting the target, just check the distance to it.
    distanceFromTarget = (cup.target.position - ball.position).length()
    if (distanceFromTarget <= cup.target.radius) // if we 'hit the target'
        handleHit()