Flashゲーム講座 & アクションスクリプトサンプル集



円同士の衝突を計算する

 
 
サンプルをダウンロード
 
 
サンプルをダウンロード
 


■衝突する円を表現するのに必要なパラメータ

 
円の衝突に必要なパラメータは以下の通りです。
 
円の中心座標を、変数 (px, py) とします。
円の速度を、変数 (dx, dy) とします。
円の半径を、変数 r とします。
円の質量を、変数 m とします。お好みで設定します。
 
 
円の衝突に必要なパラメータ

// 円A
var ca = {
	px : 0,	// x 座標
	py : 0,	// y 座標
	dx : 2,	// x 方向の速度
	dy : 3,	// y 方向の速度
	r  :10,	// 半径
	m  : 1	// 質量
};

// 円B
var ca = {
	px : 0,	// x 座標
	py : 0,	// y 座標
	dx : 2,	// x 方向の速度
	dy : 3,	// y 方向の速度
	r  :10,	// 半径
	m  : 1	// 質量
};
 

■円同士が衝突したか調べる(簡易チェック)

 
円同士で当たり判定があるかを調べます。
 
 
三平方の定理から…
 
 
「円Aの中心座標」から「円Bのの中心座標」までの距離が、「円Aの半径」と「円Bの半径」を足した値より小さければ、めり込んでいる事がわかります。
 
 
円Aと円Bが重なっているか調べる

if((b.px - a.px) * (b.px - a.px) + (b.py - a.py) * (b.py - a.py) < (a.r + b.r) * (a.r + b.r)){

	// 当たりあり
}
 
当たっている事がわかれば、めり込まない位置まで座標を移動する必要があります。
 
このとき下の図のように、 時間をさかのぼるように進行方向を逆に戻る補正が理想ですが、計算が複雑です。
 
 
 
ここでは、簡単に計算ができる方法で妥協してみます。
 
以下の図のように最短の距離で離すようにしてみます。
 
 
 
まずめり込んだ量を調べます。
 
円Bから円Aまでのベクトルを調べて距離を取得します。
 
円Aと円Bの半径を足して、距離を引けば、めり込んだ長さが得られます。
 
円Aと円Bのめり込んだ量を調べる

var vx = (a.px - b.px);
var vy = (a.py - b.py);
var len = Math.sqrt(vx * vx + vy * vy);
var distance = (a.r + b.r) - len;
 
円Aの補正方向を調べます。
 
円Bから円Aまでのベクトルを正規化します。
 
円Bの補正方向は、このベクトルを反転したものになります。
 
円Aの補正方向を調べる

var vx = (a.px - b.px);
var vy = (a.py - b.py);
var len = Math.sqrt(vx * vx + vy * vy);
var distance = a.r + b.r - len;

if(len > 0)	len = 1 / len;
vx *= len;
vy *= len;
 
めり込んだ長さの半分ずつを補正方向に移動します。
 
円Aと円Bを重ならない位置まで補正する

var vx = (a.px - b.px);
var vy = (a.py - b.py);
var len = Math.sqrt(vx * vx + vy * vy);
var distance = a.r + b.r - len;

if(len > 0)	len = 1 / len;
vx *= len;
vy *= len;

distance /= 2.0;
a.px += vx * distance;
a.py += vy * distance;
b.px -= vx * distance;
b.py -= vy * distance;
 
これで、円同士が重なっているかの判定と、円同士を重ならない位置まで補正する事ができました。
 
次は、より詳細な衝突判定の検出を調べてみましょう。
 

■円同士がいつ衝突するか調べる(精密チェック)

 
円Aと円Bが、いずれ衝突するとします。
 
衝突までに必要なフレーム時間を f とします。
 
円同士が衝突した瞬間は、点で接触する事になります。このときの、
 
円Aの座標を (ax , ay) とします。
 
円Bの座標を (bx , by) とします。
 
これらのパラメータを求めてみたいと思います。
 
 
 
円Aの現在の位置から、 f フレーム時間移動し続けると、衝突する瞬間の座標に到達します。
 
円Aが f フレーム時間移動すると衝突した瞬間の座標(ax , ay)となる

