5
3

More than 3 years have passed since last update.

紙吹雪を飛ばしてみよう

Last updated at Posted at 2019-12-02

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

ゲーム開発中、プレイ内容に応じて演出を加えたくなることがあります。この記事では、そういった演出に利用できそうな紙吹雪エンジン kamihubuki-js を Akashic Engine 上で実行する手順を解説します。

kamihubuki-js とは

kamihubuki-js は2Dの紙吹雪専用物理エンジンです。エンジン自体は描画機能を持っておらず、利用者が実装する必要があります。

confetti-anim.gif

動画は kamihubuki-js から転載。

kamihubuki-js の導入

kamihubuki-js は npmjs に publish されていません。GitHubからインストールします。

npm install blackspotbear/kamihubuki-js

Akashic Engine からの利用

kamihubuki-js 付属のサンプルの1つは Akashic Engine への組み込み例となっています。これを参考にします。1つ1つみていきましょう。

紙吹雪の生成

main.ts#L12-L45

main.ts#L12-L45
function createConfetti(x: number, y: number): kh.Confetti {
    const vx = 0;
    const vy = 200;
    const angle = g.game.random.get(0, 100) / 100 * Math.PI;

    const fins: kh.Fin[] = [
        {
            angle: g.game.random.get(-100, 100) / 100 * (Math.PI / 2),
            size: 30,
            armAngle: 0,
            armLength: 1
        },
        {
            angle: g.game.random.get(-100, 100) / 100 * (Math.PI / 2),
            size: 30,
            armAngle: g.game.random.get(50, 100) / 100 * Math.PI,
            armLength: 1
        }
    ];

    const co = new kh.Confetti({
        x: x, y: y,
        vx: vx, vy: vy,
        angle: angle,
        fins: fins,
        av: 0,
        M: 0.5,
        K: 0.02,
        I: 3,
        RD: 0.99
    });

    return co;
}

createConfetti() で kamihubuki-js の Confetti インスタンスを生成しています。 kamihubuki-js では1つの紙吹雪につき1つの Confetti インスタンスを用意します。

コンストラクタには初期位置(x、y)、初速度(vx, vy)、空気抵抗(K)などの値が渡されています。 しかしこれだけでは、紙吹雪が回転する様子を扱うことができません。 そこで kamihubuki-js では、質点にフィンを取り付けます。フィンが受ける風の力で質点が回転します。 fins がフィンのパラメータです。

fins パラメータによって、質点から任意の数の腕を伸ばし、その先にフィンを取り付けることができます。

physics-model.ja.png

図はkamihubuki-jsから転載。

フィンの取り付け方によって、紙吹雪はさまざまな運動をするようになります。

紙吹雪の描画

main.ts#L47-L97

main.ts#L47-L97
function createConfettiEntity(scene: g.Scene, co: kh.Confetti, noCollision: boolean): g.E {
    const w = 20;
    const h = 10;
    const w2 = w / 2;
    const h2 = h / 2;

    const rect = new g.FilledRect({
        scene: scene,
        width: w,
        height: h,
        x: co.x - w2,
        y: co.y - h2,
        cssColor: colors[colorIndex]
    });

    colorIndex = (colorIndex + 1) % colors.length;

    rect.update.add(() => {
        co.update(1 / g.game.fps, windX, windY, 0, 200);


        // collision detection and resolution.
        if (noCollision) {
            if (co.x < 0 || co.x > g.game.width || co.y > g.game.height) {
                rect.destroy();
            }
        } else {
            const k = 0.5;
            if (co.x < 0) {
                co.x = 0;
                co.vx = Math.abs(co.vx) * k;
            } else if (co.x > g.game.width) {
                co.x = g.game.width;
                co.vx = -Math.abs(co.vx) * k;
            }
            if (co.y < -g.game.height) {
                co.y = -g.game.height;
                co.vy = 0;
            } else if (co.y > g.game.height) {
                co.y = g.game.height;
                co.vy = -Math.abs(co.vy) * k;
            }
        }

        rect.x = co.x - w2;
        rect.y = co.y - h2;
        rect.angle = -co.angle / Math.PI * 180;
        rect.modified();
    });

    return rect;
}

createConfettiEntity() は紙吹雪を描画するインスタンスを生成する関数です。サンプルでは、紙吹雪の描画に g.FilledRect を利用しています。色の変更や画面端との衝突処理を省いて、大切なところだけ抜き出します。

    rect.update.add(() => {
        co.update(1 / g.game.fps, windX, windY, 0, 200);

        // 省略

        rect.x = co.x - w2;
        rect.y = co.y - h2;
        rect.angle = -co.angle / Math.PI * 180;
        rect.modified();
    });

rectupdate イベントで co.update() を実行するようにしています。これによって、毎フレーム紙吹雪の状態を更新しています。次に紙吹雪の位置と角度を rect に反映しています。 kamihubuki-js は角度をラジアンで扱っていますので、Akashicの角度の単位である度に変換していることに注意していください。

Akashic Engine 製コンテンツに紙吹雪を導入する手順は以上となります。

応用: 光沢のある紙吹雪

サンプルに少し手を加えて、光沢のある紙吹雪にしてみましょう。紙吹雪がある角度では白く光るようにします。

白く光る処理は、ここでは簡単に rect にもう1つ光沢のための g.FilledRect インスタンスを重ねることします。

    // const rect = new g.FilledRect... の後ろに以下を追加する。
    // 光沢を表現する g.FilledRect インスタンス。
    const specular = new g.FilledRect({
        scene: scene,
        // rect と同じサイズだとわずかに rect の輪郭が滲み出るので、少し大きくする。
        width: rect.width + 2,
        height: rect.height + 2,
        x: -1,
        y: -1,
        opacity: 0,
        cssColor: "white",
        // 加算合成を指定することで、光っている感じを出す。
        compositeOperation: g.CompositeOperation.Lighter
    });
    rect.append(specular);

次に specular の透明度を変更する処理を追加します。

    // co.update( ... ) の後ろに以下を追加する。
    let t = Math.abs((co.angle / Math.PI) % 2);
    if (t > 1) t = 2 - t;
    specular.opacity = Math.pow(t, 5);
    specular.modified();

co.angle から、角度に比例して 0 ~ 1 の間で変化する値 t を求めています。これを specular.opacity に与えると、角度に応じて specular が姿を現して光沢となります。 Math.pow(t, 5) としているのは t の変化に緩急をつけるためです。

次のグラフの黒い線、青い線、赤い線は順に Math.pow の第2引数が 1, 2, 5 の時の specular.opacity の変化を表しています。第2引数が大きな値であるほど、鋭く光沢が現れることがわかります。

pow.png

角度に応じて白く光るようになりました(わかりやすくするため、背景色を黒にしています)。

confetti-specular.gif

最後に

kamihubuki-js 付属のサンプルでは g.FilledRect で紙吹雪を描画していますが、桜の花びらや紅葉、雪のような画像を用意して物理量を調整すれば、四季の表現にも使えそうです。

このような演出には akashic-animation を用いることも1つの方法です。 akashic-animation は SpriteStudio で作成したアニメーションの再生を行うことことができます。また、ただアニメーションを再生するだけでなく、当たり判定やプログラムからアニメーションを上書きするといった機能もあります。

5
3
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
5
3