using System; using System.Collections.Generic; using UnityEditor; using UnityEngine; #if UNITY_2023_2_OR_NEWER using UnityEngine.Analytics; #endif namespace Unity.Cinemachine.Editor { [InitializeOnLoad] static class CinemachineEditorAnalytics { const int k_MaxEventsPerHour = 360; const int k_MaxNumberOfElements = 1000; const string k_VendorKey = "unity.cinemachine"; const string k_CreateVcamEventName = "cm_create_vcam"; const string k_VcamsOnPlayEventName = "cm_vcams_on_play"; // register an event handler when the class is initialized static CinemachineEditorAnalytics() { EditorApplication.playModeStateChanged += SendAnalyticsOnPlayEnter; } #if UNITY_2023_2_OR_NEWER [AnalyticInfo(eventName: k_CreateVcamEventName, vendorKey: k_VendorKey, maxEventsPerHour:k_MaxEventsPerHour, maxNumberOfElements:k_MaxNumberOfElements)] class CreateEventAnalytic : IAnalytic { public string vcam_created; // vcam created from Create -> Cinemachine menu public bool TryGatherData(out IAnalytic.IData data, out Exception error) { error = null; data = new CreateEventData { vcam_created = vcam_created}; return true; } } [Serializable] class CreateEventData : IAnalytic.IData #else struct CreateEventData #endif { public string vcam_created; // vcam created from Create -> Cinemachine menu } /// /// Send analytics event when using Create -> Cinemachine menu /// /// Name of the vcam created public static void SendCreateEvent(string name) { if (!EditorAnalytics.enabled) return; #if UNITY_2023_2_OR_NEWER EditorAnalytics.SendAnalytic(new CreateEventAnalytic { vcam_created = name }); #else var data = new CreateEventData { vcam_created = name }; // Register our event EditorAnalytics.RegisterEventWithLimit(k_CreateVcamEventName, k_MaxEventsPerHour, k_MaxNumberOfElements, k_VendorKey); // Send the data to the database EditorAnalytics.SendEventWithLimit(k_CreateVcamEventName, data); #endif } #if UNITY_2023_2_OR_NEWER [AnalyticInfo(eventName: k_VcamsOnPlayEventName, vendorKey: k_VendorKey, maxEventsPerHour:k_MaxEventsPerHour, maxNumberOfElements:k_MaxNumberOfElements)] class PlayModeEventAnalytic : IAnalytic { public bool TryGatherData(out IAnalytic.IData data, out Exception error) { error = null; var projectData = new ProjectData(); CollectOnPlayEnterData(ref projectData); data = projectData; return true; } } [Serializable] class ProjectData : IAnalytic.IData { public int brain_count; public int vcam_count; public int cam_count; public VcamData[] vcams; public float time_elapsed; } #else [Serializable] struct ProjectData { public int brain_count; public int vcam_count; public int cam_count; public List vcams; public float time_elapsed; } #endif /// /// Send analytics event when using entering playmode /// /// State change to detect entering playmode static void SendAnalyticsOnPlayEnter(PlayModeStateChange state) { // Only send analytics if it is enabled if (!EditorAnalytics.enabled) return; // Only send data when entering playmode if (state != PlayModeStateChange.EnteredPlayMode) return; #if UNITY_2023_2_OR_NEWER EditorAnalytics.SendAnalytic(new PlayModeEventAnalytic()); #else var projectData = new ProjectData(); CollectOnPlayEnterData(ref projectData); // Register our event EditorAnalytics.RegisterEventWithLimit(k_VcamsOnPlayEventName, k_MaxEventsPerHour, k_MaxNumberOfElements, k_VendorKey); // Send the data to the database EditorAnalytics.SendEventWithLimit(k_VcamsOnPlayEventName, projectData); #endif } /// /// Send analytics event when using entering playmode /// /// State change to detect entering playmode static void CollectOnPlayEnterData(ref ProjectData projectData) { var startTime = Time.realtimeSinceStartup; var vcamCount = CinemachineCore.VirtualCameraCount; var vcamDatas = new List(); // collect data from all vcams for (int i = 0; i < vcamCount; ++i) { var vcamBase = CinemachineCore.GetVirtualCamera(i); CollectVcamData(vcamBase, i.ToString(), ref vcamDatas); } projectData.brain_count = CinemachineBrain.ActiveBrainCount; projectData.vcam_count = CinemachineCore.VirtualCameraCount; projectData.cam_count = Camera.allCamerasCount; #if UNITY_2023_2_OR_NEWER projectData.vcams = vcamDatas.ToArray(); #else projectData.vcams = vcamDatas; #endif projectData.time_elapsed = Time.realtimeSinceStartup - startTime; } static void CollectVcamData(CinemachineVirtualCameraBase vcamBase, string id, ref List vcamDatas) { if (vcamBase == null) return; var vcamData = new VcamData(id, vcamBase); // VirtualCamera var vcam = vcamBase as CinemachineCamera; if (vcam != null) { vcamData.SetTransitionsAndLens(vcam.BlendHint, vcam.Lens); vcamData.SetComponents(vcam.GetComponents()); vcamDatas.Add(vcamData); return; } var vcamChildren = vcamBase.GetComponentsInChildren(); for (var c = 1; c < vcamChildren.Length; c++) { if (vcamChildren[c].ParentCamera == (ICinemachineCamera)vcamBase) CollectVcamData(vcamChildren[c], id + "." + c, ref vcamDatas); } } [Serializable] struct VcamData { public string id; public string vcam_class; public bool has_follow_target; public bool has_lookat_target; public string blend_hint; public bool inherit_position; public string standby_update; public string mode_overwrite; public string body_component; public string aim_component; public string noise_component; public int custom_component_count; public string[] extensions; public int custom_extension_count; public VcamData(string id, CinemachineVirtualCameraBase vcamBase) : this() { var _ = 0; this.id = id; vcam_class = GetTypeName(vcamBase.GetType(), ref _); has_follow_target = vcamBase.Follow != null; has_lookat_target = vcamBase.LookAt != null; blend_hint = ""; inherit_position = false; standby_update = vcamBase.StandbyUpdate.ToString(); mode_overwrite = ""; body_component = ""; aim_component = ""; noise_component = ""; custom_component_count = 0; custom_extension_count = 0; var vcamExtensions = vcamBase.Extensions; if (vcamExtensions != null) { extensions = new string[vcamExtensions.Count]; for (var i = 0; i < vcamExtensions.Count; i++) { extensions[i] = (GetTypeName(vcamExtensions[i].GetType(), ref custom_extension_count)); } } else { extensions = Array.Empty(); } } public void SetTransitionsAndLens(CinemachineCore.BlendHints hints, LensSettings lens) { blend_hint = hints.ToString(); inherit_position = (hints & CinemachineCore.BlendHints.InheritPosition) != 0; mode_overwrite = lens.ModeOverride.ToString(); } public void SetComponents(CinemachineComponentBase[] cmComps) { custom_component_count = 0; body_component = aim_component = noise_component = ""; if (cmComps != null) { for (var i = 0; i < cmComps.Length; i++) { if (cmComps[i] == null) continue; var componentName = GetTypeName(cmComps[i].GetType(), ref custom_component_count); switch (cmComps[i].Stage) { case CinemachineCore.Stage.Body: body_component = componentName; break; case CinemachineCore.Stage.Aim: aim_component = componentName; break; case CinemachineCore.Stage.Noise: noise_component = componentName; break; default: break; } } } } static string GetTypeName(Type type, ref int customTypeCount) { if (typeof(CinemachineBrain).Assembly != type.Assembly) { ++customTypeCount; return "Custom"; } return type.Name; } } } }