LoginSignup
2
0

More than 1 year has passed since last update.

Pixi.jsで円同士の衝突判定を実装する

Posted at

はじめに

Pixi.jsを使って円同士の衝突判定を実装する機会があったので、初めてPixi.jsを使用する方に出来るだけ分かりやすくまとめていきます。

① PIXI.Application の作成

まずは、PIXI.Applicationを作成します。

PIXI.Applicationとは土台のようなもので、ここに色々なオブジェクトを追加することで、画像を表示させたり、図形を描画したりできます。
PIXI.Applicationには色々なオプションを設定することができますが、今回はサイズ、背景色を設定します。
Canvasの縦幅、横幅は画面いっぱいにして、背景色は白色にします。
Pixi.jsでの色の指定は全て16進数で行うので、白色を設定する場合には0xffffffと記述します。

import * as PIXI from 'pixi.js'

const app = new PIXI.Application({
  width: window.innerWidth,
  height: window.innerHeight,
  backgroundColor: 0xffffff
});

// bodyにApplication追加
document.body.appendChild(app.view);

② 衝突させる円を作成する

次に、衝突させる円を作成していきます。

円を作成するにはPIXI.Graphicsの drawCircle を使用し、色指定にはbeginFillを使用して ff00ff に塗りつぶします。


// 円の半径
const radius = 50

const circle1 = new PIXI.Graphics()
     .beginFill(0xff00ff)
     .drawCircle(0, 0, radius)
     .endFill()

次に、作成した円をPIXI.Spriteに追加します。

PIXI.Spriteは基本的に画像を表示させるために使用しますが、今回はGraphicを追加します。
PIXI.Graphicsで座標の情報を保持してしまうと、基準点が左上になってしまうので Drag & Dropの際にズレが生じてしまいます。
そのため、描画する座標の情報はPIXI.Spriteで保持し、基準点は中心に設定します。

circleSprite1.anchor.set(0.5)
circleSprite1.position.set(200, 200)

また、物体の衝突判定の際に使用するため、作成したSpriteの情報を配列に格納します。
最後にapp.stageに作成したSpriteを追加して表示させます。

以下、ここまでのコードの全容です。


// 円の情報をもつ配列
let circles = []

// 円の半径
const radius = 50

// 衝突させる円1
const circleSprite1 = new PIXI.Sprite()
const circle1 = new PIXI.Graphics()
     .beginFill(0xff00ff)
     .drawCircle(0, 0, radius)
     .endFill()
circleSprite1.addChild(circle1)
circleSprite1.anchor.set(0.5)
circleSprite1.position.set(200, 200)
circles.push(circleSprite1)

// 衝突させる円2
const circleSprite2 = new PIXI.Sprite()
const circle2 = new PIXI.Graphics()
     .beginFill(0xff00ff)
     .drawCircle(0, 0, radius)
     .endFill()
circleSprite2.addChild(circle2)
circleSprite2.anchor.set(0.5)
circleSprite2.position.set(400,400)
circles.push(circleSprite2)

app.stage.addChild(circleSprite1)
app.stage.addChild(circleSprite2)

スクリーンショット 2021-11-26 16.08.21.png

③ drag and dropで操作する円を作成する

次にdrag and dropで操作する円を作成します。
①と同じ要領でPIXI.Graphicsで円を描画し、Spriteに追加します。

// dragさせる円の半径
const dragCircleRadius = 50

// dragさせる円
const dragCircle = new PIXI.Graphics()
     .beginFill(0x0000ff)
     .drawCircle(0, 0, dragCircleRadius)
     .endFill()

const dragCircleSprite = new PIXI.Sprite()
dragCircleSprite.addChild(dragCircle)

Spriteのpointer up や pointer moveイベントを有効化するために、interactive を true に設定します。
わかりやすいように buttonMode もtrueにして、カーソルがpointerになるようにします。

dragCircleSprite.interactive = true
dragCircleSprite.buttonMode = true

次に作成したSpriteにイベントを登録させます。
第一引数にイベントの種類、第二引数に実行する関数を指定します。

dragCircleSprite.on('pointerdown', onPointerDown)
dragCircleSprite.on('pointerup', onPointerUp)

今ドラッグしているか否かの状態は isDragging で保持します。


// ドラッグ状態を管理
let isDragging = false

// pointer down時のevent
function onPointerDown(){
  isDragging = true
  dragCircleSprite.on('pointermove', onPointerMove)
}

// pointer up時のevent
function onPointerUp() {
  isDragging = false
  dragCircleSprite.removeListener('pointermove')
}

