// // Procedural Lightning for Unity // (c) 2015 Digital Ruby, LLC // Source code may be used for personal or commercial projects. // Source code may NOT be redistributed or sold. // #define SHOW_MANUAL_WARNING using UnityEngine; using System.Collections; using System.Collections.Generic; namespace DigitalRuby.ThunderAndLightning { /// /// Lightning bolt prefab base script /// public abstract class LightningBoltPrefabScriptBase : LightningBoltScript { #if DEBUG && SHOW_MANUAL_WARNING private static bool showedManualWarning; #endif private readonly List batchParameters = new List(); private readonly System.Random random = new System.Random(); /// Reduces the probability that additional bolts from CountRange will actually happen (0 - 1). [Header("Lightning Spawn Properties")] [SingleLineClamp("How long to wait before creating another round of lightning bolts in seconds", 0.001, double.MaxValue)] public RangeOfFloats IntervalRange = new RangeOfFloats { Minimum = 0.05f, Maximum = 0.1f }; /// How many lightning bolts to emit for each interval [SingleLineClamp("How many lightning bolts to emit for each interval", 0.0, 100.0)] public RangeOfIntegers CountRange = new RangeOfIntegers { Minimum = 1, Maximum = 1 }; /// Reduces the probability that additional bolts from CountRange will actually happen (0 - 1). [Tooltip("Reduces the probability that additional bolts from CountRange will actually happen (0 - 1).")] [Range(0.0f, 1.0f)] public float CountProbabilityModifier = 1.0f; /// Delay in seconds (range) before each additional lightning bolt in count range is emitted public RangeOfFloats DelayRange = new RangeOfFloats { Minimum = 0.0f, Maximum = 0.0f }; /// For each bolt emitted, how long should it stay in seconds [SingleLineClamp("For each bolt emitted, how long should it stay in seconds", 0.01, 10.0)] public RangeOfFloats DurationRange = new RangeOfFloats { Minimum = 0.06f, Maximum = 0.12f }; /// How long (in seconds) this game object should live before destroying itself. Leave as 0 for infinite. [Header("Lightning Appearance Properties")] [SingleLineClamp("The trunk width range in unity units (x = min, y = max)", 0.0001, 100.0)] public RangeOfFloats TrunkWidthRange = new RangeOfFloats { Minimum = 0.1f, Maximum = 0.2f }; /// How long (in seconds) this game object should live before destroying itself. Leave as 0 for infinite. [Tooltip("How long (in seconds) this game object should live before destroying itself. Leave as 0 for infinite.")] [Range(0.0f, 1000.0f)] public float LifeTime = 0.0f; /// Generations (1 - 8, higher makes more detailed but more expensive lightning) [Tooltip("Generations (1 - 8, higher makes more detailed but more expensive lightning)")] [Range(1, 8)] public int Generations = 6; /// The chaos factor that determines how far the lightning main trunk can spread out, higher numbers spread out more. 0 - 1. [Tooltip("The chaos factor that determines how far the lightning main trunk can spread out, higher numbers spread out more. 0 - 1.")] [Range(0.0f, 1.0f)] public float ChaosFactor = 0.075f; /// The chaos factor that determines how far the forks of the lightning can spread out, higher numbers spread out more. 0 - 1. [Tooltip("The chaos factor that determines how far the forks of the lightning can spread out, higher numbers spread out more. 0 - 1.")] [Range(0.0f, 1.0f)] public float ChaosFactorForks = 0.095f; /// Intensity of the lightning [Tooltip("Intensity of the lightning")] [Range(0.0f, 10.0f)] public float Intensity = 1.0f; /// The intensity of the glow [Tooltip("The intensity of the glow")] [Range(0.0f, 10.0f)] public float GlowIntensity = 0.1f; /// The width multiplier for the glow, 0 - 64 [Tooltip("The width multiplier for the glow, 0 - 64")] [Range(0.0f, 64.0f)] public float GlowWidthMultiplier = 4.0f; /// What percent of time the lightning should fade in and out. For example, 0.15 fades in 15% of the time and fades out 15% of the time, with full visibility 70% of the time. [Tooltip("What percent of time the lightning should fade in and out. For example, 0.15 fades in 15% of the time and fades out 15% of the time, with full visibility 70% of the time.")] [Range(0.0f, 0.5f)] public float FadePercent = 0.15f; /// Modify the duration of lightning fade in. [Tooltip("Modify the duration of lightning fade in.")] [Range(0.0f, 1.0f)] public float FadeInMultiplier = 1.0f; /// Modify the duration of fully lit lightning. [Tooltip("Modify the duration of fully lit lightning.")] [Range(0.0f, 1.0f)] public float FadeFullyLitMultiplier = 1.0f; /// Modify the duration of lightning fade out. [Tooltip("Modify the duration of lightning fade out.")] [Range(0.0f, 1.0f)] public float FadeOutMultiplier = 1.0f; /// 0 - 1, how slowly the lightning should grow. 0 for instant, 1 for slow. [Tooltip("0 - 1, how slowly the lightning should grow. 0 for instant, 1 for slow.")] [Range(0.0f, 1.0f)] public float GrowthMultiplier; /// How much smaller the lightning should get as it goes towards the end of the bolt. For example, 0.5 will make the end 50% the width of the start. [Tooltip("How much smaller the lightning should get as it goes towards the end of the bolt. For example, 0.5 will make the end 50% the width of the start.")] [Range(0.0f, 10.0f)] public float EndWidthMultiplier = 0.5f; /// How forked should the lightning be? (0 - 1, 0 for none, 1 for lots of forks) [Tooltip("How forked should the lightning be? (0 - 1, 0 for none, 1 for lots of forks)")] [Range(0.0f, 1.0f)] public float Forkedness = 0.25f; /// Minimum distance multiplier for forks [Range(0.0f, 10.0f)] [Tooltip("Minimum distance multiplier for forks")] public float ForkLengthMultiplier = 0.6f; /// Fork distance multiplier variance. Random range of 0 to n that is added to Fork Length Multiplier. [Range(0.0f, 10.0f)] [Tooltip("Fork distance multiplier variance. Random range of 0 to n that is added to Fork Length Multiplier.")] public float ForkLengthVariance = 0.2f; /// Forks have their EndWidthMultiplier multiplied by this value [Tooltip("Forks have their EndWidthMultiplier multiplied by this value")] [Range(0.0f, 10.0f)] public float ForkEndWidthMultiplier = 1.0f; /// Light parameters [Header("Lightning Light Properties")] [Tooltip("Light parameters")] public LightningLightParameters LightParameters; /// Maximum number of lights that can be created per batch of lightning [Tooltip("Maximum number of lights that can be created per batch of lightning")] [Range(0, 64)] public int MaximumLightsPerBatch = 8; /// Manual or automatic mode. Manual requires that you call the Trigger method in script. Automatic uses the interval to create lightning continuously. [Header("Lightning Trigger Type")] [Tooltip("Manual or automatic mode. Manual requires that you call the Trigger method in script. Automatic uses the interval to create lightning continuously.")] public bool ManualMode; /// Turns lightning into automatic mode for this number of seconds, then puts it into manual mode. [Tooltip("Turns lightning into automatic mode for this number of seconds, then puts it into manual mode.")] [Range(0.0f, 120.0f)] public float AutomaticModeSeconds; /// Custom handler to modify the transform of each lightning bolt, useful if it will be alive longer than a few frames and needs to scale and rotate based on the position of other objects. [Header("Lightning custom transform handler")] [Tooltip("Custom handler to modify the transform of each lightning bolt, useful if it will be alive longer than a few frames and needs to scale and rotate based " + "on the position of other objects.")] public LightningCustomTransformDelegate CustomTransformHandler; /// /// Override the random generator for the bolts /// public System.Random RandomOverride { get; set; } private float nextLightningTimestamp; private float lifeTimeRemaining; private void CalculateNextLightningTimestamp(float offset) { nextLightningTimestamp = (IntervalRange.Minimum == IntervalRange.Maximum ? IntervalRange.Minimum : offset + IntervalRange.Random()); } private void CustomTransform(LightningCustomTransformStateInfo state) { if (CustomTransformHandler != null) { CustomTransformHandler.Invoke(state); } } private void CallLightning() { CallLightning(null, null); } private void CallLightning(Vector3? start, Vector3? end) { System.Random r = (RandomOverride ?? random); int count = CountRange.Random(r); for (int i = 0; i < count; i++) { LightningBoltParameters p = CreateParameters(); if (CountProbabilityModifier >= 0.9999f || i == 0 || (float)p.Random.NextDouble() <= CountProbabilityModifier) { p.CustomTransform = (CustomTransformHandler == null ? (System.Action)null : CustomTransform); CreateLightningBolt(p); if (start != null) { p.Start = start.Value; } if (end != null) { p.End = end.Value; } } else { LightningBoltParameters.ReturnParametersToCache(p); } } CreateLightningBoltsNow(); } /// /// Create lightning bolts immediately /// protected void CreateLightningBoltsNow() { int tmp = LightningBolt.MaximumLightsPerBatch; LightningBolt.MaximumLightsPerBatch = MaximumLightsPerBatch; CreateLightningBolts(batchParameters); LightningBolt.MaximumLightsPerBatch = tmp; batchParameters.Clear(); } /// /// Populate lightning bolt parameters from script /// /// Parameters to populate protected override void PopulateParameters(LightningBoltParameters parameters) { base.PopulateParameters(parameters); parameters.RandomOverride = RandomOverride; float duration = DurationRange.Random(parameters.Random); float trunkWidth = TrunkWidthRange.Random(parameters.Random); parameters.Generations = Generations; parameters.LifeTime = duration; parameters.ChaosFactor = ChaosFactor; parameters.ChaosFactorForks = ChaosFactorForks; parameters.TrunkWidth = trunkWidth; parameters.Intensity = Intensity; parameters.GlowIntensity = GlowIntensity; parameters.GlowWidthMultiplier = GlowWidthMultiplier; parameters.Forkedness = Forkedness; parameters.ForkLengthMultiplier = ForkLengthMultiplier; parameters.ForkLengthVariance = ForkLengthVariance; parameters.FadePercent = FadePercent; parameters.FadeInMultiplier = FadeInMultiplier; parameters.FadeOutMultiplier = FadeOutMultiplier; parameters.FadeFullyLitMultiplier = FadeFullyLitMultiplier; parameters.GrowthMultiplier = GrowthMultiplier; parameters.EndWidthMultiplier = EndWidthMultiplier; parameters.ForkEndWidthMultiplier = ForkEndWidthMultiplier; parameters.DelayRange = DelayRange; parameters.LightParameters = LightParameters; } /// /// Start /// protected override void Start() { base.Start(); CalculateNextLightningTimestamp(0.0f); lifeTimeRemaining = (LifeTime <= 0.0f ? float.MaxValue : LifeTime); } /// /// Update /// protected override void Update() { base.Update(); if (Time.timeScale <= 0.0f) { return; } else if ((lifeTimeRemaining -= LightningBoltScript.DeltaTime) < 0.0f) { GameObject.Destroy(gameObject); } if ((nextLightningTimestamp -= LightningBoltScript.DeltaTime) <= 0.0f) { CalculateNextLightningTimestamp(nextLightningTimestamp); if (ManualMode) { #if DEBUG && SHOW_MANUAL_WARNING if (!showedManualWarning) { showedManualWarning = true; Debug.LogWarning("Lightning bolt script is in manual mode. Trigger method must be called."); } #endif } else { CallLightning(); } } if (AutomaticModeSeconds > 0.0f) { AutomaticModeSeconds = Mathf.Max(0.0f, AutomaticModeSeconds - LightningBoltScript.DeltaTime); ManualMode = (AutomaticModeSeconds == 0.0f); } } /// /// OnDrawGizmos /// protected virtual void OnDrawGizmos() { #if UNITY_EDITOR if (!HideGizmos) { Gizmos.color = Color.white; UnityEditor.Handles.color = Color.white; } #endif } /// /// Derived classes can override and can call this base class method last to add the lightning bolt parameters to the list of batched lightning bolts /// /// Lightning bolt creation parameters public override void CreateLightningBolt(LightningBoltParameters p) { batchParameters.Add(p); // do not call the base method, we batch up and use CreateLightningBolts } /// /// Manually trigger the lightning once /// public void Trigger() { Trigger(-1.0f); } /// /// Manually trigger lightning /// /// Number of seconds to turn on automatic lightning for (sets AutomaticModeSeconds). public void Trigger(float seconds) { CallLightning(); if (seconds >= 0.0f) { AutomaticModeSeconds = Mathf.Max(0.0f, seconds); } } /// /// Manually trigger lightning /// /// Start position /// End position public void Trigger(Vector3? start, Vector3? end) { CallLightning(start, end); } } /// /// Lightning bolt prefab script, base script to create lightning using a prefab /// public class LightningBoltPrefabScript : LightningBoltPrefabScriptBase { /// The source game object, can be null [Header("Start/end")] [Tooltip("The source game object, can be null")] public GameObject Source; /// The destination game object, can be null [Tooltip("The destination game object, can be null")] public GameObject Destination; /// X, Y and Z for variance from the start point. Use positive values. [Tooltip("X, Y and Z for variance from the start point. Use positive values.")] public Vector3 StartVariance; /// X, Y and Z for variance from the end point. Use positive values. [Tooltip("X, Y and Z for variance from the end point. Use positive values.")] public Vector3 EndVariance; #if UNITY_EDITOR /// /// OnDrawGizmos /// protected override void OnDrawGizmos() { base.OnDrawGizmos(); if (HideGizmos) { return; } else if (Source != null) { Gizmos.DrawIcon(Source.transform.position, "LightningPathStart.png"); } if (Destination != null) { Gizmos.DrawIcon(Destination.transform.position, "LightningPathNext.png"); } if (Source != null && Destination != null) { Gizmos.DrawLine(Source.transform.position, Destination.transform.position); Vector3 direction = (Destination.transform.position - Source.transform.position); Vector3 center = (Source.transform.position + Destination.transform.position) * 0.5f; float arrowSize = Mathf.Min(2.0f, direction.magnitude) * 2.0f; UnityEditor.Handles.ArrowHandleCap(0, center, Quaternion.LookRotation(direction), arrowSize, EventType.Repaint); } } #endif /// /// Create a lightning bolt /// /// Parameters public override void CreateLightningBolt(LightningBoltParameters parameters) { parameters.Start = (Source == null ? parameters.Start : Source.transform.position); parameters.End = (Destination == null ? parameters.End : Destination.transform.position); parameters.StartVariance = StartVariance; parameters.EndVariance = EndVariance; base.CreateLightningBolt(parameters); } } }