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

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



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に置いてあります。

コメント

このブログの人気の投稿

投稿時間:2021-06-17 05:05:34 RSSフィード2021-06-17 05:00 分まとめ(1274件)

投稿時間:2021-06-20 02:06:12 RSSフィード2021-06-20 02:00 分まとめ(3871件)

投稿時間:2020-12-01 09:41:49 RSSフィード2020-12-01 09:00 分まとめ(69件)