前回の回答
前回の宿題は、バウンド時の跳ね返り方を制御するんでしたね。
では回答。
const main = () => {
const canvas = document.getElementById(’canvas’);
const ctx = canvas.getContext(’2d’);
// -------------- 計算用定数定義 -------------- //
// 開始時点の時間を取得しておく
let prev_time = new Date();
// ボールの半径
const r = 10;
// ボールの初期座標
let x = 50;
let y = 440;
// ボールの初速度
let vx = 30;
let vy = -80;
// 重力加速度
const a = 10;
// 反発係数
const e = 0.8;
// -------------- 描画処理 -------------- //
const draw = () => {
ctx.clearRect(0, 0, 640, 480);
// 現在時刻を取得
const now = new Date();
// 開始時点から現在までの経過時間
const diffSecond = (now.getTime() - prev_time.getTime()) * 0.01;
// 次の計算に備えて、 prev_time に now を代入
prev_time = now;
// 現在の x 軸方向の位置
x = x + (vx * diffSecond);
// 横方向に飛び出していたら、壁にぶつかっていると判定
if (x < 0) {
x = -x; // 壁から飛び出した分を戻す
vx = -vx; // 動きを反転
}
else if (x > 640) {
x = 640 - (x - 640); // 壁から飛び出した分を戻す
vx = -(vx * e); // 動きを反転
}
// 現在の y 軸方向の位置
y = y + ((vy * diffSecond) + (0.5 * a * diffSecond * diffSecond));
// 下方向に飛び出していたら、床にぶつかっていると判定
if (y > 480) {
y = 480 - (y - 480); // 床から飛び出した分を戻す
vy = -(vy * e); // 動きを反転
}
else {
vy = vy + (a * diffSecond); // 床にぶつかっていない時は通常通りの動きをさせる
}
ctx.fillStyle = ’rgb(63, 81, 181)’;
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI*2, false)
ctx.fill();
// 次回画面更新時に draw を実行するように指定
requestAnimationFrame(draw);
};
// 一回目描画実行
draw();
};
document.addEventListener(’DOMContentLoaded’, main);
新しく入っているのは反発係数と呼ばれるものですね。
x, y 方向どちらも
vy = -(vy * e); // 動きを反転
こんな感じで、動きが反転するときに反発係数 e をかけています。
これによって、速度が減衰するようになるのですね。
最終回!
さて、5 回続けた物理演算ネタも今回が最後です。
ここまできたら二つのボールの衝突とかやりたいなーとか思いますよね?
私もちょっと思っていたのですが、残念ながらボール同士の衝突は結構難しいんですよね。
というのも、ぶつかるときのお互いの角度も考慮しなければならないので
壁との衝突のように単純に速度を反転したりするだけではそれっぽい感じにならないのです。
ということで、最終回はライブラリを使いましょう。
物理演算ライブラリを使う!
これまで canvas 上でライブラリを使わず物理演算してきたのは、
物理演算ってシンプルに考えたらこんな感じで簡単にかけるんだよというのを見てもらいたかったのですが、
もうちょっと複雑な事をしようとすると、結構数学を頑張らないと行けなくなってきます。
それはそれで、頑張るのも良いのですが、これは試験ではないですからね。
自力で解く必要は全くないので、ラクできるところはラクしましょう。
ということで、ライブラリを使います。
https://github.com/liabru/matter-js
matter-js という、シンプルな 2 次元物理演算用のライブラリです。
残念ながら 2018 年末ごろから更新が止まっているようですが、
一通り演算に困らない程度のものは用意されているのでこれを使いましょう。
ライブラリを使うには、HTML に script タグを追加したらいいですね。
https://www.jsdelivr.com/package/npm/matter-js
jsdelivr から取得できるので、
スクリプトタグを
<script src="https://cdn.jsdelivr.net/npm/matter-js@0.14.2/build/matter.min.js"></script> <script src="./index.js"></script>
こんな感じで、index.js の前に読み込むように html の方に設定しましょう。
(順番が逆だとうまく動かないのでご注意)
js は前回とはガラッと変わってこんな感じで。
// モジュール設定
const Engine = Matter.Engine,
Render = Matter.Render,
World = Matter.World,
Bodies = Matter.Bodies,
Body = Matter.Body;
// 物理エンジン作成
var engine = Engine.create();
// canvas 取得
const canvas = document.getElementById(’canvas’);
// 画面作成
var render = Render.create({
canvas,
engine: engine,
options: {
width: canvas.width,
height: canvas.height,
background: ’#FFF’,
wireframes: false
}
});
// 壁を追加
World.add(engine.world, [
Bodies.rectangle(canvas.width / 2, canvas.height - 10, canvas.width, 20, { isStatic: true }),
Bodies.rectangle(canvas.width - 10, canvas.height / 2, 20, canvas.height, { isStatic: true }),
Bodies.rectangle(10, canvas.height / 2, 20, canvas.height, { isStatic: true }),
]);
// 二つの円を作成
var ballA = Bodies.circle(400, 200, 10, { restitution: 1.2 });
var ballB = Bodies.circle(200, 50, 10, { restitution: 1.2 });
// 作成した円を追加
World.add(engine.world, [ballA, ballB]);
// ボールA, B に投げる力を加える
Body.applyForce(
ballA,
{ x: ballA.position.x, y: ballA.position.y },
{ x: 0.01, y: 0.005 }
);
Body.applyForce(
ballB,
{ x: ballB.position.x, y: ballB.position.y },
{ x: -0.01, y: 0.003 }
);
// 物理計算開始
Engine.run(engine);
// 画面描画開始
Render.run(render);
これだけで結果はこうなります。
めちゃめちゃ簡単!
しかも、よく見てもらったら分かるように、
何もしなくてもボール同士の衝突まで勝手に計算してくれていますね。
コードを見直してもらうと、
これまでは挙動についての計算がコード全体の大半を占めていたのに対し、
今回ライブラリを使うことで、
- ボールを設置する
- 壁を設置する
- ボールに力を加える
などの、状況の設定のみで物理アニメーションができています。
本格的な物理アニメーションを作るなら、やはりライブラリを使おう
物理アニメーションはライブラリを使った方が、かなりラクに思ったような挙動を表現できますね。
ただ、内部でどのようなことが行われているのか、と言うことについては、
今回のように簡単には知っておくと良いでしょう。
自分でライブラリを修正するのは数学的な知識を求められるため、なかなか難しい場合が多いのですが、
それでもライブラリを使っていて「あれ?思ったように動かないな?」と言うときでも、
今回学んだような物理アニメーションの作り方を知っていると、役に立つことも色々あるかと思います。
これからも、ただ便利なものを使うだけではなくて、元々どういう考えで作られているのかと言うことについて、
たまには考えてみてくださいね。
