1. ホーム
  2. Web制作
  3. html5

Html5 Canvasアニメーションの基本的な衝突検出の実装

2022-02-01 21:12:07

Canvasで衝突判定を行うには、ゲームエンジンを直接使うことが多いのですが( Cocos2d-JS は、その 白鷺 ) や物理エンジン ( ボックス2D 内蔵されている衝突判定機能の内部構造について、気になったことはありませんか?以下では、基本的な衝突検出の技術について説明します。

1. 矩形ベースの衝突検出

衝突判定とは、物体が重なっているかどうかを判定することですが、ここでは、対象となる衝突体が直方体であると仮定します。以下の例では、Aを固定、Bをマウスの動きに追従させる2つの矩形オブジェクトA、B(以下A,B)を作成します。

1, Rectオブジェクトの作成

ここでは、新しい Rect.js を作成し、Rect オブジェクトを作成して、プロトタイプメソッド draw を追加します。このメソッドは、現在のオブジェクトのプロパティ(場所、サイズ)に基づいて、入力された canvas オブジェクト(コンテキスト)に描画します。

コードは以下の通りです。

function Rect(x,y,width,height) {
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
}

Rect.prototype.draw = function(context){
    context.save();
    context.translate(this.x,this.y);
    context.fillRect(0,0,this.width,this.height);
    context.restore();
}

2. マウス位置の取得

B はマウスに追従する必要があるため、canvas 上のマウスの現在位置を検出する必要があります。Capturemouse関数を作成して、入力されるドキュメントノード(要素)上のマウスの動きを検出し、マウスオブジェクト(マウスのx, y座標を含む)を返すようにします。

コードは次のようになります。

function Capturemouse (element) {
    var mouse={x:null,y:null};
    element.addEventListener('mousemove',function (event) {
        var x, y;
        if(event.pageX || event.pageY){
            x = event.pageX;
            y = event.pageY;
        }else{
            x = event.clientX + document.body.scrollLeft+
                document.documentElement.scrollLeft;
            y = event.clientY+document.body.scrollTop+
                document.documentElement.scrollTop;
        }
        x -=element.offsetLeft;
        y -= element.offsetTop;
        mouse.x = x;
        mouse.y = y;
    },false);
    return mouse;
}

3. 衝突検出

A,Bが重なるかどうかを検出するためには、まず、重なりが発生するかどうかを議論する際に、以下のように重なりがない4つのケースを見ることから始めることができます。

以下は、4つの状態の判定です。

1. rectB.y+rectB.height < rectA.y
2, rectB.y > rectA.x + rectA.width
3. rectB.y > rectA.y + rectA.height。
4、rectB.x + rectB.width < rectA.x

重なっていない状態を判断する方法はわかったが、重なっている状態を判断するにはどうすればよいのだろうか。そう、quot;invert"! この関数は、2つのRectオブジェクトの引数を渡し、2つのRectオブジェクトが重なり合ったときに真を返します。

コードはこのようになります。

function Intersect(rectA,rectB) {
    return ! (rectB.y + rectB.height < rectA.y || rectB.y > rectA.x + rectA.width ||
        rectB.y > rectA.y + rectA.height|| rectB.x + rectB.width < rectA.x)
}

4. アニメーションのループ

animationjsを新規に作成し、アニメーション関数requestAnimationFrame()を設定します。

次の2つは、ループ本体で行います。

  • 次のフレームを描画するために、現在のキャンバスをクリア" します。
  • A,B が重なっているかどうかをチェックし、重なっている場合は、コンソールに対話形式で出力します!
  • キャンバス上の現在のマウスの動きを検出し、マウスの位置を B の position プロパティに更新します。
  • 新しい位置プロパティを元にA,Bを再描画(もちろん、Aの位置は更新されないが、ループするたびにキャンバスがクリアされるので再描画が必要)

コードはこのようになります。

function drawAnimation() {
    window.requestAnimationFrame(drawAnimation);
    context.clearRect(0, 0, canvas.width, canvas.height);
    if(Intersect(rectA,rectB)){
     console.log('interact!!!!') ;
    }
    if(mouse.x){
        rectB.x = mouse.x;
        rectB.y = mouse.y;
    }
    rectA.draw(context);
    rectB.draw(context);
}

3. 初期化

Init.jsを新規に作成し、canvas要素を取得してマウスの動きを検出できるようにし、RectオブジェクトAとBを初期化し、最後にアニメーションのループを開始させます。

コードは次のようになります。

window.onload = function () {
    canvas = document.getElementById('collCanvas');
    context = canvas.getContext('2d');
    Capturemouse(canvas);
    rectA = new Rect(canvas.width/2,canvas.height/2,100,100);
    rectB = new Rect(100,100,100,100);
    drawAnimation();
}

2. サークルベースの衝突検出

矩形の衝突の後、円形の衝突について説明しましょう、我々はまた、2つの円オブジェクトAとB(以下、A、Bと呼ぶ)を作成します、ここでAの位置は固定されており、Bは移動するマウスに従って、A、Bが重なるときコンソールは、プロンプトインターセクトになります

1, 円形オブジェクトの作成

function Circle(x,y,radius) {
    this.x = x;
    this.y = y;
    this.radius = radius;
}

Circle.prototype.draw = function(context){
    context.save();
    context.translate(this.x,this.y);
    context.beginPath();
    context.arc(0,0,this.radius,0,Math.PI*2,false);
    context.fill();
    context.restore();
}

2. 円形衝突の検出

円同士の衝突検出は、2つの円中心間の距離と2つの円半径の和を比較するだけでよく、2つの円中心間の距離が2つの円半径の和より小さい場合に衝突が発生する。

次の図のようになります。

そこで、まず、2つの円の中心間の距離を計算する必要があるのですが、ここでは、次のように2点間の距離の公式を用います。

2つの円の中心間の距離が得られると、2つの円の半径の和と比較し、距離が半径の和より小さければ真を返す。

ここで、Interaect関数を更新します。

コードは以下の通りです。

function Intersect(circleA,circleB) {
    var dx = circleA.x-circleB.x;
    var dy = circleA.y-circleB.y;
    var distance = Math.sqrt(dx*dx+dy*dy);
    return distance < (circleA.radius + circleB.radius);
}

3. アニメーションのループ

animation.jsを更新します。ここでは、RectオブジェクトをCircleオブジェクトに置き換えています。

コードは次のようになります。

function drawAnimation() {
    window.requestAnimationFrame(drawAnimation);
    context.clearRect(0, 0, canvas.width, canvas.height);
    if(Intersect(circleA,circleB)){
     console.log('interact!!!!') ;
    }
    if(mouse.x){
        circleB.x = mouse.x;
        circleB.y = mouse.y;
    }
    circleA.draw(context);
    circleB.draw(context);
}

4. 初期設定

Init.jsを更新し、CircleオブジェクトAとBを初期化し、最後にアニメーションのループを開く。

コードは次のようになります。

window.onload = function () {
    canvas = document.getElementById('collCanvas');
    context = canvas.getContext('2d');
    Capturemouse(canvas);
    circleA = new Circle(canvas.width/2,canvas.height/2,100);
    circleB = new Circle(100,100,100);
    drawAnimation();
}


3. 矩形と円との衝突判定に基づく

これまでの説明は、単一の図形同士の衝突検出でしたが、ここでは矩形と円との衝突を検出します。

1. 衝突の検出

矩形検出と同様に、衝突が発生しなかった4つのケースを見てみましょう。

次の図です。

以下は、4つの状態の判定です。

  • Circle.y + Circle.radius < Rect.y
  • Circle.x - Circle.radius > Rect.x + Rect.width。
  • Circle.y - Circle.radius > Rect.y + Rect.height。
  • Circle.x + Circle.radius < Rect.x

Rect オブジェクトと Circle オブジェクトを関数に渡すことで、Rect オブジェクトが Circle オブジェクトに重なったときに true を返すように、Interaect 関数を更新して、重ならない状態を "reverse" できるようにします。

コードは次のようになります。

function Intersect(Rect,Circle) {
    return ! (Circle.y + Circle.radius < Rect.y ||
             Circle.x - Circle.radius > Rect.x + Rect.width ||
             Circle.y - Circle.radius > Rect.y + Rect.height ||
             Circle.x + Circle.radius < Rect.x)
}

2. アニメーションのループ

animation.js を更新します。ここでは、円形オブジェクトをマウスに追従するように動かし、固定位置にある矩形オブジェクトとの衝突を検出するようにします。

コードはこのようになります。

function drawAnimation() {
    window.requestAnimationFrame(drawAnimation);
    context.clearRect(0, 0, canvas.width, canvas.height);
    if(Intersect(rect,circle)){
     console.log('interact!!!!') ;
    }
    if(mouse.x){
        circle.x = mouse.x;
        circle.y = mouse.y;
    }
    circle.draw(context);
    rect.draw(context);
}

3. 初期設定

Init.jsを更新し、CircleオブジェクトとRectオブジェクトを初期化し、最後にアニメーションのループを開きます。

コードは次のようになります。

window.onload = function () {
    canvas = document.getElementById('collCanvas');
    context = canvas.getContext('2d');
    Capturemouse(canvas);
    circle = new Circle(100,100,100);
    rect = new Rect(canvas.width/2,canvas.height/2,100,100);
    drawAnimation();
}

以上、本記事の全内容をご紹介しましたが、皆様の学習のお役に立てれば幸いです。また、Script Houseをより一層応援していただければ幸いです。