ax = a.px + a.dx * f    … (1)
ay = a.py + a.dy * f    … (2)
 
円Bの現在の位置から、 f フレーム時間移動し続けると、衝突する瞬間の座標に到達します。
 
円Bが f フレーム時間移動すると衝突した瞬間の座標(bx , by)となる

bx = b.px + b.dx * f    … (3)
by = b.py + b.dy * f    … (4)
 
三平方の定理から…
 
 
衝突した瞬間の「円Aの中心座標」から「円Bのの中心座標」までの距離は、「円Aの半径」と「円Bの半径」を足した値に等しくなります。
 
 
三平方の定理から、中心座標の距離と、2つの円の半径を足した値が等しくなる

(ax - bx) * (ax - bx) + (ay - by) * (ay - by) = (a.r + b.r) * (a.r + b.r)    … (5)
 
この5つの式から、フレーム時間 f を求める事ができます。
 
式を整理して二次方程式( ax2 + bx + c = 0 ) の形にまとめます。
 
三平方の定理から、中心座標の距離と、2つの円の半径を足した値が等しくなる

ax = a.px + a.dx * f
ay = a.py + a.dy * f
bx = b.px + b.dx * f
by = b.py + b.dy * f
(ax - bx) * (ax - bx) + (ay - by) * (ay - by) = (a.r + b.r) * (a.r + b.r) 

↓

(ax * ax) - (ax * bx) - 
(ax * bx) + (bx * bx) + 
(ay * ay) - (ay * by) - 
(ay * by) + (by * by) = (a.r + b.r) * (a.r + b.r)

↓

((a.px + a.dx * f) * (a.px + a.dx * f)) - ((a.px + a.dx * f) * (b.px + b.dx * f)) - 
((a.px + a.dx * f) * (b.px + b.dx * f)) + ((b.px + b.dx * f) * (b.px + b.dx * f)) + 
((a.py + a.dy * f) * (a.py + a.dy * f)) - ((a.py + a.dy * f) * (b.py + b.dy * f)) - 
((a.py + a.dy * f) * (b.py + b.dy * f)) + ((b.py + b.dy * f) * (b.py + b.dy * f)) = (a.r + b.r) * (a.r + b.r)

↓

((a.px * a.px) + (a.px * a.dx * f) + (a.dx * f * a.px) + (a.dx * f * a.dx * f)) - 
((a.px * b.px) + (a.px * b.dx * f) + (a.dx * f * b.px) + (a.dx * f * b.dx * f)) - 
((a.px * b.px) + (a.px * b.dx * f) + (a.dx * f * b.px) + (a.dx * f * b.dx * f)) + 
((b.px * b.px) + (b.px * b.dx * f) + (b.dx * f * b.px) + (b.dx * f * b.dx * f)) + 
((a.py * a.py) + (a.py * a.dy * f) + (a.dy * f * a.py) + (a.dy * f * a.dy * f)) - 
((a.py * b.py) + (a.py * b.dy * f) + (a.dy * f * b.py) + (a.dy * f * b.dy * f)) - 
((a.py * b.py) + (a.py * b.dy * f) + (a.dy * f * b.py) + (a.dy * f * b.dy * f)) + 
((b.py * b.py) + (b.py * b.dy * f) + (b.dy * f * b.py) + (b.dy * f * b.dy * f)) = (a.r + b.r) * (a.r + b.r)


(   (a.px * a.px) + (a.px * a.dx * f) + (a.dx * a.px * f) + (a.dx * a.dx * f * f)) + 
( - (a.px * b.px) - (a.px * b.dx * f) - (a.dx * b.px * f) - (a.dx * b.dx * f * f)) + 
( - (a.px * b.px) - (a.px * b.dx * f) - (a.dx * b.px * f) - (a.dx * b.dx * f * f)) + 
(   (b.px * b.px) + (b.px * b.dx * f) + (b.dx * b.px * f) + (b.dx * b.dx * f * f)) + 
(   (a.py * a.py) + (a.py * a.dy * f) + (a.dy * a.py * f) + (a.dy * a.dy * f * f)) + 
( - (a.py * b.py) - (a.py * b.dy * f) - (a.dy * b.py * f) - (a.dy * b.dy * f * f)) + 
( - (a.py * b.py) - (a.py * b.dy * f) - (a.dy * b.py * f) - (a.dy * b.dy * f * f)) + 
(   (b.py * b.py) + (b.py * b.dy * f) + (b.dy * b.py * f) + (b.dy * b.dy * f * f)) = (a.r + b.r) * (a.r + b.r)

