private void ConvertToRecordableClip(AnimationTrack track) { if (track == null || !track.hasClips) { return; } UndoExtensions.RegisterTrack(track, L10n.Tr("ConvertToRecordableClip")); var clip = track.GetClips().First(); var delta = (float)clip.start; var duration = clip.duration; var animationAsset = clip.asset as AnimationPlayableAsset; if (animationAsset == null) { return; } var animationClipSource = animationAsset.clip; var animationName = animationClipSource.name; foreach (var c in track.GetClips()) { track.DeleteClip(c); } var recordableClip = track.CreateRecordableClip(animationName); recordableClip.start = delta; recordableClip.duration = duration; var newAnimationClip = (recordableClip.asset as AnimationPlayableAsset).clip; newAnimationClip.name = animationName; var setting = AnimationUtility.GetAnimationClipSettings(animationClipSource); AnimationUtility.SetAnimationClipSettings(newAnimationClip, setting); newAnimationClip.frameRate = animationClipSource.frameRate; EditorCurveBinding[] curveBindings = AnimationUtility.GetCurveBindings(animationClipSource); for (int i = 0; i < curveBindings.Length; i++) { AnimationUtility.SetEditorCurve(newAnimationClip, curveBindings[i], AnimationUtility.GetEditorCurve(animationClipSource, curveBindings[i])); } EditorUtility.SetDirty(track); }
public void ExportSingleTimelineClipTest() { string cubeSpecialPath = FindPathInUnitTests("Scene/CubeSpecial.prefab"); GameObject myCube = AddAssetToScene(cubeSpecialPath); string folderPath = GetRandomFileNamePath(extName: ""); string filePath = null; var exportData = new Dictionary <GameObject, IExportData>(); PlayableDirector pd = myCube.GetComponent <PlayableDirector> (); if (pd != null) { foreach (PlayableBinding output in pd.playableAsset.outputs) { AnimationTrack at = output.sourceObject as AnimationTrack; var atComponent = pd.GetGenericBinding(at) as Component; Assert.That(atComponent, Is.Not.Null); var atObject = atComponent.gameObject; // One file by animation clip foreach (TimelineClip timeLineClip in at.GetClips()) { Assert.That(timeLineClip.animationClip, Is.Not.Null); filePath = string.Format("{0}/{1}@{2}", folderPath, atObject.name, "Recorded.fbx"); exportData[atObject] = ModelExporter.GetExportData(atObject, timeLineClip.animationClip); break; } } } Assert.That(filePath, Is.Not.Null); Assert.That(exportData, Is.Not.Null); // This version of ExportObjects is private. Use reflection // ModelExporter.ExportObjects(filePath, new Object[1]{myCube}, null, exportData); var internalMethod = typeof(ModelExporter).GetMethod("ExportObjects", BindingFlags.Static | BindingFlags.NonPublic, null, new Type[] { typeof(string), typeof(UnityEngine.Object[]), typeof(IExportOptions), typeof(Dictionary <GameObject, IExportData>) }, null); internalMethod.Invoke(null, new object[] { filePath, new UnityEngine.Object[1] { myCube }, null, exportData }); FileAssert.Exists(filePath); }
public void ExportSingleTimelineClipTest() { string cubeSpecialPath = FindPathInUnitTests("Scene/CubeSpecial.prefab"); GameObject myCube = AddAssetToScene(cubeSpecialPath); string folderPath = GetRandomFileNamePath(extName: ""); string filePath = null; TimelineClip timelineClipToExport = null; UnityEditor.Selection.activeObject = myCube; PlayableDirector pd = myCube.GetComponent <PlayableDirector>(); if (pd != null) { foreach (PlayableBinding output in pd.playableAsset.outputs) { AnimationTrack at = output.sourceObject as AnimationTrack; var atComponent = pd.GetGenericBinding(at) as Component; Assert.That(atComponent, Is.Not.Null); var atObject = atComponent.gameObject; // One file by animation clip foreach (TimelineClip timeLineClip in at.GetClips()) { Assert.That(timeLineClip.animationClip, Is.Not.Null); filePath = $"{folderPath}/{atObject.name}@Recorded.fbx"; timelineClipToExport = timeLineClip; break; } } } Assert.That(filePath, Is.Not.Null); var exportOptions = new ExportModelSettingsSerialize(); exportOptions.SetModelAnimIncludeOption(ExportSettings.Include.Anim); ModelExporter.ExportTimelineClip(filePath, timelineClipToExport, pd, exportOptions); FileAssert.Exists(filePath); }
public void ExportSingleTimelineClipFromExportDataTest() { string cubeSpecialPath = FindPathInUnitTests("Scene/CubeSpecial.prefab"); GameObject myCube = AddAssetToScene(cubeSpecialPath); string folderPath = GetRandomFileNamePath(extName: ""); string filePath = null; var exportData = new Dictionary <GameObject, IExportData>(); PlayableDirector pd = myCube.GetComponent <PlayableDirector>(); if (pd != null) { foreach (PlayableBinding output in pd.playableAsset.outputs) { AnimationTrack at = output.sourceObject as AnimationTrack; var atComponent = pd.GetGenericBinding(at) as Component; Assert.That(atComponent, Is.Not.Null); var atObject = atComponent.gameObject; // One file by animation clip foreach (TimelineClip timeLineClip in at.GetClips()) { Assert.That(timeLineClip.animationClip, Is.Not.Null); filePath = $"{folderPath}/{atObject.name}@Recorded.fbx"; exportData[atObject] = ModelExporter.GetExportData(atObject, timeLineClip.animationClip); break; } } } Assert.That(filePath, Is.Not.Null); Assert.That(exportData, Is.Not.Null); ModelExporter.ExportObjects(filePath, new UnityEngine.Object[1] { myCube }, null, exportData); FileAssert.Exists(filePath); }
// 导出AnimationTrack private int ExportAnimationTrack(AnimationTrack animationTrack, JSONObject trackListArr, JSONObject clipListArr) { JSONObject trackJSON = GenerateBaseTrack(animationTrack, PlaybaleTrackTypeMap["AnimationTrack"]); trackJSON.AddField("applyAvatarMask", animationTrack.applyAvatarMask); JSONObject infiniteClipOffsetPositionArr = new JSONObject(JSONObject.Type.ARRAY); JSONObject infiniteClipOffsetRotationArr = new JSONObject(JSONObject.Type.ARRAY); trackJSON.AddField("infiniteClipOffsetPosition", infiniteClipOffsetPositionArr); trackJSON.AddField("infiniteClipOffsetRotation", infiniteClipOffsetRotationArr); #if UNITY_2018_3_OR_NEWER trackJSON.AddField("trackOffset", TrackOffsetMap[animationTrack.trackOffset]); #else if (animationTrack.applyOffsets) { trackJSON.AddField("trackOffset", TrackOffsetMap["ApplyTransformOffsets"]); } else { trackJSON.AddField("trackOffset", TrackOffsetMap["Auto"]); } #endif #if UNITY_2019_1_OR_NEWER infiniteClipOffsetPositionArr.Add(-animationTrack.infiniteClipOffsetPosition.x); infiniteClipOffsetPositionArr.Add(animationTrack.infiniteClipOffsetPosition.y); infiniteClipOffsetPositionArr.Add(animationTrack.infiniteClipOffsetPosition.z); infiniteClipOffsetRotationArr.Add(-animationTrack.infiniteClipOffsetRotation.x); infiniteClipOffsetRotationArr.Add(animationTrack.infiniteClipOffsetRotation.y); infiniteClipOffsetRotationArr.Add(animationTrack.infiniteClipOffsetRotation.z); infiniteClipOffsetRotationArr.Add(-animationTrack.infiniteClipOffsetRotation.w); #else infiniteClipOffsetPositionArr.Add(-animationTrack.openClipOffsetPosition.x); infiniteClipOffsetPositionArr.Add(animationTrack.openClipOffsetPosition.y); infiniteClipOffsetPositionArr.Add(animationTrack.openClipOffsetPosition.z); infiniteClipOffsetRotationArr.Add(-animationTrack.openClipOffsetRotation.x); infiniteClipOffsetRotationArr.Add(animationTrack.openClipOffsetRotation.y); infiniteClipOffsetRotationArr.Add(animationTrack.openClipOffsetRotation.z); infiniteClipOffsetRotationArr.Add(-animationTrack.openClipOffsetRotation.w); #endif if (animationTrack.avatarMask != null) { WXAvatarMask mask = new WXAvatarMask(animationTrack.avatarMask); string uid = AddDependencies(mask); trackJSON.AddField("avatarMask", uid); } else { trackJSON.AddField("avatarMask", new JSONObject(JSONObject.Type.NULL)); } JSONObject positionArr = new JSONObject(JSONObject.Type.ARRAY); positionArr.Add(-animationTrack.position.x); positionArr.Add(animationTrack.position.y); positionArr.Add(animationTrack.position.z); trackJSON.AddField("position", positionArr); JSONObject rotationArr = new JSONObject(JSONObject.Type.ARRAY); rotationArr.Add(-animationTrack.rotation.x); rotationArr.Add(animationTrack.rotation.y); rotationArr.Add(animationTrack.rotation.z); rotationArr.Add(-animationTrack.rotation.w); trackJSON.AddField("rotation", rotationArr); JSONObject matchTargetFieldsJSON = new JSONObject(JSONObject.Type.OBJECT); trackJSON.AddField("matchTargetFields", matchTargetFieldsJSON); matchTargetFieldsJSON.AddField("PositionX", (animationTrack.matchTargetFields & MatchTargetFields.PositionX) == MatchTargetFields.PositionX); matchTargetFieldsJSON.AddField("PositionY", (animationTrack.matchTargetFields & MatchTargetFields.PositionY) == MatchTargetFields.PositionY); matchTargetFieldsJSON.AddField("PositionZ", (animationTrack.matchTargetFields & MatchTargetFields.PositionZ) == MatchTargetFields.PositionZ); matchTargetFieldsJSON.AddField("RotationX", (animationTrack.matchTargetFields & MatchTargetFields.RotationX) == MatchTargetFields.RotationX); matchTargetFieldsJSON.AddField("RotationY", (animationTrack.matchTargetFields & MatchTargetFields.RotationY) == MatchTargetFields.RotationY); matchTargetFieldsJSON.AddField("RotationZ", (animationTrack.matchTargetFields & MatchTargetFields.RotationZ) == MatchTargetFields.RotationZ); UnityEditor.SerializedObject serializedObject = new UnityEditor.SerializedObject(animationTrack); UnityEditor.SerializedProperty serializedClip = serializedObject.FindProperty("m_Clips"); JSONObject clipsIndexArr = trackJSON.GetField("clips"); if (animationTrack.inClipMode) // 普通clip // 貌似有时候序列化的m_Clips顺序跟 timelineClipList 的顺序对不上,但是很难复现。没有找到顺序可以必定对上的方法,先这样吧 { IEnumerable <TimelineClip> timelineClipList = animationTrack.GetClips(); int num = 0; foreach (TimelineClip timelineClip in timelineClipList) { JSONObject clipJSON = GenerateBaseTimelineClip(timelineClip, PlaybaleClipTypeMap["Animation"]); JSONObject clipData = new JSONObject(JSONObject.Type.OBJECT); float m_PostExtrapolationTime = (float)serializedClip.FindPropertyRelative("Array.data[" + num + "].m_PostExtrapolationTime").doubleValue; float m_PreExtrapolationTime = (float)serializedClip.FindPropertyRelative("Array.data[" + num + "].m_PreExtrapolationTime").doubleValue; clipJSON.SetField("postExtrapolationTime", m_PostExtrapolationTime); clipJSON.SetField("preExtrapolationTime", m_PreExtrapolationTime); clipJSON.AddField("data", clipData); bool m_Recordable = serializedClip.FindPropertyRelative("Array.data[" + num + "].m_Recordable").boolValue; clipData.AddField("recordable", m_Recordable); string clipPath = ExportAnimationClip(timelineClip.animationClip); if (string.IsNullOrEmpty(clipPath)) { clipData.AddField("clip", JSONObject.nullJO); } else { clipData.AddField("clip", clipPath); } AnimationPlayableAsset asset = (AnimationPlayableAsset)timelineClip.asset; // clipData.AddField("clipCaps", ClipCapsMap.ContainsKey(timelineClip.clipCaps) ? ClipCapsMap[timelineClip.clipCaps] : ClipCapsMap[ClipCaps.None]); // clipData.AddField("duration", (float)asset.duration); #if UNITY_2018_3_OR_NEWER // 2018_3才开始支持 clipData.AddField("applyFootIK", asset.applyFootIK); #else clipData.AddField("applyFootIK", false); #endif #if UNITY_2019_1_OR_NEWER // 2019_1才开始支持 clipData.AddField("loop", LoopModeMap[asset.loop]); #else clipData.AddField("loop", LoopModeMap["UseSourceAsset"]); #endif clipData.AddField("useTrackMatchFields", asset.useTrackMatchFields); JSONObject clipMatchTargetFieldsJSON = new JSONObject(JSONObject.Type.OBJECT); clipData.AddField("matchTargetFields", clipMatchTargetFieldsJSON); clipMatchTargetFieldsJSON.AddField("PositionX", (asset.matchTargetFields & MatchTargetFields.PositionX) == MatchTargetFields.PositionX); clipMatchTargetFieldsJSON.AddField("PositionY", (asset.matchTargetFields & MatchTargetFields.PositionY) == MatchTargetFields.PositionY); clipMatchTargetFieldsJSON.AddField("PositionZ", (asset.matchTargetFields & MatchTargetFields.PositionZ) == MatchTargetFields.PositionZ); clipMatchTargetFieldsJSON.AddField("RotationX", (asset.matchTargetFields & MatchTargetFields.RotationX) == MatchTargetFields.RotationX); clipMatchTargetFieldsJSON.AddField("RotationY", (asset.matchTargetFields & MatchTargetFields.RotationY) == MatchTargetFields.RotationY); clipMatchTargetFieldsJSON.AddField("RotationZ", (asset.matchTargetFields & MatchTargetFields.RotationZ) == MatchTargetFields.RotationZ); JSONObject clipPositionArr = new JSONObject(JSONObject.Type.ARRAY); clipPositionArr.Add(-asset.position.x); clipPositionArr.Add(asset.position.y); clipPositionArr.Add(asset.position.z); clipData.AddField("position", clipPositionArr); JSONObject clipRotationArr = new JSONObject(JSONObject.Type.ARRAY); clipRotationArr.Add(-asset.rotation.x); clipRotationArr.Add(asset.rotation.y); clipRotationArr.Add(asset.rotation.z); clipRotationArr.Add(-asset.rotation.w); clipData.AddField("rotation", clipRotationArr); clipsIndexArr.Add(ExportTimelineClip(clipJSON, clipListArr)); num++; } } else // infiniteClip { #if UNITY_2019_1_OR_NEWER // 2019_1才开始支持 AnimationClip infiniteClip = animationTrack.infiniteClip; #else // 序列化取出私有变量 UnityEditor.SerializedObject trackSerializedObject = new UnityEditor.SerializedObject(animationTrack); UnityEditor.SerializedProperty animClipSerialize = trackSerializedObject.FindProperty("m_AnimClip"); AnimationClip infiniteClip = animClipSerialize.objectReferenceValue as AnimationClip; #endif string infinityClipPath = ExportAnimationClip(infiniteClip); if (string.IsNullOrEmpty(infinityClipPath)) { trackJSON.SetField("infinityClip", JSONObject.nullJO); } else { trackJSON.SetField("infinityClip", infinityClipPath); } } // 导出子track JSONObject childrenJSON = trackJSON.GetField("children"); IEnumerable <TrackAsset> childTrackAssetList = animationTrack.GetChildTracks(); List <int> indexList = ExportTrackList(childTrackAssetList, trackListArr, clipListArr); foreach (int index in indexList) { childrenJSON.Add(index); } return(ExportTrack(trackJSON, trackListArr)); }
public bool SetTrack(string trackName, int layerIndex, apAnimPlayUnit.BLEND_METHOD blendMethod, apPortrait portrait, apAnimPlayManager animPlayManager) { #if UNITY_2017_1_OR_NEWER //PlayableDirector playDirector = _parentTrackSet._playableDirector; PlayableAsset playAsset = _parentTrackSet._playableAsset; #endif _trackName = trackName; _layerIndex = layerIndex; #if UNITY_2017_1_OR_NEWER _layerOrder = _layerIndex; #endif _blendMethod = blendMethod; if (string.IsNullOrEmpty(_trackName)) { //이름이 빈칸이거나 null이다. return(false); } _clipData = new List <TimelineClipData>(); #if UNITY_2017_1_OR_NEWER _timelineClips = new List <TimelineClip>(); _clipDataByTrack = new Dictionary <TimelineClip, TimelineClipData>(); #endif _nClipData = 0; //연결을 하자 bool isFind = false; #if UNITY_2017_1_OR_NEWER _animationTrack = null; #endif //그 전에 AnimClip <-> AnimationClipAsset을 서로 연결해야한다. apAnimClip curAnimClip = null; AnimationClip curAnimationClipAsset = null; Dictionary <AnimationClip, apAnimClip> animClipAsset2AnimClip = new Dictionary <AnimationClip, apAnimClip>(); for (int i = 0; i < portrait._animClips.Count; i++) { curAnimClip = portrait._animClips[i]; if (curAnimClip == null) { continue; } curAnimationClipAsset = curAnimClip._animationClipForMecanim; if (curAnimationClipAsset == null) { continue; } if (animClipAsset2AnimClip.ContainsKey(curAnimationClipAsset)) { continue; } animClipAsset2AnimClip.Add(curAnimationClipAsset, curAnimClip); } //apAnimClip에 해당하는 apOptRootUnit을 알아야 한다. //apAnimPlayData에 그 정보가 저장되어 있으니 참조하자 apAnimPlayData curPlayData = null; Dictionary <apAnimClip, apOptRootUnit> animClip2RootUnit = new Dictionary <apAnimClip, apOptRootUnit>(); for (int i = 0; i < animPlayManager._animPlayDataList.Count; i++) { curPlayData = animPlayManager._animPlayDataList[i]; if (curPlayData == null) { continue; } if (curPlayData._linkedAnimClip == null || curPlayData._linkedOptRootUnit == null) { continue; } if (animClip2RootUnit.ContainsKey(curPlayData._linkedAnimClip)) { continue; } //apAnimClip -> apOptRootUnit으로 연결 데이터 추가 animClip2RootUnit.Add(curPlayData._linkedAnimClip, curPlayData._linkedOptRootUnit); } #if UNITY_2017_1_OR_NEWER foreach (PlayableBinding playableBinding in playAsset.outputs) { #endif #if UNITY_2018_1_OR_NEWER bool isAnimTrack = playableBinding.sourceObject != null && playableBinding.sourceObject is AnimationTrack; #elif UNITY_2017_1_OR_NEWER bool isAnimTrack = playableBinding.streamType == DataStreamType.Animation; #endif //if (playableBinding.streamType != DataStreamType.Animation) #if UNITY_2017_1_OR_NEWER if (!isAnimTrack) { //애니메이션 타입이 아니라면 패스 continue; } AnimationTrack animTrack = playableBinding.sourceObject as AnimationTrack; if (animTrack == null) { continue; } //if(animTrack.isEmpty) //{ // //클립이 아예 없는데용 // continue; //} if (!animTrack.name.Equals(_trackName)) { //이름이 다르다. continue; } if (animTrack.isEmpty) { //클립이 아예 없는데용 //continue; Debug.LogWarning("AnyPortrait : ( Warning ) No Clip in the requested track. [ " + trackName + " ]"); //일단 처리는 하자 } //이름도 같고 유효한 트랙을 찾았다! isFind = true; _animationTrack = animTrack; AnimationClip animClipInTrack = null; apAnimClip targetAnimClip = null; apOptRootUnit targetRootUnit = null; foreach (TimelineClip timelineClip in _animationTrack.GetClips()) { //Track의 TimelineClip 중에서 유효한 AnimationClip만 선택한다. animClipInTrack = timelineClip.animationClip; if (!animClipAsset2AnimClip.ContainsKey(animClipInTrack)) { //유효한 AnimationClip이 아니다. continue; } targetAnimClip = animClipAsset2AnimClip[animClipInTrack]; if (targetAnimClip == null) { //animClip이 비어있다. continue; } if (!animClip2RootUnit.ContainsKey(targetAnimClip)) { //apAnimClip -> apOptRootUnit을 조회할 수 없다. continue; } targetRootUnit = animClip2RootUnit[targetAnimClip]; if (targetRootUnit == null) { //RootUnit이 null이다. continue; } TimelineClipData newClipData = new TimelineClipData(timelineClip, targetAnimClip, targetRootUnit); _clipData.Add(newClipData); _timelineClips.Add(timelineClip); _clipDataByTrack.Add(timelineClip, newClipData); //_clipDataByAnimClip.Add(targetAnimClip, newClipData); _nClipData++; } //Debug.Log("Track [" + trackName + "] Added"); //<<추가>> 시간대에 따라서 Sort _clipData.Sort(delegate(TimelineClipData a, TimelineClipData b) { return((int)((a._timelineClip.start - b._timelineClip.start) * 100.0)); }); if (isFind) { break; } } #endif if (!isFind) { Debug.LogError("AnyPortrait : No track with the requested name. [" + trackName + "]"); } return(isFind); }