using UnityEngine; using System.Collections.Generic; namespace SynapticPro { /// /// Controller for Synaptic ShieldPro shader /// Manages hit effects, ripples, and shield state /// [ExecuteAlways] public class ShieldController : MonoBehaviour { [System.Serializable] public class HitRipple { public Vector3 worldPosition; public float startTime; public float strength; public float speed; public bool active; } [Header("Target")] public Renderer targetRenderer; public int materialIndex = 0; [Header("Shield State")] public bool shieldActive = true; [Range(0f, 1f)] public float shieldStrength = 1f; [Range(0f, 1f)] public float shieldOpacity = 0.5f; [Header("Hit Ripples")] public int maxRipples = 4; public float rippleDuration = 1f; public float rippleSpeed = 5f; public float rippleStrength = 1f; [Header("Hit Flash")] public bool enableHitFlash = true; public Color hitFlashColor = new Color(1f, 0.8f, 0.5f, 1f); public float hitFlashDuration = 0.1f; public float hitFlashIntensity = 2f; [Header("Damage State")] public bool enableDamageState = true; [Range(0f, 1f)] public float damageLevel = 0f; public Color damagedColor = new Color(1f, 0.3f, 0.2f, 1f); public float damageFlickerSpeed = 10f; public float damageFlickerIntensity = 0.3f; [Header("Break Effect")] public float breakThreshold = 0.9f; public ParticleSystem breakParticles; public AudioSource breakAudio; public AudioClip breakSound; [Header("Regeneration")] public bool enableRegeneration = true; public float regenerationDelay = 3f; public float regenerationRate = 0.2f; [Header("Events")] public UnityEngine.Events.UnityEvent onShieldHit; public UnityEngine.Events.UnityEvent onShieldBreak; public UnityEngine.Events.UnityEvent onShieldRestore; private Material material; private List ripples = new List(); private float lastHitTime; private float hitFlashTimer; private bool isFlashing; private bool isBroken; // Shader property IDs private static readonly int ShieldStrengthID = Shader.PropertyToID("_ShieldStrength"); private static readonly int ShieldOpacityID = Shader.PropertyToID("_ShieldOpacity"); private static readonly int HitPositionID = Shader.PropertyToID("_HitPosition"); private static readonly int HitTimeID = Shader.PropertyToID("_HitTime"); private static readonly int RippleSpeedID = Shader.PropertyToID("_RippleSpeed"); private static readonly int RippleStrengthID = Shader.PropertyToID("_RippleStrength"); private static readonly int DamageLevelID = Shader.PropertyToID("_DamageLevel"); private static readonly int HitFlashColorID = Shader.PropertyToID("_HitFlashColor"); private static readonly int HitFlashIntensityID = Shader.PropertyToID("_HitFlashIntensity"); // For multiple ripples private static readonly int RipplePositions1ID = Shader.PropertyToID("_RipplePosition1"); private static readonly int RipplePositions2ID = Shader.PropertyToID("_RipplePosition2"); private static readonly int RipplePositions3ID = Shader.PropertyToID("_RipplePosition3"); private static readonly int RipplePositions4ID = Shader.PropertyToID("_RipplePosition4"); private static readonly int RippleTimes1ID = Shader.PropertyToID("_RippleTime1"); private static readonly int RippleTimes2ID = Shader.PropertyToID("_RippleTime2"); private static readonly int RippleTimes3ID = Shader.PropertyToID("_RippleTime3"); private static readonly int RippleTimes4ID = Shader.PropertyToID("_RippleTime4"); private void OnEnable() { SetupMaterial(); InitializeRipples(); } private void SetupMaterial() { if (targetRenderer == null) targetRenderer = GetComponent(); if (targetRenderer != null && targetRenderer.sharedMaterials.Length > materialIndex) { if (Application.isPlaying) { material = targetRenderer.materials[materialIndex]; } else { material = targetRenderer.sharedMaterials[materialIndex]; } } } private void InitializeRipples() { ripples.Clear(); for (int i = 0; i < maxRipples; i++) { ripples.Add(new HitRipple()); } } private void Update() { if (material == null) return; // Update base shield properties material.SetFloat(ShieldStrengthID, shieldActive ? shieldStrength : 0f); material.SetFloat(ShieldOpacityID, shieldOpacity); // Update ripples UpdateRipples(); // Update hit flash UpdateHitFlash(); // Update damage state UpdateDamageState(); // Handle regeneration UpdateRegeneration(); } private void UpdateRipples() { float currentTime = Time.time; // Deactivate expired ripples foreach (var ripple in ripples) { if (ripple.active && currentTime - ripple.startTime > rippleDuration) { ripple.active = false; } } // Send ripple data to shader for (int i = 0; i < Mathf.Min(4, ripples.Count); i++) { var ripple = ripples[i]; Vector4 posStrength = ripple.active ? new Vector4(ripple.worldPosition.x, ripple.worldPosition.y, ripple.worldPosition.z, ripple.strength) : Vector4.zero; float time = ripple.active ? currentTime - ripple.startTime : -1f; switch (i) { case 0: material.SetVector(RipplePositions1ID, posStrength); material.SetFloat(RippleTimes1ID, time); break; case 1: material.SetVector(RipplePositions2ID, posStrength); material.SetFloat(RippleTimes2ID, time); break; case 2: material.SetVector(RipplePositions3ID, posStrength); material.SetFloat(RippleTimes3ID, time); break; case 3: material.SetVector(RipplePositions4ID, posStrength); material.SetFloat(RippleTimes4ID, time); break; } } material.SetFloat(RippleSpeedID, rippleSpeed); material.SetFloat(RippleStrengthID, rippleStrength); } private void UpdateHitFlash() { if (!enableHitFlash || !isFlashing) return; hitFlashTimer -= Time.deltaTime; if (hitFlashTimer <= 0) { isFlashing = false; material.SetFloat(HitFlashIntensityID, 0f); } else { float flashAmount = (hitFlashTimer / hitFlashDuration) * hitFlashIntensity; material.SetColor(HitFlashColorID, hitFlashColor); material.SetFloat(HitFlashIntensityID, flashAmount); } } private void UpdateDamageState() { if (!enableDamageState) return; material.SetFloat(DamageLevelID, damageLevel); // Flickering when damaged if (damageLevel > 0.5f) { float flicker = Mathf.Sin(Time.time * damageFlickerSpeed) * damageFlickerIntensity * damageLevel; material.SetFloat(ShieldOpacityID, shieldOpacity + flicker); } } private void UpdateRegeneration() { if (!enableRegeneration || !Application.isPlaying) return; if (isBroken) { // Wait for regeneration delay after break if (Time.time - lastHitTime > regenerationDelay) { isBroken = false; shieldActive = true; damageLevel = 0.5f; // Start at half strength onShieldRestore?.Invoke(); } } else if (damageLevel > 0 && Time.time - lastHitTime > regenerationDelay * 0.5f) { // Gradual regeneration damageLevel = Mathf.Max(0, damageLevel - regenerationRate * Time.deltaTime); } } /// /// Register a hit on the shield /// public void OnHit(Vector3 worldPosition, float damage = 0.1f) { if (!shieldActive || isBroken) return; lastHitTime = Time.time; // Add ripple AddRipple(worldPosition); // Trigger flash if (enableHitFlash) { isFlashing = true; hitFlashTimer = hitFlashDuration; } // Apply damage if (enableDamageState) { damageLevel = Mathf.Min(1f, damageLevel + damage); // Check for break if (damageLevel >= breakThreshold) { BreakShield(); } } onShieldHit?.Invoke(); } /// /// Add a ripple effect at the specified world position /// public void AddRipple(Vector3 worldPosition) { // Find inactive ripple or oldest ripple HitRipple targetRipple = null; float oldestTime = float.MaxValue; foreach (var ripple in ripples) { if (!ripple.active) { targetRipple = ripple; break; } else if (ripple.startTime < oldestTime) { oldestTime = ripple.startTime; targetRipple = ripple; } } if (targetRipple != null) { targetRipple.worldPosition = worldPosition; targetRipple.startTime = Time.time; targetRipple.strength = rippleStrength; targetRipple.speed = rippleSpeed; targetRipple.active = true; } } /// /// Break the shield /// public void BreakShield() { if (isBroken) return; isBroken = true; shieldActive = false; damageLevel = 1f; // Play break effects if (breakParticles != null) { breakParticles.transform.position = transform.position; breakParticles.Play(); } if (breakAudio != null && breakSound != null) { breakAudio.clip = breakSound; breakAudio.Play(); } onShieldBreak?.Invoke(); } /// /// Instantly restore shield to full /// public void RestoreShield() { isBroken = false; shieldActive = true; damageLevel = 0f; onShieldRestore?.Invoke(); } /// /// Set shield state /// public void SetShieldActive(bool active) { if (isBroken && active) RestoreShield(); else shieldActive = active; } #if UNITY_EDITOR private void OnValidate() { SetupMaterial(); InitializeRipples(); } private void OnDrawGizmosSelected() { // Draw active ripples Gizmos.color = Color.cyan; foreach (var ripple in ripples) { if (ripple.active) { float radius = (Time.time - ripple.startTime) * rippleSpeed; Gizmos.DrawWireSphere(ripple.worldPosition, Mathf.Min(radius, 5f)); } } } #endif } }