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);
}
}
}