技能主要更新,修复卡顿并加入动画,以及各种其他更新。
This commit is contained in:
@@ -0,0 +1,430 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using System.IO;
|
||||
|
||||
public class BgmParticleEmitter : MonoBehaviour
|
||||
{
|
||||
[Header("Audio")]
|
||||
public AudioSource source;
|
||||
public FFTWindow fftWindow = FFTWindow.BlackmanHarris;
|
||||
|
||||
[Header("Rendering")]
|
||||
public Material particleMaterial;
|
||||
public Texture2D particleTexture;
|
||||
public string particleTextureRelativePath = "artworks/UI_UI/新版主界面 (2)/矩形 2-1.png";
|
||||
public string fallbackTextureRelativePath = "artworks/UI_UI/新版主界面 (2)/矩形 2-1.png";
|
||||
public bool useTextureAlphaOnly = true;
|
||||
|
||||
[Header("Emission")]
|
||||
public int spectrumSize = 256;
|
||||
public float minRate = 2f;
|
||||
public float maxRate = 30f;
|
||||
public float minSpeed = 25f;
|
||||
public float maxSpeed = 80f;
|
||||
public float minSize = 4f;
|
||||
public float maxSize = 8f;
|
||||
public float energyScale = 50f;
|
||||
public float startLifetime = 34.2f;
|
||||
|
||||
[Header("Rotation Drift")]
|
||||
public Vector2 startRotationRange = new Vector2(0f, 360f);
|
||||
public Vector2 angularVelocityRange = new Vector2(3f, 12f);
|
||||
|
||||
[Header("Spawn Area")]
|
||||
public float spawnMargin = 40f;
|
||||
public float spawnHeight = 10f;
|
||||
|
||||
private ParticleSystem mainPs;
|
||||
private float[] spectrum;
|
||||
private RectTransform canvasRect;
|
||||
private static Material defaultParticleMat;
|
||||
private static Texture2D circleTex;
|
||||
private Material runtimeMat;
|
||||
private Vector2 lastCanvasSize;
|
||||
private bool hasLastCanvasSize;
|
||||
private float lastSpawnMargin;
|
||||
private float lastSpawnHeight;
|
||||
private bool spawnAreaInitialized;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
BuildIfNeeded();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
BuildIfNeeded();
|
||||
}
|
||||
|
||||
private void BuildIfNeeded()
|
||||
{
|
||||
if (mainPs == null)
|
||||
mainPs = GetComponent<ParticleSystem>();
|
||||
|
||||
if (mainPs == null)
|
||||
{
|
||||
mainPs = gameObject.AddComponent<ParticleSystem>();
|
||||
}
|
||||
|
||||
if (particleTexture == null)
|
||||
particleTexture = LoadTextureFromFile();
|
||||
#if UNITY_EDITOR
|
||||
if (particleTexture == null)
|
||||
particleTexture = LoadTextureFromAsset();
|
||||
#endif
|
||||
if (particleTexture != null && useTextureAlphaOnly)
|
||||
particleTexture = ConvertTextureToWhiteAlpha(particleTexture);
|
||||
if (particleTexture == null)
|
||||
particleTexture = GetCircleTexture();
|
||||
|
||||
ConfigureSystem(mainPs, Color.white, 10);
|
||||
|
||||
var shadow = transform.Find("Shadow");
|
||||
if (shadow != null)
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
Destroy(shadow.gameObject);
|
||||
else
|
||||
DestroyImmediate(shadow.gameObject);
|
||||
}
|
||||
|
||||
if (canvasRect == null)
|
||||
{
|
||||
var canvas = GetComponentInParent<Canvas>();
|
||||
if (canvas != null)
|
||||
canvasRect = canvas.GetComponent<RectTransform>();
|
||||
}
|
||||
|
||||
if (spectrum == null || spectrum.Length != spectrumSize)
|
||||
spectrum = new float[spectrumSize];
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
UpdateSpawnArea();
|
||||
if (!Application.isPlaying)
|
||||
return;
|
||||
|
||||
if (source == null)
|
||||
{
|
||||
var mgr = BgmPlaybackManager.Instance;
|
||||
if (mgr != null) source = mgr.audioSource;
|
||||
}
|
||||
|
||||
if (source == null || source.clip == null)
|
||||
return;
|
||||
|
||||
if (spectrum == null || spectrum.Length != spectrumSize)
|
||||
spectrum = new float[spectrumSize];
|
||||
|
||||
source.GetSpectrumData(spectrum, 0, fftWindow);
|
||||
|
||||
float energy = 0f;
|
||||
int start = 2;
|
||||
int end = Mathf.Min(64, spectrumSize - 1);
|
||||
for (int i = start; i <= end; i++)
|
||||
energy += spectrum[i];
|
||||
energy /= Mathf.Max(1, end - start + 1);
|
||||
|
||||
float level = Mathf.Clamp01(energy * Mathf.Max(1f, energyScale));
|
||||
float rate = Mathf.Lerp(minRate, maxRate, level);
|
||||
float speed = Mathf.Lerp(minSpeed, maxSpeed, level);
|
||||
float size = Mathf.Lerp(minSize, maxSize, level);
|
||||
|
||||
ApplyRuntimeParams(mainPs, rate, speed, size);
|
||||
}
|
||||
|
||||
private void ConfigureSystem(ParticleSystem ps, Color color, int order)
|
||||
{
|
||||
var main = ps.main;
|
||||
main.loop = true;
|
||||
main.playOnAwake = true;
|
||||
main.startLifetime = startLifetime;
|
||||
main.startSpeed = minSpeed;
|
||||
main.startSize = minSize;
|
||||
main.startColor = color;
|
||||
main.simulationSpace = ParticleSystemSimulationSpace.Local;
|
||||
main.scalingMode = ParticleSystemScalingMode.Hierarchy;
|
||||
main.maxParticles = 2000;
|
||||
main.startRotation3D = true;
|
||||
main.startRotationX = new ParticleSystem.MinMaxCurve(startRotationRange.x * Mathf.Deg2Rad, startRotationRange.y * Mathf.Deg2Rad);
|
||||
main.startRotationY = new ParticleSystem.MinMaxCurve(startRotationRange.x * Mathf.Deg2Rad, startRotationRange.y * Mathf.Deg2Rad);
|
||||
main.startRotationZ = new ParticleSystem.MinMaxCurve(startRotationRange.x * Mathf.Deg2Rad, startRotationRange.y * Mathf.Deg2Rad);
|
||||
|
||||
var emission = ps.emission;
|
||||
emission.rateOverTime = minRate;
|
||||
|
||||
var shape = ps.shape;
|
||||
shape.enabled = true;
|
||||
shape.shapeType = ParticleSystemShapeType.Box;
|
||||
shape.scale = new Vector3(200f, spawnHeight, 0f);
|
||||
|
||||
var vel = ps.velocityOverLifetime;
|
||||
vel.enabled = true;
|
||||
vel.space = ParticleSystemSimulationSpace.Local;
|
||||
vel.x = new ParticleSystem.MinMaxCurve(-5f, 5f);
|
||||
vel.y = new ParticleSystem.MinMaxCurve(-minSpeed, -minSpeed * 1.2f);
|
||||
vel.z = new ParticleSystem.MinMaxCurve(0f, 0f);
|
||||
|
||||
var renderer = ps.GetComponent<ParticleSystemRenderer>();
|
||||
renderer.sortingOrder = order;
|
||||
renderer.renderMode = ParticleSystemRenderMode.Billboard;
|
||||
var mat = GetParticleMaterial();
|
||||
renderer.material = mat;
|
||||
renderer.sharedMaterial = mat;
|
||||
|
||||
var rot = ps.rotationOverLifetime;
|
||||
rot.enabled = true;
|
||||
rot.separateAxes = true;
|
||||
rot.x = new ParticleSystem.MinMaxCurve(angularVelocityRange.x * Mathf.Deg2Rad, angularVelocityRange.y * Mathf.Deg2Rad);
|
||||
rot.y = new ParticleSystem.MinMaxCurve(angularVelocityRange.x * Mathf.Deg2Rad, angularVelocityRange.y * Mathf.Deg2Rad);
|
||||
rot.z = new ParticleSystem.MinMaxCurve(angularVelocityRange.x * Mathf.Deg2Rad, angularVelocityRange.y * Mathf.Deg2Rad);
|
||||
}
|
||||
|
||||
private void ApplyRuntimeParams(ParticleSystem ps, float rate, float speed, float size)
|
||||
{
|
||||
if (ps == null) return;
|
||||
var main = ps.main;
|
||||
main.startSpeed = speed;
|
||||
main.startSize = size;
|
||||
main.startLifetime = startLifetime;
|
||||
var emission = ps.emission;
|
||||
emission.rateOverTime = rate;
|
||||
var vel = ps.velocityOverLifetime;
|
||||
vel.x = new ParticleSystem.MinMaxCurve(-5f, 5f);
|
||||
vel.y = new ParticleSystem.MinMaxCurve(-speed, -speed * 1.2f);
|
||||
vel.z = new ParticleSystem.MinMaxCurve(0f, 0f);
|
||||
}
|
||||
|
||||
private void UpdateSpawnArea()
|
||||
{
|
||||
var canvas = GetComponentInParent<Canvas>();
|
||||
if (canvas != null)
|
||||
{
|
||||
var rt = canvas.GetComponent<RectTransform>();
|
||||
if (canvasRect == null || canvasRect != rt)
|
||||
canvasRect = rt;
|
||||
}
|
||||
|
||||
Vector2 size = canvasRect != null ? canvasRect.rect.size : new Vector2(Screen.width, Screen.height);
|
||||
bool sizeChanged = !hasLastCanvasSize || size != lastCanvasSize;
|
||||
bool marginChanged = !spawnAreaInitialized || !Mathf.Approximately(lastSpawnMargin, spawnMargin);
|
||||
bool heightChanged = !spawnAreaInitialized || !Mathf.Approximately(lastSpawnHeight, spawnHeight);
|
||||
|
||||
if (!sizeChanged && !marginChanged && !heightChanged)
|
||||
return;
|
||||
|
||||
hasLastCanvasSize = true;
|
||||
lastCanvasSize = size;
|
||||
lastSpawnMargin = spawnMargin;
|
||||
lastSpawnHeight = spawnHeight;
|
||||
spawnAreaInitialized = true;
|
||||
|
||||
var shape = mainPs.shape;
|
||||
shape.scale = new Vector3(size.x, spawnHeight, 0f);
|
||||
|
||||
var rect = transform as RectTransform;
|
||||
if (rect != null)
|
||||
{
|
||||
if (rect.anchorMin != new Vector2(0.5f, 1f)) rect.anchorMin = new Vector2(0.5f, 1f);
|
||||
if (rect.anchorMax != new Vector2(0.5f, 1f)) rect.anchorMax = new Vector2(0.5f, 1f);
|
||||
if (rect.pivot != new Vector2(0.5f, 0.5f)) rect.pivot = new Vector2(0.5f, 0.5f);
|
||||
if (rect.anchoredPosition != new Vector2(0f, spawnMargin)) rect.anchoredPosition = new Vector2(0f, spawnMargin);
|
||||
if (rect.localScale != Vector3.one) rect.localScale = Vector3.one;
|
||||
}
|
||||
else
|
||||
{
|
||||
var targetPos = new Vector3(0f, size.y * 0.5f + spawnMargin, 0f);
|
||||
if (transform.localPosition != targetPos) transform.localPosition = targetPos;
|
||||
if (transform.localScale != Vector3.one) transform.localScale = Vector3.one;
|
||||
}
|
||||
}
|
||||
|
||||
public void ForceLayoutUpdate()
|
||||
{
|
||||
UpdateSpawnArea();
|
||||
}
|
||||
|
||||
private static Material GetDefaultParticleMaterial()
|
||||
{
|
||||
if (defaultParticleMat == null)
|
||||
{
|
||||
defaultParticleMat = Resources.GetBuiltinResource<Material>("Default-Particle.mat");
|
||||
if (defaultParticleMat == null || defaultParticleMat.shader == null || !defaultParticleMat.shader.isSupported)
|
||||
{
|
||||
Shader shader = Shader.Find("Particles/Standard Unlit");
|
||||
if (shader == null) shader = Shader.Find("Universal Render Pipeline/Particles/Unlit");
|
||||
if (shader == null) shader = Shader.Find("Particles/Standard");
|
||||
if (shader == null) shader = Shader.Find("Unlit/Texture");
|
||||
if (shader != null)
|
||||
defaultParticleMat = new Material(shader);
|
||||
}
|
||||
|
||||
if (defaultParticleMat != null && defaultParticleMat.mainTexture == null)
|
||||
defaultParticleMat.mainTexture = GetCircleTexture();
|
||||
}
|
||||
return defaultParticleMat;
|
||||
}
|
||||
|
||||
private Material GetParticleMaterial()
|
||||
{
|
||||
Material baseMat = GetSafeParticleMaterial();
|
||||
if (baseMat == null)
|
||||
baseMat = CreateFallbackMaterial();
|
||||
if (baseMat == null)
|
||||
baseMat = GetDefaultParticleMaterial();
|
||||
if (baseMat == null)
|
||||
baseMat = CreateFallbackMaterial();
|
||||
if (baseMat != null && baseMat.mainTexture == null && particleTexture == null)
|
||||
baseMat = CreateFallbackMaterial();
|
||||
if (runtimeMat == null || runtimeMat.shader != baseMat.shader)
|
||||
runtimeMat = new Material(baseMat);
|
||||
if (particleTexture != null)
|
||||
runtimeMat.mainTexture = particleTexture;
|
||||
else if (runtimeMat.mainTexture == null)
|
||||
runtimeMat.mainTexture = GetCircleTexture();
|
||||
|
||||
if (runtimeMat.HasProperty("_Color"))
|
||||
runtimeMat.SetColor("_Color", Color.white);
|
||||
if (runtimeMat.HasProperty("_TintColor"))
|
||||
runtimeMat.SetColor("_TintColor", Color.white);
|
||||
if (runtimeMat.HasProperty("_BaseColor"))
|
||||
runtimeMat.SetColor("_BaseColor", Color.white);
|
||||
if (runtimeMat.HasProperty("_MainTex"))
|
||||
runtimeMat.SetTexture("_MainTex", runtimeMat.mainTexture);
|
||||
if (runtimeMat.HasProperty("_BaseMap"))
|
||||
runtimeMat.SetTexture("_BaseMap", runtimeMat.mainTexture);
|
||||
return runtimeMat;
|
||||
}
|
||||
|
||||
private Material CreateFallbackMaterial()
|
||||
{
|
||||
Shader shader = Shader.Find("Universal Render Pipeline/Particles/Unlit");
|
||||
if (shader == null) shader = Shader.Find("Particles/Standard Unlit");
|
||||
if (shader == null) shader = Shader.Find("Particles/Standard");
|
||||
if (shader == null) shader = Shader.Find("Unlit/Transparent");
|
||||
if (shader == null) shader = Shader.Find("Unlit/Texture");
|
||||
if (shader == null) return null;
|
||||
return new Material(shader);
|
||||
}
|
||||
|
||||
private Material GetSafeParticleMaterial()
|
||||
{
|
||||
if (particleMaterial == null) return null;
|
||||
Shader shader = particleMaterial.shader;
|
||||
if (shader == null) return null;
|
||||
string name = shader.name;
|
||||
if (string.IsNullOrEmpty(name)) return null;
|
||||
string lower = name.ToLowerInvariant();
|
||||
if (lower.Contains("particle"))
|
||||
return particleMaterial;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Texture2D GetCircleTexture()
|
||||
{
|
||||
if (circleTex != null)
|
||||
return circleTex;
|
||||
|
||||
const int size = 32;
|
||||
circleTex = new Texture2D(size, size, TextureFormat.ARGB32, false);
|
||||
circleTex.wrapMode = TextureWrapMode.Clamp;
|
||||
circleTex.filterMode = FilterMode.Bilinear;
|
||||
|
||||
Vector2 center = new Vector2((size - 1) * 0.5f, (size - 1) * 0.5f);
|
||||
float radius = size * 0.5f * 0.9f;
|
||||
for (int y = 0; y < size; y++)
|
||||
{
|
||||
for (int x = 0; x < size; x++)
|
||||
{
|
||||
float dist = Vector2.Distance(new Vector2(x, y), center);
|
||||
float t = Mathf.InverseLerp(radius * 0.9f, radius, dist);
|
||||
float alpha = Mathf.Clamp01(1f - t);
|
||||
circleTex.SetPixel(x, y, new Color(1f, 1f, 1f, alpha));
|
||||
}
|
||||
}
|
||||
circleTex.Apply();
|
||||
return circleTex;
|
||||
}
|
||||
|
||||
private Texture2D LoadTextureFromFile()
|
||||
{
|
||||
string relPath = particleTextureRelativePath;
|
||||
if (string.IsNullOrEmpty(relPath))
|
||||
relPath = fallbackTextureRelativePath;
|
||||
if (string.IsNullOrEmpty(relPath))
|
||||
return null;
|
||||
|
||||
string path = Path.Combine(Application.dataPath, relPath);
|
||||
if (!File.Exists(path) && !string.IsNullOrEmpty(fallbackTextureRelativePath))
|
||||
{
|
||||
string fallback = Path.Combine(Application.dataPath, fallbackTextureRelativePath);
|
||||
if (File.Exists(fallback))
|
||||
{
|
||||
particleTextureRelativePath = fallbackTextureRelativePath;
|
||||
path = fallback;
|
||||
}
|
||||
}
|
||||
if (!File.Exists(path))
|
||||
return null;
|
||||
|
||||
var data = File.ReadAllBytes(path);
|
||||
var tex = new Texture2D(2, 2, TextureFormat.ARGB32, false);
|
||||
if (tex.LoadImage(data))
|
||||
return tex;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Texture2D ConvertTextureToWhiteAlpha(Texture2D src)
|
||||
{
|
||||
if (src == null) return null;
|
||||
try
|
||||
{
|
||||
var pixels = src.GetPixels32();
|
||||
if (pixels == null || pixels.Length == 0) return src;
|
||||
var tex = new Texture2D(src.width, src.height, TextureFormat.ARGB32, false);
|
||||
for (int i = 0; i < pixels.Length; i++)
|
||||
{
|
||||
byte a = pixels[i].a;
|
||||
pixels[i] = new Color32(255, 255, 255, a);
|
||||
}
|
||||
tex.SetPixels32(pixels);
|
||||
tex.Apply();
|
||||
tex.wrapMode = TextureWrapMode.Clamp;
|
||||
tex.filterMode = FilterMode.Bilinear;
|
||||
return tex;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return src;
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private Texture2D LoadTextureFromAsset()
|
||||
{
|
||||
string relPath = particleTextureRelativePath;
|
||||
if (string.IsNullOrEmpty(relPath))
|
||||
relPath = fallbackTextureRelativePath;
|
||||
if (string.IsNullOrEmpty(relPath))
|
||||
return null;
|
||||
string assetPath = "Assets/" + relPath.TrimStart('/', '\\');
|
||||
return UnityEditor.AssetDatabase.LoadAssetAtPath<Texture2D>(assetPath);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void OnValidate()
|
||||
{
|
||||
if (particleTexture == null)
|
||||
{
|
||||
const string path = "Assets/artworks/UI_UI/新版主界面 (2)/矩形 2-1.png";
|
||||
particleTexture = UnityEditor.AssetDatabase.LoadAssetAtPath<Texture2D>(path);
|
||||
}
|
||||
|
||||
if (runtimeMat != null && particleTexture != null)
|
||||
runtimeMat.mainTexture = particleTexture;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user