Skip to content

Instantly share code, notes, and snippets.

@staggartcreations
Created December 21, 2022 10:24
Show Gist options
  • Save staggartcreations/b6cfc8d4ce747b35a1f1e2dfd1aa832a to your computer and use it in GitHub Desktop.
Save staggartcreations/b6cfc8d4ce747b35a1f1e2dfd1aa832a to your computer and use it in GitHub Desktop.
Unity component for creating a set of animated point lights
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;
using UnityEngine.Rendering;
[ExecuteInEditMode]
public class LightCircle : MonoBehaviour {
[Header("Lights")]
[Range(1,32)]
public int amount = 16;
[Min(1f)]
public float diameter = 10f;
[Space]
[Range(0f, 1f)]
public float hueShift = 0f;
[Range(0f, 1f)]
public float saturation = 1f;
[Space]
[Min(0f)]
public float baseIntensity = 15f;
[Min(1f)]
public float baseRange = 25f;
[Header("Spheres")]
public float scale = 0.2f;
public float emission = 0.1f;
[Header("Animation")]
public float rotateSpeed = 1f;
public float waveSpeed = 0.1f;
[Space]
[Min(0f)]
public float upwardsAmplitude = 0f;
public float upwardsFrequency = 2f;
[Space]
[Min(0f)]
public float inwardAmplitude = 0f;
public float inwardFrequency = 2f;
private Light[] lights = new Light[0];
MaterialPropertyBlock props;
private void Update()
{
if(lights.Length != amount)
{
for (int i = 0; i < lights.Length; i++)
{
if (lights[i]) DestroyImmediate(lights[i].gameObject);
}
lights = new Light[amount];
for (int i = 0; i < amount; i++)
{
float t = (float)i / (float)amount;
GameObject lightObj = new GameObject();
lightObj.name = "Light " + (i + 1);
lights[i] = lightObj.AddComponent<Light>();
lights[i].type = LightType.Point;
lightObj.transform.parent = this.transform;
//Don't want to save the created objects. Since their transform is modified it would dirty the scene.
//Simply recreate them as needed...
lightObj.hideFlags = HideFlags.DontSave;
//Add MeshRenderer
GameObject sphereObj = GameObject.CreatePrimitive(PrimitiveType.Sphere);
sphereObj.transform.parent = lightObj.transform;
sphereObj.transform.localPosition = Vector3.zero;
MeshRenderer sphereRenderer = sphereObj.GetComponent<MeshRenderer>();
sphereRenderer.shadowCastingMode = ShadowCastingMode.Off;
Material material = new Material(sphereRenderer.sharedMaterial.shader);
material.enableInstancing = true;
material.name = "Emissive";
material.SetFloat("_EmissionEnabled", 1.0f);
material.globalIlluminationFlags |= MaterialGlobalIlluminationFlags.RealtimeEmissive;
material.EnableKeyword("_EMISSION"); //Built-in RP & URP
sphereRenderer.material = material;
}
UpdateLights();
}
var time = Time.time;
#if UNITY_EDITOR
if (!Application.isPlaying) time = (float)EditorApplication.timeSinceStartup;
#endif
Animate(1f, time);
}
private void Animate(float baseSpeed, float time)
{
for (int i = 0; i < lights.Length; i++)
{
float t = (float)i / ((float)lights.Length);
float angle = (i * Mathf.PI * 2 / amount) + (baseSpeed * rotateSpeed * time);
//Position in a ring
Vector3 targetPosition = new Vector3(Mathf.Cos(angle), 0f, Mathf.Sin(angle)) * diameter;
//Bob up and down
targetPosition.y = Mathf.Sin((t * upwardsFrequency * amount) + time * baseSpeed * waveSpeed) * upwardsAmplitude;
//Swing in/out
var amp = diameter + (Mathf.Sin((t * inwardFrequency * amount) + time * baseSpeed * waveSpeed) * inwardAmplitude);
targetPosition.x = targetPosition.normalized.x * amp;
targetPosition.z = targetPosition.normalized.z * amp;
lights[i].transform.position = this.transform.localToWorldMatrix.MultiplyPoint(targetPosition);
}
}
private void UpdateLights()
{
for (int i = 0; i < lights.Length; i++)
{
float t = (float)i / ((float)lights.Length);
lights[i].intensity = baseIntensity;
lights[i].range = baseRange;
lights[i].color = Color.HSVToRGB(Mathf.PingPong(t + hueShift, 1f), saturation, 1f);
MeshRenderer sphereRenderer = lights[i].GetComponentInChildren<MeshRenderer>();
sphereRenderer.transform.localScale = Vector3.one * scale;
props.SetColor("_Color", lights[i].color); //Built-in RP
props.SetColor("_BaseColor", lights[i].color); //URP
props.SetColor("_EmissionColor", lights[i].color * baseIntensity * emission); //Built-in RP + URP
sphereRenderer.SetPropertyBlock(props);
}
}
private void OnValidate()
{
UpdateLights();
}
private void OnEnable()
{
props = new MaterialPropertyBlock();
#if UNITY_EDITOR
if (!Application.isPlaying) EditorApplication.update += Update;
#endif
}
#if UNITY_EDITOR
private void OnDisable()
{
if (!Application.isPlaying) EditorApplication.update -= Update;
}
#endif
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment