马黑黑 发表于 2024-4-13 07:58

canvas画布中小球碰撞简单处理

<style>
#mama { margin: 20px auto; width: 600px; height: 400px; border: 1px solid gray; position: relative; }
.mum { position: relative; margin: 0; padding: 10px; font: normal 16px/20px Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; color: black; background: rgba(240, 240, 240,.95); box-shadow: 2px 2px 4px gray; border: thick groove lightblue; border-radius: 6px; }
.mum ::selection { background-color: rgba(0,100,100,.35); }
.mum div { margin: 0; padding: 0; }
.mum cl-cd { display: block; position: relative; margin: 0 0 0 50px; padding: 0 0 0 10px; white-space: pre-wrap; overflow-wrap: break-word; border-left: 1px solid silver; }
.mum cl-cd::before { position: absolute; content: attr(data-idx); width: 50px; color: gray; text-align: right; transform: translate(-70px); }
.tRed { color: red; }
.tBlue { color: blue; }
.tGreen { color: green; }
.tDarkRed { color: darkred; }
.tMagenta { color: magenta; }
</style>

<h2>效果:点击方框任意处生成小球,限量10个 ——</h2>
<div id="mama"></div>
<h2>代码:</h2>
<div class='mum'>
<cl-cd data-idx="1">&lt;<span class="tDarkRed">style</span>&gt;</cl-cd>
<cl-cd data-idx="2">#mama {</cl-cd>
<cl-cd data-idx="3">&nbsp; &nbsp; <span class="tBlue">margin:</span> 20px auto;</cl-cd>
<cl-cd data-idx="4">&nbsp; &nbsp; <span class="tBlue">width:</span> 600px;</cl-cd>
<cl-cd data-idx="5">&nbsp; &nbsp; <span class="tBlue">height:</span> 400px;</cl-cd>
<cl-cd data-idx="6">&nbsp; &nbsp; <span class="tBlue">border:</span> 1px solid gray;</cl-cd>
<cl-cd data-idx="7">&nbsp; &nbsp; <span class="tBlue">position:</span> relative;</cl-cd>
<cl-cd data-idx="8">}</cl-cd>
<cl-cd data-idx="9">&lt;<span class="tDarkRed">/style</span>&gt;</cl-cd>
<cl-cd data-idx="10">&nbsp;</cl-cd>
<cl-cd data-idx="11">&lt;<span class="tDarkRed">div</span> <span class="tRed">id</span>=<span class="tMagenta">"mama"</span>&gt;&lt;<span class="tDarkRed">/div</span>&gt;</cl-cd>
<cl-cd data-idx="12">&nbsp;</cl-cd>
<cl-cd data-idx="13">&lt;<span class="tDarkRed">script</span>&gt;</cl-cd>
<cl-cd data-idx="14"><span class="tGreen">//创建画布</span></cl-cd>
<cl-cd data-idx="15"><span class="tBlue">var</span> canv = <span class="tRed">document</span>.createElement(<span class="tMagenta">'canvas'</span>);</cl-cd>
<cl-cd data-idx="16"><span class="tGreen">//画布尺寸及宽高变量与父元素一致</span></cl-cd>
<cl-cd data-idx="17"><span class="tBlue">var</span> ww = canv.width = mama.offsetWidth;</cl-cd>
<cl-cd data-idx="18"><span class="tBlue">var</span> hh = canv.height = mama.offsetHeight;</cl-cd>
<cl-cd data-idx="19"><span class="tGreen">//mama添加画布</span></cl-cd>
<cl-cd data-idx="20">mama.appendChild(canv);</cl-cd>
<cl-cd data-idx="21"><span class="tGreen">//获得画笔</span></cl-cd>
<cl-cd data-idx="22"><span class="tBlue">var</span> ctx = canv.getContext(<span class="tMagenta">'2d'</span>);</cl-cd>
<div class="tGreen"><cl-cd data-idx="23">/* 小球数组 用于实时记录小球数据</cl-cd>
<cl-cd data-idx="24">   小球个体以对象形式存储圆心坐标、半径、颜色、xy方向移动速度</cl-cd>
<cl-cd data-idx="25">   {<span class="tBlue">x:</span> x, <span class="tBlue">y:</span> y, <span class="tBlue">r:</span> r, <span class="tBlue">color:</span> color, <span class="tBlue">speedX:</span> speedX, <span class="tBlue">speedY:</span> speedY}</cl-cd>
<cl-cd data-idx="26">*/</cl-cd></div>
<cl-cd data-idx="27"><span class="tBlue">var</span> balls = [];</cl-cd>
<cl-cd data-idx="28"><span class="tGreen">//动画标识,小球总数 </span></cl-cd>
<cl-cd data-idx="29"><span class="tBlue">var</span> raf = null, total = 10;</cl-cd>
<cl-cd data-idx="30">&nbsp;</cl-cd>
<cl-cd data-idx="31"><span class="tGreen">//获取运动速度函数 -1 或 1 </span></cl-cd>
<cl-cd data-idx="32"><span class="tBlue">var</span> speed = () =&gt; <span class="tRed">Math</span>.random() &lt; 0.5 ? -1&nbsp;: 1;</cl-cd>
<cl-cd data-idx="33">&nbsp;</cl-cd>
<cl-cd data-idx="34"><span class="tGreen">//绘制小球函数 圆心坐标+半径+填充颜色</span></cl-cd>
<cl-cd data-idx="35"><span class="tBlue">var</span> drawBall = (x,y,r,color) =&gt; {</cl-cd>
<cl-cd data-idx="36">&nbsp; &nbsp; ctx.save();</cl-cd>
<cl-cd data-idx="37">&nbsp; &nbsp; ctx.beginPath();</cl-cd>
<cl-cd data-idx="38">&nbsp; &nbsp; ctx.fillStyle = color;</cl-cd>
<cl-cd data-idx="39">&nbsp; &nbsp; ctx.arc(x,y,r,0,2*<span class="tRed">Math</span>.PI);</cl-cd>
<cl-cd data-idx="40">&nbsp; &nbsp; ctx.fill();</cl-cd>
<cl-cd data-idx="41">&nbsp; &nbsp; ctx.restore();</cl-cd>
<cl-cd data-idx="42">};</cl-cd>
<cl-cd data-idx="43">&nbsp;</cl-cd>
<cl-cd data-idx="44"><span class="tGreen">//小球运动函数 依据圆心坐标和半径绘制</span></cl-cd>
<cl-cd data-idx="45"><span class="tBlue">var</span> move = (ball) =&gt; {</cl-cd>
<cl-cd data-idx="46">&nbsp; &nbsp; <span class="tBlue">var</span> x = ball.x,</cl-cd>
<cl-cd data-idx="47">&nbsp; &nbsp; &nbsp; &nbsp; y = ball.y,</cl-cd>
<cl-cd data-idx="48">&nbsp; &nbsp; &nbsp; &nbsp; r = ball.r,</cl-cd>
<cl-cd data-idx="49">&nbsp; &nbsp; &nbsp; &nbsp; color = ball.color,</cl-cd>
<cl-cd data-idx="50">&nbsp; &nbsp; &nbsp; &nbsp; spdX = ball.speedX,</cl-cd>
<cl-cd data-idx="51">&nbsp; &nbsp; &nbsp; &nbsp; spdY = ball.speedY;</cl-cd>
<cl-cd data-idx="52">&nbsp; &nbsp; x += spdX;</cl-cd>
<cl-cd data-idx="53">&nbsp; &nbsp; y += spdY;</cl-cd>
<cl-cd data-idx="54">&nbsp; &nbsp; <span class="tGreen">//边界处理</span></cl-cd>
<cl-cd data-idx="55">&nbsp; &nbsp; <span class="tBlue">if</span>(x - r &lt; 0 || x + r &gt; ww) spdX = - spdX;</cl-cd>
<cl-cd data-idx="56">&nbsp; &nbsp; <span class="tBlue">if</span>(y - r &lt; 0 || y + r &gt; hh) spdY = - spdY;</cl-cd>
<cl-cd data-idx="57">&nbsp; &nbsp; <span class="tGreen">//记录小球变更数据</span></cl-cd>
<cl-cd data-idx="58">&nbsp; &nbsp; ball.x = x;</cl-cd>
<cl-cd data-idx="59">&nbsp; &nbsp; ball.y = y;</cl-cd>
<cl-cd data-idx="60">&nbsp; &nbsp; ball.speedX = spdX;</cl-cd>
<cl-cd data-idx="61">&nbsp; &nbsp; ball.speedY = spdY;</cl-cd>
<cl-cd data-idx="62">&nbsp; &nbsp; <span class="tGreen">//绘制变更后的小球</span></cl-cd>
<cl-cd data-idx="63">&nbsp; &nbsp; drawBall(x,y,r,color);</cl-cd>
<cl-cd data-idx="64">};</cl-cd>
<cl-cd data-idx="65">&nbsp;</cl-cd>
<cl-cd data-idx="66"><span class="tGreen">//渲染函数</span></cl-cd>
<cl-cd data-idx="67"><span class="tBlue">var</span> render = () =&gt; {</cl-cd>
<cl-cd data-idx="68">&nbsp; &nbsp; ctx.clearRect(0,0,ww,hh); <span class="tGreen">//擦除画布</span></cl-cd>
<cl-cd data-idx="69">&nbsp; &nbsp; <span class="tGreen">//双<span class="tBlue">for</span>循环检测球与球间是否碰撞</span></cl-cd>
<cl-cd data-idx="70">&nbsp; &nbsp; <span class="tBlue">for</span>(<span class="tBlue">var</span> j = 0; j &lt; balls.length; j ++) {</cl-cd>
<cl-cd data-idx="71">&nbsp; &nbsp; &nbsp; &nbsp; <span class="tBlue">for</span>(<span class="tBlue">var</span> k = 0; k &lt; balls.length; k ++) {</cl-cd>
<cl-cd data-idx="72">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="tGreen">//两球间的圆心点坐标间距</span></cl-cd>
<cl-cd data-idx="73">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="tBlue">var</span> dx = balls.x - balls.x, dy = balls.y - balls.y;</cl-cd>
<cl-cd data-idx="74">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="tGreen">//两球间的圆心距离</span></cl-cd>
<cl-cd data-idx="75">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="tBlue">var</span> distance = <span class="tRed">Math</span>.sqrt(dx * dx + dy * dy);</cl-cd>
<cl-cd data-idx="76">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="tGreen">//若距离小于两球半径之和则判为碰撞,令运动方向和原来的互反</span></cl-cd>
<cl-cd data-idx="77">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="tBlue">if</span>(distance &lt; (balls.r + balls.r)) {</cl-cd>
<cl-cd data-idx="78">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; balls.speedX = -balls.speedX;</cl-cd>
<cl-cd data-idx="79">    &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; balls.speedY = -balls.speedY;</cl-cd>
<cl-cd data-idx="80">    &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; balls.speedX = -balls.speedX;</cl-cd>
<cl-cd data-idx="81">    &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; balls.speedY = -balls.speedY;</cl-cd>
<cl-cd data-idx="82">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }</cl-cd>
<cl-cd data-idx="83">&nbsp; &nbsp; &nbsp; &nbsp; }</cl-cd>
<cl-cd data-idx="84">&nbsp; &nbsp; &nbsp; &nbsp; move(balls); <span class="tGreen">//移动小球</span></cl-cd>
<cl-cd data-idx="85">&nbsp; &nbsp; }</cl-cd>
<cl-cd data-idx="86">&nbsp; &nbsp; <span class="tGreen">//请求关键帧动画</span></cl-cd>
<cl-cd data-idx="87">&nbsp; &nbsp; raf = requestAnimationFrame(render);</cl-cd>
<cl-cd data-idx="88">};</cl-cd>
<cl-cd data-idx="89">&nbsp;</cl-cd>
<cl-cd data-idx="90"><span class="tGreen">//画布单击时生成小球</span></cl-cd>
<cl-cd data-idx="91">canv.onclick = (e) =&gt; {</cl-cd>
<cl-cd data-idx="92">&nbsp; &nbsp; total --;</cl-cd>
<cl-cd data-idx="93">&nbsp; &nbsp; <span class="tBlue">if</span>(total &lt; 0) <span class="tBlue">return</span>;</cl-cd>
<cl-cd data-idx="94">&nbsp; &nbsp; cancelAnimationFrame(raf);</cl-cd>
<cl-cd data-idx="95">&nbsp; &nbsp; <span class="tBlue">var</span> x = e.offsetX, y = e.offsetY, r = 20, color = `#${<span class="tRed">Math</span>.random().toString(16).substr(-6)}`;</cl-cd>
<cl-cd data-idx="96">&nbsp; &nbsp; <span class="tGreen">//防止小球太靠近边缘</span></cl-cd>
<cl-cd data-idx="97">&nbsp; &nbsp; <span class="tBlue">if</span>(x &lt; 25) x = 25;</cl-cd>
<cl-cd data-idx="98">&nbsp; &nbsp; <span class="tBlue">if</span>(x &gt; ww - 25) x = ww - 25;</cl-cd>
<cl-cd data-idx="99">&nbsp; &nbsp; <span class="tBlue">if</span>(y &lt; 25) y = 25;</cl-cd>
<cl-cd data-idx="100">&nbsp; &nbsp; <span class="tBlue">if</span>(y &gt; hh - 25) y = hh - 25;</cl-cd>
<cl-cd data-idx="101">&nbsp; &nbsp; balls.push({<span class="tBlue">x:</span> x, <span class="tBlue">y:</span> y, <span class="tBlue">r:</span> r, <span class="tBlue">color:</span> color, <span class="tBlue">speedX:</span> speed(), <span class="tBlue">speedY:</span> speed()});</cl-cd>
<cl-cd data-idx="102">&nbsp; &nbsp; render();</cl-cd>
<cl-cd data-idx="103">};</cl-cd>
<cl-cd data-idx="104">&lt;<span class="tDarkRed">/script</span>&gt;</cl-cd>
</div>

