四角や丸などの図形を演算処理するサンプルはいくつかライブラリがありましたが、テキストを使ったサンプルが見つからなかったため、リサーチして備忘録として投稿します。ライブラリの選定ですが、物理演算のライブラリはいくつかありましたが、軽量かつスマートフォンにも対応している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情報(座標情報など)が入っています。
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情報を使ってテキスト表示を行います。
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)
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()
}
これで物理演算されたテキスト表示が可能だが、この状態だとテキスト表示のコンテナだけが物理演算された表示に
なってしまい、テキスト自体は回転しません。そこで、Bodyを描画するときの頂点情報を元に、Bodyの傾きを算出して、テキストを回転させる。2点の座標がわかればata2
を使って角度を算出できる
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)
回転をロック
物理演算をさせる対象を選択し、演算に回転などの制約を加えることができます。回転の制約を加える場合は、物理演算される前に呼ばれるイベントを登録し、コールバック内で回転速度や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)
}
}
デモはGitHubに置いてあります。