↓

(f * f) * ((a.dx * a.dx) - (a.dx * b.dx) - (a.dx * b.dx) + (b.dx * b.dx) + (a.dy * a.dy) - (a.dy * b.dy) - (a.dy * b.dy) + (b.dy * b.dy)) + 
(    f) * ((a.px * a.dx) + (a.dx * a.px) - (a.px * b.dx) - (a.dx * b.px) - (a.px * b.dx) - (a.dx * b.px) + (b.px * b.dx) + (b.dx * b.px) + (a.py * a.dy) + (a.dy * a.py) - (a.py * b.dy) - (a.dy * b.py) - (a.py * b.dy) - (a.dy * b.py) + (b.py * b.dy) + (b.dy * b.py)) + 
          ((a.px * a.px) - (a.px * b.px) - (a.px * b.px) + (b.px * b.px) + (a.py * a.py) - (a.py * b.py) - (a.py * b.py) + (b.py * b.py) - (a.r + b.r) * (a.r + b.r)) = 0:
 
「二次方程式の根の公式」を使って解くと、答えは2つ得られます
 
1つ目は、接触する瞬間のフレーム時間 f0 です。
 
 
2つ目は、円同士が通り抜けて離れる瞬間のフレーム時間 f1 です。
 
 
フレーム時間を計算する

var _a =     (a.dx * a.dx) - 2 * (a.dx * b.dx) +     (b.dx * b.dx) +     (a.dy * a.dy) - 2 * (a.dy * b.dy) +     (b.dy * b.dy);
var _b = 2 * (a.px * a.dx) - 2 * (a.px * b.dx) - 2 * (a.dx * b.px) + 2 * (b.px * b.dx) + 2 * (a.py * a.dy) - 2 * (a.py * b.dy) - 2 * (a.dy * b.py) + 2 * (b.py * b.dy);
var _c =     (a.px * a.px) - 2 * (a.px * b.px) +     (b.px * b.px) +     (a.py * a.py) - 2 * (a.py * b.py) +     (b.py * b.py) - (a.r + b.r) * (a.r + b.r);
var _d = Math.sqrt(_b * _b - 4 * _a * _c);

var f0 = (- _b - _d) / (2 * _a);	// 接触する瞬間
var f1 = (- _b + _d) / (2 * _a);	// 離れる瞬間
 
f0 と f1 の値から以下の事がわかります。
 
状態
f0 と f1 がどちらもプラス円同士は近づいている
f0 と f1 どちらもマイナス円同士は離れている
f0 と f1 の符号が反転円同士はめり込んでいる
f0 が 0 以上、 1 以下の値現在のフレームで衝突する
 
f0 から、衝突後の座標を計算します。
 
衝突する瞬間の座標を計算する

var ax = a.px + a.dx * f0;
var ay = a.py + a.dy * f0;
var bx = b.px + b.dx * f0;
var by = b.py + b.dy * f0;
 
円同士が衝突しない場合は、この式が成り立たないとき…?という事で、
 
平方根を計算する部分が 0 以下になる場合、円同士は衝突しないと判定してみます。
 
フレーム時間を計算する

var _a =     (a.dx * a.dx) - 2 * (a.dx * b.dx) +     (b.dx * b.dx) +     (a.dy * a.dy) - 2 * (a.dy * b.dy) +     (b.dy * b.dy);
var _b = 2 * (a.px * a.dx) - 2 * (a.px * b.dx) - 2 * (a.dx * b.px) + 2 * (b.px * b.dx) + 2 * (a.py * a.dy) - 2 * (a.py * b.dy) - 2 * (a.dy * b.py) + 2 * (b.py * b.dy);
var _c =     (a.px * a.px) - 2 * (a.px * b.px) +     (b.px * b.px) +     (a.py * a.py) - 2 * (a.py * b.py) +     (b.py * b.py) - (a.r + b.r) * (a.r + b.r);
var _d = _b * _b - 4 * _a * _c;