<script>
var canv = document.createElement('canvas');
var ww = canv.width = mama.offsetWidth;
var hh = canv.height = mama.offsetHeight;
mama.appendChild(canv);
var ctx = canv.getContext('2d');
var balls = [];
var raf = null, total = 10;
var speed = () => Math.random() < 0.5 ? -1 : 1;

var drawBall = (x,y,r,color) => {
        ctx.save();
        ctx.beginPath();
        ctx.fillStyle = color;
        ctx.arc(x,y,r,0,2*Math.PI);
        ctx.fill();
        ctx.restore();
};

var move = (ball) => {
        var x = ball.x,
                y = ball.y,
                r = ball.r,
                color = ball.color,
                spdX = ball.speedX,
                spdY = ball.speedY;
        x += spdX;
        y += spdY;
        if(x - r < 0 || x + r > ww) spdX = - spdX;
        if(y - r < 0 || y + r > hh) spdY = - spdY;
        ball.x = x;
        ball.y = y;
        ball.speedX = spdX;
        ball.speedY = spdY;
        drawBall(x,y,r,color);
};

var render = () => {
        ctx.clearRect(0,0,ww,hh);
        for(var j = 0; j < balls.length; j ++) {
                for(var k = 0; k < balls.length; k ++) {
                        var dx = balls.x - balls.x, dy = balls.y - balls.y;
                        var distance = Math.sqrt(dx * dx + dy * dy);
                        if(distance < (balls.r + balls.r)) {
                                balls.speedX = -balls.speedX;
                            balls.speedY = -balls.speedY;
                            balls.speedX = -balls.speedX;
                            balls.speedY = -balls.speedY;
                        }
                }
                move(balls);
        }
        raf = requestAnimationFrame(render);
};

