発想に稲妻落とせ!電撃パーティクル【第1回パーティクル応用講座】

はじめに

どうも、アドオンパーティクル講座となります。
更に不定期となりますが、ついに応用編へと入っていきます。
ここから難易度が大きく上がっていきますが、前回までのやり方の通り分かりやすく解説していきます。
頑張ってまいりましょう!

前回の記事はこちら!

1.稲妻が落ちたかのような電撃パーティクル

今回はなんと、とてもダイナミックな動きを見せてくれるパーティクルを解説いたします!
少し前に私がアドオンパーティクルで作ったものになります。本当にできたときは私もびっくりしたものです。

(ちょっとダサイって言わないでね、悲しんじゃいます。そういう時はね、自分で頑張って、カッコよくするんだよ。)

2.jsonファイルの中身(1.10.0format対応)

記述例を載せていきます。非常に長い文が目立ちますが一つずつ見ていけば怖くありませんよ。
※こちらの記述例は執筆者の持つ技術を駆使したうえでのものになっています。より効率的な書き方があるかもしれませんが、一例としてご覧ください。

{
  "format_version":"1.10.0",
  "particle_effect": {
    "description": {
      "identifier": "particle_test_tip_6",
      "basic_render_parameters": {
        "material": "particles_alpha",
        "texture": "textures/particle/animparticle"
      },
      "components": {
        "minecraft:emitter_rate_steady": {
          "spawn_rate": 500,
          "max_particles": 200
        },
        "minecraft:emitter_lifetime_once": {
          "active_time": 2
        },
        "minecraft:emitter_shape_point": {
          "offset": [
            "variable.route = variable.route + 1.0; variable.route = variable.route > 9.0 ? 0.0 : variable.route; variable.Xadd = variable.route > 8 ? Math.random(-0.10, 0.10) : variable.Xadd; variable.X = variable.X + variable.Xadd; return variable.X;",
            "variable.CustomY = variable.CustomY + -0.1; return variable.CustomY;",
            "variable.Zadd = variable.route > 8 ? Math.random(-0.10, 0.10) : variable.Zadd; variable.Z = variable.Z + variable.Zadd; return variable.Z;"],
          "direction": [0.0, 0.0, 0.0]
        },
        "minecraft:particle_initial_speed": 1.2,

        "minecraft:particle_initial_spin": {
          "rotation": 0,
          "rotation_rate": 0
        },

        "minecraft:particle_lifetime_expression": {
          "max_lifetime": 1.5
        },

        "minecraft:particle_motion_dynamic": {
          "linear_acceleration": [ 0.0, 0.0, 0.0],
          "linear_drag_coefficient": 1,
          "rotation_drag_coefficient": 0.0
        },

        "minecraft:particle_appearance_billboard": {
          "size": [ 0.16, 0.16],
          "facing_camera_mode": "rotate_xyz",

          "uv": {
            "texture_width": 8,
            "texture_height": 8,
            "flipbook": {
              "base_UV": [ 0, 0 ],
              "size_UV": [ 8, 8 ],
              "step_UV": [ 0, 0 ],
              "frames_per_second": 8,
              "max_frame": 1,
              "stretch_to_lifetime": false,
              "loop": false
            }
          }
        },
        "minecraft:particle_appearance_tinting": {
          "color": [0.2 ,1.0 ,0.5 ,0.0]
        }
      }
    }
  }
}

“minecraft:emitter_shape_point”の部分ですね。それはもう呆れるほど長いです。

3.コンポーネント解説

まず演算の流れについて解説します。
“minecraft:emitter_shape_point”は、パーティクルが召喚されるたびに更新や実行といった処理がされます。これは、“minecraft:emitter_rate_steady”で指定した頻度と同様のタイミングとなります。
例えば、“minecraft:emitter_shape_point”の中でとある変数が1追加されるという書き方をした場合、パーティクルを召喚するたびに123…と増えていくわけです。
これだけだと何のことかさっぱりだと思いますが、パーティクルがいくつ出たかというのを調べるのにとても重要だったりします。

それでは本文に入っていきましょう。横長で分かりづらいため、一部改行しています。これをそのままコピペしても動きませんよ。

