using System; using System.Collections.Generic; using System.Linq; using UnityEditor.Build; using UnityEditor.Build.Reporting; using UnityEngine.Playables; using UnityEngine.SceneManagement; using UnityEngine.Timeline; namespace UnityEditor.Timeline.Analytics { class TimelineSceneInfo { public Dictionary trackCount = new Dictionary { {"ActivationTrack", 0}, {"AnimationTrack", 0}, {"AudioTrack", 0}, {"ControlTrack", 0}, {"PlayableTrack", 0}, {"UserType", 0}, {"Other", 0} }; public Dictionary userTrackTypesCount = new Dictionary(); public HashSet uniqueDirectors = new HashSet(); public int numTracks = 0; public int minDuration = int.MaxValue; public int maxDuration = int.MinValue; public int minNumTracks = int.MaxValue; public int maxNumTracks = int.MinValue; public int numRecorded = 0; } [Serializable] struct TrackInfo { public string name; public double percent; } [Serializable] class TimelineEventInfo { public int num_timelines; public int min_duration, max_duration; public int min_num_tracks, max_num_tracks; public double recorded_percent; public List track_info = new List(); public string most_popular_user_track = string.Empty; public TimelineEventInfo(TimelineSceneInfo sceneInfo) { num_timelines = sceneInfo.uniqueDirectors.Count; min_duration = sceneInfo.minDuration; max_duration = sceneInfo.maxDuration; min_num_tracks = sceneInfo.minNumTracks; max_num_tracks = sceneInfo.maxNumTracks; recorded_percent = Math.Round(100.0 * sceneInfo.numRecorded / sceneInfo.numTracks, 1); foreach (KeyValuePair kv in sceneInfo.trackCount.Where(x => x.Value > 0)) { track_info.Add(new TrackInfo() { name = kv.Key, percent = Math.Round(100.0 * kv.Value / sceneInfo.numTracks, 1) }); } if (sceneInfo.userTrackTypesCount.Any()) { most_popular_user_track = sceneInfo.userTrackTypesCount .First(x => x.Value == sceneInfo.userTrackTypesCount.Values.Max()).Key; } } public static bool IsUserType(Type t) { string nameSpace = t.Namespace; return string.IsNullOrEmpty(nameSpace) || !nameSpace.StartsWith("UnityEngine.Timeline"); } } static class TimelineAnalytics { static TimelineSceneInfo _timelineSceneInfo = new TimelineSceneInfo(); class TimelineAnalyticsPreProcess : IPreprocessBuildWithReport { public int callbackOrder { get { return 0; } } public void OnPreprocessBuild(BuildReport report) { _timelineSceneInfo = new TimelineSceneInfo(); } } class TimelineAnalyticsProcess : IProcessSceneWithReport { public int callbackOrder { get { return 0; } } public void OnProcessScene(Scene scene, BuildReport report) { PlayableDirector[] directorsInScene = GetAllDirectorsInScene(); IEnumerable timelines = directorsInScene.Select(pd => pd.playableAsset).OfType().Distinct(); foreach (var timeline in timelines) { if (_timelineSceneInfo.uniqueDirectors.Add(timeline)) { _timelineSceneInfo.numTracks += timeline.flattenedTracks.Count(); _timelineSceneInfo.minDuration = Math.Min(_timelineSceneInfo.minDuration, (int)(timeline.duration * 1000)); _timelineSceneInfo.maxDuration = Math.Max(_timelineSceneInfo.maxDuration, (int)(timeline.duration * 1000)); _timelineSceneInfo.minNumTracks = Math.Min(_timelineSceneInfo.minNumTracks, timeline.flattenedTracks.Count()); _timelineSceneInfo.maxNumTracks = Math.Max(_timelineSceneInfo.maxNumTracks, timeline.flattenedTracks.Count()); foreach (var track in timeline.flattenedTracks) { string key = track.GetType().Name; if (_timelineSceneInfo.trackCount.ContainsKey(key)) { _timelineSceneInfo.trackCount[key]++; } else { if (TimelineEventInfo.IsUserType(track.GetType())) { _timelineSceneInfo.trackCount["UserType"]++; if (_timelineSceneInfo.userTrackTypesCount.ContainsKey(key)) _timelineSceneInfo.userTrackTypesCount[key]++; else _timelineSceneInfo.userTrackTypesCount[key] = 1; } else _timelineSceneInfo.trackCount["Other"]++; } if (track.clips.Any(x => x.recordable)) _timelineSceneInfo.numRecorded++; else { var animationTrack = track as AnimationTrack; if (animationTrack != null) { if (animationTrack.CanConvertToClipMode()) _timelineSceneInfo.numRecorded++; } } } } } } } static PlayableDirector[] GetAllDirectorsInScene() { #if UNITY_2023_1_OR_NEWER return UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsSortMode.None); #else return UnityEngine.Object.FindObjectsOfType(); #endif } class TimelineAnalyticsPostProcess : IPostprocessBuildWithReport { public int callbackOrder { get { return 0; } } public void OnPostprocessBuild(BuildReport report) { if (_timelineSceneInfo.uniqueDirectors.Count > 0) { var timelineEvent = new TimelineEventInfo(_timelineSceneInfo); EditorAnalytics.SendEventTimelineInfo(timelineEvent); } } } } }