using System.Collections.Generic; using UnityEngine; /// /// ムービングライトの角度を一括コントロールする /// namespace StudioMaron { public class LightAngleController : MonoBehaviour { // Inspectorに表示するパラメータはLight Color Parameterと同じにしている // つまりこのコントローラのインスペクタで設定するのは初期設定用 // エディタ拡張でOperation Modeに応じたパラメータだけ表示する public List lightAngleList = new List(); public Transform lookAtTarget; [Header("-----Parameter--------------------------------------------------------------------------------")] [HideInInspector] public LightAngleParameter.OperationMode operationMode = LightAngleParameter.OperationMode.None; [Space(10)] [Header("-----All Fixed-----------------------------------------------------------------------------")] [HideInInspector] [Range(-90, 90)] public float allFixed_Tilt = 0; [HideInInspector] [Range(-180, 180)] public float allFixed_Pan = 0; [HideInInspector] [Range(0, 1)] public float allFixed_NoiseRange = 0f; [HideInInspector] [Range(0, 1)] public float allFixed_NoiseSpeed = 0f; [Space(10)] [Header("-----Line Fixed-----------------------------------------------------------------------------")] [HideInInspector] [Range(-90, 90)] public float lineFixed_Tilt = 0; [HideInInspector] public bool lineFixed_Asymmetry = false; [HideInInspector] public AnimationCurve lineFixed_TiltCurve = AnimationCurve.Linear(timeStart: 0f, valueStart: 0f, timeEnd: 1f, valueEnd: 1f); [Space(10)] [HideInInspector] [Range(-180, 180)] public float lineFixed_Pan = 0; [HideInInspector] [Range(0, 1)] public float lineFixed_NoiseRange = 0f; [HideInInspector] [Range(0, 1)] public float lineFixed_NoiseSpeed = 0f; [Space(10)] [Header("-----Random Fixed-----------------------------------------------------------------------------")] [HideInInspector] [Range(-90, 90)] public float randomFixed_MaxTilt = 90; [HideInInspector] [Range(-90, 90)] public float randomFixed_MinTilt = -90; [Space(10)] [HideInInspector] [Range(-180, 180)] public float randomFixed_MaxPan = 180; [HideInInspector] [Range(-180, 180)] public float randomFixed_MinPan = -180; [Space(10)] [HideInInspector] public int randomFixed_RandomSeed = 1234; [HideInInspector] [Range(0, 1)] public float randomFixed_NoiseRange = 0f; [HideInInspector] [Range(0, 1)] public float randomFixed_NoiseSpeed = 0f; [Space(10)] [Header("-----Rotaion-----------------------------------------------------------------------------")] [HideInInspector] [Range(0, 90)] public float rotation_Tilt = 15; [HideInInspector] [Min(0.001f)] public float rotation_CycleTime = 3; [HideInInspector] [Range(0, 1)] public float rotation_Sync = 0; [HideInInspector] [Range(0, 1)] public float rotation_ReverseRate = 0; [HideInInspector] public int rotation_RandomSeed = 1234; [Space(10)] [Header("-----Tilt Wave-----------------------------------------------------------------------------")] [HideInInspector] [Range(-90, 90)] public float tiltWave_MaxTilt = 30; [HideInInspector] [Range(-90, 90)] public float tiltWave_MinTilt = -30; [HideInInspector] [Min(0.001f)] public float tiltWave_CycleTime = 5; [HideInInspector] [Range(0, 1)] public float tiltWave_Sync = 0; [HideInInspector] [Range(-180, 180)] public float tiltWave_Pan = 0; [Space(10)] [Header("-----Search Light-----------------------------------------------------------------------------")] [HideInInspector] [Range(0.01f, 1)] public float searchLight_Range = 0.5f; [HideInInspector] [Range(0.01f, 1)] public float searchLight_Speed = 0.5f; [HideInInspector] public int searchLight_RandomSeed = 1234; [Space(10)] [Header("-----Look At Target-----------------------------------------------------------------------------")] [HideInInspector] public Vector3 lookAtTarget_LookAtWorldOffset = Vector3.zero; [HideInInspector] [Range(0, 1)] public float lookAtTarget_NoiseRange = 0f; [HideInInspector] [Range(0, 1)] public float lookAtTarget_NoiseSpeed = 0f; [Space(10)] [Header("-----Option-----------------------------------------------------------------------------")] // Timeline clipからUse Reference、Mix Referenceモードを使う場合に設定する [HideInInspector] public LightAngleReference reference; [Space(10)] [HideInInspector] public int noiseRandomSeed = 1234; // フレームレートをmax60にして負荷を減らす用の変数 float fps = 60; float deltaTime; int count; // Timelineからパラメータの更新があったフラグ bool parameterChangedByTimeline; // Light Angle Parameterのリスト // Timelineからこのリストを更新し、Update時に計算する [HideInInspector] public List previousList = new List(); // Light Angle Infoのリスト // このリストを各Light Angleに送り、Light Angleが角度を計算する List infoList = new List(); List info; // 計算用の変数 float coef; float sign; float ref_weight; // Start時にLight Color Parameterを作って初期化 private void Start() { CreateParameter(); } // 空のLight Color Parameterを作ってInspectorの項目を反映し、previousListに入れる void CreateParameter() { var param = new LightAngleParameter(); param.weight = 1; param.clipTime = 0; param.operationMode = operationMode; param.allFixed_Pan = allFixed_Pan; param.allFixed_Tilt = allFixed_Tilt; param.allFixed_NoiseRange = allFixed_NoiseRange; param.allFixed_NoiseSpeed = allFixed_NoiseSpeed; param.lineFixed_Pan = lineFixed_Pan; param.lineFixed_Tilt = lineFixed_Tilt; param.lineFixed_TiltCurve = lineFixed_TiltCurve; param.lineFixed_Asymmetry = lineFixed_Asymmetry; param.lineFixed_NoiseRange = lineFixed_NoiseRange; param.lineFixed_NoiseSpeed = lineFixed_NoiseSpeed; param.randomFixed_MaxPan = randomFixed_MaxPan; param.randomFixed_MinPan = randomFixed_MinPan; param.randomFixed_MaxTilt = randomFixed_MaxTilt; param.randomFixed_MinTilt = randomFixed_MinTilt; param.randomFixed_RandomSeed = randomFixed_RandomSeed; param.randomFixed_NoiseRange = randomFixed_NoiseRange; param.randomFixed_NoiseSpeed = randomFixed_NoiseSpeed; param.rotation_Tilt = rotation_Tilt; param.rotation_CycleTime = rotation_CycleTime; param.rotation_Sync = rotation_Sync; param.rotation_ReverseRate = rotation_ReverseRate; param.rotation_RandomSeed = rotation_RandomSeed; param.tiltWave_MaxTilt = tiltWave_MaxTilt; param.tiltWave_MinTilt = tiltWave_MinTilt; param.tiltWave_CycleTime = tiltWave_CycleTime; param.tiltWave_Sync = tiltWave_Sync; param.tiltWave_Pan = tiltWave_Pan; param.searchLight_Range = searchLight_Range; param.searchLight_Speed = searchLight_Speed; param.searchLight_RandomSeed = searchLight_RandomSeed; param.lookAtTarget_LookAtWorldOffset = lookAtTarget_LookAtWorldOffset; param.lookAtTarget_NoiseRange = lookAtTarget_NoiseRange; param.lookAtTarget_NoiseSpeed = lookAtTarget_NoiseSpeed; param.noiseRandomSeed = noiseRandomSeed; SetParameter(new List() { param }); } // Light Angle Parameterのリストを作成する void SetParameter(List list) { previousList.Clear(); previousList.AddRange(list); } // Inspectorの数値を編集したらライトに反映させる public void OnValidate() { CreateParameter(); UpdateAngle(); } // フレームレート60(1.6msごと)に処理を行う void Update() { // フレームレート60に制限する処理 deltaTime += Time.deltaTime; if (deltaTime < 1 / fps) return; count = (int)(deltaTime * fps); deltaTime -= count / fps; // Timelineによる変更がない場合、前回使ったLightColorParameterの内部時間を進めて再利用する if (!parameterChangedByTimeline) { foreach (var o in previousList) o.clipTime += count / fps; } parameterChangedByTimeline = false; // Angleの更新 UpdateAngle(); } // Angleの更新 void UpdateAngle() { // Use Referenceモード用のWeightの初期化 ref_weight = 0; // これから計算するパラメータリスト内にUse Referenceモードがあれば、計算用のWeightに追加する for (int j = 0; j < previousList.Count; j++) { if (previousList[j].operationMode == LightAngleParameter.OperationMode.UseReference) { ref_weight += previousList[j].weight; } } // ライト毎に角度を計算してinfoListに入れる for (int i = 0; i < lightAngleList.Count; i++) { if (lightAngleList[i] == null) continue; // infoListのクリア infoList.Clear(); // Use ReferenceかMix Referenceがあれば、Referenceのパラメータを取得しinfoListに入れる if (ref_weight != 0) { if (reference != null) { infoList.AddRange(CalcAngle(reference.GetReferenceAngleParameter(), i, lightAngleList.Count, ref_weight)); } } // パラメータリストから角度を計算しinfoListに入れる infoList.AddRange(CalcAngle(previousList, i, lightAngleList.Count)); // 各Light AngleにinfoListを送る lightAngleList[i].SetPanTiltSlerp(infoList); } } // 角度の計算 List CalcAngle(List paramList, int n, int length, float weightCoef = 1) { info = new List(); // パラメータごとにinfoを作り、infoListに追加する for (int k = 0; k < paramList.Count; k++) { info.Add(new LightAngleInfo()); info[k].weight = paramList[k].weight * weightCoef; info[k].noiseRandomSeed = paramList[k].noiseRandomSeed + n; // Operation Modeごとの処理 switch (paramList[k].operationMode) { // Noneモードではinfoを無効扱いにする case LightAngleParameter.OperationMode.None: info[k].isInvalid = true; break; // All Fixedモードは全てのライトが同じ角度 case LightAngleParameter.OperationMode.All_Fixed: info[k].pan = paramList[k].allFixed_Pan; info[k].tilt = paramList[k].allFixed_Tilt; info[k].noiseRange = paramList[k].allFixed_NoiseRange; info[k].noiseSpeed = paramList[k].allFixed_NoiseSpeed; break; // Line Fixedモードでは左右対称でV字に角度をつける case LightAngleParameter.OperationMode.Line_Fixed: if(length <= 2) { if(n == 0) coef = -1; else coef = 1; info[k].pan = paramList[k].lineFixed_Pan * coef; if (paramList[k].lineFixed_Asymmetry) coef = paramList[k].lineFixed_TiltCurve.Evaluate(n / (length - 1f)); else coef = paramList[k].lineFixed_TiltCurve.Evaluate(1); info[k].tilt = paramList[k].lineFixed_Tilt * coef; } else { // Pan // 配列の前半 if (n < (length - 1) / 2f) coef = -1 * (1 - n / ((length - 1) / 2f)); // 配列の真ん中 else if (n == (length - 1) / 2f) coef = 0; // 配列の後半 else coef = (n - (length - 1) / 2f) / ((length - 1) / 2f); info[k].pan = paramList[k].lineFixed_Pan * coef; // Tilt // 左右非対称にして1列にする場合 if (paramList[k].lineFixed_Asymmetry) coef = paramList[k].lineFixed_TiltCurve.Evaluate(n / (length - 1f)); // 左右対称 else { // 配列が偶数 if (length % 2 == 0) { // 配列の前半 if (n < length / 2f) coef = paramList[k].lineFixed_TiltCurve.Evaluate(1 - n / ((length / 2f) - 1)); // 配列の後半 else coef = paramList[k].lineFixed_TiltCurve.Evaluate((n - (length / 2f)) / ((length / 2f) - 1)); } // 配列が奇数 else { // 配列の前半 if (n < (length - 1) / 2f) coef = paramList[k].lineFixed_TiltCurve.Evaluate(1 - n / ((length - 1) / 2f)); // 配列の真ん中 else if (n == (length - 1) / 2f) coef = paramList[k].lineFixed_TiltCurve.Evaluate(0); // 配列の後半 else coef = paramList[k].lineFixed_TiltCurve.Evaluate((n - (length - 1) / 2f) / ((length - 1) / 2f)); } } info[k].tilt = paramList[k].lineFixed_Tilt * coef; } info[k].noiseRange = paramList[k].lineFixed_NoiseRange; info[k].noiseSpeed = paramList[k].lineFixed_NoiseSpeed; break; // Random Fixedモードではシード値で乱数を作ってランダムな角度でライトを配置する case LightAngleParameter.OperationMode.Random_Fixed: coef = RandomFloat(paramList[k].randomFixed_RandomSeed + n); info[k].pan = Mathf.Lerp(paramList[k].randomFixed_MinPan, paramList[k].randomFixed_MaxPan, coef); coef = RandomFloat(paramList[k].randomFixed_RandomSeed * 2 + n); info[k].tilt = Mathf.Lerp(paramList[k].randomFixed_MinTilt, paramList[k].randomFixed_MaxTilt, coef); info[k].noiseRange = paramList[k].randomFixed_NoiseRange; info[k].noiseSpeed = paramList[k].randomFixed_NoiseSpeed; break; // Rotationモードは一定のTilt角でグルグル回す case LightAngleParameter.OperationMode.Rotation: coef = RandomFloat(paramList[k].rotation_RandomSeed + n) * (1 - paramList[k].rotation_Sync); sign = RandomFloat(paramList[k].rotation_RandomSeed + n) < paramList[k].rotation_ReverseRate ? -1 : 1; info[k].pan = 360 * frac((Time.time / paramList[k].rotation_CycleTime) + coef) * sign; info[k].tilt = paramList[k].rotation_Tilt; break; // Tilt WaveモードはTiltの上げ下げループ case LightAngleParameter.OperationMode.Tilt_Wave: // Pan // 配列の前半 if (n < (length - 1) / 2f) { coef = -1 * (1 - n / ((length - 1) / 2f)); } // 配列の真ん中 else if (n == (length - 1) / 2f) { coef = 0; } // 配列の後半 else { coef = (n - (length - 1) / 2f) / ((length - 1) / 2f); } info[k].pan = paramList[k].tiltWave_Pan * coef; // Tilt coef = Mathf.Sin(2 * Mathf.PI * Time.time / paramList[k].tiltWave_CycleTime - (2 * Mathf.PI * n / length) * (1 - paramList[k].tiltWave_Sync)); info[k].tilt = (paramList[k].tiltWave_MaxTilt + paramList[k].tiltWave_MinTilt) * 0.5f + (paramList[k].tiltWave_MaxTilt - paramList[k].tiltWave_MinTilt) * 0.5f * coef; break; // Search Lightモードはサーチライトっぽい動き case LightAngleParameter.OperationMode.Search_Light: // Pan coef = RandomFloat(paramList[k].searchLight_RandomSeed + n); sign = RandomFloat(paramList[k].searchLight_RandomSeed + n) < 0.5f ? -1 : 1; info[k].pan = 360 * frac((Time.time * paramList[k].searchLight_Speed * 0.25f) + coef) * sign; // Tilt coef = Mathf.Sin(2 * Mathf.PI * Time.time * paramList[k].searchLight_Speed * 0.63f - (2 * Mathf.PI * n / length)); info[k].tilt = paramList[k].searchLight_Range * 60 * coef; break; // Look At Target case LightAngleParameter.OperationMode.LookAt_Target: if (lookAtTarget != null) { info[k].isLookAtTarget = true; info[k].targetPosition = lookAtTarget.position + paramList[k].lookAtTarget_LookAtWorldOffset; info[k].noiseRange = paramList[k].lookAtTarget_NoiseRange; info[k].noiseSpeed = paramList[k].lookAtTarget_NoiseSpeed; } else info[k].isInvalid = true; break; // Use ReferenceはReferenceの挙動を参照する。乱数のシード値もReferenceと同じになることに注意 case LightAngleParameter.OperationMode.UseReference: info[k].isInvalid = true; break; } } return info; } // Timeline clipから発動する処理 public void SetParameterFromTimeline(List parameter) { #if UNITY_EDITOR if (UnityEditor.EditorApplication.isPlaying) { SetParameter(parameter); } else { SetParameter(parameter); UpdateAngle(); } #else SetParameter(parameter); #endif } // Timelineのプレビュー終了時の処理 public void ReapplyParameter() { #if UNITY_EDITOR if (!UnityEditor.EditorApplication.isPlaying) { CreateParameter(); UpdateAngle(); } #endif } /// /// 疑似乱数を作る /// Vector2 st = new Vector2(); float RandomFloat(int seed) { st = new Vector2(seed, seed); return frac(Mathf.Sin(Vector2.Dot(st, new Vector2(12.9898f, 78.233f))) * 43758.5453f); } float frac(float f) { return f - Mathf.Floor(f); } // 子オブジェクトからLight Angleを検索して取得する // Inspectorの右上から発動できる [ContextMenu("Get LightAngle component from children")] public void GetLightListFromChildren() { var array = GetComponentsInChildren(); if (array.Length == 0) return; var list = new List(array); lightAngleList.Clear(); lightAngleList.AddRange(list); } } }