Akashic Advent Calendar 2019 七日目の記事です。
Akashic Engine では画面に画像や文字などを表示するために、様々なクラスが用意されています。それらはどれも g.E
の派生クラスとして実装されています。この記事では、ちょっと変わった g.E
の派生クラスとして、 append()
された要素をなんでもプリンのようにプルプルさせる E (以降カスタムE)を実装してみます。
ゴールの確認
最初に完成形を確認しましょう。
プリンの画像は こちら のものを使用しています。
プリンがクリックされるとプルプルします。プリンの画像は g.Sprite
で表示されていて、動きはカスタムEによるものです。
二つの課題
プリンを実現するためには、「震える運動」と「画像の変形」の2つの機能が必要そうです。1つ1つ検討します。
震える運動
震える運動を考えます。プリンは変形すると元の形に戻ろうとします。これはバネの動きに似ています。
次のような、一方が固定され、もう一方に質点が繋がれたバネを考えてみます。質点を移動させると、振動して元の位置に戻ります。
+∂∂∂∂∂∂∂∂∂∂∂∂∂∂∂∂∂∂∂∂∂∂∂●
|-------- len ---------|
-
- : バネを固定した位置。
- ● : 質点
- len: バネの自然長(伸び縮みしていない、本来の長さ)。
そのほかにもバネの運動に必要なパラメータがあります。
- k: バネ係数。バネの伸び縮みに対する復元力を求めるための量。
- m: 質点の重さ。
- d: 質点の速度を減衰させる係数。
これらを用いて、バネの先端に取り付けられた質点の運動をコードの形で表現してみます。
/**
* バネの先端に取り付けられた質点の位置と速度を求める。
*
* @param dt 経過時間。
* @param x 質点の位置。
* @param v 質点の速度。
*/
function massSpringSolver(dt: number, x: number, v: number): { x: number, v: number } {
const k = 64; // バネ係数。
const len = 10; // バネの自然長。
const m = 1; // バネの先端に繋がれた質点の質量。
const d = 0.95; // 徐々に減速させるための値。
const dx = x - len; // バネの伸び縮みを求める。
const f = -dx * k; // 伸び縮みにバネ係数をかけて、質点に加わる力を求める。
const a = f / m; // 質点に加わっている力を質点の質量で割って、加速度を求める。
v += a * dt; // 質点を加速させる。
v *= d; // 質点が徐々に静止するよう、速度を減衰させる。
x += v * dt; // 速度に応じて質点の位置を更新する。
return { x, v }; // 新しい質点の位置と速度を返す。
}
振動しながら徐々に元の位置に戻る、という処理が実現できそうです。
画像の変形
次に、質点の運動を用いて画像を変形することを考えます。
Akashic Engine のレンダリング機能を司る g.Renderer
は画像の平行移動・回転・拡大縮小に行列を用います。行列は2行3列で、コード上は1次元配列で表されます。
const matrix = [
a, b,
c, d,
e, f
];
この6つの数値はどんな働きがあるのでしょうか。詳しい説明は省略しますが、 (a, b) はX軸を、(c, d)はY軸を変形させる働きがあります。
次の行列は平行移動・回転・拡大縮小しません。
const matrix = [
1, 0,
0, 1,
0, 0
];
次の行列はY軸方向に2倍に拡大します。
const matrix = [
1, 0,
0, 2,
0, 0
];
バネの力で運動する質点の位置を行列にうまく組み込めば、プルプルとした変形が実現できそうです。
完成
振動の方法と、それを変形に用いる方法がわかりました。これらを組み込んだカスタムE Puddinizer
のコードは次のようになります。
interface PuddinizerParameterObject extends g.EParameterObject {
/** バネ定数 */
k: number;
/** 質量 */
m: number;
/** 減衰係数 */
d: number;
}
class Puddinizer extends g.E {
private k: number;
private m: number;
private d: number;
private px: number;
private py: number;
private vx: number;
private vy: number;
constructor(param: PuddinizerParameterObject) {
super(param);
this.k = param.k;
this.m = param.m;
this.d = param.d;
this.px = 0;
this.py = 0;
this.vx = 0;
this.vy = 0;
// 毎フレーム自身を更新する。
this.update.add(() => this.onUpdate());
}
/**
* プリンを突っつく。
*
* @param px 質点の位置(X成分)
* @param py 質点の位置(Y成分)
* @param vx 質点の速度(X成分)
* @param vy 質点の速度(Y成分)
*/
poke(px: number, py: number, vx: number, vy: number): void {
this.px = px;
this.py = py;
this.vx = vx;
this.vy = vy;
}
renderSelf(renderer: g.Renderer, camera?: g.Camera): boolean {
// ここで設定した行列が子の E に作用する。
renderer.transform([
1, 0,
// Y軸を表す成分に質点の位置を加味する。
0 + this.px, 1 + this.py,
0, 0
]);
return true;
}
private onUpdate(): void {
const dt = 1 / g.game.fps;
const ax = (-this.px * this.k) / this.m;
const ay = (-this.py * this.k) / this.m;
this.vx *= this.d;
this.vy *= this.d;
this.vx += ax * dt;
this.vy += ay * dt;
this.px += this.vx * dt;
this.py += this.vy * dt;
this.modified();
}
}
振動の計算は onUpdate()
で、それを用いた行列の設定は renderSelf()
で行われています。
ここまでの検討とは次の点で違いがあるので注意してください。
- 平面上の振動を計算するため、質点の位置を px, py の2変数で表している。
- 位置 (0, 0) に固定された自然長 0 のバネに質点が繋がれている、として振動を計算している。
プルプルさせるカスタムEの実装は以上です。Puddinizer
のデモはGitHubから入手できます。
最後に
この記事では子要素に動きをつけるカスタムEを実装しました。質量やバネ定数を変更すると、色々な質感が表現できます。ゲームなら、スライムのダメージ表現といった使い方もできそうです。