canv.onclick = (e) => {
        total --;
        if(total < 0) return;
        cancelAnimationFrame(raf);
        var x = e.offsetX, y = e.offsetY, r = 20, color = `#${Math.random().toString(16).substr(-6)}`;
        if(x < 25) x = 25;
        if(x > ww - 25) x = ww - 25;
        if(y < 25) y = 25;
        if(y > hh - 25) y = hh - 25;
        balls.push({x: x, y: y, r: r, color: color, speedX: speed(), speedY: speed()});
        render();
};
</script>

马黑黑 发表于 2024-4-13 08:11

小球碰撞后的运动形态在本例中简化为:碰撞画小球的运动方向与原来的运动方向互反。

但小球碰撞后的运动形态是相当复杂的,需要考虑的因素很多。例如,质量、加重力、摩擦力、撞击角度,还有防止小球重叠及沿边线滑行等等,会涉及到很多高阶理数知识。

本例,由于不考虑上述这些诸多因素,小球碰撞后的运动出现一些有趣的现象,比如两两纠缠、沿边线滑行等。两两纠缠,偶尔是三个或更多小球纠缠在一处,这种情况需要合适的外力撞击,也就是另一个小球撞走其中的一个,可以将纠缠的小球打散,有时候需要这样两三次的碰撞。沿边线滑行可以避免,本例没有加入这个纠偏机制。

