using System; using System.Collections.Generic; using System.Reflection; using log4net; using Managers; using Unity.Mathematics; using Unity.VisualScripting; using UnityEngine; using UnityEngine.UIElements; using static AffectingForcesManager; [ExecuteInEditMode] public class NimbleZone : MonoBehaviour { private static ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private static Transform _nimbleZoneTransform; public AffectingForcesManager forcesManager; public GameObject renderedZoneObject; [SerializeField] private List NimbleZoneColors = new(); private Dictionary _nimbleZoneColors = new Dictionary { {Gravity.DownGravity, Color.green }, {Gravity.UpGravity, Color.magenta }, {Gravity.NoGravity, Color.red }, {Gravity.InwardsGravity, Color.blue }, {Gravity.OutwardsGravity, Color.yellow }, }; [SerializeField] private static float gravityFactor = 30f; [SerializeField] private Gravity outsideGravityFunction = Gravity.NoGravity; [SerializeField] private Zone zone = Zone.NimbleZone; // Ripple properties // These influence the shader on the nimble zone [SerializeField] private float rippleFrequency = 3f; [SerializeField] private float rippleDensity = 30f; [SerializeField] private float rippleAmplitude = 0.1f; [SerializeField] private float rippleRadius = 1f; [SerializeField] private float rippleDuration = 1f; [SerializeField] private float impactVelocityModifier = 1f; [SerializeField, Tooltip("Minimum ripple effect intensity.")] [Range(0, 1)] private float minImpact = 0.2f; [SerializeField, Tooltip("Velocity which makes the highest/most intense ripples.")] private float maxVelocity = 45f; private int maxRippleAmount = 5; private MeshRenderer meshRenderer; private Material material; void Awake() { foreach (GravityColorEntry entry in NimbleZoneColors) { _nimbleZoneColors[entry.gravity] = entry.color; } meshRenderer = renderedZoneObject.GetComponent(); _nimbleZoneTransform = gameObject.transform; ApplyZoneColor(meshRenderer); #if UNITY_EDITOR if (!Application.isPlaying) return; #endif material = meshRenderer.material; ResetRippleShaderProperties(); } /// /// Array of the available gravities. /// private Func[] gravityFunctions = { DownGravity, UpGravity, NoGravity, InwardsGravity, OutwardsGravity }; /// /// Function which returns a gravity zero vector. /// private static readonly Func NoGravity = new((gravitySource, target) => new Vector3()); /// /// Function which returns a gravity vector downwards, depending /// on the parent transforms rotation. /// The parenting transform for a ship is the arena it's in. /// private static readonly Func DownGravity = new((gravitySource, target) => gravitySource.rotation * Vector3.down * gravityFactor); /// /// Function which returns a gravity vector upwards, depending /// on the parent transforms rotation. /// The parenting transform for a ship is the arena it's in. /// private static readonly Func UpGravity = new((gravitySource, target) => gravitySource.rotation * Vector3.up * gravityFactor); /// /// Function which returns a gravity vector towards the center of the parenting transform. /// The parenting transform for a ship is the arena it's in. /// private static readonly Func InwardsGravity = new((gravitySource, target) => (target.position - gravitySource.position).normalized * -gravityFactor); /// /// Function which returns a gravity vector outwards from the center of the parenting transform. /// The parenting transform for a ship is the arena it's in. /// private static readonly Func OutwardsGravity = new((gravitySource, target) => (target.position - gravitySource.position).normalized * gravityFactor); public Func GetGravityFunction(Gravity gravity) { return gravityFunctions[(int)gravity]; } private void OnTriggerEnter(Collider collider) { int instanceID = collider.gameObject.GetInstanceID(); if (collider.tag == "Spike" && collider.attachedRigidbody.velocity.magnitude > 1) { SpawnRipple(collider, false); return; } if (collider.tag == "Ship") { forcesManager.SetGravityForInstance(instanceID, NoGravity, transform); forcesManager.SetZoneForInstance(instanceID, zone); } } private void OnTriggerExit(Collider collider) { int instanceID = collider.gameObject.GetInstanceID(); if (collider.tag == "Spike") { SpawnRipple(collider, true); return; } if (collider.tag == "Ship") { forcesManager.SetGravityForInstance(instanceID, GetGravityFunction(outsideGravityFunction), transform); forcesManager.SetZoneForInstance(instanceID, Zone.OutsideZone); } } private void Update() { #if UNITY_EDITOR if (!Application.isPlaying) return; #endif material.SetFloat("_ShaderTime", Time.timeSinceLevelLoad); } private void ApplyZoneColor(MeshRenderer renderer) { Color color = _nimbleZoneColors[outsideGravityFunction]; MaterialPropertyBlock materialPropertyBlock = new(); materialPropertyBlock.SetColor("_BaseColor", color); renderer.SetPropertyBlock(materialPropertyBlock); } /// /// Calculates the effect which a given velocity has on a ripple property. /// /// Initial value of the ripple property /// Velocity of the impact /// private float ImpactVelocityEffect(float initial, float velocity) { return math.max(math.smoothstep(0, maxVelocity, velocity), minImpact) * impactVelocityModifier * initial; } /// /// Spawns ripples on the shader of the nimble zone. /// Up to 5 parallel ripples. /// /// /// private void SpawnRipple(Collider collider, bool isOutwardsRipple) { Rigidbody body = collider.attachedRigidbody; GameObject gameObject = collider.gameObject; float velocity = body.velocity.magnitude; Vector3 position = gameObject.transform.position - transform.position; position = transform.InverseTransformDirection(position).normalized; Vector4[] rippleOrigins = material.GetVectorArray("_RippleOrigins"); float currentTime = Time.timeSinceLevelLoad; float[] startedTimes = material.GetFloatArray("_RippleStartTimes"); float[] startedRippleDurations = material.GetFloatArray("_RippleDurations"); float[] rippleAmplitudes = material.GetFloatArray("_RippleAmplitudes"); float[] rippleFrequencies = material.GetFloatArray("_RippleFrequencies"); float[] rippleDensities = material.GetFloatArray("_RippleDensities"); float[] rippleRadii = material.GetFloatArray("_RippleRadii"); if (startedTimes == null) { Log.Warn("Ripple shader properties are null. Reseting shader"); ResetRippleShaderProperties(); return; } for (int i = 0; i < startedTimes.Length; ++i) { if (startedTimes[i] + startedRippleDurations[i] < currentTime) { rippleOrigins[i] = new Vector4(position.x, position.y, position.z, 0); material.SetVectorArray("_RippleOrigins", rippleOrigins); startedTimes[i] = currentTime; material.SetFloatArray("_RippleStartTimes", startedTimes); float amplitude = isOutwardsRipple ? rippleAmplitude : -rippleAmplitude; rippleAmplitudes[i] = ImpactVelocityEffect(amplitude, velocity); rippleFrequencies[i] = ImpactVelocityEffect(rippleFrequency, velocity); rippleDensities[i] = ImpactVelocityEffect(rippleDensity, velocity); startedRippleDurations[i] = ImpactVelocityEffect(rippleDuration, velocity); rippleRadii[i] = ImpactVelocityEffect(rippleRadius, velocity); material.SetFloatArray("_RippleAmplitudes", rippleAmplitudes); material.SetFloatArray("_RippleFrequencies", rippleFrequencies); material.SetFloatArray("_RippleDensities", rippleDensities); material.SetFloatArray("_RippleAmplitudes", rippleAmplitudes); material.SetFloatArray("_RippleDurations", startedRippleDurations); material.SetFloatArray("_RippleRadii", rippleRadii); break; } } } /// /// Resets the ripple shaders exposed properties to 0 /// private void ResetRippleShaderProperties() { Vector4[] rippleOrigins = new Vector4[maxRippleAmount]; float[] startedTimes = new float[maxRippleAmount]; float[] rippleAmplitudes = new float[maxRippleAmount]; float[] rippleFrequencies = new float[maxRippleAmount]; float[] rippleDensities = new float[maxRippleAmount]; float[] rippleDurations = new float[maxRippleAmount]; float[] rippleRadii = new float[maxRippleAmount]; // Initialize Ripple Shader Properties material.SetVectorArray("_RippleOrigins", rippleOrigins); material.SetFloatArray("_RippleStartTimes", startedTimes); material.SetFloatArray("_RippleAmplitudes", rippleAmplitudes); material.SetFloatArray("_RippleFrequencies", rippleFrequencies); material.SetFloatArray("_RippleDensities", rippleDensities); material.SetFloatArray("_RippleDurations", rippleDurations); material.SetFloatArray("_RippleRadii", rippleRadii); } }