js小球重力和碰撞效果

2,867 阅读6分钟

最近在玩小程序中一个比较火的游戏,最!强!弹!一!弹!!!,真的是超级模型的一款小游戏,打发时间堪比抖音。。。。。。,今天就来研究下关于小球如何有重力效果互相碰撞反弹的效果。

1. 重力效果实现

  • 首先我们当然是要先画个球球啦!
.ball {
    width: 100px;
    height: 100px;
    border-radius: 50%;
    background: #ff5532;
    position: absolute;
}
  • 然后我们需要给小球规定一个初始的X,Y上的速度和一个让小球运动起来的定时器timer
var speedX = 6;
var speedY = 0;
var timer = null;
  • 定义小球在X,Y上可以移动的最大距离。
//document.documentElement.clientHeight(屏幕高度)
//ball.offsetHeight(小球的高度)
var maxMoveH = document.documentElement.clientHeight - ball.offsetHeight;
var maxMoveW = document.documentElement.clientWidth - ball.offsetWidth;
  • 如果达到最小或最大距离,折返运动。
//y轴横向移动超过做大可移动高度,就反弹折回。 
if(ballMoveY >= maxMoveH || ballMoveY <= 0){
    speedY = -speedY;
    ballMoveY = maxMoveH;
}
//x轴横向移动超过做大可移动宽度,就反弹折回。
if(ballMoveX >= maxMoveW){
    speedX = -speedX;
    //定死最大宽度
    ballMoveX = maxMoveW;
    speedX = speedX*0.95;
}
//移动到右边边界就自动折返。
if(ballMoveX <= 0){
    speedX = -speedX;
    speedX = speedX*0.95;
}
  • 为了防止小球不在弹起后,还在x轴上移动。
if(Math.abs(speedX) < 0.2 && ballMoveY >= maxMoveH){
    clearInterval(timer);
}
  • 下面是完整代码。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style type="text/css">
        .ball {
            width: 100px;
            height: 100px;
            border-radius: 50%;
            background: #ff6632;
            position: absolute;
        }
    </style>
</head>
<body>
    <button id="moveStart">点击触发</button>
    <div id="ball" class="ball"></div>
</body>
<script>
    window.onload = function() {
        var ball = document.getElementById("ball");
        var moveStart = document.getElementById("moveStart");
        moveStart.onclick = function() {
            var speedX = 6;
            var speedY = 0;
            var timer = null;
            moveStart();
            function moveStart() {
                clearInterval(timer);
                timer = setInterval(function(){ 
                    var ballMoveY, ballMoveX;
                    var maxMoveH = document.documentElement.clientHeight - ball.offsetHeight;
                    var maxMoveW = document.documentElement.clientWidth - ball.offsetWidth;
                    speedY += 6;
                    //横向移动会有能量损耗
                    speedX = speedX*0.99;

                    ballMoveY = ball.offsetTop + speedY;
                    ballMoveX = ball.offsetLeft + speedX;

                    if(ballMoveY >= maxMoveH || ballMoveY <= 0){
                        speedY = -speedY;
                        ballMoveY = maxMoveH;
                    }

                    //x轴横向移动超过做大可移动宽度,就反弹折回
                    if(ballMoveX >= maxMoveW){
                        speedX = -speedX;
                        ballMoveX = maxMoveW;
                        speedX = speedX*0.95;
                    }


                    if(ballMoveX <= 0){
                        speedX = -speedX;
                        speedX = speedX*0.95;
                    }

                    //如果速度小于0.2且小球不在弹起,则不让小球在移动了
                    if(Math.abs(speedX) < 0.2 && ballMoveY >= maxMoveH){
                        clearInterval(timer);
                    }

                    ball.style.top = ballMoveY + 'px';
                    ball.style.left = ballMoveX + 'px';
                },30) 
            }
        }
    }
</script>
</html>

上述大概就是简单的一个js仿重力的简单实现。

现在我们来看一看神奇的碰撞效果。

首先,我们需要知道实现小球碰撞需要哪些方法。

  1. 随机生成方法。
function random(type) {
    switch(type){
        case 'color': 
            return 'rgb(' + Math.floor(Math.random()*255) + ',' +  Math.floor(Math.random()*255) + ',' +  Math.floor(Math.random()*255) + ')';
        case 'top':
            return Math.floor(Math.random()*280) + 'px';
        case 'left':
            return Math.floor(Math.random()*280) + 'px';
        case 'speed':
            return Math.floor((Math.random() - 0.5)*20);
        default: 
            return false;
    }
}
  1. 定义一个碰撞区域。
 #content {
    width: 300px;
    height: 300px;
    border: 1px solid #f00;
    position: relative;
}
  1. 创建小球。