红影 发表于 2024-4-13 10:16

又是个带互动的代码,有趣{:4_173:}

红影 发表于 2024-4-13 10:17

看到了小球纠缠,在被另一个小球棒打鸳鸯前,会携手同行{:4_173:}

红影 发表于 2024-4-13 10:22

看到了沿边线滑行,浅接触的滑行能被撞击改变滑行。深接触的滑行,被撞击后也很难脱身,但是可以被小球纠缠带离滑行状态。

红影 发表于 2024-4-13 10:25

沿边线滑行的少,纠缠的多。纠缠被撞开容易,滑行被带出来难。

马黑黑 发表于 2024-4-13 12:01

红影 发表于 2024-4-13 10:25
沿边线滑行的少,纠缠的多。纠缠被撞开容易,滑行被带出来难。

滑行的贷不出来了的

马黑黑 发表于 2024-4-13 12:02

红影 发表于 2024-4-13 10:22
看到了沿边线滑行,浅接触的滑行能被撞击改变滑行。深接触的滑行,被撞击后也很难脱身,但是可以被小球纠缠 ...

运动就是这么奇妙,有如人生:有时候被带偏了,轻则可得脱身,重则深陷其中

马黑黑 发表于 2024-4-13 12:03

红影 发表于 2024-4-13 10:16
又是个带互动的代码,有趣

