using System.Collections.Generic; using RR.Game2D.Behaviour; using UnityEngine; using UnityEngine.VFX; namespace RR.Game2D.FX { public static class MvpVFXUtility { private static readonly Dictionary LoadedAssets = new(); private static readonly string[] ColorPropertyNames = { "Color", "TintColor", "BaseColor", "_Color", "_BaseColor" }; private static readonly string[] TexturePropertyNames = { "MainTexture", "mainTexture", "_MainTex", "_BaseMap", "_BaseColorMap" }; private static readonly string[] GradientPropertyNames = { "ColorOverLife", "colorOverLife", "Color", "Gradient" }; private static readonly string[] FloatPropertyNames = { "ParticleCount", "particleCount", "Count", "count" }; private static readonly string[] AnimationCurvePropertyNames = { "SizeOverLife", "sizeOverLife", "Size", "size" }; private static readonly string[] ScalePropertyNames = { "Scale", "scale", "BaseScale", "baseScale" }; public static VisualEffectAsset LoadResourceAsset(string resourcePath) { if (string.IsNullOrWhiteSpace(resourcePath)) return null; if (LoadedAssets.TryGetValue(resourcePath, out VisualEffectAsset cached)) return cached; VisualEffectAsset asset = Resources.Load(resourcePath); LoadedAssets[resourcePath] = asset; return asset; } public static void Spawn( VisualEffectAsset asset, string fallbackResourcePath, Vector3 position, float lifetime = 1f) { Spawn(asset, fallbackResourcePath, position, lifetime, null); } public static GameObject Spawn( VisualEffectAsset asset, string fallbackResourcePath, Vector3 position, float lifetime, MvpVFXSpawnSettings settings) { VisualEffectAsset resolvedAsset = asset ? asset : LoadResourceAsset(fallbackResourcePath); if (!resolvedAsset || VFXPool.Instance == null) return null; int instanceCount = Mathf.Max(1, settings?.instanceCount ?? 1); GameObject firstInstance = null; for (int i = 0; i < instanceCount; i++) { Vector3 spawnPosition = position + GetSpreadOffset(settings); GameObject instance = VFXPool.Instance.Spawn( resolvedAsset, spawnPosition, lifetime, (spawnedInstance, visualEffect) => ApplySettings(spawnedInstance, visualEffect, settings)); if (instance == null) continue; if (settings != null && settings.overrideSizeOverLife && settings.sizeOverLife != null) { VisualEffect visualEffect = instance.GetComponent(); bool hasVfxCurve = false; if (visualEffect != null) { foreach (string propertyName in AnimationCurvePropertyNames) { if (visualEffect.HasAnimationCurve(propertyName)) { hasVfxCurve = true; break; } } } if (!hasVfxCurve) { VFXPool.Instance.AnimateScaleOverLifetime(instance, settings.scale, settings.sizeOverLife, lifetime); } } if (firstInstance == null) firstInstance = instance; } return firstInstance; } public static Vector3 GetColliderBottomPosition(Collider2D collider, Transform fallbackTransform, float yOffset = 0.18f) { if (collider == null) return fallbackTransform ? fallbackTransform.position : Vector3.zero; Bounds bounds = collider.bounds; return new Vector3(bounds.center.x, bounds.min.y + yOffset, bounds.center.z); } private static Vector3 GetSpreadOffset(MvpVFXSpawnSettings settings) { if (settings == null || (settings.spreadRadiusX <= 0f && settings.spreadRadiusY <= 0f)) return Vector3.zero; Vector2 randomPoint = Random.insideUnitCircle; return new Vector3( randomPoint.x * settings.spreadRadiusX, randomPoint.y * settings.spreadRadiusY, 0f); } private static void ApplySettings(GameObject instance, MvpVFXSpawnSettings settings) { ApplySettings(instance, instance.GetComponent(), settings); } private static void ApplySettings(GameObject instance, VisualEffect visualEffect, MvpVFXSpawnSettings settings) { Vector3 scale = Vector3.one; Quaternion rotation = Quaternion.identity; if (settings != null) { scale = settings.useScale3D ? settings.scale3D : Vector3.one * settings.scale; rotation = settings.rotation; } scale.x = Mathf.Max(0.01f, scale.x); scale.y = Mathf.Max(0.01f, scale.y); scale.z = Mathf.Max(0.01f, scale.z); instance.transform.localScale = scale; instance.transform.rotation = rotation; ResetOverrides(instance, visualEffect); if (visualEffect != null) { foreach (string propertyName in ScalePropertyNames) { if (visualEffect.HasVector3(propertyName)) visualEffect.SetVector3(propertyName, scale); else if (visualEffect.HasFloat(propertyName)) visualEffect.SetFloat(propertyName, Mathf.Max(scale.x, scale.y)); } } if (settings == null) return; if (settings.overrideColor && visualEffect != null) { Vector4 color = new Vector4(settings.color.r, settings.color.g, settings.color.b, settings.color.a); foreach (string propertyName in ColorPropertyNames) { if (visualEffect.HasVector4(propertyName)) visualEffect.SetVector4(propertyName, color); } } if (settings.overrideParticleCount && visualEffect != null) { foreach (string propertyName in FloatPropertyNames) { if (visualEffect.HasFloat(propertyName)) visualEffect.SetFloat(propertyName, Mathf.Max(0f, settings.particleCount)); } } if (settings.overrideTexture && settings.texture != null && visualEffect != null) { foreach (string propertyName in TexturePropertyNames) { if (visualEffect.HasTexture(propertyName)) visualEffect.SetTexture(propertyName, settings.texture); } } if (settings.overrideColorOverLife && settings.colorOverLife != null && visualEffect != null) { foreach (string propertyName in GradientPropertyNames) { if (visualEffect.HasGradient(propertyName)) visualEffect.SetGradient(propertyName, settings.colorOverLife); } } if (settings.overrideSizeOverLife && settings.sizeOverLife != null && visualEffect != null) { foreach (string propertyName in AnimationCurvePropertyNames) { if (visualEffect.HasAnimationCurve(propertyName)) visualEffect.SetAnimationCurve(propertyName, settings.sizeOverLife); } } Renderer renderer = instance.GetComponent(); if (renderer == null) return; if (!settings.overrideColor && (!settings.overrideTexture || settings.texture == null)) return; MaterialPropertyBlock block = new MaterialPropertyBlock(); if (settings.overrideColor) { block.SetColor("_Color", settings.color); block.SetColor("_BaseColor", settings.color); block.SetColor("_TintColor", settings.color); } if (settings.overrideTexture && settings.texture != null) { block.SetTexture("_MainTex", settings.texture); block.SetTexture("_BaseMap", settings.texture); block.SetTexture("_BaseColorMap", settings.texture); } renderer.SetPropertyBlock(block); } private static void ResetOverrides(GameObject instance, VisualEffect visualEffect) { if (visualEffect != null) { foreach (string propertyName in ColorPropertyNames) { if (visualEffect.HasVector4(propertyName)) visualEffect.ResetOverride(propertyName); } foreach (string propertyName in TexturePropertyNames) { if (visualEffect.HasTexture(propertyName)) visualEffect.ResetOverride(propertyName); } foreach (string propertyName in GradientPropertyNames) { if (visualEffect.HasGradient(propertyName)) visualEffect.ResetOverride(propertyName); } foreach (string propertyName in FloatPropertyNames) { if (visualEffect.HasFloat(propertyName)) visualEffect.ResetOverride(propertyName); } foreach (string propertyName in AnimationCurvePropertyNames) { if (visualEffect.HasAnimationCurve(propertyName)) visualEffect.ResetOverride(propertyName); } foreach (string propertyName in ScalePropertyNames) { if (visualEffect.HasVector3(propertyName)) visualEffect.ResetOverride(propertyName); else if (visualEffect.HasFloat(propertyName)) visualEffect.ResetOverride(propertyName); } } Renderer renderer = instance.GetComponent(); if (renderer != null) renderer.SetPropertyBlock(null); } } }