【C#】マイクラで花火を打ち上げる(万華鏡型花火製作)

イクラで花火を打ち上げる場合、/summon コマンドと NBT を用いて花火の形状や色等を決めます。これを連続して打ち上げることで様々な形の花火を演出することが出来ます。

今回は万華鏡型と呼ばれる花火を打ち上げるための方法を紹介します。

1. 万華鏡型の花火

土浦全国花火競技大会によれば、このような形を万華鏡というみたいです。

f:id:takunology:20210713234536p:plain

www.tsuchiura-hanabi.jp

この花火を打ち上げるには2種類の花火が必要です。1つ目は大玉花火、2つ目はバースト型の花火です。マイクラには花火の形が5種類しかないので、これらを駆使して本物の花火に近づけていくしかありません。

ちなみに、大玉花火(左側)とバースト型(右側)はそれぞれこのような形です。

f:id:takunology:20210713235026p:plain

バーストに関しては、Motion:[x, y, z] のNBTを付与することによって、打ち出す方向(角度)を決めることが出来ます。イメージとしてはベクトルによる力の合成です。

2. 万華鏡花火の理想形

万華鏡花火を実装するには、バースト型の打ち上げる開始地点を、大玉花火の中心にする必要があります。大玉花火が爆発して、その中心から放射状にバースト型の花火を打ち出すことによって万華鏡の花火を再現します。ここで、重要になってくるのが高さです。

花火の NBT には LifeTime というタグがあり、打ち上げてから爆発するまでの時間を Tick 単位 (1Tick = 1/20秒) で指定することが出来ます。もちろん、0 Tick にすればその座標でバースト花火を打ち出せばすぐに出来ますが、「打ち上げたい」場合は上昇していく大玉花火の中心を求める必要があります。この高さ(y座標)を決めるのが重要です。

f:id:takunology:20210714000047p:plain

橙色の円が大玉花火、青い矢印がバースト型です。理想形のように、大玉花火の中心からバースト型の花火を打ち出せるように決めることを目標にします。

と、その前に斜めの方向にバースト型の花火を打ち出す方法について説明します。

3. ベクトルの合成

高校物理ではベクトルを用いた力の合成というものを扱ったと思いますが、バースト型花火を打ち出すにはベクトルの概念を知っておくと簡単に実装できるようになります。ベクトルの合成は画像のように、2つのベクトル(A, B)を平行移動させて平行四辺形を作ります。2つのベクトルの原点から、その平行四辺形の対角線上にある頂点を結ぶ新しいベクトル(赤い矢印)が、合成によって出来たベクトルです。つまり、2つの方向に力を加えると、ある1つのベクトル(C)へ物体が動きます。

f:id:takunology:20210714001242p:plain

これをバースト型の花火で確認します。マイクラでは力(動き)を与える NBT に Motion:[x, y, z] があります。それぞれ x座標、y座標、z座標に力を加えることが出来ます。画像の画面正面の方向が x 座標とします。このとき、垂直方向が y 座標、水平方向が z 座標となります。ちなみに y 座標は上にいくほど値が大きくなり、z 座標は右へいくほど値が大きくなります。

例えば、Motion[0.0, 2.0, 2.0] というNBTを付け加えると、バースト型の花火はこのようになります。

f:id:takunology:20210714002319p:plain

この場合、y方向に 2.0、z方向に 2.0 なのでそれぞれのベクトルを平行移動させると正方形ができます。このとき、角度は45度になります。ちなみに、Motion の値をバラバラにすると角度を細かく調整出来ますが、ここでは割愛します。(内積をつかうと角度が求まります。)

4. LifeTimeと高さの依存性

花火を打ち上げる場合、LifeTime を指定する必要があります。例えば、LifeTime を 20, 30, 40 にして大玉花火を打ち上げてみると、このように高さが異なります。つまり、LifeTime を設定するたびに爆発する高さが変わるので、バースト型の花火も合わせなければ、理想形の万華鏡型花火にはなりません。

f:id:takunology:20210714003329p:plain

高さとLifeTimeの間には一見、比例関係があるように思えますが、ちゃんと測定すると放物線を描きます。例えば、LifeTime = 10 から 70 までで高さを測定し、フィッティングするとこのようなグラフになります。

f:id:takunology:20210722072419p:plain

ここで、多項式近似(2次関数)を用いるとそれぞれ係数が求まります。なお、x には任意の LifeTime が入ります。

例えば、LifeTime を 20 Tick に設定すると、

 \displaystyle{
y = 0.02060 \times 20^2 - 0.02976 \times 20 + 3.28571 = 10.93051
}

