花火を打ち上げる

花火は代表的な「パーティクルエフェクト」だが、
その見た目のシンプルさに対して案外実装は難しい。
なぜなら粒をランダムにではなく、規則的に放たなければいないからだ。
そうしないと花火っぽく見えないのだ。

ちなみに花火の粒は「星」と呼ぶので、ここでもそう呼ぶことにする。

星.png
Star


class 星クラス
{
  method 星クラス()
  {
    CreateImage(name="Image"
      , ox="Center", oy="Middle", image="星.png", blend="Add");
  }
  method OnEnter()
  {
    Enter(to="Image");

    // ゆっくり落ちていく
    Move(to="Image", y=rand_range(50,300), time=3000, step="Acc5");
  }
}

class 花火クラス
{
  method 花火クラス()
  {
  }

  method OnEnter()
  {
    int $number = 0;

    // 星を用意する
    command $star = @星クラス();

    // 多重の円の形になるように星を飛ばす
    int $ring = rand_range(6,10); // 円の数
    float $max_r = Float(rand_range(100,200)); // 最大半径
    for(int $j=0; $j<$ring; $j++)
    {
      int $divide = $j*4; // 円を構成する星の数
      float $r = $max_r*sin(pi()/2.0*Float($j)/Float($ring)); // 飛距離
      int $offset = mod($j,3)==0 ? 0 : 360/$divide/2; // たまにずらす
      for(int $i=0; $i<$divide; $i++)
      {
        string $name = "星"+String($number++);
        ThreadCreate(call=@星スレッド(name=$name, star=$star
          , move=$r, angle=360*$i/$divide+$offset+rand_range(-2,2)));
      }
    }

    WaitDecor();
    Delete();
  }

  method 星スレッド(string $name, command $star, float $move, int $angle)
  {
    CreateObject(name=$name, class=$star);
    Enter(to=$name);

    float $x = $move*cos(radian(Float($angle)));
    float $y = -$move*sin(radian(Float($angle)));
	int   $time = rand_range(1900,2100);

    Move(to=$name, time=$time, x=$x, y=$y, step="Dec5");
    Opaque(to=$name, time=rand_range(1500,2500), alpha=0%, step="Acc5");
    WaitDecor(to=$name);
    Delete(to=$name);
  }
}

method Main()
{
  CreateColor(name="背景", w=1280, h=720, color=black);
  Enter(to="背景");

  int $number = 0;
  while(true)
  {
    string $name = "花火"+String($number++);
    CreateObject(name=$name
      , x=rand_range(100,1280-100), y=rand_range(260,460)
      , class=@花火クラス());
    Enter(to=$name);
    wait rand_range(200,1000);
  }
}
YouTube Preview Image

このスクリプトのポイントはfor文を使っているところだ。
for文はwhile文と同じように繰り返しを意味する文だが、特定回数繰り返す場合に主に使う。

最初に言ったように星をランダムに放っても花火っぽくはならない。
星は規則的に放つ必要がある。
理由は実物の花火の構造を考えれば自明だ。

花火の玉には星が円形に綺麗に並べて詰められて、その真ん中に爆薬が詰められている。
花火が爆発すると、綺麗に並んでいた星が一斉に放たれる。
この時星の放たれ方は決してランダムではない。
星が詰められていた位置によって決められた方向へと飛んでいくのだ。

さらに実際花火が炸裂した時の姿は円形ではなく球形である。
そのあたりも注意が必要で、飛距離でsin計算をしているのはそのためだ。

うーん、説明しててもややこしい。
とにかく大変なのである。

それではもうちょっと手を入れてみよう。
星が単色で地味なので、カラフルにしてみよう。


class 星クラス
{
  color $m_color2;

  method 星クラス(color $color1, color $color2)
  {
    operate $m_color2 = $color2;

    CreateImage(name="Image"
      , ox="Center", oy="Middle", image="星.png", blend="Add"
      , dynamic_color=$color1, dynamic_blend="Color");
  }
  method OnEnter()
  {
    Enter(to="Image");

    // ゆっくり落ちていく
    Move(to="Image", y=rand_range(50,300), time=3000, step="Acc5");
    Color(to="Image", color=$m_color2, time=1500, step="AccSin");
  }
}

class 花火クラス
{
  method 花火クラス()
  {
  }

  method OnEnter()
  {
    int $number = 0;

    // 星を用意する
    color $color1 = RGB(rand_range(0,255),rand_range(0,255),rand_range(0,255));
    color $color2 = RGB(rand_range(0,255),rand_range(0,255),rand_range(0,255));
    command $star = @星クラス(color1=$color1, color2=$color2);

    // 多重の円の形になるように星を飛ばす
    int $ring = rand_range(6,10); // 円の数
    float $max_r = Float(rand_range(100,200)); // 最大半径
    for(int $j=0; $j<$ring; $j++)
    {
      int $divide = $j*4; // 円を構成する星の数
      float $r = $max_r*sin(pi()/2.0*Float($j)/Float($ring)); // 飛距離
      int $offset = mod($j,3)==0 ? 0 : 360/$divide/2; // たまにずらす
      for(int $i=0; $i<$divide; $i++)
      {
        string $name = "星"+String($number++);
        ThreadCreate(call=@星スレッド(name=$name, star=$star
          , move=$r, angle=360*$i/$divide+$offset+rand_range(-2,2)));
      }
    }

    WaitDecor();
    Delete();
  }

  method 星スレッド(string $name, command $star, float $move, int $angle)
  {
    CreateObject(name=$name, class=$star);
    Enter(to=$name);

    float $x = $move*cos(radian(Float($angle)));
    float $y = -$move*sin(radian(Float($angle)));
    int   $time = rand_range(1900,2100);

    Move(to=$name, time=$time, x=$x, y=$y, step="Dec5");
    Opaque(to=$name, time=rand_range(1500,2500), alpha=0%, step="Acc5");
    WaitDecor(to=$name);
    Delete(to=$name);
  }
}

method Main()
{
  CreateColor(name="背景", w=1280, h=720, color=black);
  Enter(to="背景");

  int $number = 0;
  while(true)
  {
    string $name = "花火"+String($number++);
    CreateObject(name=$name
      , x=rand_range(100,1280-100), y=rand_range(260,460)
      , class=@花火クラス());
    Enter(to=$name);
    wait rand_range(200,1000);
  }
}
YouTube Preview Image

星をカラフルにすると言ったが、新たな素材は何も使っていない。
CreateImage命令のdynamic_color, dynamic_blendというパラメータを使っている。
このパラメータは画像に動的に色を合成することを指定するパラメータで
これを使って星の色をランダムに変えているのだ。

さらにColor命令は指定色を後から変える命令で、
星の色がだんだんと変化するような演出を加えている。


Leave a Reply

*