"minecraft:emitter_shape_point": {
  "offset": [
    "variable.route = variable.route + 1.0;
     variable.route = variable.route > 9.0 ? 0.0 : variable.route;
     variable.Xadd = variable.route > 8 ? Math.random(-0.10, 0.10) : variable.Xadd;
     variable.X = variable.X + variable.Xadd;
     return variable.X;",

    "variable.CustomY = variable.CustomY + -0.1;
     return variable.CustomY;",

    "variable.Zadd = variable.route > 8 ? Math.random(-0.10, 0.10) : variable.Zadd;
     variable.Z = variable.Z + variable.Zadd;
     return variable.Z;"
     
     ],
  "direction": [0.0, 0.0, 0.0]
}

offset内の上の行から解説していきます。

variable.route = variable.route + 1.0;

“variable.route”は独自の変数です。” + 1.0″といった記述がされているので、パーティクルが出る度に1ずつ加算されていきます。
今回は、電撃パーティクルを作るに当たって一定間隔で向きが変わるようなものを作る必要がありました。
つまり、“variable.route”はその一定間隔の長さを計るために用意した変数です。

ちなみに記述文の見方ですが、プログラミング言語ではよくあるような形になっています。
対象 = 値1 + 値2;

今回の例だと“variable.route”に、“variable.route”の値に1を足した値を代入する、というものになります。
(プログラミング言語では、加算するとき「 対象 += 1 」や「 対象++ 」みたいな書き方もありますが、MoLangでは未対応です…。)

また、こういった計算を区切るには最後にセミコロン(” ; “)を入れてあげましょう。

variable.route = variable.route > 9.0 ? 0.0 : variable.route;

非常に奇妙な書き方ですが、このように見ます。
対象 = 条件 ? 真であればここの値を代入 : 偽であればここの値を代入

条件を満たしているか否かで、対象に代入する値を変化させることができます。
1つ目の解説で言っていたのはこれのことで、“variable.route”9(9.0)を上回っていれば0.0にリセットしてしまいます。そうでなければ“variable.route”を代入します。

ちなみに、これは実は執筆している途中で気づいたのですが、ここまでの流れを一つの構文にまとめる方法を見つけました。

variable.route = variable.route > 9.0 ? 0.0 : variable.route + 1.0;

このように書くと、条件を満たさないうちは常に“variable.route”が加算されていくような式になっています。
現時点ではこれによるメリットは見つけていませんが、こういった効率的(かもしれない)記述方法というのは、時に軽量化へのヒントだったりします。
根気のある方は色々な記述のしかたをしてみるのもいいでしょう!

variable.Xadd = variable.route > 8 ? Math.random(-0.10, 0.10) : variable.Xadd;

variable.X = variable.X + variable.Xadd;
return variable.X;

三行を一気に解説します。こちらの記述内容は、Z座標の部分も同じです。
“variable.route”8以上で、-0.100.10のランダムな値を“variable.Xadd”に代入します。
そして、“variable.X”“variable.Xadd”の値を加算し、“variable.X”の値を返します。
この書き方は非常に重要で、二行目と三行目の処理は常に行われます。
つまり、“variable.route”8を上回らない限り、X(Z)座標は常に一定値(“variable.Xadd”の値ぶん)加算され続けます。
また逆に8を上回ると“variable.Xadd”の値は変化することにより、稲妻のランダムな動き方を再現しているわけです。

そして、さりげなく見たことない記述が出てきたと思います。
returnは値を返すことを指しており、単純な計算だけであれば不要ですが今回はいくつかの演算をしたため、どの値を答えとして出すかどうかも指定してあげる必要があります。

variable.CustomY = variable.CustomY + -0.1;

return variable.CustomY;

ここまでが読み解ければ、これ以降の文も問題ないと思います。単純にY座標を-0.1ずつ加算していく感じですね。
この文は非常に無駄が多く、“variable.CustomY – 0.1”という書き方でも問題ないと思います。

反面教師…というわけではないのですが、ここにアレンジを加えればもっと面白い動きができるかな?と皆さんにはちょっと期待してみます。自信のある方はぜひ挑戦してみましょう。

終わりに

解説は以上となります。
今回の内容は、慣れないとかなり疲れるものだと思います。これを無事理解したあなたは素晴らしい!おめでとう!分からなくてもやり遂げたのならそれでよし!
先ほども書きましたが、もし自信のある方は自分なりに構文をアレンジして、もっと様々な動きをさせてみるといいと思います。
これほどまでにアドオンパーティクルは自由度があるというのを、今回の記事ではお見せできたと思います。ぜひパーティクルをもっともっと遊んでやってください。

それではお疲れ様でした!


前回↓

次回↓
更新をお待ちください。