if(_d <= 0){
	// 当たりなし
	
}else{
	// 当たりあり
	_d = Math.sqrt(_d);

	var f0 = (- _b - _d) / (2 * _a);	// 接触する瞬間
	var f1 = (- _b + _d) / (2 * _a);	// 離れる瞬間
}
 
これで円Aと円Bが衝突するかしないかの判定と、
 
衝突するまでのフレーム時間と、
 
点で接触してるときの円Aの座標と円Bの座標
 
が求まりました。
 
次は、衝突後の移動方向を調べてみましょう。
 

■円同士の衝突後の方向を調べる

 
下の図のようにクリーンヒットした場合は、勢いのある反応がありそうです。
 
 
 
逆に、下の図のようにカスヒットした場合は、勢いが無さそうです。
 
 
「力の方向」と、「作用点から重心までの方向」が同じ場合、
 
移動運動が発生し、回転運動は発生しません
 
 
「力の方向」と、「作用点から重心までの方向」が 垂直である場合、
 
移動運動は発生しなくなり、回転運動が発生します
 
 
では円Aと円Bの速度ベクトルを、「移動運動を発生させるベクトル」と「回転運動を発生させるベクトル」の2つに分離してみます。
 
 
 
円Aから円Bへ移動運動を発生させるベクトルを(amx,amy) とします。
 
円Aから円Bへ回転運動を発生させるベクトルを(arx,ary) とします。
 
円Bから円Aへ移動運動を発生させるベクトルを(bmx,bmy) とします。
 
円Bから円Aへ回転運動を発生させるベクトルを(brx,bry) とします。
 
速度ベクトルを重心方向と垂直な方向に分離する

var t;
var vx = (b.px - a.px);
var vy = (b.py - a.py);

t = -(vx * a.dx + vy * a.dy) / (vx * vx + vy * vy);
var arx = a.dx + vx * t;
var ary = a.dy + vy * t;

t = -(-vy * a.dx + vx * a.dy) / (vy * vy + vx * vx);
var amx = a.dx - vy * t;
var amy = a.dy + vx * t;

t = -(vx * b.dx + vy * b.dy) / (vx * vx + vy * vy);
var brx = b.dx + vx * t;
var bry = b.dy + vy * t;

t = -(-vy * b.dx + vx * b.dy) / (vy * vy + vx * vx);
var bmx = b.dx - vy * t;
var bmy = b.dy + vx * t;
 
回転運動を発生させるベクトルですが、今回は回転を考慮しないのでそのままにします。
 
移動運動を発生させるベクトル同士を使って衝突を計算してみたいと思います。
 
衝突を計算するためには、反発係数 e が必要になります。
 
反発係数 e には 0.0 ~ 1.0 までのお好みの値を設定します。
 
反発係数を設定する

var e = 1.0;	// 反発係数
 
さて、円同士が正面衝突したとします。
 
円Aの衝突前の速度を av0 、衝突後の速度を av1
 
円Bの衝突前の速度を bv0 、衝突後の速度を bv1
 
円Aの質量を am
 
円Bの質量を bm
 
とします。
 
 
まず反発係数は、以下の関係があります。
 
反発係数

e = - (bv1 - av1) / (bv0 - av0)    … (1)
 
速度と質量を乗算すると、運動量となります。
 
運動量を計算する

運動量 = 質量 × 速度
 
運動量保存の法則により、衝突前の円の運動量の合計値と、衝突後の円の運動量の合計値は等しくなります。
 
運動量保存の法則

am * av0 + bm * bv0 = am * av1 + bm * bv1    … (2)
 
この2つの式を使うと衝突後の速度がわかります。
 
式を整理してみます。
 
2つの式を整理する

e = - (bv1 - av1) / (bv0 - av0)
am * av0 + bm * bv0 = am * av1 + bm * bv1

↓

bv1 = - e * (bv0 - av0) + av1
am * av0 + bm * bv0 = am * av1 + bm * bv1