となります。つまり、打ち上げる地点から約 10.9 ブロック目で大玉花火が爆発するということになります。なので、この座標に合わせてバースト型の花火を打ち出せば理想形の万華鏡型花火が作れるということになります。

5. 実装例と実行結果

とりあえず原理がわかってきたところで花火を作ってみます。例によって MinecraftConnection ライブラリを使用します。

www.nuget.org

実装例です。

using MinecraftConnection;
using MinecraftConnection.Items;

namespace SampleFireworks
{
    class Program
    {
        static string address = "127.0.0.1";
        static ushort port = 25575;
        static string pass = "minecraft";

        static MinecraftCommands commands = new MinecraftCommands(address, port, pass);

        static void Main(string[] args)
        {
            int x = -940 + 20;
            int y = 74;
            int z = -798;

            while (true)
            {
                KalaydScope(x, y, z, 20);
                commands.Wait(1000);
            }
        }

        static void KalaydScope(int x, int y, int z, int time)
        {
            Fireworks fireworksMain = new Fireworks(time, 2, FireworksShapes.LargeBall, false, false, FireworksColors.RED, FireworksColors.RED);
            Fireworks fireworksOut1 = new Fireworks(0, 2, FireworksShapes.Burst, false, true, FireworksColors.LIGHTBLUE, FireworksColors.LIGHTBLUE);
            Fireworks fireworksOut2 = new Fireworks(0, 2, FireworksShapes.Burst, false, true, FireworksColors.PINK, FireworksColors.PINK);
            Fireworks fireworksOut3 = new Fireworks(0, 2, FireworksShapes.Burst, false, true, FireworksColors.LIME, FireworksColors.LIME);
            Fireworks fireworksOut4 = new Fireworks(0, 2, FireworksShapes.Burst, false, true, FireworksColors.YELLOW, FireworksColors.YELLOW);

            // ここで大玉花火が爆発する座標を求める(切片は 1 にするとうまくいきます)
            double diff = (0.020595 * time * time) - (0.02976 * time) + 1;
            // 爆発する座標を元の座標に加算する
            int sub = (int)diff;
            // Motion の NBT の値を設定
            double motion = 2.5;
            double revMotion = -2.5;
            // 斜め方向の大きさを調整するための倍率
            double rate = 0.8;

            commands.SetOffFireworks(x, y, z, fireworksMain);
            commands.SetOffFireworks(x, y, z, fireworksMain);
            // 大玉花火の爆発とタイミングをあわせるための待機時間 = LifeTime × 60
            commands.Wait(time * 60);
            commands.SetOffFireworks(x, y + sub, z, fireworksOut1.Motion(0, motion * rate, motion * rate));
            commands.SetOffFireworks(x, y + sub, z, fireworksOut1.Motion(0, revMotion * rate, revMotion * rate));
            commands.SetOffFireworks(x, y + sub, z, fireworksOut2.Motion(0, motion * rate, revMotion * rate));
            commands.SetOffFireworks(x, y + sub, z, fireworksOut2.Motion(0, revMotion * rate, motion * rate));
            commands.SetOffFireworks(x, y + sub, z, fireworksOut3.Motion(0, motion, 0));
            commands.SetOffFireworks(x, y + sub, z, fireworksOut3.Motion(0, revMotion, 0));
            commands.SetOffFireworks(x, y + sub, z, fireworksOut4.Motion(0, 0, motion));
            commands.SetOffFireworks(x, y + sub, z, fireworksOut4.Motion(0, 0, revMotion));
        }
    }
}

なんだかごちゃごちゃしていますが、先程説明した高さを求める計算式と、Motion による角度調整を行っています。また、大玉花火の爆発タイミングと、バースト型花火の打ち上げタイミングですが、LifeTime を 60 倍することでいい感じのタイミングになります。(ここだけ直感的ですみません...。)

実行してみると、万華鏡型の花火になります。

f:id:takunology:20210714010211g:plain

色や形、打ち出す方向などを色々と試してみると面白いと思います。

新型コロナの影響で全国各地の花火大会が中止になっています。個人的には夏といえば花火なので、花火大会がなくなってしまうのは非常に残念に思います。ですが、マイクラを使えば、オンラインで(プレイヤーを招待したりして)花火大会が楽しめると思います!

イベントのお知らせ

2021年7月21日(水)20:00 より、MS Tech Camp #9 を開催します!
イベントでは Azure Cognitive Service の1つである Face API を使って、ある画像内の顔を分析します。興味のある方はぜひご参加ください!(社会人の方も大歓迎です!)

mspjp.connpass.com