pointer moveイベントではドラッグ状態がtrueの場合のみ、Spriteの座標を現在のマウスの位置に合わせます。
引数からInteraction Eventを参照できるので、座標の情報はそこから取得します。

// pointer move時のevent
function onPointerMove(e) {
  if(isDragging) {
    const target = e.currentTarget
    const position = e.data.getLocalPosition(app.stage)
    target.position.set(position.x, position.y)
  }
}

以下ここまでのコードの全容です。
公式Exampleの drag and drop も参考にしてみてください。


// ドラッグ状態を管理
let isDragging = false

// dragさせる円の半径
const dragCircleRadius = 50

// dragさせる円
const dragCircle = new PIXI.Graphics()
     .beginFill(0x0000ff)
     .drawCircle(0, 0, dragCircleRadius)
     .endFill()

const dragCircleSprite = new PIXI.Sprite()
dragCircleSprite.addChild(dragCircle)

// interactiveをtrueにしないとイベントが発火しない
dragCircleSprite.interactive = true
dragCircleSprite.buttonMode = true
// 初期のポジション
dragCircleSprite.position.set(200,400)
// アンカーポイントを真ん中に設定
dragCircleSprite.anchor.set(0.5)

// pointerイベントの登録
dragCircleSprite.on('pointerdown', onPointerDown)
dragCircleSprite.on('pointerup', onPointerUp)

// pointer down時のevent
function onPointerDown(){
  isDragging = true
  dragCircleSprite.on('mousemove', onPointerMove)
}

// pointer up時のevent
function onPointerUp() {
  isDragging = false
  dragCircleSprite.removeListener('mousemove')
}

// pointer move時のevent
function onPointerMove(e) {
  if(isDragging) {
    const target = e.currentTarget
    const position = e.data.getLocalPosition(app.stage)
    target.position.set(position.x, position.y)
  }
}

app.stage.addChild(dragCircleSprite)

画面収録-2021-11-26-16.00.42.gif

③ 円同士の衝突を判定するロジック

では、本題の円同士の衝突判定です。
まず、当たり判定となる基準ですが、ドラッグしている円が既に描画されている円に衝突した時(重なった時)に当たり判定とします。
これを実現するために、2つの円それぞれの中心からの距離と半径の和を比べて、半径の和 > 2つの円の距離となれば当たり、と判定します。

スクリーンショット 2021-11-27 11.45.14.png

2つの円の距離は三平方の定理を使って求めます。

スクリーンショット 2021-11-27 12.10.49.png

直角三角形では、斜辺cの2乗は,他の辺a,bをそれぞれ2乗した数の和に等しい、というやつです。
この定理をどのように使用するかと言いますと、、、

スクリーンショット 2021-11-27 11.57.03.png

図のように、2つの円の中心を結んだものを斜辺cとし直角三角形を作ると、それぞれx座標同士、y座標同士の距離が2辺 a, bとなり、
直角三角形では,2つの辺の長さがわかると,三平方の定理を使って他の1辺の長さが計算できるので、中心座標の数字がわかればcの長さを計算することが可能になります。

Math.hypot() 関数は、各引数の二乗の合計値の平方根を返しますので、これを使用すると簡単に上記の式が実現できそうです。
コードに落とし込むと、、、


const a = x1 - x2
const b = y1 - y2
const c = Math.hypot(a, b)
if(c < radius + dragCircleRadius) {
    // 当たり
  return
} else {
  // 当たってない
}

④ pointer moveの時に衝突判定

上記のロジックをpointer move時のイベント関数内に実装します。
衝突判定させる円が複数個あるため、円をまとめた配列をループして判定するようにします。
衝突した時にSpriteの透明度を下げるようにします。

function onPointerMove(e) {
  if(isDragging) {
    const target = e.currentTarget
    const position = e.data.getLocalPosition(app.stage)
    target.position.set(position.x, position.y)

    for(let i = 0; i < circles.length; i++) {
      const centerPosition = {
        x: circles[i].x,
        y: circles[i].y
      }

      const a = centerPosition.x - position.x
      const b = centerPosition.y - position.y
      const c = Math.hypot(a, b)
      if(c < radius + dragCircleRadius) {
        dragCircleSprite.alpha = 0.3
        return
      } else {
        dragCircleSprite.alpha = 1
      }
    }
  }
}

画面収録-2021-11-27-15.18.40.gif

いい感じになりました。

コードの全容はこちらのcodepen に載せましたのでよければご参考までに!

2
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0