CinemachineImpulseDefinitionPropertyDrawer.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. using UnityEngine;
  2. using UnityEditor;
  3. using System.Collections.Generic;
  4. namespace Cinemachine.Editor
  5. {
  6. [CustomPropertyDrawer(typeof(CinemachineImpulseDefinition))]
  7. internal sealed class CinemachineImpulseDefinitionPropertyDrawer : PropertyDrawer
  8. {
  9. const int vSpace = 2;
  10. const int kGraphHeight = 8; // lines
  11. #pragma warning disable 649 // variable never used
  12. CinemachineImpulseDefinition m_MyClass;
  13. #pragma warning restore 649
  14. GUIContent m_TimeText = null;
  15. float m_TimeTextWidth;
  16. SerializedProperty m_ShapeProperty;
  17. float m_ShapePropertyHeight;
  18. SerializedProperty m_ImpulseTypeProperty;
  19. SerializedProperty m_DissipationRateProperty;
  20. float m_SpreadPropertyHeight;
  21. public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
  22. {
  23. m_ImpulseTypeProperty = property.FindPropertyRelative(() => m_MyClass.m_ImpulseType);
  24. var mode = (CinemachineImpulseDefinition.ImpulseTypes)m_ImpulseTypeProperty.intValue;
  25. if (mode == CinemachineImpulseDefinition.ImpulseTypes.Legacy)
  26. return LegacyModeGetPropertyHeight(property, label);
  27. int lines = 3;
  28. m_ShapePropertyHeight = EditorGUIUtility.singleLineHeight + vSpace;
  29. m_ShapeProperty = property.FindPropertyRelative(() => m_MyClass.m_ImpulseShape);
  30. if (m_ShapeProperty.isExpanded)
  31. {
  32. lines += kGraphHeight;
  33. m_ShapePropertyHeight *= 1 + kGraphHeight;
  34. }
  35. m_ShapePropertyHeight -= vSpace;
  36. m_DissipationRateProperty = property.FindPropertyRelative(() => m_MyClass.m_DissipationRate);
  37. if (mode != CinemachineImpulseDefinition.ImpulseTypes.Uniform)
  38. {
  39. m_SpreadPropertyHeight = EditorGUIUtility.singleLineHeight + vSpace;
  40. if (m_DissipationRateProperty.isExpanded)
  41. {
  42. lines += kGraphHeight;
  43. m_SpreadPropertyHeight *= 1 + kGraphHeight;
  44. }
  45. m_SpreadPropertyHeight -= vSpace;
  46. lines += 2;
  47. if (mode == CinemachineImpulseDefinition.ImpulseTypes.Propagating)
  48. ++lines;
  49. }
  50. return lines * (EditorGUIUtility.singleLineHeight + vSpace);
  51. }
  52. public override void OnGUI(Rect rect, SerializedProperty property, GUIContent label)
  53. {
  54. // Using BeginProperty / EndProperty on the parent property means that
  55. // prefab override logic works on the entire property.
  56. EditorGUI.BeginProperty(rect, label, property);
  57. var mode = (CinemachineImpulseDefinition.ImpulseTypes)m_ImpulseTypeProperty.intValue;
  58. if (mode == CinemachineImpulseDefinition.ImpulseTypes.Legacy)
  59. {
  60. LegacyModeOnGUI(rect, property, label);
  61. return;
  62. }
  63. rect.height = EditorGUIUtility.singleLineHeight;
  64. EditorGUI.PropertyField(rect, property.FindPropertyRelative(() => m_MyClass.m_ImpulseChannel));
  65. rect.y += rect.height + vSpace;
  66. // Impulse type
  67. EditorGUI.PropertyField(rect, m_ImpulseTypeProperty);
  68. rect.y += rect.height + vSpace;
  69. if (mode != CinemachineImpulseDefinition.ImpulseTypes.Uniform)
  70. {
  71. // Propaation speed
  72. if (mode == CinemachineImpulseDefinition.ImpulseTypes.Propagating)
  73. {
  74. EditorGUI.PropertyField(rect, property.FindPropertyRelative(() => m_MyClass.m_PropagationSpeed));
  75. rect.y += rect.height + vSpace;
  76. }
  77. // Dissipation
  78. EditorGUI.PropertyField(rect, property.FindPropertyRelative(() => m_MyClass.m_DissipationDistance));
  79. rect.y += rect.height + vSpace;
  80. // Spread combo
  81. rect.height = m_SpreadPropertyHeight;
  82. DrawSpreadCombo(rect, property);
  83. rect.y += rect.height + vSpace; rect.height = EditorGUIUtility.singleLineHeight;
  84. }
  85. // Impulse Shape combo
  86. rect.height = m_ShapePropertyHeight;
  87. DrawImpulseShapeCombo(rect, property);
  88. rect.y += rect.height + vSpace; rect.height = EditorGUIUtility.singleLineHeight;
  89. EditorGUI.EndProperty();
  90. }
  91. void DrawImpulseShapeCombo(Rect fullRect, SerializedProperty property)
  92. {
  93. float floatFieldWidth = EditorGUIUtility.fieldWidth + 2;
  94. SerializedProperty timeProp = property.FindPropertyRelative(() => m_MyClass.m_ImpulseDuration);
  95. if (m_TimeText == null)
  96. {
  97. m_TimeText = new GUIContent(" s", timeProp.tooltip);
  98. m_TimeTextWidth = GUI.skin.label.CalcSize(m_TimeText).x;
  99. }
  100. var graphRect = fullRect;
  101. graphRect.y += EditorGUIUtility.singleLineHeight + vSpace;
  102. graphRect.height -= EditorGUIUtility.singleLineHeight + vSpace;
  103. var indentLevel = EditorGUI.indentLevel;
  104. Rect r = fullRect; r.height = EditorGUIUtility.singleLineHeight;
  105. r = EditorGUI.PrefixLabel(r, EditorGUI.BeginProperty(
  106. r, new GUIContent(m_ShapeProperty.displayName, m_ShapeProperty.tooltip), property));
  107. m_ShapeProperty.isExpanded = EditorGUI.Foldout(r, m_ShapeProperty.isExpanded, GUIContent.none);
  108. bool isCustom = m_ShapeProperty.intValue == (int)CinemachineImpulseDefinition.ImpulseShapes.Custom;
  109. r.width -= floatFieldWidth + m_TimeTextWidth;
  110. if (isCustom)
  111. r.width -= 2 * r.height;
  112. EditorGUI.BeginChangeCheck();
  113. {
  114. EditorGUI.PropertyField(r, m_ShapeProperty, GUIContent.none);
  115. if (EditorGUI.EndChangeCheck())
  116. InvalidateImpulseGraphSample();
  117. if (!isCustom && Event.current.type == EventType.Repaint && m_ShapeProperty.isExpanded)
  118. DrawImpulseGraph(graphRect, CinemachineImpulseDefinition.GetStandardCurve(
  119. (CinemachineImpulseDefinition.ImpulseShapes)m_ShapeProperty.intValue));
  120. }
  121. if (isCustom)
  122. {
  123. SerializedProperty curveProp = property.FindPropertyRelative(() => m_MyClass.m_CustomImpulseShape);
  124. r.x += r.width;
  125. r.width = 2 * r.height;
  126. EditorGUI.BeginChangeCheck();
  127. EditorGUI.PropertyField(r, curveProp, GUIContent.none);
  128. if (EditorGUI.EndChangeCheck())
  129. {
  130. curveProp.animationCurveValue = RuntimeUtility.NormalizeCurve(curveProp.animationCurveValue, true, false);
  131. curveProp.serializedObject.ApplyModifiedProperties();
  132. InvalidateImpulseGraphSample();
  133. }
  134. if (Event.current.type == EventType.Repaint && m_ShapeProperty.isExpanded)
  135. DrawImpulseGraph(graphRect, curveProp.animationCurveValue);
  136. }
  137. // Time
  138. float oldWidth = EditorGUIUtility.labelWidth;
  139. EditorGUIUtility.labelWidth = m_TimeTextWidth;
  140. r.x += r.width; r.width = floatFieldWidth + EditorGUIUtility.labelWidth;
  141. EditorGUI.BeginChangeCheck();
  142. EditorGUI.PropertyField(r, timeProp, m_TimeText);
  143. if (EditorGUI.EndChangeCheck())
  144. timeProp.floatValue = Mathf.Max(timeProp.floatValue, 0);
  145. EditorGUIUtility.labelWidth = oldWidth;
  146. EditorGUI.indentLevel = indentLevel;
  147. }
  148. const int kNumSamples = 100;
  149. Vector3[] m_ImpulseGraphSnapshot = new Vector3[kNumSamples+1];
  150. float m_ImpulseGraphZero;
  151. Vector2 m_ImpulseGraphSize;
  152. void InvalidateImpulseGraphSample() { m_ImpulseGraphSize = Vector2.zero; }
  153. void DrawImpulseGraph(Rect rect, AnimationCurve curve)
  154. {
  155. // Resample if necessary
  156. if (m_ImpulseGraphSize != rect.size)
  157. {
  158. m_ImpulseGraphSize = rect.size;
  159. float minY = 0;
  160. float maxY = 0.1f;
  161. for (int i = 0; i <= kNumSamples; ++i)
  162. {
  163. var x = (float)i / kNumSamples;
  164. var y = curve.Evaluate(x);
  165. minY = Mathf.Min(y, minY);
  166. maxY = Mathf.Max(y, maxY);
  167. m_ImpulseGraphSnapshot[i] = new Vector2(x * rect.width, y);
  168. }
  169. var range = maxY - minY;
  170. m_ImpulseGraphZero = -minY / range;
  171. m_ImpulseGraphZero = rect.height * (1f - m_ImpulseGraphZero);
  172. // Apply scale
  173. for (int i = 0; i <= kNumSamples; ++i)
  174. m_ImpulseGraphSnapshot[i].y = rect.height * (1f - (m_ImpulseGraphSnapshot[i].y - minY) / range);
  175. }
  176. EditorGUI.DrawRect(rect, new Color(0.2f, 0.2f, 0.2f, 1));
  177. var oldMatrix = Handles.matrix;
  178. Handles.matrix = Handles.matrix * Matrix4x4.Translate(rect.position);
  179. Handles.color = new Color(0, 0, 0, 1);
  180. Handles.DrawLine(new Vector3(0, m_ImpulseGraphZero, 0), new Vector3(rect.width, m_ImpulseGraphZero, 0));
  181. Handles.color = new Color(1, 0.8f, 0, 1);
  182. Handles.DrawPolyLine(m_ImpulseGraphSnapshot);
  183. Handles.matrix = oldMatrix;
  184. }
  185. void DrawSpreadCombo(Rect fullRect, SerializedProperty property)
  186. {
  187. var graphRect = fullRect;
  188. graphRect.y += EditorGUIUtility.singleLineHeight + vSpace;
  189. graphRect.height -= EditorGUIUtility.singleLineHeight + vSpace;
  190. var indentLevel = EditorGUI.indentLevel;
  191. Rect r = fullRect; r.height = EditorGUIUtility.singleLineHeight;
  192. r = EditorGUI.PrefixLabel(r, EditorGUI.BeginProperty(
  193. r, new GUIContent(m_DissipationRateProperty.displayName, m_DissipationRateProperty.tooltip), property));
  194. m_DissipationRateProperty.isExpanded = EditorGUI.Foldout(r, m_DissipationRateProperty.isExpanded, GUIContent.none);
  195. EditorGUI.BeginChangeCheck();
  196. EditorGUI.Slider(r, m_DissipationRateProperty, 0, 1, GUIContent.none);
  197. if (EditorGUI.EndChangeCheck())
  198. InvalidateSpreadGraphSample();
  199. if (Event.current.type == EventType.Repaint && m_DissipationRateProperty.isExpanded)
  200. DrawSpreadGraph(graphRect, m_DissipationRateProperty.floatValue);
  201. EditorGUI.indentLevel = indentLevel;
  202. }
  203. Vector3[] m_SpreadGraphSnapshot = new Vector3[kNumSamples+1];
  204. Vector2 m_SpreadGraphSize;
  205. void InvalidateSpreadGraphSample() { m_SpreadGraphSize = Vector2.zero; }
  206. void DrawSpreadGraph(Rect rect, float spread)
  207. {
  208. // Resample if necessary
  209. if (m_SpreadGraphSize != rect.size)
  210. {
  211. m_SpreadGraphSize = rect.size;
  212. for (int i = 0; i <= kNumSamples >> 1; ++i)
  213. {
  214. var x = (float)i / kNumSamples;
  215. var y = CinemachineImpulseManager.EvaluateDissipationScale(spread, Mathf.Abs(1 - x * 2));
  216. m_SpreadGraphSnapshot[i] = new Vector2(x * rect.width, rect.height * (1 - y));
  217. m_SpreadGraphSnapshot[kNumSamples - i] = new Vector2((1 - x) * rect.width, rect.height * (1 - y));
  218. }
  219. }
  220. EditorGUI.DrawRect(rect, new Color(0.2f, 0.2f, 0.2f, 1));
  221. var oldMatrix = Handles.matrix;
  222. Handles.matrix = Handles.matrix * Matrix4x4.Translate(rect.position);
  223. Handles.color = new Color(0, 0, 0, 1);
  224. Handles.DrawLine(new Vector3(rect.width * 0.5f, 0, 0), new Vector3(rect.width * 0.5f, rect.height, 0));
  225. Handles.color = new Color(0, 0.6f, 1, 1);
  226. Handles.DrawPolyLine(m_SpreadGraphSnapshot);
  227. Handles.matrix = oldMatrix;
  228. }
  229. // Legacy mode
  230. float HeaderHeight { get { return EditorGUIUtility.singleLineHeight * 1.5f; } }
  231. float DrawHeader(Rect rect, string text)
  232. {
  233. float delta = HeaderHeight - EditorGUIUtility.singleLineHeight;
  234. rect.y += delta; rect.height -= delta;
  235. EditorGUI.LabelField(rect, new GUIContent(text), EditorStyles.boldLabel);
  236. return HeaderHeight;
  237. }
  238. string HeaderText(SerializedProperty property)
  239. {
  240. var attrs = property.serializedObject.targetObject.GetType()
  241. .GetCustomAttributes(typeof(HeaderAttribute), false);
  242. if (attrs != null && attrs.Length > 0)
  243. return ((HeaderAttribute)attrs[0]).header;
  244. return null;
  245. }
  246. List<string> mHideProperties = new List<string>();
  247. float LegacyModeGetPropertyHeight(SerializedProperty prop, GUIContent label)
  248. {
  249. SignalSourceAsset asset = null;
  250. float height = 0;
  251. mHideProperties.Clear();
  252. string prefix = prop.name;
  253. prop.NextVisible(true); // Skip outer foldout
  254. do
  255. {
  256. if (!prop.propertyPath.Contains(prefix)) // if it is part of an array, then it won't StartWith prefix
  257. break;
  258. string header = HeaderText(prop);
  259. if (header != null)
  260. height += HeaderHeight + vSpace;
  261. // Do we hide this property?
  262. bool hide = false;
  263. if (prop.name == SerializedPropertyHelper.PropertyName(() => m_MyClass.m_RawSignal))
  264. asset = prop.objectReferenceValue as SignalSourceAsset;
  265. if (prop.name == SerializedPropertyHelper.PropertyName(() => m_MyClass.m_RepeatMode))
  266. hide = asset == null || asset.SignalDuration <= 0;
  267. else if (prop.name == SerializedPropertyHelper.PropertyName(() => m_MyClass.m_Randomize))
  268. hide = asset == null || asset.SignalDuration > 0;
  269. else
  270. {
  271. hide = prop.name == SerializedPropertyHelper.PropertyName(() => m_MyClass.m_ImpulseShape)
  272. || prop.name == SerializedPropertyHelper.PropertyName(() => m_MyClass.m_CustomImpulseShape)
  273. || prop.name == SerializedPropertyHelper.PropertyName(() => m_MyClass.m_ImpulseDuration)
  274. || prop.name == SerializedPropertyHelper.PropertyName(() => m_MyClass.m_DissipationRate);
  275. }
  276. if (hide)
  277. mHideProperties.Add(prop.name);
  278. else
  279. height += EditorGUI.GetPropertyHeight(prop, false) + vSpace;
  280. } while (prop.NextVisible(prop.isExpanded));
  281. return height;
  282. }
  283. void LegacyModeOnGUI(Rect rect, SerializedProperty prop, GUIContent label)
  284. {
  285. string prefix = prop.name;
  286. prop.NextVisible(true); // Skip outer foldout
  287. do
  288. {
  289. if (!prop.propertyPath.Contains(prefix)) // if it is part of an array, then it won't StartWith prefix
  290. break;
  291. string header = HeaderText(prop);
  292. if (header != null)
  293. {
  294. rect.height = HeaderHeight;
  295. DrawHeader(rect, header);
  296. rect.y += HeaderHeight + vSpace;
  297. }
  298. if (mHideProperties.Contains(prop.name))
  299. continue;
  300. rect.height = EditorGUI.GetPropertyHeight(prop, false);
  301. EditorGUI.PropertyField(rect, prop);
  302. rect.y += rect.height + vSpace;
  303. } while (prop.NextVisible(prop.isExpanded));
  304. }
  305. }
  306. }