2023/03/11 : 初稿
2023/03/13 : コード修正
Unity : 2021.3.15f1
やりたいこと
Unityで再生中のParticleSystemに対して、
スクリプトでアルファを変化させたい。
実現方法
ググると大体
ParticleSystem.main.startColor
をいじる記事が出ますが、
どうも新たに放出される粒子には効くものの、
既存の粒子には影響なさそう。
粒子の寿命が短いならそれでもいいんですが、
今すぐ、パーティクル全体をフェードイン/アウトさせたい!
といった場合に対処しづらい。
なので、colorOverLifetimeの方をいじることにしました。
(大体のパーティクルはこれ使ってるでしょうし)
///
/// @file ParticleUtils.cs
/// @author KyYukimoto
///
using UnityEngine;
namespace Utils
{
public class ParticleUtils : MonoBehaviour
{
// system
ParticleSystem System;
// gradient
struct GradientData
{
Gradient Src;
int AlphaKeyNum;
GradientAlphaKey[] StoredAlphaKeys;
GradientAlphaKey[] AlphaKeys;
bool IsAlphaModified;
// Setup
public void Setup(Gradient src)
{
if (src == null || Src != null) {
return;
}
Src = src;
StoredAlphaKeys = src.alphaKeys;
AlphaKeyNum = (StoredAlphaKeys == null) ? 0 : StoredAlphaKeys.Length;
}
// Cleanup
public void Cleanup()
{
if (Src == null) {
return;
}
if (IsAlphaModified) {
Src.alphaKeys = StoredAlphaKeys;
}
this = default;
}
// SetAlphaRate
public void SetAlphaRate(float alpha)
{
if (Src == null || AlphaKeyNum <= 0) {
return;
}
if (AlphaKeys == null) {
AlphaKeys = new GradientAlphaKey[AlphaKeyNum];
}
for (int i = 0; i < AlphaKeyNum; i++) {
var key = StoredAlphaKeys[i];
key.alpha *= alpha;
AlphaKeys[i] = key;
}
Src.alphaKeys = AlphaKeys;
IsAlphaModified = true;
}
}
// MinMaxGradientData
struct MinMaxGradientData
{
public ParticleSystem.MinMaxGradient Src;
public bool HasSetup { get; private set; }
Color Color;
Color ColorMin;
Color ColorMax;
GradientData Gradient;
GradientData GradientMin;
GradientData GradientMax;
// Setup
public void Setup(ParticleSystem.MinMaxGradient src)
{
if (HasSetup) {
return;
}
HasSetup = true;
Src = src;
Color = src.color;
ColorMin = src.colorMin;
ColorMax = src.colorMax;
Gradient.Setup(src.gradient);
GradientMin.Setup(src.gradientMin);
GradientMax.Setup(src.gradientMax);
}
// Cleanup
public void Cleanup()
{
if (!HasSetup) {
return;
}
HasSetup = false;
Gradient.Cleanup();
GradientMin.Cleanup();
GradientMax.Cleanup();
this = default;
}
// SetAlphaRate
public void SetAlphaRate(float alpha)
{
if (!HasSetup) {
return;
}
Src.color = new Color(Color.r, Color.g, Color.b, Color.a * alpha);
Src.colorMin = new Color(ColorMin.r, ColorMin.g, ColorMin.b, ColorMin.a * alpha);
Src.colorMax = new Color(ColorMax.r, ColorMax.g, ColorMax.b, ColorMax.a * alpha);
Gradient.SetAlphaRate(alpha);
GradientMin.SetAlphaRate(alpha);
GradientMax.SetAlphaRate(alpha);
}
}
MinMaxGradientData StartColor;
MinMaxGradientData LifeColor;
// アルファ
float Alpha = 1;
// Start
void Start()
{
// パラメータを保持
StoreOriginalColor();
}
// OnDestroy
void OnDestroy()
{
// パラメータをもとに戻す
RestoreOriginalColor();
}
// 元の色を保存
void StoreOriginalColor()
{
// すでに保存済み
if (System != null) {
return;
}
// 保存
System = GetComponent<ParticleSystem>();
if (System == null) {
return;
}
// life or main
var life = System.colorOverLifetime;
if (life.enabled) {
LifeColor.Setup(life.color);
} else {
var main = System.main;
StartColor.Setup(main.startColor);
}
// 既にリクエストされていたらここで適用
Apply(Alpha, true);
}
// 元の色に戻す
void RestoreOriginalColor()
{
// alpha
Alpha = 1;
// 復元
StartColor.Cleanup();
LifeColor.Cleanup();
}
// アルファ変更
public void SetAlphaRate(float alpha)
{
// check
if (System == null) {
Alpha = alpha;
return;
}
// 変更
Apply(alpha, false);
}
// アルファ戻す
public void ResetAlphaRate() { SetAlphaRate(1); }
// Apply
void Apply(float alpha, bool force)
{
// alpha
if (!force && Mathf.Approximately(alpha, Alpha)) {
return;
}
Alpha = alpha;
// life
if (LifeColor.HasSetup) {
var life = System.colorOverLifetime;
if (life.enabled) {
LifeColor.SetAlphaRate(alpha);
life.color = LifeColor.Src;
}
}
// main
if (StartColor.HasSetup) {
var main = System.main;
StartColor.SetAlphaRate(Alpha);
main.startColor = StartColor.Src;
}
}
}
}
このスクリプトをParticleSystemのあるgameObjectにAddComponentして、
必要に応じてSetAlphaRate()に0から1の値を入れてあげれば、
即座に粒子のアルファが変化するはず。
注意点として、
少なくともGradient.alphaKeysをreadするとGC.Allocがよばれてしまう。
上の実装では、write時にはよばれてなさそうなんだけどよばれてたら残念なことに
結局のところ、専用のシェーダー作ってそっちで対応したほうがいいのかも。