LoginSignup
10
4

More than 5 years have passed since last update.

Matter.jsを使ってテキストを物理演算させる

Posted at

03.gif

四角や丸などの図形を演算処理するサンプルはいくつかライブラリがありましたが、テキストを使ったサンプルが見つからなかったため、リサーチして備忘録として投稿します。ライブラリの選定ですが、物理演算のライブラリはいくつかありましたが、軽量かつスマートフォンにも対応しているMatter.jsを使っています。

Custom Render

Matter.jsは図形表示用にデフォルトのレンダーを使っています
。このレンダーはWorldに物理演算対象のBodyオブジェクトを追加すると、あとは自動的にBodyを演算処理に沿って表示してくれます。ただし、このデフォルトのレンダーはテキスト表示には対応していません。代わりにCustom Renderを使うように指示されています。Custom Renderとは、演算処理されたBody情報を使ってCanvas内に自前でBodyを描画する方法のようです。

通常のRenderは以下の感じに使います。

通常のレンダー
const Engine = Matter.Engine
const Render = Matter.Render
const engine = Engine.create()
const render = Render.create({
    element: document.getElementById('app'),
    engine: engine,
    options: {
    wireframes: false, 
    width: 300, 
    height: 400,
    background: 'rgba(255, 0, 0, 0.5)'
    }
})

Render.run(render)

このRenderの代わりに自前で描画する。Matter.Composite.allBodies(this.engine.world)で取得するbodiesには物理計算された結果 Body情報(座標情報など)が入っています。

CustomRender
this.render()

render () {
        // NOTE: Worldに追加した全てのBody要素を取得
        const bodies = Matter.Composite.allBodies(this.engine.world)
        const canvas = document.getElementById('canvas')
        const context = canvas.getContext('2d')

        window.requestAnimationFrame(this.render)

        // NOTE: 一つのBody要素に入っている点座標(rectangleであれば4頂点情報)をつなげて
        //           四角を描画する
        for (let i = 0; i < bodies.length; i += 1) {
            const part = bodies[i]
            const vertices = bodies[i].vertices
            context.moveTo(vertices[0].x, vertices[0].y)

            for (var j = 1; j < vertices.length; j += 1) {
                context.lineTo(vertices[j].x, vertices[j].y)
            }

            context.lineTo(vertices[0].x, vertices[0].y)
        }

        context.lineWidth = 1.5
        context.strokeStyle = '#000000'
        context.stroke()
    }    
}

テキスト表示

Bodyを作る際に、text情報をoptionとして登録しておき、Custom Renderを使ってCanvas上にレンダリングする際にBody内のtext情報を使ってテキスト表示を行います。

テキスト付きのBodyを作成
const Bodies = Matter.Bodies
const World = Matter.World
const x = Math.random() * screen.width * 2
const y = 0
const wordBody = Bodies.rectangle(
    x,
    y,
    200,
    100,
    { restitution: 0.95,
        friction: 0,
        render: {
            fillStyle: '#FFFFFF',
            text: {
                fillStyle: '#000000',
                content: content,
                size: 50
            }
        }
    })
World.add(this.engine.world, wordBody)
CustomRender内でテキストを描画
render () {
    var bodies = Matter.Composite.allBodies(this.engine.world)
    var canvas = document.getElementById('canvas')
    var context = canvas.getContext('2d')

    window.requestAnimationFrame(this.render)

    context.fillStyle = '#FFFFFF'
    context.fillRect(0, 0, canvas.width, canvas.height)
    context.globalAlpha = 1
    context.beginPath()

    for (var i = 0; i < bodies.length; i += 1) {
        var part = bodies[i]

        if (part.render.text) {
            var fontsize = 30
            var fontfamily = part.render.text.family || 'Arial'
            var color = part.render.text.color || '#FF0000'

            if (part.render.text.size) {
                fontsize = part.render.text.size
            } else if (part.circleRadius) {
                fontsize = part.circleRadius / 2
            }

            var content = ''
            if (typeof part.render.text === 'string') {
                content = part.render.text
            } else if (part.render.text.content) {
                content = part.render.text.content
            }

            context.fillStyle = 'black'
            context.save()
            context.translate(part.position.x, part.position.y)

            context.textBaseline = 'middle'
            context.textAlign = 'center'
            context.fillStyle = color
            context.font = fontsize + 'px ' + fontfamily
            context.fillText(content, 0, 0)
            context.restore()
            context.fillStyle = 'blue'
            context.fillRect(part.position.x, part.position.y, 10, 10)
        }
        var vertices = bodies[i].vertices
        context.moveTo(vertices[0].x, vertices[0].y)

        for (var j = 1; j < vertices.length; j += 1) {
            context.lineTo(vertices[j].x, vertices[j].y)
        }

        context.lineTo(vertices[0].x, vertices[0].y)
    }

    context.lineWidth = 1.5
    context.strokeStyle = '#000000'
    context.stroke()
}

01.gif

これで物理演算されたテキスト表示が可能だが、この状態だとテキスト表示のコンテナだけが物理演算された表示に
なってしまい、テキスト自体は回転しません。そこで、Bodyを描画するときの頂点情報を元に、Bodyの傾きを算出して、テキストを回転させる。2点の座標がわかればata2を使って角度を算出できる

CustomRenderに追加
context.save()
context.translate(part.position.x, part.position.y)

// NOTE: テキストを回転させる
const x = bodies[i].vertices[1].x - bodies[i].vertices[0].x
const y = bodies[i].vertices[1].y - bodies[i].vertices[0].y
const radian = Math.atan2(y, x)
context.rotate(radian)

02.gif

回転をロック

物理演算をさせる対象を選択し、演算に回転などの制約を加えることができます。回転の制約を加える場合は、物理演算される前に呼ばれるイベントを登録し、コールバック内で回転速度やConstraintなど設定・制御を行うと良いようです。

回転させない制約
const Events = Matter.Events
Events.on(this.engine, 'beforeUpdate', this.matterBeforeUpdate)

matterBeforeUpdate (event) {
    // NOTE: 座標が更新される前に各ボディを回転させないように設定させる
    // http://brm.io/matter-js/docs/classes/Body.html#method_setAngularVelocity
    const Body = Matter.Body
    for (var i = 0; i < this.wordBodyList.length; i++) {
        const wordBody = this.wordBodyList[i]
        Body.setAngularVelocity(wordBody, 0)
    }
}

03.gif

デモはGitHubに置いてあります。

10
4
0

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
10
4