function createBall(num){
    var p = null;
    for(var i=0; i < num; i++){
        p = document.createElement('p');
        p.innerHTML = i + 1;
        p.style.background = random('color');
        p.style.color = '#fff';
        p.style.top = random('top');
        p.style.left = random('left');
        p.style['textAlign'] = 'center';
        p.id = 'ball' + i;
        content.appendChild(p);
        ballList.push(p);
        moveBall(p);
    }
}
  1. 小球移动。
function moveBall(ball, startSX, startSY) {
    var speedX = random('speed'),
        speedY = random('speed'),
        ballMoveX = null,
        speedObj = {},
        ballMoveY = null;
    timer = setInterval(function(){
        ballMoveY = ball.offsetTop + speedY;
        ballMoveX = ball.offsetLeft + speedX;
        if(ballMoveX >= maxMoveX || ballMoveX <= 0){
            speedX = -speedX;
        }
        if(ballMoveY >= maxMoveY || ballMoveY <= 0){
            speedY = -speedY;
        }
        ball.style.top = ballMoveY + 'px';
        ball.style.left = ballMoveX + 'px';

        speedObj = crashBall(ball, speedX, speedY);
        speedX = speedObj.speedX;
        speedY = speedObj.speedY;
    }, 30)
}
  1. 小球碰撞。

存在8种位置碰撞的情况。

function crashBall(ball, speedX, speedY) {
    var speedObj = {};
    for(var i = 0; i<ballList.length; i++){
        //自己不跟自己比较
        if(ball.id !== ballList[i].id && Math.abs(ball.offsetLeft - ballList[i].offsetLeft) <= 20 && Math.abs(ball.offsetTop - ballList[i].offsetTop) <= 20){
            //**********对象小球在碰撞小球方位的8个情况***********
            //左上方
            if(ball.offsetLeft < ballList[i].offsetLeft && ball.offsetTop < ballList[i].offsetTop){
                speedX > 0 && (speedX = -speedX);
                speedY > 0 && (speedY = -speedY);
            }
            //正上方(不会影响水平运动的速度)
            if(ball.offsetLeft === ballList[i].offsetLeft && ball.offsetTop < ballList[i].offsetTop){
                speedY > 0 && (speedY = -speedY);
            }
            //右上方
            if(ball.offsetLeft > ballList[i].offsetLeft && ball.offsetTop < ballList[i].offsetTop){
                speedX < 0 && (speedX = -speedX);
                speedY > 0 && (speedY = -speedY);
            }
            //正右方
            if(ball.offsetLeft > ballList[i].offsetLeft && ball.offsetTop === ballList[i].offsetTop){
                speedX < 0 && (speedX = -speedX);
            }
            //右下方
            if(ball.offsetLeft > ballList[i].offsetLeft && ball.offsetTop > ballList[i].offsetTop){
                speedX < 0 && (speedX = -speedX);
                speedY < 0 && (speedY = -speedY);
            }
            //正下方
            if(ball.offsetLeft === ballList[i].offsetLeft && ball.offsetTop > ballList[i].offsetTop){
                speedY < 0 && (speedY = -speedY);
            }
            //左下方
            if(ball.offsetLeft < ballList[i].offsetLeft && ball.offsetTop > ballList[i].offsetTop){
                speedX > 0 && (speedX = -speedX);
                speedY < 0 && (speedY = -speedY);
            }
            //正左方
            if(ball.offsetLeft < ballList[i].offsetLeft && ball.offsetTop === ballList[i].offsetTop){
                speedX > 0 && (speedX = -speedX);
            }
        }
    }
    speedObj.speedX = speedX;
    speedObj.speedY = speedY;
    return speedObj;
}

完整代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style type="text/css">
        html,body {
            margin: 0;
            padding: 0;
        }

        #content {
            width: 300px;
            height: 300px;
            border: 1px solid #f00;
            position: relative;
        }

        #content p {
            position: absolute;
            width: 20px;
            height: 20px;
            border-radius: 50%;
            margin: 0;
        }

    </style>
</head>
<body>
    <div id="content"></div>
