この記事でのバージョン
Unity 2021.3.11f1
UniRx 7.1.0
UniTask 2.3.1
概要
Unityでエフェクトを表現する手法として、ParticleSystemコンポーネントが用いられることが多いかと思いますが、その際の再生・停止・停止待ちの方法について紹介します。
前提
ParticleSystemコンポーネントが階層上にいくつも用いられたGameObjectのPrefabが用意されているとします。
再生
public class ParticleObject : MonoBehaviour { // ParticleSystemを直接Serialize // ScriptableObjectを経由してもOK [SerializeField] private ParticleSystem _effectPrefab; private ParticleSystem _effectInstance; private void Awake() { _effectInstance = Instantiate(_effectPrefab, transform); } public void Play() { if (_effectInstance) { _effectInstance.Play(); } } }
ParticleSystemをInstantiateしたeffectInstanceのPlay関数を呼ぶことで、ルート以下全てのParticleSystemが再生されます。
Play関数を呼ぶタイミングによって再生タイミングをコントロールできますが、ParticleSystem側の設定でPlayOnAwakeがオンになっている場合はInstantiateしたタイミングで自動で再生されてしまうため、PlayOnAwakeの設定は基本的にオフにしておいた方が無難かもしれません。
停止
public class ParticleObject : MonoBehaviour { [SerializeField] private ParticleSystem _effectPrefab; private ParticleSystem _effectInstance; private void Awake() { _effectInstance = Instantiate(_effectPrefab, transform); } public void Play() { if (_effectInstance) { _effectInstance.Play(); } } public void Stop() { if (_effectInstance) { _effectInstance.Stop(); } } }
停止も同様にeffectInstanceのStop関数を呼ぶことで、ルート以下全てのParticleSystemが停止されます。
停止待ち
ワンショットエフェクトの再生終わりを待ちたいとき、ループエフェクトに対してStopを呼び完全に停止するのを待ちたいときはParticleSystemのStopActionを使用します。
また、そのままではStopActionのコールバックを受け取れないため、動的にComponentを追加しつつ、UniRxを利用してイベントを通知するようにしています。
なお、ここでいう停止待ちとはParticleの放出が止まっているかつ、Particleが全て消滅している状態まで待つことを指します。
ObservableParticleSystemTrigger.cs
[DisallowMultipleComponent] public class ObservableParticleSystemTrigger : ObservableTriggerBase { private Subject<Unit> _onParticleSystemStopped; private void OnParticleSystemStopped() { _onParticleSystemStopped.OnNext(Unit.Default); } public IObservable<Unit> OnParticleSystemStoppedAsObservable() { return _onParticleSystemStopped ??= new Subject<Unit>(); } protected override void RaiseOnCompletedOnDestroy() { _onParticleSystemStopped?.OnCompleted(); } }
まず、ObservableTriggerBaseを継承したクラスを用意し、OnParticleSystemStoppedのコールバックを受け取ってそれをObservableで通知できるようにします。
UniRxExtensions.cs
public static class UniRxExtensions { public static IObservable<Unit> OnParticleSystemStoppedAsObservable(this ParticleSystem particleSystem) { if (particleSystem == null) return Observable.Empty<Unit>(); // StopActionをCallbackに変更 var mainModule = particleSystem.main; mainModule.stopAction = ParticleSystemStopAction.Callback; var trigger = particleSystem.gameObject.GetComponent<ObservableParticleSystemTrigger>(); if (trigger == null) trigger = particleSystem.gameObject.AddComponent<ObservableParticleSystemTrigger>(); return trigger.OnParticleSystemStoppedAsObservable(); } }
次に、ParticleSystemの拡張メソッドを用意し、先ほど作成したObservableParticleSystemTriggerを取得もしくは追加すると同時にIObservable
また、ParticleSystemのStopActionもCallbackに変更します。
public class ParticleObject : MonoBehaviour { [SerializeField] private ParticleSystem _effectPrefab; private ParticleSystem _effectInstance; private void Awake() { _effectInstance = Instantiate(_effectPrefab, transform); _effectInstance.Play(); } public void Stop() { if (_effectInstance) { _effectInstance.Stop(); } } public async UniTask WaitStop(CancellationToken cancellationToken) { if (_effectInstance == null) return; // OnCompleteは呼んでいないので第1引数はtrue await _effectInstance.OnParticleSystemStoppedAsObservable().ToUniTask(true, cancellationToken); } }
ParticleSystemからOnParticleSystemStoppedAsObservable関数が呼べるようになったので、あとはこのObservableを利用して停止したときの処理を記述します。
(今回はUniTaskに変換しawaitで待てるようにしています)
なお、戻り値がUniTaskで問題ない場合はUniTaskの中で拡張メソッドがすでに用意されており、以下のように書けば待つことが可能ですが、あらかじめParticleSystemのStopActionをCallbackにしておく必要があります。
public async UniTask WaitStop(CancellationToken cancellationToken) { if (_effectInstance == null) return; await _effectInstance.GetAsyncParticleSystemStoppedTrigger().OnParticleSystemStoppedAsync(cancellationToken); }