12using System.Collections.Generic;
32 [Header(
"Leap Recording Settings")]
36 [Header(
"Key Bindings")]
49 protected HashSet<string>
_takenNames =
new HashSet<string>();
71 protected List<Frame> _leapData;
72 protected List<CurveData> _curves;
73 protected Dictionary<AudioSource, RecordedAudio> _audioData;
74 protected Dictionary<Transform, List<TransformData>> _transformData;
75 protected Dictionary<Component, SerializedObject> _initialComponentData;
76 protected Dictionary<Component, List<ActivityData>> _behaviourActivity;
78 protected void Reset() {
82 protected void Start() {
90 protected void LateUpdate() {
95 if ((Hands.Left !=
null || Hands.Right !=
null) && (
recordWhen &
RecordOn.HandPresence) != 0) {
104 finishRecording(
new ProgressBar());
115 public void BeginRecording() {
123 _leapData =
new List<Frame>();
125 _curves =
new List<CurveData>();
126 _audioData =
new Dictionary<AudioSource, RecordedAudio>();
127 _transformData =
new Dictionary<Transform, List<TransformData>>();
128 _initialComponentData =
new Dictionary<Component, SerializedObject>();
129 _behaviourActivity =
new Dictionary<Component, List<ActivityData>>();
139 public void FinishRecording() {
140 finishRecording(
new ProgressBar());
143 protected void finishRecording(ProgressBar progress) {
146 if (targetFolderPath ==
null) {
147 if (gameObject.scene.IsValid() && !
string.IsNullOrEmpty(gameObject.scene.path)) {
148 string sceneFolder = Path.GetDirectoryName(gameObject.scene.path);
149 targetFolderPath = Path.Combine(sceneFolder,
"Recordings");
151 targetFolderPath = Path.Combine(
"Assets",
"Recordings");
155 int folderSuffix = 1;
156 string finalSubFolder;
158 finalSubFolder = Path.Combine(targetFolderPath,
recordingName +
" " + folderSuffix.ToString().PadLeft(2,
'0'));
160 }
while (
Directory.Exists(finalSubFolder));
162 string dataDirectory = Path.Combine(finalSubFolder,
"_Data");
164 Directory.CreateDirectory(dataDirectory);
165 Directory.CreateDirectory(finalSubFolder);
166 AssetDatabase.Refresh();
168 progress.Begin(6,
"Saving Recording",
"", () => {
173 foreach (var autoProxy
in GetComponentsInChildren<AutoValueProxy>()) {
174 autoProxy.autoPushingEnabled =
true;
177 progress.Begin(3,
"",
"Reverting Scene State", () => {
179 progress.Begin(_transformData.Count,
"",
"", () => {
180 foreach (var pair in _transformData) {
182 var transform = pair.Key;
183 var data = pair.Value;
185 if (transform == null || data.Count == 0) {
189 data[0].ApplyTo(transform);
194 progress.Begin(_curves.Count,
"",
"", () => {
195 AnimationClip tempClip = new AnimationClip();
196 foreach (var data in _curves) {
198 AnimationUtility.SetEditorCurve(tempClip, data.binding, data.curve);
200 tempClip.SampleAnimation(gameObject, 0);
204 progress.Begin(_initialComponentData.Count,
"",
"", () => {
205 foreach (var pair in _initialComponentData) {
207 var component = pair.Key;
208 var sobj = pair.Value;
210 if (component == null || component is Transform) {
216 if (component is MethodRecording ||
217 component is RecordedAudio) {
221 var flags = sobj.FindProperty(
"m_ObjectHideFlags");
223 Debug.LogError(
"Could not find hide flags for " + component);
230 int originalFlags = flags.intValue;
231 flags.intValue = ~originalFlags;
232 flags.intValue = originalFlags;
236 sobj.ApplyModifiedProperties();
237 } catch (Exception e) {
238 Debug.LogError(
"Exception when trying to apply properties to " + component);
239 Debug.LogException(e);
245 progress.Begin(1,
"",
"Patching Materials: ", () => {
246 GetComponentsInChildren(
true, _recorders);
247 foreach (var recorder
in _recorders) {
248 DestroyImmediate(recorder);
252 var allMaterials = Resources.FindObjectsOfTypeAll<Material>().
254 Where(AssetDatabase.IsMainAsset).
257 var renderers = GetComponentsInChildren<Renderer>(includeInactive:
true);
259 progress.Begin(renderers.Length,
"",
"", () => {
260 foreach (var renderer in renderers) {
261 progress.Step(renderer.name);
263 var materials = renderer.sharedMaterials;
264 for (int i = 0; i < materials.Length; i++) {
265 var material = materials[i];
266 if (material == null) {
270 if (!AssetDatabase.IsMainAsset(material)) {
271 var matchingMaterial = allMaterials.Query().FirstOrDefault(m => material.name.Contains(m.name) &&
272 material.shader == m.shader);
274 if (matchingMaterial != null) {
275 materials[i] = matchingMaterial;
279 renderer.sharedMaterials = materials;
284 progress.Begin(_behaviourActivity.Count,
"",
"Converting Activity Data: ", () => {
285 foreach (var pair
in _behaviourActivity) {
286 var targetBehaviour = pair.Key;
287 var activityData = pair.Value;
289 if (targetBehaviour ==
null) {
293 progress.Step(targetBehaviour.name);
295 string path = AnimationUtility.CalculateTransformPath(targetBehaviour.transform, transform);
296 Type type = targetBehaviour.GetType();
297 string propertyName =
"m_Enabled";
299 AnimationCurve curve =
new AnimationCurve();
300 foreach (var dataPoint
in activityData) {
301 int index = curve.AddKey(dataPoint.time, dataPoint.enabled ? 1 : 0);
302 AnimationUtility.SetKeyLeftTangentMode(curve, index, AnimationUtility.TangentMode.Constant);
303 AnimationUtility.SetKeyRightTangentMode(curve, index, AnimationUtility.TangentMode.Constant);
306 if (curve.IsConstant()) {
310 var binding = EditorCurveBinding.FloatCurve(path, type, propertyName);
312 if (_curves.Query().Any(c => c.binding == binding)) {
313 Debug.LogError(
"Binding already existed?");
314 Debug.LogError(binding.path +
" : " + binding.propertyName);
318 _curves.Add(
new CurveData() {
325 progress.Begin(_transformData.Count,
"",
"Converting Transform Data: ", () => {
326 foreach (var pair
in _transformData) {
327 var targetTransform = pair.Key;
328 var targetData = pair.Value;
330 progress.Step(targetTransform.name);
332 string path = AnimationUtility.CalculateTransformPath(targetTransform, transform);
334 bool isActivityConstant =
true;
335 bool isPositionConstant =
true;
336 bool isRotationConstant =
true;
337 bool isScaleConstant =
true;
340 bool startEnabled = targetData[0].enabled;
341 Vector3 startPosition = targetData[0].localPosition;
342 Quaternion startRotation = targetData[0].localRotation;
343 Vector3 startScale = targetData[0].localScale;
344 for (
int i = 1; i < targetData.Count; i++) {
345 isActivityConstant &= targetData[i].enabled == startEnabled;
346 isPositionConstant &= targetData[i].localPosition == startPosition;
347 isRotationConstant &= targetData[i].localRotation == startRotation;
348 isScaleConstant &= targetData[i].localScale == startScale;
352 for (
int i = 0; i < TransformData.CURVE_COUNT; i++) {
353 string propertyName = TransformData.GetName(i);
354 Type type = typeof(Transform);
356 AnimationCurve curve =
new AnimationCurve();
357 var dataType = TransformData.GetDataType(i);
360 case TransformDataType.Position:
361 if (isPositionConstant)
continue;
363 case TransformDataType.Rotation:
364 if (isRotationConstant)
continue;
366 case TransformDataType.Scale:
367 if (isScaleConstant)
continue;
369 case TransformDataType.Activity:
370 if (isActivityConstant)
continue;
371 type = typeof(GameObject);
375 for (
int j = 0; j < targetData.Count; j++) {
376 int index = curve.AddKey(targetData[j].time, targetData[j].GetFloat(i));
377 if (dataType == TransformDataType.Activity) {
378 AnimationUtility.SetKeyLeftTangentMode(curve, index, AnimationUtility.TangentMode.Constant);
379 AnimationUtility.SetKeyRightTangentMode(curve, index, AnimationUtility.TangentMode.Constant);
383 var binding = EditorCurveBinding.FloatCurve(path, type, propertyName);
385 if (_curves.Query().Any(c => c.binding == binding)) {
386 Debug.LogError(
"Duplicate object was created??");
387 Debug.LogError(
"Named " + targetTransform.name +
" : " + binding.path +
" : " + binding.propertyName);
389 _curves.Add(
new CurveData() {
398 progress.Begin(_curves.Count,
"",
"Compressing Data: ", () => {
399 _curves.Sort((a, b) => a.binding.propertyName.CompareTo(b.binding.propertyName));
400 foreach (var data
in _curves) {
401 using (
new ProfilerSample(
"A")) {
402 EditorCurveBinding binding = data.binding;
403 AnimationCurve curve = data.curve;
405 progress.Step(binding.propertyName);
407 GameObject animationGameObject;
409 var animatedObj = AnimationUtility.GetAnimatedObject(gameObject, binding);
410 if (animatedObj is GameObject) {
411 animationGameObject = animatedObj as GameObject;
413 animationGameObject = (animatedObj as
Component).gameObject;
417 bool isMatBinding = binding.propertyName.StartsWith(
"material.") &&
418 binding.type.IsSubclassOf(typeof(Renderer));
422 if (curve.IsConstant() && !isMatBinding) {
426 if (_curves.Query().Where(p => p.binding.path == binding.path &&
427 p.binding.type == binding.type &&
428 p.binding.propertyName.TrimEnd(2) == binding.propertyName.TrimEnd(2)).
429 All(k => k.curve.IsConstant())) {
435 using (
new ProfilerSample(
"B")) {
436 curve = AnimationCurveUtil.Compress(curve, Mathf.Epsilon, checkSteps: 3);
439 Transform targetTransform =
null;
440 var targetObj = AnimationUtility.GetAnimatedObject(gameObject, binding);
441 if (targetObj is GameObject) {
442 targetTransform = (targetObj as GameObject).transform;
444 targetTransform = (targetObj as
Component).transform;
446 Debug.LogError(
"Target obj was of type " + targetObj.GetType().Name);
453 progress.Begin(4,
"Finalizing Assets",
"", () => {
454 var postProcessComponent = gameObject.AddComponent<HierarchyPostProcess>();
456 GameObject myGameObject = gameObject;
458 DestroyImmediate(
this);
461 progress.Step(
"Creating Method Recording Files...");
462 var methodRecordings = myGameObject.GetComponentsInChildren<MethodRecording>();
463 for (
int i = 0; i < methodRecordings.Length; i++) {
464 var methodRecording = methodRecordings[i];
465 string fullPath = Path.Combine(finalSubFolder,
"MethodRecording_" + i +
".asset");
466 methodRecording.ExitRecordingMode(fullPath);
469 postProcessComponent.dataFolder =
new AssetFolder(dataDirectory);
472 progress.Begin(_curves.Count,
"",
"", () => {
473 string curveFile = Path.Combine(dataDirectory,
"Curves.data");
474 using (var writer = File.CreateText(curveFile)) {
475 foreach (var data in _curves) {
476 progress.Step(data.binding.propertyName);
478 var bindingData = new EditorCurveBindingData() {
479 path = data.binding.path,
480 propertyName = data.binding.propertyName,
481 typeName = data.binding.type.Name,
485 writer.WriteLine(JsonUtility.ToJson(bindingData));
491 if (_leapData.Count > 0) {
492 progress.Begin(_leapData.Count,
"",
"", () => {
493 string leapFile = Path.Combine(dataDirectory,
"Frames.data");
495 using (var writer = File.CreateText(leapFile)) {
496 for (int i = 0; i < _leapData.Count; i++) {
497 progress.Step(
"Frame " + i);
498 writer.WriteLine(JsonUtility.ToJson(_leapData[i]));
504 progress.Step(
"Creating Final Prefab...");
506 postProcessComponent.recordingName = recordingName;
507 postProcessComponent.assetFolder =
new AssetFolder(finalSubFolder);
509 string prefabPath = Path.Combine(finalSubFolder, recordingName +
" Raw.prefab");
510 #if UNITY_2018_3_OR_NEWER
511 var path = prefabPath.Replace(
'\\',
'/');
512 PrefabUtility.SaveAsPrefabAsset(myGameObject, path, out
bool didSucceed);
513 if (!didSucceed) {
Debug.LogError($
"Error saving prefab asset to {path}"); }
515 PrefabUtility.CreatePrefab(prefabPath.Replace(
'\\',
'/'), myGameObject);
518 AssetDatabase.Refresh();
520 EditorApplication.isPlaying =
false;
524 protected void recordData() {
525 using (
new ProfilerSample(
"Dispatch PreRecord Event")) {
526 if (OnPreRecordFrame !=
null) {
531 using (
new ProfilerSample(
"Search For New Components")) {
532 using (
new ProfilerSample(
"Get Components In Children")) {
533 GetComponentsInChildren(
true, _components);
536 for (
int i = 0; i < _components.Count; i++) {
537 var component = _components[i];
539 if (!_initialComponentData.ContainsKey(component)) {
540 using (
new ProfilerSample(
"Handle New Component")) {
541 _initialComponentData[component] =
new SerializedObject(component);
544 if (component is Transform) {
545 using (
new ProfilerSample(
"Handle New Transform")) {
546 var transform = component as Transform;
548 _transformData[transform] =
new List<TransformData>();
550 var parent = transform.parent;
551 if (parent !=
null) {
552 var newName = transform.name;
554 for (
int j = 0; j < parent.childCount; j++) {
555 var sibling = parent.GetChild(j);
556 if (sibling != transform && sibling.name == transform.name) {
557 transform.name = transform.name +
" " + transform.gameObject.GetInstanceID();
565 if (component is AudioSource) {
566 using (
new ProfilerSample(
"Handle New AudioSource")) {
567 var source = component as AudioSource;
568 var data = source.gameObject.AddComponent<RecordedAudio>();
569 data.target = source;
570 _audioData[source] = data;
574 if (component is PropertyRecorder) {
575 using (
new ProfilerSample(
"Handle New PropertyRecorder")) {
576 var recorder = component as PropertyRecorder;
577 foreach (var binding
in recorder.GetBindings(gameObject)) {
578 _curves.Add(
new CurveData() {
580 curve =
new AnimationCurve(),
581 accessor =
new PropertyAccessor(gameObject, binding, failureIsZero:
true)
587 if (((component is Behaviour) || (component is Renderer) || (component is Collider)) &&
588 !(component is PropertyRecorder)) {
589 using (
new ProfilerSample(
"Handle New Behaviour")) {
590 _behaviourActivity[component] =
new List<ActivityData>();
596 if (component is IValueProxy) (component as IValueProxy).OnPullValue();
600 using (
new ProfilerSample(
"Record Custom Data")) {
601 float time = Time.time - _startTime;
602 for (
int i = 0; i < _curves.Count; i++) {
603 _curves[i].SampleNow(time);
607 using (
new ProfilerSample(
"Record Transform Data")) {
608 foreach (var pair
in _transformData) {
609 var list = pair.Value;
610 var transform = pair.Key;
616 if (list.Count == 0 && Time.time > _startTime) {
617 list.Add(
new TransformData() {
618 time = Time.time - _startTime - Time.deltaTime,
620 localPosition = transform.localPosition,
621 localRotation = transform.localRotation,
622 localScale = transform.localScale
626 list.Add(
new TransformData() {
627 time = Time.time - _startTime,
628 enabled = transform.gameObject.activeSelf,
629 localPosition = transform.localPosition,
630 localRotation = transform.localRotation,
631 localScale = transform.localScale
636 using (
new ProfilerSample(
"Record Behaviour Activity Data")) {
637 foreach (var pair
in _behaviourActivity) {
638 pair.Value.Add(
new ActivityData() {
639 time = Time.time - _startTime,
640 enabled = EditorUtility.GetObjectEnabled(pair.Key) == 1
645 if (provider !=
null && recordLeapData) {
646 using (
new ProfilerSample(
"Record Leap Data")) {
647 Frame newFrame =
new Frame();
649 newFrame.CurrentFramesPerSecond = 1.0f / Time.smoothDeltaTime;
650 newFrame.Timestamp = (long)((Time.time - _startTime) * 1e6);
651 newFrame.Id = Time.frameCount - _startFrame;
652 _leapData.Add(newFrame);
657 protected struct ActivityData {
662 protected enum TransformDataType {
669 protected struct CurveData {
670 public EditorCurveBinding binding;
671 public AnimationCurve curve;
672 public PropertyAccessor accessor;
674 public void SampleNow(
float time) {
675 curve.AddKey(time, accessor.Access());
679 protected struct TransformData {
680 public const int CURVE_COUNT = 11;
689 public float GetFloat(
int index) {
694 return localPosition[index];
699 return localRotation[index - 3];
703 return localScale[index - 7];
705 return enabled ? 1 : 0;
707 throw new Exception();
710 public static string GetName(
int index) {
715 return "m_LocalPosition." +
"xyz"[index];
720 return "m_LocalRotation." +
"xyzw"[index - 3];
724 return "m_LocalScale" +
"xyz"[index - 7];
728 throw new Exception();
731 public static TransformDataType GetDataType(
int index) {
736 return TransformDataType.Position;
741 return TransformDataType.Rotation;
745 return TransformDataType.Scale;
747 return TransformDataType.Activity;
749 throw new Exception();
752 public void ApplyTo(Transform transform) {
753 transform.localPosition = localPosition;
754 transform.localRotation = localRotation;
755 transform.localScale = localScale;
756 transform.gameObject.SetActive(enabled);
765 protected void OnGUI() {
766 var guiRect =
new Rect(_guiMargins.x, _guiMargins.y, _guiSize.x, _guiSize.y);
768 GUI.Box(guiRect,
"");
770 GUILayout.BeginArea(guiRect.PadInner(5F));
771 GUILayout.BeginVertical();
774 GUILayout.Label(
"Ready to record.");
775 if (GUILayout.Button(
"Start Recording (" + beginRecordingKey.ToString() +
")",
776 GUILayout.ExpandHeight(
true))) {
780 GUILayout.Label(
"Recording.");
781 if (GUILayout.Button(
"Stop Recording (" + finishRecordingKey.ToString() +
")",
782 GUILayout.ExpandHeight(
true))) {
783 finishRecording(
new ProgressBar());
787 GUILayout.EndVertical();
UnityEngine.Component Component
A convenient serializable representation of an asset folder. Only useful for editor scripts since ass...
virtual string Path
Gets or sets the folder path. This path will always be a path relative to the asset folder,...
Provides Frame object data to the Unity application by firing events as soon as Frame data is availab...
abstract Frame CurrentFrame
The current frame for this update cycle, in world space.
HashSet< string > _takenNames
List< Component > _components
KeyCode finishRecordingKey
static HierarchyRecorder instance
List< Collider > _tempCollider
List< Behaviour > _tempBehaviour
KeyCode beginRecordingKey
List< Renderer > _tempRenderer
List< PropertyRecorder > _recorders
static Action OnPreRecordFrame
static Action OnBeginRecording
A Query object is a type of immutable ordered collection of elements that can be used to perform usef...