今回は、軽量な2次元物理演算エンジン「Matter.js」を紹介します。
これまで、物理演算エンジンとして「Box2DJS」や「Physijs」や「Cannon.js」などを紹介してきました。どれも高機能なものですが、ファイルが重く、負荷が高いものと言わざるえません。「three.js」といった別のライブラリと併用となると、物理演算の負荷軽減が重要になります。また、スマートフォンで使用しようと考えると、ファイルサイズが大きいものや、負荷の高いものを使用するのは避けたいところです。
今回紹介する「Matter.js」は、物理演算エンジンとして、必要な機能を揃えておきながら、圧縮後のファイルが76KBと軽く、演算負荷に関してもモバイルDemoを見る限り、iPhone6で60FPSが出ているので、問題なく使用できる感じがあります。さらにはスマートフォンの傾きに合わせて、重力が適応されるといったサンプルもあり、PCのみならずスマートフォンやタブレットを意識した作りになっています。
サンプルも豊富で、ドキュメントもちゃんと揃っています(英語)ので、非常にとっつきやすいと思います。本体のソースコードも、綺麗に整理されているので、抵抗なくソースを読むことが出来るかと思います。
基本的な使い方
基本的な使い方は、
Matter.Engine.create
メソッドで、物理演算エンジンを生成します。第1引数にコンテナとなる要素、第2引数にオプションを渡します。Matter.Bodies.rectangle
メソッドやMatter.Bodies.circle
メソッドで演算対象となるMatter.Body
要素を生成します。- 生成した
Matter.Body
要素を、Matter.World.add
メソッドで、物理演算対象に追加します。第1引数に、(1)で生成した物理演算エンジンのプロパティのworld
を、第2引数に(2)で生成したMatter.Body
要素の配列を渡します。 - 最後に、
Matter.Engine.run
メソッドで物理演算エンジンを起動させます。引数に(1)で生成した物理演算エンジンを渡します。
<!DOCTYPE html>
<html lang="ja">
<head>
<title>Sample</title>
</head>
<body>
<div id="container"></div>
<script src="./js/matter.min.js"></script>
<script>
// Engine生成
var engine = Matter.Engine.create(document.getElementById("container"), {
render: {
options: {
wireframes: true, // ワイヤー表示
width: 640, // canvasの横幅
height: 480, // canvasの高さ
background: "rgba(0, 0, 0, 1)"
}
}
});
// 床生成
floor = Matter.Bodies.rectangle(320, 240, 480, 10, {
isStatic: true // 固定
});
// 床追加
Matter.World.add(engine.world, [floor]);
for (var i = 0; i < 20; i++) {
// ボール生成
var x = Math.random() * 640;
var y = Math.random() * 480 - 480;
ball = Matter.Bodies.circle(x, y, 20, {
density: 0.001, // 質量
frictionAir: 0.01, // 空気抵抗
restitution: 1, // 弾力性
friction: 0.01 // 摩擦
});
// ボール追加
Matter.World.add(engine.world, [ball]);
}
// 物理演算実行
Matter.Engine.run(engine);
</script>
</body>
</html>
基本的な使い方は以上です。DEMOにあるSlingshot Gameのような物理演算を利用したゲームなら、すぐに作れそうですね。
マルチデバイス(PC、スマフォ対応)ブラウザゲーム
もう少し突っ込んで、マルチデバイスを意識したブラウザゲームを作ってみました。
– ゲームサンプル:http://knockknock.jp/sample/MatterJS/index.html
「フ○ッピーバード」のようなゲームなら簡単に作れるのでは、と思ってサンプルを作りました。操作は簡単で、イカがどこにも接触しないようにいいタイミングで、画面をタップする(PCの場合は何かしらのキーを押す)だけです。
しかし、物理演算エンジンで、「フ○ッピーバード」と同様のものが実装できると思っていたのですが、予想と違う結果でした。重力と反対方向に一定の力を加えることで、イカをジャンプさせていますが、物体の重力加速度が強ければ強いほど、強い力でジャンプしなければ、同じ高さでジャンプする事ができません。結果としては、イカに上方向に一定の力を加えた場合、上昇と落下の状態によって、ジャンプの高さが変わってしまうのです。物理法則でいうとなるべくしてなった結果なのですが。そのため「フ○ッピーバード」よりも、物理法則に沿った難易度の高いものとなっています。
何が正解かはわかりませんが、リアルが正解とは限らないのゲームなんですね。奥が深い。
ソースコード解説
ソースコードからポイントをピックアップして、解説します。
※ CoffeeScript、Sass、Jadeのコンパイルは、IDEで自動化(WebStromのFileWatcher機能)していますので、GulpやGruntといったツールは内包していません。サンプルとして参照していただければと思います。
演算領域の再設定
物理エンジンを作成するときに、気をつけないといけない事があります。オプション設定でcanvasの幅と高さを設定するのですが、同じ値を演算領域としてworld.bounds.max
に設定する必要があります。演算領域 == canvasサイズというわけではないので、canvasサイズ設定のさいには、演算領域も設定する必要があります。ちなみに演算領域のデフォルト値はworld.bounds.min == {x:0、y:0}
、world.bounds.max == {x:800、y:600}
になります。
※ ドキュメントには説明のない事ですので、間違っていたらご指摘ください。
追記 : 訂正です、ver0.8.0の話でした。ver1.0?(2015/09/17)では、解消済みで演算領域 == canvasサイズとなっています。
./js/index.coffee Line48〜
# 物理エンジンを作成
@_engine = Matter.Engine.create document.getElementById(element), {
render: { # レンダリングの設定
options: {
wireframes: false # ワイヤーフレームモードをoff
width: STAGE_WIDTH # canvasのwidth
height: STAGE_HEIGHT # canvasのheight
background: "./images/bg.png"
}
}
}
# 演算領域を設定
@_engine.world.bounds.max = {x: STAGE_WIDTH, y: STAGE_HEIGHT}
# 物理シュミレーションを実行
Matter.Engine.run @_engine
エンジンの停止と始動
イントロとアウトロでは、物理演算を停止状態にしておきたかったので、シーンが切り替わったタイミングで、エンジンのenabled
プロパティにboolean
を挿入しています。
./js/index.coffee Line74〜
# イントロシーン開始
setIntroScene: ->
# エンジン停止
@_engine.enabled = false
# ゲームシーン開始
setGameScene: ->
# エンジン始動
@_engine.enabled = true
衝突判定
動的なBody要素(isStatic == true
)が、動的なBody要素及び、静的なBody要素(isStatic == false
)に衝突したタイミングでcollisionStart
イベントが発生します。Matter.Events.on
メソッドで、このイベントをキャッチする事ができます。第1引数に、物理演算エンジン、第2引数にイベント名、第3引数にコールバック関数を渡します。
./js/index.coffee Line116〜
# 衝突判定
Matter.Events.on @_engine, "collisionStart", @_onHit
./js/index.coffee Line140〜
# 衝突判定
Matter.Events.off @_engine, "collisionStart", @_onHit
この他にも、
- collisionStart : 衝突開始
- collisionActive : 衝突中
- collisionEnd : 衝突終了
- mousedown : マウスダウン(タッチスタート)
- mousemove : マウス移動(タッチムーブ)
- mouseup : マウスアップ(タッチエンド)
- startdrag : ドラッグ開始で発生
- enddrag : ドラッグ終了で発生
- sleepStart : Bodyがスリープ開始
- sleepEnd : Bodyがスリープ終了
- beforeAdd : 追加前
- afterAdd : 追加後
- beforeRemove : 削除前
- afterRemove : 削除後
- beforeTick
- tick
- afterTick
- beforeUpdate : アップデート前
- afterUpdate : アップデート後
- beforeRender : レンダリング前
- afterRender : レンダリング後
といったイベントが用意されているようです。collisionStart
以外は試していないので、詳しくはわかりません。
ジャンプ
Matter.Body.applyForce
メソッドでBodyに力を加える事ができます。これを利用してジャンプを行っています。また、ジャンプの際にイカの画像を0.1秒だけ差し替えています。欲をいえば、スプレットシートのようなものが使えるといいのですが。
./js/index.coffee Line178〜
# ジャンプ
jump: ->
# Body上方向に力を加える
Matter.Body.applyForce @_body, {x: 0, y: 1}, {x: 0, y: -PLAYER_JUMP_FORCE}
# スプライト変更
@_body.render.sprite.texture = "./images/gesso2.png"
if @_intervalId
clearTimeout @_intervalId
@_intervalId = setTimeout =>
@_body.render.sprite.texture = "./images/gesso.png"
, 100
以上が、ポイントごとのソースコード解説です。
まとめ
軽量、軽負荷な2次元物理演算エンジン「Matter.js」は、いかがでしたでしょうか?次は「Matter.js」と「three.js」と組み合わせて、スマートフォンでどこまで出来るか試してみたいです。
物理演算エンジンとは関係ない話ですが、今回作ったサンプルのような、PCでもスマートフォンでもワンソースで実装可能なマルチデバイスなブラウザゲームは、アプリとは違い、審査もなく、低予算、短納期で実装可能です。おまけに、HTMLやCSSやJavaScriptといったWeb技術で実装されているので、ソーシャル連携はお手の物です。考えると、ちょっとワクワクしますね。ただし、あくまでもクライアントサイドの技術ですので、JavaScriptや通信データの改ざん等のハックの可能性を視野に入れてください。
ピンバック: 軽量な2次元物理演算エンジン「Matter.js」を使って、マルチデバイスなブラウザゲームを作ってみた。その2。 | KnockKnock