↓

am * av0 + bm * bv0 = am * av1 + bm * (- bv0 * e + av0 * e + av1)

↓

am * av0 + bm * bv0 = am * av1 - bv0 * e * bm + av0 * e * bm + av1 * bm;

↓

am * av0 + bm * bv0 + bv0 * e * bm - av0 * e * bm = av1 * ( am + bm);

↓

av1 = (am * av0 + bm * bv0 + bv0 * e * bm - av0 * e * bm) / (am + bm)
bv1 = - e * (bv0 - av0) + av1
 
この式を使って、x 軸方向と y 軸方向でそれぞれ計算します。
 
これで移動運動を発生させるベクトル同士の、衝突後のベクトルが求まります。
 
x 方向と y 方向それぞれの衝突後の速度を計算する

var e = 1.0;
var adx = (a.m * amx + b.m * bmx + bmx * e * b.m - amx * e * b.m) / (a.m + b.m);
var bdx = - e * (bmx - amx) + adx;
var ady = (a.m * amy + b.m * bmy + bmy * e * b.m - amy * e * b.m) / (a.m + b.m);
var bdy = - e * (bmy - amy) + ady;
 
回転運動を発生させるベクトルを加算すると、衝突後の速度となります。
 
 
回転運動を発生させるベクトルを加算して衝突後の速度を計算

a.dx = adx + arx;
a.dy = ady + ary;
b.dx = bdx + brx;
b.dy = bdy + bry;
 
これで衝突後の速度が求まりました。
 




矩形を使った地形との衝突を計算する

 
 
サンプルをダウンロード
 
 
サンプルをダウンロード
 


 
矩形のパラメータ

矩形と矩形の衝突を計算したいと思います。ここでは、プレイヤーと地形との当たり判定という事で、地形側の矩形は完全に固定し、プレイヤー側の矩形が壁から押されるようにします。
以下のパラメータを使用します。

 

// プレイヤーのパラメータ -------------
my_x = 0;         // 座標
my_y = 0;

my_dx = 0;        // 移動量
my_dy = 0;

my_rect_x = -10;  // 矩形x座標(座標からのオフセット)
my_rect_y = -10;  // 矩形y座標(座標からのオフセット)
my_rect_w = 20;   // 矩形幅
my_rect_h = 20;   // 矩形高さ


// ブロックのパラメータ ---------------
bl_rect_x =  50;  // 矩形x座標
bl_rect_y = 100;  // 矩形y座標
bl_rect_w = 100;  // 矩形幅
bl_rect_h =  50;  // 矩形高さ

 
まず矩形Aと矩形Bとで当たり判定を取ります。当たり判定の具体的な解説はこちら

 
 

    // プレイヤーの矩形
    var my_x_min = my_rect_x + my_x;      // x最小
    var my_y_min = my_rect_y + my_y;      // y最小
    var my_x_max = my_x_min + my_rect_w;  // x最大
    var my_y_max = my_y_min + my_rect_h;  // y最大
    
    // ブロックの矩形
    var bl_x_min = bl_rect_x;             // x最小
    var bl_y_min = bl_rect_y;             // y最小
    var bl_x_max = bl_x_min + bl_rect_w;  // x最大
    var bl_y_max = bl_y_min + bl_rect_h;  // y最大
    
    if(my_x_max < bl_x_min){
    }else if(my_y_max < bl_y_min){
    }else if(my_x_min > bl_x_max){
    }else if(my_y_min > bl_y_max){
    }else{
        // 当たり判定あり
    }
 
 
重なっているのが分かったら、重ならない位置まで補正します。
とりあえず、壁に近ければ外に外に出すように下のように考えて見ましょう。
 
 
 
 
 
これで計算すると「上下方向」と「左右方向」のどちらも移動する必ずどこかの角に移動してしまいます。 そこで「上下方向」か「左右方向」どちらか1つの方向だけ移動するようにします。

どちらに移動するかは、補正するまでの移動距離が短いほうにします。
 
 
ただこれを計算するとちょっと複雑になるため、もうひとつ別のやり方を考えて見ましょう。
 
 
サンプルではこの方法を採用しています。
 