可以晚上一阵子,即便只是观看

马黑黑 发表于 2024-4-13 12:05

红影 发表于 2024-4-13 10:17
看到了小球纠缠,在被另一个小球棒打鸳鸯前,会携手同行

这叫什么来着,一场浪漫的邂逅

红影 发表于 2024-4-13 13:10

马黑黑 发表于 2024-4-13 12:01
滑行的贷不出来了的

可以带出来的,我留神多看了一会,几乎都能带出来的。

马黑黑 发表于 2024-4-13 13:10

红影 发表于 2024-4-13 13:10
可以带出来的,我留神多看了一会,几乎都能带出来的。

酱紫呀?我倒没认真看过太久

红影 发表于 2024-4-13 13:10

马黑黑 发表于 2024-4-13 12:02
运动就是这么奇妙,有如人生:有时候被带偏了,轻则可得脱身,重则深陷其中

这说法很有意思,还真是这么回事呢{:4_173:}

红影 发表于 2024-4-13 13:11

马黑黑 发表于 2024-4-13 12:03
可以晚上一阵子,即便只是观看

是啊,也很好玩,看着这些小球演绎人生{:4_173:}

马黑黑 发表于 2024-4-13 13:11

红影 发表于 2024-4-13 13:10
这说法很有意思,还真是这么回事呢

人生写照

马黑黑 发表于 2024-4-13 13:12

红影 发表于 2024-4-13 13:11
是啊,也很好玩,看着这些小球演绎人生

{:4_181:}

红影 发表于 2024-4-13 13:12

马黑黑 发表于 2024-4-13 12:05
这叫什么来着,一场浪漫的邂逅

这样浪漫的邂逅还挺容易就看到呢{:4_173:}

马黑黑 发表于 2024-4-13 13:13

红影 发表于 2024-4-13 13:12
这样浪漫的邂逅还挺容易就看到呢

概率还不低,符合当下社会潮流

红影 发表于 2024-4-13 13:14

马黑黑 发表于 2024-4-13 13:11
人生写照

一切看似由机遇决定的,其实按行进线路,很多相遇却是必然。

马黑黑 发表于 2024-4-13 13:14

红影 发表于 2024-4-13 13:14
一切看似由机遇决定的,其实按行进线路,很多相遇却是必然。

是的,数理规则
页: [1] 2
查看完整版本: canvas画布中小球碰撞简单处理