LoginSignup
3
1

More than 3 years have passed since last update.

カスタムE で (((🍮)))

Last updated at Posted at 2019-12-06

Akashic Advent Calendar 2019 七日目の記事です。

Akashic Engine では画面に画像や文字などを表示するために、様々なクラスが用意されています。それらはどれも g.E の派生クラスとして実装されています。この記事では、ちょっと変わった g.E の派生クラスとして、 append() された要素をなんでもプリンのようにプルプルさせる E (以降カスタムE)を実装してみます。

ゴールの確認

最初に完成形を確認しましょう。

akashic-pudding-demo.gif

プリンの画像は こちら のものを使用しています。

プリンがクリックされるとプルプルします。プリンの画像は 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を実装しました。質量やバネ定数を変更すると、色々な質感が表現できます。ゲームなら、スライムのダメージ表現といった使い方もできそうです。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1