半分を境にどちらに補正するか決めていましたが、あえて範囲を縮めて見ましょう。 下の図のようにめり込み許容量を決めておいてそこに当たっていた場合補正するようにします。
 
 
 
 
 

    MAX_LENGTH = 10;    // めり込み許容範囲

    // プレイヤーの矩形
    var my_x_min = my_rect_x + my_x;      // x最小
    var my_y_min = my_rect_y + my_y;      // y最小
    var my_x_max = my_x_min + my_rect_w;  // x最大
    var my_y_max = my_y_min + my_rect_h;  // y最大
    
    // ブロックの矩形
    var bl_x_min = bl_rect_x;             // x最小
    var bl_y_min = bl_rect_y;             // y最小
    var bl_x_max = bl_x_min + bl_rect_w;  // x最大
    var bl_y_max = bl_y_min + bl_rect_h;  // y最大
    
    if(my_x_max < bl_x_min){
    }else if(my_y_max < bl_y_min){
    }else if(my_x_min > bl_x_max){
    }else if(my_y_min > bl_y_max){
    }else{
        // ブロックの上端
        if(my_y_max - bl_y_min < MAX_LENGTH){
            my_y = bl_y_min - my_rect_y - my_rect_h;  // 上に補正
        }

        // ブロックの下端
        if(bl_y_max - my_y_min < MAX_LENGTH){
            my_y = bl_y_max - my_rect_y;              // 下に補正
        }

        // ブロックの左端
        if(my_x_max - bl_x_min < MAX_LENGTH){
            my_x = bl_x_min - my_rect_x - my_rect_w;  // 左に補正
        }

        // ブロックの右端
        if(bl_x_max - my_x_min < MAX_LENGTH){
            my_x = bl_x_max - my_rect_x;              // 右に補正
        }

   }
 
これだと自動的に壁に近いほうに移動する事ができます。
ただ、欠点があってめり込みの許容範囲より多くめり込むと地形をすり抜けてしまいます。
 
 
そういうわけで最高移動量を決めておいて、移動量がめり込み許容範囲より大きい値にならないようにします。
 

    MAX_LENGTH = 10;    // めり込み許容範囲

    if(my_dx > MAX_LENGTH)  my_dx = MAX_LENGTH;
    if(my_dx <-MAX_LENGTH)  my_dx =-MAX_LENGTH;
    if(my_dy > MAX_LENGTH)  my_dy = MAX_LENGTH;
    if(my_dy <-MAX_LENGTH)  my_dy =-MAX_LENGTH;

 
また、角の挙動が少しおかしくなるので次に解説する、「移動している方向によって当たり判定を取らない」処理を入れてみましょう。
 
もうひとつ、アクションゲームのようなブロックの上に乗ったりする挙動を作るにはさらに細工が必要です。

下の図を見てください。めめくんがジャンプしてブロックに乗ろうとしています。上の画像のような挙動を行いたいのですが これまで紹介したやり方だとジャンプ上昇中に、ブロックに吸着してしまいます。
 
 
そこで移動量の方向によって当たり判定を無視するようにします。
そもそも上昇中の物体が、ブロックの上端と接触するという事は、自然界ではありえない事です。という事は、 y 方向の移動量がマイナスのときは上端の当たり判定を無視しても問題なさそうです。
 
