using UnityEngine; using UnityEngine.Timeline; using System.Collections.Generic; using UnityEngine.Playables; namespace UnityEditor.Formats.Fbx.Exporter { /// /// Export data containing extra information required to export /// internal interface IExportData { HashSet Objects { get; } } /// /// Export data containing what to export when /// exporting animation only. /// internal class AnimationOnlyExportData : IExportData { // map from animation clip to GameObject that has Animation/Animator // component containing clip public Dictionary animationClips; // set of all GameObjects to export public HashSet goExportSet; public HashSet Objects { get { return goExportSet; } } // map from GameObject to component type to export public Dictionary exportComponent; // first clip to export public AnimationClip defaultClip; public AnimationOnlyExportData( Dictionary animClips, HashSet exportSet, Dictionary exportComponent ) { this.animationClips = animClips; this.goExportSet = exportSet; this.exportComponent = exportComponent; this.defaultClip = null; } public AnimationOnlyExportData() { this.animationClips = new Dictionary(); this.goExportSet = new HashSet(); this.exportComponent = new Dictionary(); this.defaultClip = null; } /// /// collect all object dependencies for given animation clip /// public void CollectDependencies( AnimationClip animClip, GameObject rootObject, IExportOptions exportOptions ) { Debug.Assert(rootObject != null); Debug.Assert(exportOptions != null); if (this.animationClips.ContainsKey(animClip)) { // we have already exported gameobjects for this clip return; } // NOTE: the object (animationRootObject) containing the animation is not necessarily animated // when driven by an animator or animation component. this.animationClips.Add(animClip, rootObject); foreach (EditorCurveBinding uniCurveBinding in AnimationUtility.GetCurveBindings(animClip)) { Object uniObj = AnimationUtility.GetAnimatedObject(rootObject, uniCurveBinding); if (!uniObj) { continue; } GameObject unityGo = ModelExporter.GetGameObject(uniObj); if (!unityGo) { continue; } if (!exportOptions.AnimateSkinnedMesh && unityGo.GetComponent()) { continue; } // If we have a clip driving a camera or light then force the export of FbxNodeAttribute // so that they point the right way when imported into Maya. if (unityGo.GetComponent()) this.exportComponent[unityGo] = typeof(Light); else if (unityGo.GetComponent()) this.exportComponent[unityGo] = typeof(Camera); else if ((uniCurveBinding.type == typeof(SkinnedMeshRenderer)) && unityGo.GetComponent()) { // only export mesh if there are animation keys for it (e.g. for blendshapes) if (FbxPropertyChannelPair.TryGetValue(uniCurveBinding.propertyName, out FbxPropertyChannelPair[] channelPairs)) { this.exportComponent[unityGo] = typeof(SkinnedMeshRenderer); } } this.goExportSet.Add(unityGo); } } /// /// collect all objects dependencies for animation clips. /// public void CollectDependencies( AnimationClip[] animClips, GameObject rootObject, IExportOptions exportOptions ) { Debug.Assert(rootObject != null); Debug.Assert(exportOptions != null); foreach (var animClip in animClips) { CollectDependencies(animClip, rootObject, exportOptions); } } /// /// Get the GameObject that the clip is bound to in the timeline. /// /// /// The GameObject bound to the timeline clip or null if none. private static GameObject GetGameObjectBoundToTimelineClip(TimelineClip timelineClip, PlayableDirector director = null) { object parentTrack = timelineClip.GetParentTrack(); AnimationTrack animTrack = parentTrack as AnimationTrack; var inspectedDirector = director ? director : UnityEditor.Timeline.TimelineEditor.inspectedDirector; if (!inspectedDirector) { Debug.LogWarning("No Timeline selected in inspector, cannot retrieve GameObject bound to track"); return null; } Object animationTrackObject = inspectedDirector.GetGenericBinding(animTrack); GameObject animationTrackGO = null; if (animationTrackObject is GameObject) { animationTrackGO = animationTrackObject as GameObject; } else if (animationTrackObject is Animator) { animationTrackGO = (animationTrackObject as Animator).gameObject; } if (animationTrackGO == null) { Debug.LogErrorFormat("Could not export animation track object of type {0}", animationTrackObject.GetType().Name); return null; } return animationTrackGO; } /// /// Get the GameObject and it's corresponding animation clip from the given timeline clip. /// /// /// KeyValuePair containing GameObject and corresponding AnimationClip public static KeyValuePair GetGameObjectAndAnimationClip(TimelineClip timelineClip, PlayableDirector director = null) { var animationTrackGO = GetGameObjectBoundToTimelineClip(timelineClip, director); if (!animationTrackGO) { return new KeyValuePair(); } return new KeyValuePair(animationTrackGO, timelineClip.animationClip); } /// /// Get the filename of the format {model}@{anim}.fbx from the given timeline clip /// /// /// filename for use for exporting animation clip public static string GetFileName(TimelineClip timelineClip) { // if the timeline clip name already contains an @, then take this as the // filename to avoid duplicate @ if (timelineClip.displayName.Contains("@")) { return timelineClip.displayName; } var goBound = GetGameObjectBoundToTimelineClip(timelineClip); if (goBound == null) { return timelineClip.displayName; } return string.Format("{0}@{1}", goBound.name, timelineClip.displayName); } } }