今回は前回ProBuilderで作った物理的に当たるサークル状の衝撃波をパーティクルシステムを使って作成してみようと思います。
前回作成した記事の姉妹記事です。
前回作成した物理的に当たるサークル状の衝撃波の作り方は
を参照してください。
今回作成するパーティクルシステムの衝撃波は物理的に当たらず、キャラクターに当たったとトリガーをするようにしています。
2パターンの作り方で衝撃波を作成したので、好みの方法を選択してください。(._.)
1パターン目は前回と同様にTransformのScaleを大きくして衝撃波を表現するもので、2パターン目は個々のパーティクルを移動させて衝撃波を表現します。
1パターン目は詳細を解説していますが、2パターン目は1パターン目とほとんど同じなので設定のみで詳細は省略しています。
Scaleを大きくして作る衝撃波
まずはScaleを大きくするパターンの衝撃波を作っていきます。
ヒエラルキー上で右クリック→Effects→Particle Systemを選択し、名前をCircleShockwave1とします。
この時点でTransformのRotationのXが-90になっていない場合は-90にしておきます。
Shapeモジュールの設定
今回作成するパーティクルはサークル状にしたいので、まずはサークル状にパーティクルを放出するようにShape項目でShapeをCircleにします。
するとサークル上からパーティクルが放出されるようになります。
このままだとCircleの中からもパーティクルが放出されるので、Radius Thicknessを0にしてサークル上で放出されるようにします。
またパーティクルが放出される場所をCircle上でループして放出させる為にArcのModeをLoopにします。
ここまでで、サークル上をループしてパーティクルが放出されるようになります。
Mainモジュールの設定
次にMainモジュールの設定を行っていきます。
衝撃波を構成する個々のパーティクルは衝撃波が発生した時に一気に表示したいのでLoopingにチェックを入れてパーティクルを繰り返し放出するようにし、Prewarmにチェックを入れて最初から全てのパーティクルを表示するようにします。
次に、このままだとパーティクルが放出されると同時に勝手に移動してしまってサークル状の衝撃波になりません。
なので、Start Speedを0にします。
Start Speedが0なのでパーティクルは放出された位置から動きません。
Start Sizeを0.3にしてパーティクルのサイズを少し小さくします。
Max Particlesを1000から100に変更します。
ここまでのMainモジュールの設定は、
上のようになります。
ここまでで以下のようなパーティクルになっているはずです。
Emissionモジュールの設定
次はパーティクルの放出をEmissionモジュールで設定します。
Rate Over Lifetimeが10となっているので、100にします。
パーティクルの生存期間中の放出数を上げたことでサークル上に表示されるパーティクルが増えました。
上のように繋がっている一つのオブジェクトのように見えますが、実際は100個のパーティクルが重なって表示されています。
Rendererモジュールの設定
次はRendererモジュールの設定をして見た目を変更します。
Materialにパーティクルに設定したいマテリアルを設定します。
今回はスタンダードアセットのParticleFireworkを設定しました。
Render ModeはBillboardのままで平面でカメラの方向を向くモードにしていますが、ここをMeshにしてメッシュにSphere等を割り当てると見た目が立体になります(実際の当たり判定部分とはなりませんが)。
Triggersモジュールの設定
最後にパーティクルがキャラクターと接触した時にトリガー(引き金)を発動しなくてはいけません。
そこでTriggersモジュールのInsideとEnterでCallbackを設定しスクリプトで接触した事を判別できるようにします。
Collidersには本来接触相手であるキャラクターのコライダを設定しますが、衝撃波はプレハブにしてインスタンス化して発生させる為、最初からコライダを設定する事は出来ません。
コライダは衝撃波に取り付けるスクリプトから設定する事にします。
パーティクルに関する処理を行うParticleScriptの作成
作成した衝撃波のパーティクルは段々と広がっていく必要があります。
その為新しくParticleScriptスクリプトを作成し、CircleShockwave1に取り付けます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class ParticleScript : MonoBehaviour { private ParticleShockwaveChara particleShockwaveChara; // パーティクルシステム private ParticleSystem ps; // ScaleUp用の経過時間 private float elapsedScaleUpTime = 0f; // Scaleを大きくする間隔時間 [SerializeField] private float scaleUpTime = 0.03f; // ScaleUpする割合 [SerializeField] private float scaleUpParam = 0.1f; // パーティクル削除用の経過時間 private float elapsedDeleteTime = 0f; // パーティクルを削除するまでの時間 [SerializeField] private float deleteTime = 5f; void Start() { ps = GetComponent<ParticleSystem>(); particleShockwaveChara = GameObject.Find("NormalChara").GetComponent<ParticleShockwaveChara>(); ps.trigger.SetCollider(0, particleShockwaveChara.transform); } // Update is called once per frame void Update () { elapsedScaleUpTime += Time.deltaTime; elapsedDeleteTime += Time.deltaTime; if(elapsedDeleteTime >= deleteTime) { Destroy(gameObject); } if (elapsedScaleUpTime > scaleUpTime) { transform.localScale += new Vector3(scaleUpParam, scaleUpParam, scaleUpParam); elapsedScaleUpTime = 0f; } } public void OnParticleTrigger () { if (ps != null) { // Particle型のインスタンス生成 List<ParticleSystem.Particle> inside = new List<ParticleSystem.Particle>(); List<ParticleSystem.Particle> enter = new List<ParticleSystem.Particle>(); // Inside、Enterのパーティクルを取得 int numInside = ps.GetTriggerParticles(ParticleSystemTriggerEventType.Inside, inside); int numEnter = ps.GetTriggerParticles(ParticleSystemTriggerEventType.Enter, enter); // データがあればキャラクターに接触した if(numInside != 0 || numEnter != 0) { Debug.Log("接触"); if(particleShockwaveChara.GetState() != ParticleShockwaveChara.State.damage) { particleShockwaveChara.Damage(); } } // わかりやすくキャラクターと接触したパーティクルの色を赤に変更 for (int i = 0; i < numInside; i++) { ParticleSystem.Particle p = inside[i]; p.startColor = new Color32(255, 0, 0, 255); inside[i] = p; } for (int i = 0; i < numEnter; i++) { ParticleSystem.Particle p = enter[i]; p.startColor = new Color32(255, 0, 0, 255); enter[i] = p; } // パーティクルデータの設定 ps.SetTriggerParticles(ParticleSystemTriggerEventType.Inside, inside); ps.SetTriggerParticles(ParticleSystemTriggerEventType.Enter, enter); } } } |
Startメソッドでキャラクターの名前でゲームオブジェクトを検索し、そこからParticleShockwaveCharaスクリプトを取得します。
その後、パーティクルシステムのTriggerのCollidersの0番目にキャラクターのコライダを設定します。
Updateメソッドでは時間を計測してパーティクルのゲームオブジェクトのTransformのScaleを大きくしたり、指定した時間が経過したらゲームオブジェクト自体を削除する処理をしています。
OnTriggerParticleメソッドはParticleSystemのTriggersモジュールでCallbackを選択していた時に呼ばれるメソッドです。
この中でパーティクルのInside、Enterのパーティクルを全部取得し、キャラクターと接触しているもの(Collidersにキャラクターのコライダだけを設定したのでキャラクターのみが接触対象)のパーティクルの色を赤色にします。
numInsideとnumEnterの数がどちらかでも0出なければ接触したということなのでキャラクター操作スクリプトParticleShockwaveCharaのDamageメソッドを呼び出しています。
パーティクルを段々と見えなくする処理
パーティクルは今のところ最初に登場した時から消えるまで段々と見えなくなるようにはなっていません。
この処理を作るにはColor over Lifetimeを使うと簡単に出来そうですが、パーティクル個々の生成時間によって透明になっていく度合いが変わるため、全体を一緒に透明にしていく処理が難しいです。
そこでTriggersのOutsideをCallbackにして、スクリプトでパーティクル個々のStartColorのアルファ値を下げていくようにしてみます。
ParticleScriptに処理を追加します。
フィールドにalphaValueを追加します。
1 2 3 4 | // 元のパーティクルの透明度 private float alphaValue = 1f; |
OnParticleTriggerメソッド内に上の方にいくつかの処理を追記します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public void OnParticleTrigger () { if (ps != null) { // パーティクルを段々と透けさせる処理 List<ParticleSystem.Particle> outside = new List<ParticleSystem.Particle>(); int numOutside = ps.GetTriggerParticles(ParticleSystemTriggerEventType.Outside, outside); alphaValue -= (1f / deleteTime) * Time.deltaTime; alphaValue = (alphaValue <= 0f) ? 0f : alphaValue; for (int i = 0; i < numOutside; i++) { ParticleSystem.Particle p = outside[i]; p.startColor = new Color(1f, 1f, 1f, alphaValue); outside[i] = p; } ps.SetTriggerParticles(ParticleSystemTriggerEventType.Outside, outside); |
パーティクルがOutsideの状態の時に時間経過と共にalphaValueの値を下げていきます。
0以下になったら0にします。
これで処理が完成しました。
完全に綺麗に消えていくわけではなく、最後にいくつかのパーティクルが残る場合があります。
なのでDeleteTimeが5の場合はそれよりも長い間パーティクル生存時間を設定しておきます。
MainモジュールのLifetimeを6にしてパーティクルのゲームオブジェクトが消えるよりも長いパーティクル生存時間にします。
Scaleを大きくしていく衝撃波の確認
Scaleを大きくしていく衝撃波が出来上がったので、試してみましょう。
CircleShockwave1をAssetsエリアにドラッグ&ドロップにしてプレハブにします。
ヒエラルキー上のCircleShockwave1はいらないので削除します。
前回の記事
のInstantiateParticleスクリプトをMain Cameraに取り付け、ParticlePrefabにCircleShockwave1のプレハブを設定します。
上のようになりました。
Scaleを大きくしていく衝撃波の問題点
Scaleを大きくしていく衝撃波は出来上がりましたが、いくつか問題点があります。
それはサークル上にパーティクルが満遍なく放出されているように見えますが、一部かけている部分があります。
ここら辺はMainモジュールやEmission、Shape辺りをいじってうまくやるしかなさそうです。
先ほど書きましたが、MainモジュールでLoopingにチェックを入れている為、パーティクルの生存期間が過ぎると新しいパーティクルを生成します。
その為、ParticleScriptのdeleteTimeとパーティクルのLifetimeは相互に調整する必要があります。
パーティクルを移動させて作る衝撃波
次はScaleを大きくして衝撃波にするのではなく、発生させたパーティクルを移動させる衝撃波を作ってみます。
ヒエラルキー上で右クリック→Effects→Particle Systemを選択し、名前をCircleShockwave2とします。
Shapeモジュールの設定
ShapeモジュールはScaleを大きくしていく衝撃波と同じ設定です。
Mainモジュールの設定
MainモジュールもScaleを大きくしていく衝撃波とほぼ同じ設定です。
ただ、今回は全てのパーティクルが終了した時の処理をStop ActionでDestroyに指定し、ゲームオブジェクトを削除する事にします。
Emissionモジュールの設定
EmissionモジュールもScaleを大きくしていく衝撃波と同じ設定です。
Velocity over Lifetimeモジュールの設定
Velocity over Lifetimeにチェックを入れます。
Velocity over Lifetimeはパーティクルの生存期間中の速度の設定が出来ますが、今回はMainモジュールでLoopingとPrewarmにチェックを入れない為、パーティクルはMaxParticlesの数になるまで順に生成されます。
その為、パーティクル数がMaxParticlesの数を超えるまでは動かない設定にしておきます。
Velocity over Lifetimeはスクリプトから操作する事にします。
Color over Lifetime
次にパーティクルの生存期間中にパーティクルの色を変化させる為、Color over Lifetimeにチェックを入れ、Colorをクリックします。
上のようにAlphaの右の矢印部分をクリックし、Alphaの値を0にします。
こうすることで、パーティクル個々が生存期間中に段々と透明になっていき、消滅する時には完全に透明になります。
ここのパーティクルの生存期間はMainモジュールで設定したLifetimeになります。
Lifetimeは5に設定したのでパーティクルが放出されてから5秒後にパーティクルは透明になり、MainモジュールのStop ActionでDestroyを指定した為、ゲームオブジェクトが削除されます。
Triggersモジュールの設定
次にパーティクルがキャラクターと接触した時にスクリプトで判定出来るようにする為、Triggersモジュールの設定をします。
InsideとEnterでCallbackを設定し、スクリプトで判定出来るようにします。
Rendererモジュールの設定
次にRendererモジュールの設定をします。
こちらは適当にマテリアルを設定します。
パーティクルの移動処理スクリプト
CircleShockwave2に新しくParticleScript2スクリプトを作成し取り付けます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class ParticleScript2 : MonoBehaviour { [SerializeField] private ParticleShockwaveChara particleShockwaveChara; // パーティクルシステム private ParticleSystem ps; private bool flag; // 経過時間 private float elapsedTime; // Use this for initialization void Start() { ps = GetComponent<ParticleSystem>(); ps.GetComponent<Renderer>().enabled = false; particleShockwaveChara = GameObject.Find("NormalChara").GetComponent<ParticleShockwaveChara>(); ps.trigger.SetCollider(0, particleShockwaveChara.transform); // MaxParticlesを超えるパーティクルを生成するまでシミュレーションスピードを上げる var main = ps.main; main.simulationSpeed = 10f; } // Update is called once per frame void Update() { elapsedTime += Time.deltaTime; // 現在のパーティクル数がMaxParticlesを超えたらパーティクルを移動させる if (!flag && ps.particleCount >= ps.main.maxParticles) { var main = ps.main; main.simulationSpeed = 1f; flag = true; ps.GetComponent<Renderer>().enabled = true; var a = ps.velocityOverLifetime; a.radial = 2f; } } public void OnParticleTrigger() { if (ps != null && flag) { // particles List<ParticleSystem.Particle> enter = new List<ParticleSystem.Particle>(); List<ParticleSystem.Particle> inside = new List<ParticleSystem.Particle>(); // get int numEnter = ps.GetTriggerParticles(ParticleSystemTriggerEventType.Enter, enter); int numInside = ps.GetTriggerParticles(ParticleSystemTriggerEventType.Inside, inside); if (numEnter != 0 || numInside != 0) { Debug.Log("接触"); if (particleShockwaveChara.GetState() != ParticleShockwaveChara.State.damage) { particleShockwaveChara.Damage(); } } for (int i = 0; i < numEnter; i++) { ParticleSystem.Particle p = enter[i]; p.startColor = new Color32(255, 0, 0, 255); enter[i] = p; } for (int i = 0; i < numInside; i++) { ParticleSystem.Particle p = inside[i]; p.startColor = new Color32(255, 0, 0, 255); inside[i] = p; } ps.SetTriggerParticles(ParticleSystemTriggerEventType.Enter, enter); ps.SetTriggerParticles(ParticleSystemTriggerEventType.Inside, inside); } } } |
今回作成したパーティクルはMaxParticlesの値である100まで順番にパーティクルを生成します。
その為、パーティクルを100個生成し、サークル上に配置するまでに少し時間がかかります。
なので、パーティクルのシミュレーションスピードを一時的に上げて、100個生成するまでの時間を早めます。
また100個になるまでパーティクルのRendererを無効化して表示をしないようにします。
Updateメソッド内で現在のパーティクル数がMaxParticlesの数を超えた時にシミュレーションスピードを1と最初の値に戻し、Rendererを有効にし、Velocity over Lifetimeのradialを2にしてパーティクルが移動するようにします。
OnParticleTriggerメソッド内の処理はScaleを大きくしていく衝撃波と同じ処理になります。
これで機能が出来上がりました!
CircleShockwave2をAssetsエリアにドラッグ&ドロップしてプレハブにし、Main CameraのInstantiateParticleにCircleShockwave2プレハブを設定します。
ヒエラルキー上のCircleShockwave2は削除します。
パーティクルを移動させて作る衝撃波の確認
パーティクルを移動させて作る衝撃波は、
上のようになりました。
パーティクルを移動させて作る衝撃波の問題点
パーティクルを移動させて作る衝撃波もいくつか問題点があります。
Scaleを大きくしていく衝撃波と同じようにサークル上のパーティクルが一部かけてしまいます。
また先ほど書きましたが、MainモジュールでLoopingとPrewarmにチェックを入れていない為、最初からパーティクルが表示されません。
その為スクリプトで面倒くさい処理をしています。
終わりに
今回2パターンのパーティクルの衝撃波を作成しましたが、どちらも完全にうまくいったという感じには出来ませんでした。
何かうまい方法もあるかもしれませんが、ある程度は出来たので今回はこれで終わります・・・・(._.)