他にも、
y 方向の移動量がプラスのときは下端の当たり判定を無視
x 方向の移動量がマイナスのときは右端の当たり判定を無視
x 方向の移動量がプラスのときは左端の当たり判定を無視
しましょう。
 



 

    MAX_LENGTH = 10;    // めり込み許容範囲

    // プレイヤーの矩形
    var my_x_min = my_rect_x + my_x;      // x最小
    var my_y_min = my_rect_y + my_y;      // y最小
    var my_x_max = my_x_min + my_rect_w;  // x最大
    var my_y_max = my_y_min + my_rect_h;  // y最大
    
    // ブロックの矩形
    var bl_x_min = bl_rect_x;             // x最小
    var bl_y_min = bl_rect_y;             // y最小
    var bl_x_max = bl_x_min + bl_rect_w;  // x最大
    var bl_y_max = bl_y_min + bl_rect_h;  // y最大
    
    if(my_x_max < bl_x_min){
    }else if(my_y_max < bl_y_min){
    }else if(my_x_min > bl_x_max){
    }else if(my_y_min > bl_y_max){
    }else{
         // 下に移動中
       if(my_dy > 0){
            // ブロックの上端
            if(my_y_max - bl_y_min < MAX_LENGTH){
                my_y = bl_y_min - my_rect_y - my_rect_h;  // 上に補正
                }

        // 上に移動中
        }else{
            // ブロックの下端
            if(bl_y_max - my_y_min < MAX_LENGTH){
                my_y = bl_y_max - my_rect_y;              // 下に補正
            }
        }

        // 右に移動中
        if(my_dx > 0){
            // ブロックの左端
            if(my_x_max - bl_x_min < MAX_LENGTH){
                my_x = bl_x_min - my_rect_x - my_rect_w;  // 左に補正
            }

        // 左に移動中
        }else{
            // ブロックの右端
            if(bl_x_max - my_x_min < MAX_LENGTH){
                my_x = bl_x_max - my_rect_x;              // 右に補正
            }
        }

   }
 
 
それと当たり判定はブロックの上端から調べていったほうがいいでしょう。
崖ギリギリに立つという動きをすると横に移動してしまうのを防ぐためです。


 
 
 




円と線を使った地形との衝突を計算する

 
 
サンプルをダウンロード
 


 
円と線のパラメータ

円をプレイヤー、線を地形として衝突を計算したいと思います。 ここでは、プレイヤーと地形との当たり判定という事で、地形側の線は完全に固定し、プレイヤー側の円が壁から押されるようにします。
以下のパラメータを使用します。

 

// プレイヤーのパラメータ -------------
my_x = 0;      // 中心座標
my_y = 0;
my_dx = 0;     // 移動量
my_dy = 0;
my_rad = 20;   // 円の半径

// ブロックのパラメータ ---------------
ax =  50;      // 開始座標
ay = 100;
bx = 100;      // 終了座標
by =  50;

 
まず円と線分とで当たり判定を取ります。交差判定のアルゴリズムはこちらです。

今回は、「このまま進んだら地形と衝突するか?」という「交差判定」ではなく 一度壁にめり込ませてから「めり込んでいるか判定」し、壁から補正するような計算をします。

 

 
円の現在の位置から線の法線と逆方向に半径の長さの線を出して当たり判定を取ります。
 
 

    // 始点から終点のベクトル
    var sx = bx - ax;
    var sy = by - ay;

    // ベクトルを正規化
    var length = Math.sqrt((sx * sx) + (sy * sy));
    if(length > 0)    length = 1 / length;
    sx *= length;
    sy *= length;

    // 線の法線
    var nx = -(sy);
    var ny = sx;

    // 玉から面と垂直な線を出して面と交差するか調べる
    var sx = -nx * my_rad;
    var sy = -ny * my_rad;

    var d = -(ax * nx + ay * ny);
    var t = -(nx * my_x + ny * my_y + d) / (nx * sx + ny * sy);
    
    // 線と交差している(この時点では無限線)
    if(t > 0 && t <= 1){
        // 交点
        var cx = my_x + t * sx;
        var cy = my_y + t * sy;

        // 交点が線分の間にあるか調べる
        var acx = cx - ax;
        var acy = cy - ay;
        var bcx = cx - bx;
        var bcy = cy - by;
        if((acx * bcx) + (acy * bcy) <= 0){

            // 当たりあり

        }
    }
 
ただ、この当たり判定だけでは下のような位置に来たときに当たり判定をすり抜けてしまいます。
 
 
この問題は後で対応するとして、先に衝突の処理をして見ましょう。
まず線と線の交点から法線方向に半径分移動させて線の上にのるようにしましょう。
 

    my_x = cx + my_rad * nx;
    my_y = cy + my_rad * ny;

 
次に反射方向です。ここでは衝突時の反射係数( 0 ~ 1 )を 0.8 としています。
 

    var t = -(nx * my_dx + ny * my_dy)/(nx * nx + ny * ny);
    my_dx += t * nx * 2 * 0.8;
    my_dy += t * ny * 2 * 0.8;

 
