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"><<span class="tDarkRed">style</span>></cl-cd>
<cl-cd data-idx="2">#mama {</cl-cd>
<cl-cd data-idx="3"> <span class="tBlue">margin:</span> 20px auto;</cl-cd>
<cl-cd data-idx="4"> <span class="tBlue">width:</span> 600px;</cl-cd>
<cl-cd data-idx="5"> <span class="tBlue">height:</span> 400px;</cl-cd>
<cl-cd data-idx="6"> <span class="tBlue">border:</span> 1px solid gray;</cl-cd>
<cl-cd data-idx="7"> <span class="tBlue">position:</span> relative;</cl-cd>
<cl-cd data-idx="8">}</cl-cd>
<cl-cd data-idx="9"><<span class="tDarkRed">/style</span>></cl-cd>
<cl-cd data-idx="10"> </cl-cd>
<cl-cd data-idx="11"><<span class="tDarkRed">div</span> <span class="tRed">id</span>=<span class="tMagenta">"mama"</span>><<span class="tDarkRed">/div</span>></cl-cd>
<cl-cd data-idx="12"> </cl-cd>
<cl-cd data-idx="13"><<span class="tDarkRed">script</span>></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"> </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 = () => <span class="tRed">Math</span>.random() < 0.5 ? -1 : 1;</cl-cd>
<cl-cd data-idx="33"> </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) => {</cl-cd>
<cl-cd data-idx="36"> ctx.save();</cl-cd>
<cl-cd data-idx="37"> ctx.beginPath();</cl-cd>
<cl-cd data-idx="38"> ctx.fillStyle = color;</cl-cd>
<cl-cd data-idx="39"> ctx.arc(x,y,r,0,2*<span class="tRed">Math</span>.PI);</cl-cd>
<cl-cd data-idx="40"> ctx.fill();</cl-cd>
<cl-cd data-idx="41"> ctx.restore();</cl-cd>
<cl-cd data-idx="42">};</cl-cd>
<cl-cd data-idx="43"> </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) => {</cl-cd>
<cl-cd data-idx="46"> <span class="tBlue">var</span> x = ball.x,</cl-cd>
<cl-cd data-idx="47"> y = ball.y,</cl-cd>
<cl-cd data-idx="48"> r = ball.r,</cl-cd>
<cl-cd data-idx="49"> color = ball.color,</cl-cd>
<cl-cd data-idx="50"> spdX = ball.speedX,</cl-cd>
<cl-cd data-idx="51"> spdY = ball.speedY;</cl-cd>
<cl-cd data-idx="52"> x += spdX;</cl-cd>
<cl-cd data-idx="53"> y += spdY;</cl-cd>
<cl-cd data-idx="54"> <span class="tGreen">//边界处理</span></cl-cd>
<cl-cd data-idx="55"> <span class="tBlue">if</span>(x - r < 0 || x + r > ww) spdX = - spdX;</cl-cd>
<cl-cd data-idx="56"> <span class="tBlue">if</span>(y - r < 0 || y + r > hh) spdY = - spdY;</cl-cd>
<cl-cd data-idx="57"> <span class="tGreen">//记录小球变更数据</span></cl-cd>
<cl-cd data-idx="58"> ball.x = x;</cl-cd>
<cl-cd data-idx="59"> ball.y = y;</cl-cd>
<cl-cd data-idx="60"> ball.speedX = spdX;</cl-cd>
<cl-cd data-idx="61"> ball.speedY = spdY;</cl-cd>
<cl-cd data-idx="62"> <span class="tGreen">//绘制变更后的小球</span></cl-cd>
<cl-cd data-idx="63"> drawBall(x,y,r,color);</cl-cd>
<cl-cd data-idx="64">};</cl-cd>
<cl-cd data-idx="65"> </cl-cd>
<cl-cd data-idx="66"><span class="tGreen">//渲染函数</span></cl-cd>
<cl-cd data-idx="67"><span class="tBlue">var</span> render = () => {</cl-cd>
<cl-cd data-idx="68"> ctx.clearRect(0,0,ww,hh); <span class="tGreen">//擦除画布</span></cl-cd>
<cl-cd data-idx="69"> <span class="tGreen">//双<span class="tBlue">for</span>循环检测球与球间是否碰撞</span></cl-cd>
<cl-cd data-idx="70"> <span class="tBlue">for</span>(<span class="tBlue">var</span> j = 0; j < balls.length; j ++) {</cl-cd>
<cl-cd data-idx="71"> <span class="tBlue">for</span>(<span class="tBlue">var</span> k = 0; k < balls.length; k ++) {</cl-cd>
<cl-cd data-idx="72"> <span class="tGreen">//两球间的圆心点坐标间距</span></cl-cd>
<cl-cd data-idx="73"> <span class="tBlue">var</span> dx = balls.x - balls.x, dy = balls.y - balls.y;</cl-cd>
<cl-cd data-idx="74"> <span class="tGreen">//两球间的圆心距离</span></cl-cd>
<cl-cd data-idx="75"> <span class="tBlue">var</span> distance = <span class="tRed">Math</span>.sqrt(dx * dx + dy * dy);</cl-cd>
<cl-cd data-idx="76"> <span class="tGreen">//若距离小于两球半径之和则判为碰撞,令运动方向和原来的互反</span></cl-cd>
<cl-cd data-idx="77"> <span class="tBlue">if</span>(distance < (balls.r + balls.r)) {</cl-cd>
<cl-cd data-idx="78"> balls.speedX = -balls.speedX;</cl-cd>
<cl-cd data-idx="79"> balls.speedY = -balls.speedY;</cl-cd>
<cl-cd data-idx="80"> balls.speedX = -balls.speedX;</cl-cd>
<cl-cd data-idx="81"> balls.speedY = -balls.speedY;</cl-cd>
<cl-cd data-idx="82"> }</cl-cd>
<cl-cd data-idx="83"> }</cl-cd>
<cl-cd data-idx="84"> move(balls); <span class="tGreen">//移动小球</span></cl-cd>
<cl-cd data-idx="85"> }</cl-cd>
<cl-cd data-idx="86"> <span class="tGreen">//请求关键帧动画</span></cl-cd>
<cl-cd data-idx="87"> raf = requestAnimationFrame(render);</cl-cd>
<cl-cd data-idx="88">};</cl-cd>
<cl-cd data-idx="89"> </cl-cd>
<cl-cd data-idx="90"><span class="tGreen">//画布单击时生成小球</span></cl-cd>
<cl-cd data-idx="91">canv.onclick = (e) => {</cl-cd>
<cl-cd data-idx="92"> total --;</cl-cd>
<cl-cd data-idx="93"> <span class="tBlue">if</span>(total < 0) <span class="tBlue">return</span>;</cl-cd>
<cl-cd data-idx="94"> cancelAnimationFrame(raf);</cl-cd>
<cl-cd data-idx="95"> <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"> <span class="tGreen">//防止小球太靠近边缘</span></cl-cd>
<cl-cd data-idx="97"> <span class="tBlue">if</span>(x < 25) x = 25;</cl-cd>
<cl-cd data-idx="98"> <span class="tBlue">if</span>(x > ww - 25) x = ww - 25;</cl-cd>
<cl-cd data-idx="99"> <span class="tBlue">if</span>(y < 25) y = 25;</cl-cd>
<cl-cd data-idx="100"> <span class="tBlue">if</span>(y > hh - 25) y = hh - 25;</cl-cd>
<cl-cd data-idx="101"> 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"> render();</cl-cd>
<cl-cd data-idx="103">};</cl-cd>
<cl-cd data-idx="104"><<span class="tDarkRed">/script</span>></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>
小球碰撞后的运动形态在本例中简化为:碰撞画小球的运动方向与原来的运动方向互反。
但小球碰撞后的运动形态是相当复杂的,需要考虑的因素很多。例如,质量、加重力、摩擦力、撞击角度,还有防止小球重叠及沿边线滑行等等,会涉及到很多高阶理数知识。
本例,由于不考虑上述这些诸多因素,小球碰撞后的运动出现一些有趣的现象,比如两两纠缠、沿边线滑行等。两两纠缠,偶尔是三个或更多小球纠缠在一处,这种情况需要合适的外力撞击,也就是另一个小球撞走其中的一个,可以将纠缠的小球打散,有时候需要这样两三次的碰撞。沿边线滑行可以避免,本例没有加入这个纠偏机制。 又是个带互动的代码,有趣{:4_173:} 看到了小球纠缠,在被另一个小球棒打鸳鸯前,会携手同行{:4_173:} 看到了沿边线滑行,浅接触的滑行能被撞击改变滑行。深接触的滑行,被撞击后也很难脱身,但是可以被小球纠缠带离滑行状态。 沿边线滑行的少,纠缠的多。纠缠被撞开容易,滑行被带出来难。 红影 发表于 2024-4-13 10:25
沿边线滑行的少,纠缠的多。纠缠被撞开容易,滑行被带出来难。
滑行的贷不出来了的 红影 发表于 2024-4-13 10:22
看到了沿边线滑行,浅接触的滑行能被撞击改变滑行。深接触的滑行,被撞击后也很难脱身,但是可以被小球纠缠 ...
运动就是这么奇妙,有如人生:有时候被带偏了,轻则可得脱身,重则深陷其中 红影 发表于 2024-4-13 10:16
又是个带互动的代码,有趣
可以晚上一阵子,即便只是观看 红影 发表于 2024-4-13 10:17
看到了小球纠缠,在被另一个小球棒打鸳鸯前,会携手同行
这叫什么来着,一场浪漫的邂逅 马黑黑 发表于 2024-4-13 12:01
滑行的贷不出来了的
可以带出来的,我留神多看了一会,几乎都能带出来的。 红影 发表于 2024-4-13 13:10
可以带出来的,我留神多看了一会,几乎都能带出来的。
酱紫呀?我倒没认真看过太久 马黑黑 发表于 2024-4-13 12:02
运动就是这么奇妙,有如人生:有时候被带偏了,轻则可得脱身,重则深陷其中
这说法很有意思,还真是这么回事呢{:4_173:} 马黑黑 发表于 2024-4-13 12:03
可以晚上一阵子,即便只是观看
是啊,也很好玩,看着这些小球演绎人生{:4_173:} 红影 发表于 2024-4-13 13:10
这说法很有意思,还真是这么回事呢
人生写照 红影 发表于 2024-4-13 13:11
是啊,也很好玩,看着这些小球演绎人生
{:4_181:} 马黑黑 发表于 2024-4-13 12:05
这叫什么来着,一场浪漫的邂逅
这样浪漫的邂逅还挺容易就看到呢{:4_173:} 红影 发表于 2024-4-13 13:12
这样浪漫的邂逅还挺容易就看到呢
概率还不低,符合当下社会潮流 马黑黑 发表于 2024-4-13 13:11
人生写照
一切看似由机遇决定的,其实按行进线路,很多相遇却是必然。 红影 发表于 2024-4-13 13:14
一切看似由机遇决定的,其实按行进线路,很多相遇却是必然。
是的,数理规则
页:
[1]
2