</body>
<script>
    window.onload = function() {
        var content = document.getElementById('content');
        var maxMoveX = content.clientWidth - 20;
        var maxMoveY = content.clientHeight - 20;
        var timer = null;
        var ballList = [];
        //0. 公共方法(随机创建)
        function random(type) {
            switch(type){
                case 'color': 
                    return 'rgb(' + Math.floor(Math.random()*255) + ',' +  Math.floor(Math.random()*255) + ',' +  Math.floor(Math.random()*255) + ')';
                case 'top':
                    return Math.floor(Math.random()*280) + 'px';
                case 'left':
                    return Math.floor(Math.random()*280) + 'px';
                case 'speed':
                    return Math.floor((Math.random() - 0.5)*20);
                default: 
                    return false;
            }
        }
        //1. 创建小球
        function createBall(num){
            var p = null;
            for(var i=0; i < num; i++){
                p = document.createElement('p');
                p.innerHTML = i + 1;
                p.style.background = random('color');
                p.style.color = '#fff';
                p.style.top = random('top');
                p.style.left = random('left');
                p.style['textAlign'] = 'center';
                p.id = 'ball' + i;
                content.appendChild(p);
                ballList.push(p);
                moveBall(p);
            }
        }
        //2. 移动小球
        function moveBall(ball, startSX, startSY) {
            var speedX = random('speed'),
                speedY = random('speed'),
                ballMoveX = null,
                speedObj = {},
                ballMoveY = null;
            timer = setInterval(function(){
                ballMoveY = ball.offsetTop + speedY;
                ballMoveX = ball.offsetLeft + speedX;
                if(ballMoveX >= maxMoveX || ballMoveX <= 0){
                    speedX = -speedX;
                }
                if(ballMoveY >= maxMoveY || ballMoveY <= 0){
                    speedY = -speedY;
                }
                ball.style.top = ballMoveY + 'px';
                ball.style.left = ballMoveX + 'px';

                speedObj = crashBall(ball, speedX, speedY);
                speedX = speedObj.speedX;
                speedY = speedObj.speedY;
            }, 30)
        }
        //3. 处理小球间的碰撞
        function crashBall(ball, speedX, speedY) {
            var speedObj = {};
            for(var i = 0; i<ballList.length; i++){
                //自己不跟自己比较
                if(ball.id !== ballList[i].id && Math.abs(ball.offsetLeft - ballList[i].offsetLeft) <= 20 && Math.abs(ball.offsetTop - ballList[i].offsetTop) <= 20){
                    //**********对象小球在碰撞小球方位的8个情况***********
                    //左上方
                    if(ball.offsetLeft < ballList[i].offsetLeft && ball.offsetTop < ballList[i].offsetTop){
                        speedX > 0 && (speedX = -speedX);
                        speedY > 0 && (speedY = -speedY);
                    }
                    //正上方(不会影响水平运动的速度)
                    if(ball.offsetLeft === ballList[i].offsetLeft && ball.offsetTop < ballList[i].offsetTop){
                        speedY > 0 && (speedY = -speedY);
                    }
                    //右上方
                    if(ball.offsetLeft > ballList[i].offsetLeft && ball.offsetTop < ballList[i].offsetTop){
                        speedX < 0 && (speedX = -speedX);
                        speedY > 0 && (speedY = -speedY);
                    }
                    //正右方
                    if(ball.offsetLeft > ballList[i].offsetLeft && ball.offsetTop === ballList[i].offsetTop){
                        speedX < 0 && (speedX = -speedX);
                    }
                    //右下方
                    if(ball.offsetLeft > ballList[i].offsetLeft && ball.offsetTop > ballList[i].offsetTop){
                        speedX < 0 && (speedX = -speedX);
                        speedY < 0 && (speedY = -speedY);
                    }
                    //正下方
                    if(ball.offsetLeft === ballList[i].offsetLeft && ball.offsetTop > ballList[i].offsetTop){
                        speedY < 0 && (speedY = -speedY);
                    }
                    //左下方
                    if(ball.offsetLeft < ballList[i].offsetLeft && ball.offsetTop > ballList[i].offsetTop){
                        speedX > 0 && (speedX = -speedX);
                        speedY < 0 && (speedY = -speedY);
                    }
                    //正左方
                    if(ball.offsetLeft < ballList[i].offsetLeft && ball.offsetTop === ballList[i].offsetTop){
                        speedX > 0 && (speedX = -speedX);
                    }
                }
            }
            speedObj.speedX = speedX;
            speedObj.speedY = speedY;
            return speedObj;
        }
        createBall(3);
    }
</script>
</html>

关键的地方是要向清楚,两个小球间碰撞后的运动方向,和碰到方框以后的碰撞方向。

好啦,上面就是今天的全部内容啦。

end。。。