さて先ほどの当たりすり抜けの件ですが、下の図のような玉の移動をするとあたりをすり抜ける事がわかります。
 
 
という事で、玉の移動量が半径を超えないようにします。
 

    // 速度制限
    if(Math.sqrt(my_dx * my_dx + my_dy * my_dy) > my_rad){
        var length = Math.sqrt((my_dx * my_dx) + (my_dy * my_dy));
        if(length > 0)    length = 1 / length;
        my_dx = my_dx * length;
        my_dy = my_dy * length;
        my_dx *= my_rad;
        my_dy *= my_rad;
    }

 
どうしても1フレーム前の位置より現在の位置までの長さが半径を超える場合は、その距離までの交差判定を取るか、半径の長さずつ移動させて何回も当たり判定を取るといいでしょう。
 
後は、線の始点と終点近くの当たりすり抜けですが、ここは円と始点、円と終点の当たり判定を取れば解決できそうです。
 
 
点と円の当たり判定を取ります。
 

    // 始点と円の当たり判定
    var sx = my_x - ax;
    var sy = my_y - ay;
    if(sx * sx + sy * sy < my_rad * my_rad){
        // 当たりあり
    }

    // 終点と円の当たり判定
    var sx = my_x - bx;
    var sy = my_y - by;
    if(sx * sx + sy * sy < my_rad * my_rad){
        // 当たりあり
    }

 
点から円を補正します。
 

    // 始点と円の当たり判定
    var sx = my_x - ax;
    var sy = my_y - ay;
    if(sx * sx + sy * sy < my_rad * my_rad){
       // 正規化
        var length = Math.sqrt((sx * sx) + (sy * sy));
        if(length > 0)    length = 1 / length;
        sx = sx * length;
        sy = sy * length;

        // 位置補正
        my_x = ax + sx * my_rad;
        my_y = ay + sy * my_rad;
    }

    // 終点と円の当たり判定
    var sx = my_x - bx;
    var sy = my_y - by;
    if(sx * sx + sy * sy < my_rad * my_rad){
        // 正規化
        var length = Math.sqrt((sx * sx) + (sy * sy));
        if(length > 0)    length = 1 / length;
        sx = sx * length;
        sy = sy * length;

        // 位置補正
        my_x = bx + sx * my_rad;
        my_y = by + sy * my_rad;
    }

 
反射方向を計算します。衝突時の反射係数( 0 ~ 1 )を 0.8 としています。
 

    // 始点と円の当たり判定
    var sx = my_x - ax;
    var sy = my_y - ay;
    if(sx * sx + sy * sy < my_rad * my_rad){
        // 正規化
        var length = Math.sqrt((sx * sx) + (sy * sy));
        if(length > 0)    length = 1 / length;
        sx = sx * length;
        sy = sy * length;

        // 位置補正
        my_x = ax + sx * my_rad;
        my_y = ay + sy * my_rad;

        // 反射方向
        var t = -(sx * my_dx + sy * my_dy)/(sx * sx + sy * sy);
        my_dx += t * sx * 2 * 0.8;
        my_dy += t * sy * 2 * 0.8;

    }

    // 終点と円の当たり判定
    var sx = my_x - bx;
    var sy = my_y - by;
    if(sx * sx + sy * sy < my_rad * my_rad){
        // 正規化
        var length = Math.sqrt((sx * sx) + (sy * sy));
        if(length > 0)    length = 1 / length;
        sx = sx * length;
        sy = sy * length;

        // 位置補正
        my_x = bx + sx * my_rad;
        my_y = by + sy * my_rad;

        // 反射方向
        var t = -(sx * my_dx + sy * my_dy)/(sx * sx + sy * sy);
        my_dx += t * sx * 2 * 0.8;
        my_dy += t * sy * 2 * 0.8;
    }

 
これで円と線と衝突処理を計算する事ができました。
ただ、この当たり判定はかなり重いので、実際に使うときは当たり判定データを一定の座標間隔ごとに分ける等の高速化しないと実用に耐えれないでしょう。