multiple xr toolkit package
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

VRCSdkControlPanelBuilder.cs 63KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using UnityEngine;
  6. using UnityEditor;
  7. using VRC;
  8. using VRC.Core;
  9. using VRCSDK2.Validation;
  10. using VRCSDK2.Validation.Performance;
  11. using VRCSDK2.Validation.Performance.Stats;
  12. public partial class VRCSdkControlPanel : EditorWindow
  13. {
  14. public static System.Action _EnableSpatialization = null; // assigned in AutoAddONSPAudioSourceComponents
  15. const string kCantPublishContent = "Before you can upload avatars or worlds, you will need to spend some time in VRChat.";
  16. const string kCantPublishAvatars = "Before you can upload avatars, you will need to spend some time in VRChat.";
  17. const string kCantPublishWorlds = "Before you can upload worlds, you will need to spend some time in VRChat.";
  18. private const string FIX_ISSUES_TO_BUILD_OR_TEST_WARNING_STRING = "You must address the above issues before you can build or test this content!";
  19. private const string kAvatarOptimizationTipsURL = "https://docs.vrchat.com/docs/avatar-optimizing-tips";
  20. private const string kAvatarRigRequirementsURL = "https://docs.vrchat.com/docs/rig-requirements";
  21. static Texture _perfIcon_Excellent;
  22. static Texture _perfIcon_Good;
  23. static Texture _perfIcon_Medium;
  24. static Texture _perfIcon_Poor;
  25. static Texture _perfIcon_VeryPoor;
  26. static Texture _bannerImage;
  27. private void ResetIssues()
  28. {
  29. GUIErrors.Clear();
  30. GUIInfos.Clear();
  31. GUIWarnings.Clear();
  32. GUILinks.Clear();
  33. GUIStats.Clear();
  34. checkedForIssues = false;
  35. }
  36. bool checkedForIssues = false;
  37. class Issue
  38. {
  39. public string issueText;
  40. public System.Action showThisIssue;
  41. public System.Action fixThisIssue;
  42. public Issue(string text, System.Action show, System.Action fix)
  43. {
  44. issueText = text;
  45. showThisIssue = show;
  46. fixThisIssue = fix;
  47. }
  48. public class Equality : IEqualityComparer<Issue>, IComparer<Issue>
  49. {
  50. public bool Equals(Issue b1, Issue b2)
  51. {
  52. return (b1.issueText == b2.issueText);
  53. }
  54. public int Compare(Issue b1, Issue b2)
  55. {
  56. return string.Compare(b1.issueText, b2.issueText);
  57. }
  58. public int GetHashCode(Issue bx)
  59. {
  60. return bx.issueText.GetHashCode();
  61. }
  62. }
  63. }
  64. Dictionary<Object, List<Issue>> GUIErrors = new Dictionary<Object, List<Issue>>();
  65. Dictionary<Object, List<Issue>> GUIWarnings = new Dictionary<Object, List<Issue>>();
  66. Dictionary<Object, List<Issue>> GUIInfos = new Dictionary<Object, List<Issue>>();
  67. Dictionary<Object, List<Issue>> GUILinks = new Dictionary<Object, List<Issue>>();
  68. Dictionary<Object, List<KeyValuePair<string, PerformanceRating>>> GUIStats = new Dictionary<Object, List<KeyValuePair<string, PerformanceRating>>>();
  69. private string customNamespace;
  70. void AddToReport(Dictionary<Object, List<Issue>> report, Object subject, string output, System.Action show, System.Action fix)
  71. {
  72. if (subject == null)
  73. subject = this;
  74. if (!report.ContainsKey(subject))
  75. report.Add(subject, new List<Issue>());
  76. var issue = new Issue(output, show, fix);
  77. if (!report[subject].Contains(issue, new Issue.Equality()))
  78. {
  79. report[subject].Add(issue);
  80. report[subject].Sort(new Issue.Equality());
  81. }
  82. }
  83. void BuilderAssemblyReload()
  84. {
  85. ResetIssues();
  86. }
  87. void OnGUIError(Object subject, string output, System.Action show, System.Action fix)
  88. {
  89. AddToReport(GUIErrors, subject, output, show, fix);
  90. }
  91. void OnGUIWarning(Object subject, string output, System.Action show, System.Action fix)
  92. {
  93. AddToReport(GUIWarnings, subject, output, show, fix);
  94. }
  95. void OnGUIInformation(Object subject, string output)
  96. {
  97. AddToReport(GUIInfos, subject, output, null, null);
  98. }
  99. void OnGUILink(Object subject, string output, string link)
  100. {
  101. AddToReport(GUILinks, subject, output + "\n" + link, null, null);
  102. }
  103. void OnGUIStat(Object subject, string output, PerformanceRating rating)
  104. {
  105. if (subject == null)
  106. subject = this;
  107. if (!GUIStats.ContainsKey(subject))
  108. GUIStats.Add(subject, new List<KeyValuePair<string, PerformanceRating>>());
  109. GUIStats[subject].Add(new KeyValuePair<string, PerformanceRating>(output, rating));
  110. }
  111. VRCSDK2.VRC_SceneDescriptor[] scenes;
  112. VRCSDK2.VRC_AvatarDescriptor[] avatars;
  113. Vector2 scrollPos;
  114. Vector2 avatarListScrollPos;
  115. VRCSDK2.VRC_AvatarDescriptor selectedAvatar;
  116. public static void SelectAvatar( VRCSDK2.VRC_AvatarDescriptor avatar )
  117. {
  118. if (window != null)
  119. window.selectedAvatar = avatar;
  120. }
  121. private bool showAvatarPerformanceDetails
  122. {
  123. get { return EditorPrefs.GetBool("VRCSDK2_showAvatarPerformanceDetails", false); }
  124. set { EditorPrefs.SetBool("VRCSDK2_showAvatarPerformanceDetails", value); }
  125. }
  126. public void FindScenesAndAvatars()
  127. {
  128. var newScenes = (VRCSDK2.VRC_SceneDescriptor[])VRC.Tools.FindSceneObjectsOfTypeAll<VRCSDK2.VRC_SceneDescriptor>();
  129. List<VRCSDK2.VRC_AvatarDescriptor> allavatars = VRC.Tools.FindSceneObjectsOfTypeAll<VRCSDK2.VRC_AvatarDescriptor>().ToList();
  130. // select only the active avatars
  131. var newAvatars = allavatars.Where(av => (null != av) && av.gameObject.activeInHierarchy).ToArray();
  132. if (scenes != null)
  133. {
  134. foreach (var s in newScenes)
  135. if (scenes.Contains(s) == false)
  136. checkedForIssues = false;
  137. }
  138. if (avatars != null)
  139. {
  140. foreach (var a in newAvatars)
  141. if (avatars.Contains(a) == false)
  142. checkedForIssues = false;
  143. }
  144. scenes = newScenes;
  145. avatars = newAvatars;
  146. }
  147. void ShowBuilder()
  148. {
  149. if (VRC.Core.RemoteConfig.IsInitialized())
  150. {
  151. string sdkUnityVersion = VRC.Core.RemoteConfig.GetString("sdkUnityVersion");
  152. if (Application.unityVersion != sdkUnityVersion)
  153. {
  154. OnGUIWarning(null, "You are not using the recommended Unity version for the VRChat SDK. Content built with this version may not work correctly. Please use Unity " + sdkUnityVersion, null, null);
  155. }
  156. }
  157. FindScenesAndAvatars();
  158. if ((null == scenes) || (null == avatars)) return;
  159. if (scenes.Length > 0 && avatars.Length > 0)
  160. {
  161. GameObject[] gos = new GameObject[avatars.Length];
  162. for (int i = 0; i < avatars.Length; ++i)
  163. gos[i] = avatars[i].gameObject;
  164. OnGUIError(null, "a unity scene containing a VRChat Scene Descriptor should not also contain avatars.",
  165. delegate ()
  166. {
  167. List<GameObject> show = new List<GameObject>();
  168. foreach (var s in scenes)
  169. show.Add(s.gameObject);
  170. foreach (var a in avatars)
  171. show.Add(a.gameObject);
  172. Selection.objects = show.ToArray();
  173. }, null);
  174. EditorGUILayout.Separator();
  175. EditorGUILayout.BeginVertical(GUILayout.Width(SdkWindowWidth));
  176. OnGUIShowIssues();
  177. EditorGUILayout.EndVertical();
  178. }
  179. else if (scenes.Length > 1)
  180. {
  181. GameObject[] gos = new GameObject[scenes.Length];
  182. for (int i = 0; i < scenes.Length; ++i)
  183. gos[i] = scenes[i].gameObject;
  184. OnGUIError(null, "a unity scene containing a VRChat Scene Descriptor should only contain one scene descriptor.",
  185. delegate { Selection.objects = gos; }, null);
  186. EditorGUILayout.Separator();
  187. EditorGUILayout.BeginVertical(GUILayout.Width(SdkWindowWidth));
  188. OnGUIShowIssues();
  189. EditorGUILayout.EndVertical();
  190. }
  191. else if (scenes.Length == 1)
  192. {
  193. bool inScroller = false;
  194. try
  195. {
  196. bool setupRequired = OnGUISceneSetup(scenes[0]);
  197. if (!setupRequired)
  198. {
  199. if (!checkedForIssues)
  200. {
  201. ResetIssues();
  202. OnGUISceneCheck(scenes[0]);
  203. checkedForIssues = true;
  204. }
  205. OnGUISceneSettings(scenes[0]);
  206. inScroller = true;
  207. scrollPos = EditorGUILayout.BeginScrollView(scrollPos, GUILayout.Width(SdkWindowWidth));
  208. OnGUIShowIssues(scenes[0]);
  209. EditorGUILayout.EndScrollView();
  210. inScroller = false;
  211. GUILayout.FlexibleSpace();
  212. OnGUIScene(scenes[0]);
  213. }
  214. else
  215. {
  216. OnGuiFixIssuesToBuildOrTest();
  217. }
  218. }
  219. catch (System.Exception)
  220. {
  221. if (inScroller)
  222. EditorGUILayout.EndScrollView();
  223. }
  224. }
  225. else if (avatars.Length > 0)
  226. {
  227. if (!checkedForIssues)
  228. {
  229. ResetIssues();
  230. for (int i = 0; i < avatars.Length; ++i)
  231. OnGUIAvatarCheck(avatars[i]);
  232. checkedForIssues = true;
  233. }
  234. bool drawList = true;
  235. if( avatars.Length == 1 )
  236. {
  237. drawList = false;
  238. selectedAvatar = avatars[0];
  239. }
  240. if (drawList)
  241. {
  242. EditorGUILayout.BeginVertical(GUI.skin.GetStyle("HelpBox"), GUILayout.Width(SdkWindowWidth), GUILayout.MaxHeight(150));
  243. avatarListScrollPos = EditorGUILayout.BeginScrollView(avatarListScrollPos, false, false);
  244. for (int i = 0; i < avatars.Length; ++i)
  245. {
  246. var av = avatars[i];
  247. EditorGUILayout.Space();
  248. if (selectedAvatar == av)
  249. {
  250. if (GUILayout.Button(av.gameObject.name, listButtonStyleSelected, GUILayout.Width(SdkWindowWidth - 50)))
  251. selectedAvatar = null;
  252. }
  253. else
  254. {
  255. if (GUILayout.Button(av.gameObject.name, ((i & 0x01) > 0) ? (listButtonStyleOdd) : (listButtonStyleEven), GUILayout.Width(SdkWindowWidth - 50)))
  256. selectedAvatar = av;
  257. }
  258. }
  259. EditorGUILayout.EndScrollView();
  260. EditorGUILayout.EndVertical();
  261. }
  262. EditorGUILayout.BeginVertical(GUILayout.Width(SdkWindowWidth));
  263. OnGUIShowIssues();
  264. EditorGUILayout.EndVertical();
  265. EditorGUILayout.Separator();
  266. if (selectedAvatar != null)
  267. {
  268. EditorGUILayout.BeginVertical(boxGuiStyle);
  269. OnGUIAvatarSettings(selectedAvatar);
  270. EditorGUILayout.EndVertical();
  271. scrollPos = EditorGUILayout.BeginScrollView(scrollPos, false, false, GUILayout.Width(SdkWindowWidth));
  272. OnGUIShowIssues(selectedAvatar);
  273. EditorGUILayout.EndScrollView();
  274. GUILayout.FlexibleSpace();
  275. EditorGUILayout.BeginVertical(boxGuiStyle);
  276. OnGUIAvatar(selectedAvatar);
  277. EditorGUILayout.EndVertical();
  278. }
  279. }
  280. else
  281. {
  282. EditorGUILayout.Space();
  283. EditorGUILayout.LabelField("A VRC_SceneDescriptor or VRC_AvatarDescriptor is required to build VRChat SDK Content", titleGuiStyle);
  284. }
  285. }
  286. bool showLayerHelp = false;
  287. int numClients = 1;
  288. void CheckUploadChanges(VRCSDK2.VRC_SceneDescriptor scene)
  289. {
  290. if (UnityEditor.EditorPrefs.HasKey("VRCSDK2_scene_changed") &&
  291. UnityEditor.EditorPrefs.GetBool("VRCSDK2_scene_changed"))
  292. {
  293. UnityEditor.EditorPrefs.DeleteKey("VRCSDK2_scene_changed");
  294. if (UnityEditor.EditorPrefs.HasKey("VRCSDK2_capacity"))
  295. {
  296. scene.capacity = UnityEditor.EditorPrefs.GetInt("VRCSDK2_capacity");
  297. UnityEditor.EditorPrefs.DeleteKey("VRCSDK2_capacity");
  298. }
  299. if (UnityEditor.EditorPrefs.HasKey("VRCSDK2_content_sex"))
  300. {
  301. scene.contentSex = UnityEditor.EditorPrefs.GetBool("VRCSDK2_content_sex");
  302. UnityEditor.EditorPrefs.DeleteKey("VRCSDK2_content_sex");
  303. }
  304. if (UnityEditor.EditorPrefs.HasKey("VRCSDK2_content_violence"))
  305. {
  306. scene.contentViolence = UnityEditor.EditorPrefs.GetBool("VRCSDK2_content_violence");
  307. UnityEditor.EditorPrefs.DeleteKey("VRCSDK2_content_violence");
  308. }
  309. if (UnityEditor.EditorPrefs.HasKey("VRCSDK2_content_gore"))
  310. {
  311. scene.contentGore = UnityEditor.EditorPrefs.GetBool("VRCSDK2_content_gore");
  312. UnityEditor.EditorPrefs.DeleteKey("VRCSDK2_content_gore");
  313. }
  314. if (UnityEditor.EditorPrefs.HasKey("VRCSDK2_content_other"))
  315. {
  316. scene.contentOther = UnityEditor.EditorPrefs.GetBool("VRCSDK2_content_other");
  317. UnityEditor.EditorPrefs.DeleteKey("VRCSDK2_content_other");
  318. }
  319. if (UnityEditor.EditorPrefs.HasKey("VRCSDK2_release_public"))
  320. {
  321. scene.releasePublic = UnityEditor.EditorPrefs.GetBool("VRCSDK2_release_public");
  322. UnityEditor.EditorPrefs.DeleteKey("VRCSDK2_release_public");
  323. }
  324. EditorUtility.SetDirty(scene);
  325. UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene());
  326. }
  327. }
  328. bool ShouldShowLightmapWarning
  329. {
  330. get
  331. {
  332. const string GraphicsSettingsAssetPath = "ProjectSettings/GraphicsSettings.asset";
  333. SerializedObject graphicsManager = new SerializedObject(UnityEditor.AssetDatabase.LoadAllAssetsAtPath(GraphicsSettingsAssetPath)[0]);
  334. SerializedProperty lightmapStripping = graphicsManager.FindProperty("m_LightmapStripping");
  335. return lightmapStripping.enumValueIndex == 0;
  336. }
  337. }
  338. bool ShouldShowFogWarning
  339. {
  340. get
  341. {
  342. const string GraphicsSettingsAssetPath = "ProjectSettings/GraphicsSettings.asset";
  343. SerializedObject graphicsManager = new SerializedObject(UnityEditor.AssetDatabase.LoadAllAssetsAtPath(GraphicsSettingsAssetPath)[0]);
  344. SerializedProperty lightmapStripping = graphicsManager.FindProperty("m_FogStripping");
  345. return lightmapStripping.enumValueIndex == 0;
  346. }
  347. }
  348. void DrawIssueBox(MessageType msgType, Texture icon, string message, System.Action show, System.Action fix)
  349. {
  350. GUIStyle style = GUI.skin.GetStyle("HelpBox");
  351. EditorGUILayout.BeginHorizontal();
  352. if (icon != null)
  353. GUILayout.Box(new GUIContent(message, icon), style);
  354. else
  355. EditorGUILayout.HelpBox(message, msgType);
  356. EditorGUILayout.BeginVertical();
  357. GUI.enabled = show != null;
  358. if (GUILayout.Button("Select"))
  359. show();
  360. GUI.enabled = fix != null;
  361. if (GUILayout.Button("Auto Fix"))
  362. {
  363. fix();
  364. EditorApplication.MarkSceneDirty();
  365. //EditorSceneManager.MarkSceneDirty();
  366. checkedForIssues = false;
  367. Repaint();
  368. }
  369. GUI.enabled = true;
  370. EditorGUILayout.EndVertical();
  371. EditorGUILayout.EndHorizontal();
  372. //EditorGUILayout.Space();
  373. }
  374. void OnGuiFixIssuesToBuildOrTest()
  375. {
  376. GUIStyle s = new GUIStyle(EditorStyles.label) { alignment = TextAnchor.MiddleCenter };
  377. EditorGUILayout.Space();
  378. GUILayout.BeginVertical(boxGuiStyle, GUILayout.Height(WARNING_ICON_SIZE), GUILayout.Width(SdkWindowWidth));
  379. GUILayout.FlexibleSpace();
  380. EditorGUILayout.BeginHorizontal();
  381. var textDimensions = s.CalcSize(new GUIContent(FIX_ISSUES_TO_BUILD_OR_TEST_WARNING_STRING));
  382. GUILayout.Label(new GUIContent(warningIconGraphic), GUILayout.Width(WARNING_ICON_SIZE), GUILayout.Height(WARNING_ICON_SIZE));
  383. EditorGUILayout.LabelField(FIX_ISSUES_TO_BUILD_OR_TEST_WARNING_STRING, s, GUILayout.Width(textDimensions.x), GUILayout.Height(WARNING_ICON_SIZE));
  384. EditorGUILayout.EndHorizontal();
  385. GUILayout.FlexibleSpace();
  386. EditorGUILayout.EndVertical();
  387. }
  388. void OnGUIShowIssues(Object subject = null)
  389. {
  390. if (subject == null)
  391. subject = this;
  392. EditorGUI.BeginChangeCheck();
  393. GUIStyle style = GUI.skin.GetStyle("HelpBox");
  394. if (GUIErrors.ContainsKey(subject))
  395. foreach (Issue error in GUIErrors[subject].Where(s => !string.IsNullOrEmpty(s.issueText)))
  396. DrawIssueBox(MessageType.Error, null, error.issueText, error.showThisIssue, error.fixThisIssue);
  397. if (GUIWarnings.ContainsKey(subject))
  398. foreach (Issue error in GUIWarnings[subject].Where(s => !string.IsNullOrEmpty(s.issueText)))
  399. DrawIssueBox(MessageType.Warning, null, error.issueText, error.showThisIssue, error.fixThisIssue);
  400. if (GUIStats.ContainsKey(subject))
  401. {
  402. foreach (var kvp in GUIStats[subject].Where(k => k.Value == PerformanceRating.VeryPoor))
  403. GUILayout.Box(new GUIContent(kvp.Key, GetPerformanceIconForRating(kvp.Value)), style);
  404. foreach (var kvp in GUIStats[subject].Where(k => k.Value == PerformanceRating.Poor))
  405. GUILayout.Box(new GUIContent(kvp.Key, GetPerformanceIconForRating(kvp.Value)), style);
  406. foreach (var kvp in GUIStats[subject].Where(k => k.Value == PerformanceRating.Medium))
  407. GUILayout.Box(new GUIContent(kvp.Key, GetPerformanceIconForRating(kvp.Value)), style);
  408. foreach (var kvp in GUIStats[subject].Where(k => k.Value == PerformanceRating.Good || k.Value == PerformanceRating.Excellent))
  409. GUILayout.Box(new GUIContent(kvp.Key, GetPerformanceIconForRating(kvp.Value)), style);
  410. }
  411. if (GUIInfos.ContainsKey(subject))
  412. foreach (Issue error in GUIInfos[subject].Where(s => !string.IsNullOrEmpty(s.issueText)))
  413. EditorGUILayout.HelpBox(error.issueText, MessageType.Info);
  414. if (GUILinks.ContainsKey(subject))
  415. {
  416. EditorGUILayout.BeginVertical(style);
  417. foreach (Issue error in GUILinks[subject].Where(s => !string.IsNullOrEmpty(s.issueText)))
  418. {
  419. var s = error.issueText.Split('\n');
  420. EditorGUILayout.BeginHorizontal();
  421. GUILayout.Label(s[0]);
  422. if (GUILayout.Button("Open Link", GUILayout.Width(100)))
  423. Application.OpenURL(s[1]);
  424. EditorGUILayout.EndHorizontal();
  425. }
  426. EditorGUILayout.EndVertical();
  427. }
  428. if (EditorGUI.EndChangeCheck())
  429. {
  430. EditorUtility.SetDirty(subject);
  431. UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene());
  432. }
  433. }
  434. private Texture GetPerformanceIconForRating(PerformanceRating value)
  435. {
  436. if (_perfIcon_Excellent == null)
  437. _perfIcon_Excellent = Resources.Load<Texture>("PerformanceIcons/Perf_Great_32");
  438. if (_perfIcon_Good == null)
  439. _perfIcon_Good = Resources.Load<Texture>("PerformanceIcons/Perf_Good_32");
  440. if (_perfIcon_Medium == null)
  441. _perfIcon_Medium = Resources.Load<Texture>("PerformanceIcons/Perf_Medium_32");
  442. if (_perfIcon_Poor == null)
  443. _perfIcon_Poor = Resources.Load<Texture>("PerformanceIcons/Perf_Poor_32");
  444. if (_perfIcon_VeryPoor == null)
  445. _perfIcon_VeryPoor = Resources.Load<Texture>("PerformanceIcons/Perf_Horrible_32");
  446. switch (value)
  447. {
  448. case PerformanceRating.Excellent:
  449. return _perfIcon_Excellent;
  450. case PerformanceRating.Good:
  451. return _perfIcon_Good;
  452. case PerformanceRating.Medium:
  453. return _perfIcon_Medium;
  454. case PerformanceRating.Poor:
  455. return _perfIcon_Poor;
  456. case PerformanceRating.None:
  457. case PerformanceRating.VeryPoor:
  458. return _perfIcon_VeryPoor;
  459. }
  460. return _perfIcon_Excellent;
  461. }
  462. Texture2D CreateBackgroundColorImage(Color color)
  463. {
  464. int w = 4, h = 4;
  465. Texture2D back = new Texture2D(w, h);
  466. Color[] buffer = new Color[w * h];
  467. for (int i = 0; i < w; ++i)
  468. for (int j = 0; j < h; ++j)
  469. buffer[i + w * j] = color;
  470. back.SetPixels(buffer);
  471. back.Apply(false);
  472. return back;
  473. }
  474. bool IsAudioSource2D(AudioSource src)
  475. {
  476. AnimationCurve curve = src.GetCustomCurve(AudioSourceCurveType.SpatialBlend);
  477. return src.spatialBlend == 0 && (curve == null || curve.keys.Length <= 1);
  478. }
  479. void OnGUISceneCheck(VRCSDK2.VRC_SceneDescriptor scene)
  480. {
  481. CheckUploadChanges(scene);
  482. if (VRC.Core.APIUser.CurrentUser != null && VRC.Core.APIUser.CurrentUser.hasScriptingAccess && !CustomDLLMaker.DoesScriptDirExist())
  483. {
  484. CustomDLLMaker.CreateDirectories();
  485. }
  486. Vector3 g = Physics.gravity;
  487. if (g.x != 0.0f || g.z != 0.0f)
  488. OnGUIWarning(scene, "Gravity vector is not straight down. Though we support different gravity, player orientation is always 'upwards' so things don't always behave as you intend.",
  489. delegate { EditorApplication.ExecuteMenuItem("Edit/Project Settings/Physics"); /*SettingsService.OpenProjectSettings("Project/Physics");*/ }, null);
  490. if (g.y > 0)
  491. OnGUIWarning(scene, "Gravity vector is not straight down, inverted or zero gravity will make walking extremely difficult.",
  492. delegate { EditorApplication.ExecuteMenuItem("Edit/Project Settings/Physics"); /*SettingsService.OpenProjectSettings("Project/Physics");*/}, null);
  493. if (g.y == 0)
  494. OnGUIWarning(scene, "Zero gravity will make walking extremely difficult, though we support different gravity, player orientation is always 'upwards' so this may not have the effect you're looking for.",
  495. delegate { EditorApplication.ExecuteMenuItem("Edit/Project Settings/Physics"); /*SettingsService.OpenProjectSettings("Project/Physics");*/}, null);
  496. // warn those without scripting access if they choose to script locally
  497. if (VRC.Core.APIUser.CurrentUser != null && !VRC.Core.APIUser.CurrentUser.hasScriptingAccess && CustomDLLMaker.DoesScriptDirExist())
  498. {
  499. OnGUIWarning(scene, "Your account does not have permissions to upload custom scripts. You can test locally but need to contact VRChat to publish your world with scripts.", null, null);
  500. }
  501. if (scene.autoSpatializeAudioSources)
  502. {
  503. OnGUIWarning(scene, "Your scene previously used the 'Auto Spatialize Audio Sources' feature. This has been deprecated, press 'Fix' to disable. Also, please add VRC_SpatialAudioSource to all your audio sources. Make sure Spatial Blend is set to 3D for the sources you want to spatialize.",
  504. null,
  505. delegate { scene.autoSpatializeAudioSources = false; }
  506. );
  507. }
  508. var audioSources = GameObject.FindObjectsOfType<AudioSource>();
  509. foreach( var a in audioSources )
  510. {
  511. if( a.GetComponent<ONSPAudioSource>() != null )
  512. {
  513. OnGUIWarning(scene, "Found audio source(s) using ONSP, this is deprecated. Press 'fix' to convert to VRC_SpatialAudioSource.",
  514. delegate { Selection.activeObject = a.gameObject; },
  515. delegate { Selection.activeObject = a.gameObject; AutoAddSpatialAudioComponents.ConvertONSPAudioSource(a); }
  516. );
  517. break;
  518. }
  519. else if( a.GetComponent<VRCSDK2.VRC_SpatialAudioSource>() == null )
  520. {
  521. string msg = "Found 3D audio source with no VRC Spatial Audio component, this is deprecated. Press 'fix' to add a VRC_SpatialAudioSource.";
  522. if (IsAudioSource2D(a))
  523. msg = "Found 2D audio source with no VRC Spatial Audio component, this is deprecated. Press 'fix' to add a (disabled) VRC_SpatialAudioSource.";
  524. OnGUIWarning(scene, msg,
  525. delegate { Selection.activeObject = a.gameObject; },
  526. delegate { Selection.activeObject = a.gameObject; AutoAddSpatialAudioComponents.AddVRCSpatialToBareAudioSource(a); }
  527. );
  528. break;
  529. }
  530. }
  531. foreach (VRCSDK2.VRC_DataStorage ds in GameObject.FindObjectsOfType<VRCSDK2.VRC_DataStorage>())
  532. {
  533. VRCSDK2.VRC_ObjectSync os = ds.GetComponent<VRCSDK2.VRC_ObjectSync>();
  534. if (os != null && os.SynchronizePhysics)
  535. OnGUIWarning(scene, ds.name + " has a VRC_DataStorage and VRC_ObjectSync, with SynchronizePhysics enabled.",
  536. delegate { Selection.activeObject = os.gameObject; }, null);
  537. }
  538. string vrcFilePath = WWW.UnEscapeURL(UnityEditor.EditorPrefs.GetString("lastVRCPath"));
  539. int fileSize = 0;
  540. if (!string.IsNullOrEmpty(vrcFilePath) && ValidationHelpers.CheckIfAssetBundleFileTooLarge(ContentType.World, vrcFilePath, out fileSize))
  541. {
  542. OnGUIWarning(scene, ValidationHelpers.GetAssetBundleOverSizeLimitMessageSDKWarning(ContentType.World, fileSize), null, null);
  543. }
  544. if (scene.UpdateTimeInMS < (int)(1000f / 90f * 3f))
  545. OnGUIWarning(scene, "Room has a very fast update rate; experience may suffer with many users.",
  546. delegate { Selection.activeObject = scene.gameObject; },
  547. delegate { scene.UpdateTimeInMS = 100; Selection.activeObject = scene.gameObject; });
  548. foreach (GameObject go in FindObjectsOfType<GameObject>())
  549. {
  550. if (go.transform.parent == null)
  551. {
  552. // check root game objects
  553. #if UNITY_ANDROID
  554. IEnumerable<Shader> illegalShaders = WorldValidation.FindIllegalShaders(go);
  555. foreach (Shader s in illegalShaders)
  556. {
  557. OnGUIWarning(scene, "World uses unsupported shader '" + s.name + "'. This could cause low performance or future compatibility issues.", null, null);
  558. }
  559. #endif
  560. }
  561. else
  562. {
  563. //if (go.transform.parent.name == "Action-PlayHaptics")
  564. // Debug.Log("break");
  565. // check sibling game objects
  566. for (int idx = 0; idx < go.transform.parent.childCount; ++idx)
  567. {
  568. Transform t = go.transform.parent.GetChild(idx);
  569. if (t == go.transform)
  570. continue;
  571. else if (t.name == go.transform.name
  572. && !(t.GetComponent<VRCSDK2.VRC_ObjectSync>()
  573. || t.GetComponent<VRCSDK2.VRC_SyncAnimation>()
  574. || t.GetComponent<VRCSDK2.VRC_SyncVideoPlayer>()
  575. || t.GetComponent<VRCSDK2.VRC_SyncVideoStream>()))
  576. {
  577. string path = t.name;
  578. Transform p = t.parent;
  579. while (p != null)
  580. {
  581. path = p.name + "/" + path;
  582. p = p.parent;
  583. }
  584. OnGUIWarning(scene, "Sibling objects share the same path, which may break network events: " + path,
  585. delegate
  586. {
  587. List<GameObject> gos = new List<GameObject>();
  588. for (int c = 0; c < go.transform.parent.childCount; ++c)
  589. if (go.transform.parent.GetChild(c).name == go.name)
  590. gos.Add(go.transform.parent.GetChild(c).gameObject);
  591. Selection.objects = gos.ToArray();
  592. },
  593. delegate
  594. {
  595. List<GameObject> gos = new List<GameObject>();
  596. for (int c = 0; c < go.transform.parent.childCount; ++c)
  597. if (go.transform.parent.GetChild(c).name == go.name)
  598. gos.Add(go.transform.parent.GetChild(c).gameObject);
  599. Selection.objects = gos.ToArray();
  600. for (int i = 0; i < gos.Count; ++i)
  601. gos[i].name = gos[i].name + "-" + i.ToString("00");
  602. });
  603. break;
  604. }
  605. }
  606. }
  607. }
  608. }
  609. bool OnGUISceneSetup(VRCSDK2.VRC_SceneDescriptor scene)
  610. {
  611. bool mandatoryExpand = !UpdateLayers.AreLayersSetup() || !UpdateLayers.IsCollisionLayerMatrixSetup();
  612. if (mandatoryExpand)
  613. EditorGUILayout.LabelField("VRChat Scene Setup", titleGuiStyle, GUILayout.Height(50));
  614. if (!UpdateLayers.AreLayersSetup())
  615. {
  616. GUILayout.BeginVertical(boxGuiStyle, GUILayout.Height(100), GUILayout.Width(SdkWindowWidth));
  617. EditorGUILayout.BeginHorizontal();
  618. EditorGUILayout.BeginVertical(GUILayout.Width(300));
  619. EditorGUILayout.Space();
  620. GUILayout.Label("Layers", infoGuiStyle);
  621. GUILayout.Label("VRChat scenes must have the same Unity layer configuration as VRChat so we can all predict things like physics and collisions. Pressing this button will configure your project's layers to match VRChat.", infoGuiStyle, GUILayout.Width(300));
  622. EditorGUILayout.EndVertical();
  623. EditorGUILayout.BeginVertical(GUILayout.Width(150));
  624. GUILayout.Label("", GUILayout.Height(15));
  625. if (UpdateLayers.AreLayersSetup())
  626. {
  627. GUILayout.Label("Step Complete!", infoGuiStyle);
  628. }
  629. else if (GUILayout.Button("Setup Layers for VRChat"))
  630. {
  631. bool doIt = EditorUtility.DisplayDialog("Setup Layers for VRChat", "This adds all VRChat layers to your project and pushes any custom layers down the layer list. If you have custom layers assigned to gameObjects, you'll need to reassign them. Are you sure you want to continue?", "Do it!", "Don't do it");
  632. if (doIt)
  633. UpdateLayers.SetupEditorLayers();
  634. }
  635. EditorGUILayout.EndVertical();
  636. EditorGUILayout.EndHorizontal();
  637. GUILayout.EndVertical();
  638. GUILayout.Space(10);
  639. }
  640. if (!UpdateLayers.IsCollisionLayerMatrixSetup())
  641. {
  642. GUILayout.BeginVertical(boxGuiStyle, GUILayout.Height(100), GUILayout.Width(SdkWindowWidth));
  643. EditorGUILayout.BeginHorizontal();
  644. EditorGUILayout.BeginVertical(GUILayout.Width(300));
  645. EditorGUILayout.Space();
  646. GUILayout.Label("Collision Matrix", infoGuiStyle);
  647. GUILayout.Label("VRChat uses specific layers for collision. In order for testing and development to run smoothly it is necessary to configure your project's collision matrix to match that of VRChat.", infoGuiStyle, GUILayout.Width(300));
  648. EditorGUILayout.EndVertical();
  649. EditorGUILayout.BeginVertical(GUILayout.Width(150));
  650. GUILayout.Label("", GUILayout.Height(15));
  651. if (UpdateLayers.AreLayersSetup() == false)
  652. {
  653. GUILayout.Label("You must first configure your layers for VRChat to proceed. Please see above.", infoGuiStyle);
  654. }
  655. else if (UpdateLayers.IsCollisionLayerMatrixSetup())
  656. {
  657. GUILayout.Label("Step Complete!", infoGuiStyle);
  658. }
  659. else
  660. {
  661. if (GUILayout.Button("Set Collision Matrix"))
  662. {
  663. bool doIt = EditorUtility.DisplayDialog("Setup Collision Layer Matrix for VRChat", "This will setup the correct physics collisions in the PhysicsManager for VRChat layers. Are you sure you want to continue?", "Do it!", "Don't do it");
  664. if (doIt)
  665. {
  666. UpdateLayers.SetupCollisionLayerMatrix();
  667. }
  668. }
  669. }
  670. EditorGUILayout.EndVertical();
  671. EditorGUILayout.EndHorizontal();
  672. GUILayout.EndVertical();
  673. GUILayout.Space(10);
  674. }
  675. return mandatoryExpand;
  676. }
  677. void OnGUIAvatarSettings(VRCSDK2.VRC_AvatarDescriptor avatar)
  678. {
  679. GUILayout.BeginVertical(boxGuiStyle, GUILayout.Width(SdkWindowWidth));
  680. string name = avatar.gameObject.name;
  681. if (avatar.apiAvatar != null)
  682. name = (avatar.apiAvatar as ApiAvatar).name;
  683. EditorGUILayout.Space();
  684. EditorGUILayout.LabelField(name, titleGuiStyle);
  685. PipelineManager pm = avatar.GetComponent<PipelineManager>();
  686. if (pm != null && !string.IsNullOrEmpty(pm.blueprintId))
  687. {
  688. if (avatar.apiAvatar == null)
  689. {
  690. ApiAvatar av = API.FromCacheOrNew<ApiAvatar>(pm.blueprintId);
  691. av.Fetch(
  692. (c) => avatar.apiAvatar = c.Model as ApiAvatar,
  693. (c) =>
  694. {
  695. if (c.Code == 404)
  696. {
  697. Debug.LogErrorFormat("Could not load avatar {0} because it didn't exist.", pm.blueprintId);
  698. ApiCache.Invalidate<ApiWorld>(pm.blueprintId);
  699. }
  700. else
  701. Debug.LogErrorFormat("Could not load avatar {0} because {1}", pm.blueprintId, c.Error);
  702. });
  703. avatar.apiAvatar = av;
  704. }
  705. }
  706. if (avatar.apiAvatar != null)
  707. {
  708. ApiAvatar a = (avatar.apiAvatar as ApiAvatar);
  709. EditorGUILayout.LabelField("Version: " + a.version.ToString());
  710. EditorGUILayout.LabelField("Name: " + a.name);
  711. GUILayout.Label(a.description, infoGuiStyle, GUILayout.Width(400));
  712. EditorGUILayout.LabelField("Release: " + a.releaseStatus);
  713. if (a.tags != null)
  714. foreach (var t in a.tags)
  715. EditorGUILayout.LabelField("Tag: " + t);
  716. if (a.supportedPlatforms == ApiModel.SupportedPlatforms.Android || a.supportedPlatforms == ApiModel.SupportedPlatforms.All)
  717. EditorGUILayout.LabelField("Supports: Android");
  718. if (a.supportedPlatforms == ApiModel.SupportedPlatforms.StandaloneWindows || a.supportedPlatforms == ApiModel.SupportedPlatforms.All)
  719. EditorGUILayout.LabelField("Supports: Windows");
  720. //w.imageUrl;
  721. }
  722. else
  723. {
  724. EditorGUILayout.LabelField("Version: " + "Unpublished");
  725. EditorGUILayout.LabelField("Name: " + "");
  726. GUILayout.Label("", infoGuiStyle, GUILayout.Width(400));
  727. EditorGUILayout.LabelField("Release: " + "");
  728. //foreach (var t in w.tags)
  729. // EditorGUILayout.LabelField("Tag: " + "");
  730. //if (w.supportedPlatforms == ApiModel.SupportedPlatforms.Android || w.supportedPlatforms == ApiModel.SupportedPlatforms.All)
  731. // EditorGUILayout.LabelField("Supports: Android");
  732. //if (w.supportedPlatforms == ApiModel.SupportedPlatforms.StandaloneWindows || w.supportedPlatforms == ApiModel.SupportedPlatforms.All)
  733. // EditorGUILayout.LabelField("Supports: Windows");
  734. //w.imageUrl;
  735. }
  736. GUILayout.EndVertical();
  737. }
  738. void OnGUISceneSettings(VRCSDK2.VRC_SceneDescriptor scene)
  739. {
  740. GUILayout.BeginVertical(boxGuiStyle, GUILayout.Width(SdkWindowWidth));
  741. string name = "Unpublished VRChat World";
  742. if (scene.apiWorld != null)
  743. name = (scene.apiWorld as ApiWorld).name;
  744. EditorGUILayout.Space();
  745. EditorGUILayout.LabelField(name, titleGuiStyle);
  746. PipelineManager[] pms = (PipelineManager[])VRC.Tools.FindSceneObjectsOfTypeAll<PipelineManager>();
  747. if (pms.Length == 1)
  748. {
  749. if (!string.IsNullOrEmpty(pms[0].blueprintId))
  750. {
  751. if (scene.apiWorld == null)
  752. {
  753. ApiWorld world = API.FromCacheOrNew<ApiWorld>(pms[0].blueprintId);
  754. world.Fetch(null, null,
  755. (c) => scene.apiWorld = c.Model as ApiWorld,
  756. (c) =>
  757. {
  758. if (c.Code == 404)
  759. {
  760. Debug.LogErrorFormat("Could not load world {0} because it didn't exist.", pms[0].blueprintId);
  761. ApiCache.Invalidate<ApiWorld>(pms[0].blueprintId);
  762. }
  763. else
  764. Debug.LogErrorFormat("Could not load world {0} because {1}", pms[0].blueprintId, c.Error);
  765. });
  766. scene.apiWorld = world;
  767. }
  768. }
  769. else
  770. { // clear scene.apiworld if blueprint ID has been detached, so world details in builder panel are also cleared
  771. scene.apiWorld = null;
  772. }
  773. }
  774. if (scene.apiWorld != null)
  775. {
  776. ApiWorld w = (scene.apiWorld as ApiWorld);
  777. EditorGUILayout.LabelField("Version: " + w.version.ToString());
  778. EditorGUILayout.LabelField("Name: " + w.name);
  779. GUILayout.Label(w.description, infoGuiStyle, GUILayout.Width(400));
  780. EditorGUILayout.LabelField("Capacity: " + w.capacity);
  781. EditorGUILayout.LabelField("Release: " + w.releaseStatus);
  782. if (w.tags != null)
  783. foreach (var t in w.tags)
  784. EditorGUILayout.LabelField("Tag: " + t);
  785. if (w.supportedPlatforms == ApiModel.SupportedPlatforms.Android || w.supportedPlatforms == ApiModel.SupportedPlatforms.All)
  786. EditorGUILayout.LabelField("Supports: Android");
  787. if (w.supportedPlatforms == ApiModel.SupportedPlatforms.StandaloneWindows || w.supportedPlatforms == ApiModel.SupportedPlatforms.All)
  788. EditorGUILayout.LabelField("Supports: Windows");
  789. //w.imageUrl;
  790. }
  791. else
  792. {
  793. EditorGUILayout.LabelField("Version: " + "Unpublished");
  794. EditorGUILayout.LabelField("Name: " + "");
  795. GUILayout.Label("", infoGuiStyle, GUILayout.Width(400));
  796. EditorGUILayout.LabelField("Capacity: " + "");
  797. EditorGUILayout.LabelField("Release: " + "");
  798. //foreach (var t in w.tags)
  799. // EditorGUILayout.LabelField("Tag: " + "");
  800. //if (w.supportedPlatforms == ApiModel.SupportedPlatforms.Android || w.supportedPlatforms == ApiModel.SupportedPlatforms.All)
  801. // EditorGUILayout.LabelField("Supports: Android");
  802. //if (w.supportedPlatforms == ApiModel.SupportedPlatforms.StandaloneWindows || w.supportedPlatforms == ApiModel.SupportedPlatforms.All)
  803. // EditorGUILayout.LabelField("Supports: Windows");
  804. //w.imageUrl;
  805. }
  806. if (APIUser.CurrentUser.hasScriptingAccess && VRCSettings.Get().DisplayAdvancedSettings)
  807. {
  808. EditorGUILayout.Space();
  809. EditorGUILayout.BeginHorizontal();
  810. customNamespace = EditorGUILayout.TextField("Custom Namespace", customNamespace);
  811. if (GUILayout.Button("Regenerate"))
  812. {
  813. customNamespace = "vrc" + Path.GetRandomFileName().Replace(".", "");
  814. }
  815. EditorGUILayout.EndHorizontal();
  816. }
  817. GUILayout.EndVertical();
  818. }
  819. void OnGUIScene(VRCSDK2.VRC_SceneDescriptor scene)
  820. {
  821. EditorGUILayout.Space();
  822. GUILayout.BeginVertical(boxGuiStyle, GUILayout.Width(SdkWindowWidth));
  823. GUILayout.BeginHorizontal();
  824. GUILayout.BeginVertical(GUILayout.Width(300));
  825. EditorGUILayout.Space();
  826. GUILayout.Label("Offline Testing", infoGuiStyle);
  827. GUILayout.Label("Before uploading your world you may build and test it in the VRChat client. You won't be able to invite anyone from online but you can launch multiple of your own clients.", infoGuiStyle);
  828. GUILayout.EndVertical();
  829. GUILayout.BeginVertical(GUILayout.Width(200));
  830. EditorGUILayout.Space();
  831. numClients = EditorGUILayout.IntField("Number of Clients", numClients);
  832. GUI.enabled = (GUIErrors.Count == 0 && checkedForIssues);
  833. string lastUrl = VRC_SdkBuilder.GetLastUrl();
  834. bool lastBuildPresent = lastUrl != null;
  835. if (lastBuildPresent == false)
  836. GUI.enabled = false;
  837. if (VRCSettings.Get().DisplayAdvancedSettings)
  838. {
  839. if (GUILayout.Button("Last Build"))
  840. {
  841. VRC_SdkBuilder.shouldBuildUnityPackage = false;
  842. VRC_SdkBuilder.numClientsToLaunch = numClients;
  843. VRC_SdkBuilder.RunLastExportedSceneResource();
  844. }
  845. if (APIUser.CurrentUser.hasSuperPowers)
  846. {
  847. if (GUILayout.Button("Copy Test URL"))
  848. {
  849. TextEditor te = new TextEditor();
  850. te.text = lastUrl;
  851. te.SelectAll();
  852. te.Copy();
  853. }
  854. }
  855. }
  856. GUI.enabled = (GUIErrors.Count == 0 && checkedForIssues) || APIUser.CurrentUser.developerType == APIUser.DeveloperType.Internal;
  857. if (GUILayout.Button("Build & Test"))
  858. {
  859. EnvConfig.ConfigurePlayerSettings();
  860. VRC_SdkBuilder.shouldBuildUnityPackage = false;
  861. VRC.AssetExporter.CleanupUnityPackageExport(); // force unity package rebuild on next publish
  862. VRC_SdkBuilder.numClientsToLaunch = numClients;
  863. VRC_SdkBuilder.PreBuildBehaviourPackaging();
  864. VRC_SdkBuilder.ExportSceneResourceAndRun(customNamespace);
  865. }
  866. GUILayout.EndVertical();
  867. EditorGUILayout.EndHorizontal();
  868. EditorGUILayout.Space();
  869. GUILayout.EndVertical();
  870. EditorGUILayout.Space();
  871. GUILayout.BeginVertical(boxGuiStyle, GUILayout.Width(SdkWindowWidth));
  872. GUILayout.BeginHorizontal();
  873. GUILayout.BeginVertical(GUILayout.Width(300));
  874. EditorGUILayout.Space();
  875. GUILayout.Label("Online Publishing", infoGuiStyle);
  876. GUILayout.Label("In order for other people to enter your world in VRChat it must be built and published to our game servers.", infoGuiStyle);
  877. GUILayout.EndVertical();
  878. GUILayout.BeginVertical(GUILayout.Width(200));
  879. EditorGUILayout.Space();
  880. if (lastBuildPresent == false)
  881. GUI.enabled = false;
  882. if (VRCSettings.Get().DisplayAdvancedSettings)
  883. {
  884. if (GUILayout.Button("Last Build"))
  885. {
  886. if (APIUser.CurrentUser.canPublishWorlds)
  887. {
  888. VRC_SdkBuilder.shouldBuildUnityPackage = VRCSdkControlPanel.FutureProofPublishEnabled;
  889. VRC_SdkBuilder.UploadLastExportedSceneBlueprint();
  890. }
  891. else
  892. {
  893. ShowContentPublishPermissionsDialog();
  894. }
  895. }
  896. }
  897. GUI.enabled = (GUIErrors.Count == 0 && checkedForIssues) || APIUser.CurrentUser.developerType == APIUser.DeveloperType.Internal;
  898. if (GUILayout.Button("Build & Publish"))
  899. {
  900. if (APIUser.CurrentUser.canPublishWorlds)
  901. {
  902. EnvConfig.ConfigurePlayerSettings();
  903. VRC_SdkBuilder.shouldBuildUnityPackage = VRCSdkControlPanel.FutureProofPublishEnabled;
  904. VRC_SdkBuilder.PreBuildBehaviourPackaging();
  905. VRC_SdkBuilder.ExportAndUploadSceneBlueprint(customNamespace);
  906. }
  907. else
  908. {
  909. ShowContentPublishPermissionsDialog();
  910. }
  911. }
  912. GUILayout.EndVertical();
  913. EditorGUILayout.EndHorizontal();
  914. GUI.enabled = true;
  915. GUILayout.EndVertical();
  916. }
  917. void OnGUISceneLayer(int layer, string name, string description)
  918. {
  919. if (LayerMask.LayerToName(layer) != name)
  920. OnGUIError(null, "Layer " + layer + " must be renamed to '" + name + "'",
  921. delegate { EditorApplication.ExecuteMenuItem("Edit/Project Settings/Physics"); /*SettingsService.OpenProjectSettings("Project/Physics");*/ }, null);
  922. if (showLayerHelp)
  923. OnGUIInformation(null, "Layer " + layer + " " + name + "\n" + description);
  924. }
  925. bool IsAncestor(Transform ancestor, Transform child)
  926. {
  927. bool found = false;
  928. Transform thisParent = child.parent;
  929. while (thisParent != null)
  930. {
  931. if (thisParent == ancestor) { found = true; break; }
  932. thisParent = thisParent.parent;
  933. }
  934. return found;
  935. }
  936. List<Transform> FindBonesBetween(Transform top, Transform bottom)
  937. {
  938. List<Transform> list = new List<Transform>();
  939. if (top == null || bottom == null) return list;
  940. Transform bt = top.parent;
  941. while (bt != bottom && bt != null)
  942. {
  943. list.Add(bt);
  944. bt = bt.parent;
  945. }
  946. return list;
  947. }
  948. bool AnalyzeIK(VRCSDK2.VRC_AvatarDescriptor ad, GameObject avObj, Animator anim)
  949. {
  950. bool hasHead = false;
  951. bool hasFeet = false;
  952. bool hasHands = false;
  953. bool hasThreeFingers = false;
  954. //bool hasToes = false;
  955. bool correctSpineHierarchy = false;
  956. bool correctArmHierarchy = false;
  957. bool correctLegHierarchy = false;
  958. bool status = true;
  959. Transform head = anim.GetBoneTransform(HumanBodyBones.Head);
  960. Transform lfoot = anim.GetBoneTransform(HumanBodyBones.LeftFoot);
  961. Transform rfoot = anim.GetBoneTransform(HumanBodyBones.RightFoot);
  962. Transform lhand = anim.GetBoneTransform(HumanBodyBones.LeftHand);
  963. Transform rhand = anim.GetBoneTransform(HumanBodyBones.RightHand);
  964. hasHead = null != head;
  965. hasFeet = (null != lfoot && null != rfoot);
  966. hasHands = (null != lhand && null != rhand);
  967. if (!hasHead || !hasFeet || !hasHands)
  968. {
  969. OnGUIError(ad, "Humanoid avatar must have head, hands and feet bones mapped.", delegate () { Selection.activeObject = anim.gameObject; }, null);
  970. return false;
  971. }
  972. Transform lthumb = anim.GetBoneTransform(HumanBodyBones.LeftThumbProximal);
  973. Transform lindex = anim.GetBoneTransform(HumanBodyBones.LeftIndexProximal);
  974. Transform lmiddle = anim.GetBoneTransform(HumanBodyBones.LeftMiddleProximal);
  975. Transform rthumb = anim.GetBoneTransform(HumanBodyBones.RightThumbProximal);
  976. Transform rindex = anim.GetBoneTransform(HumanBodyBones.RightIndexProximal);
  977. Transform rmiddle = anim.GetBoneTransform(HumanBodyBones.RightMiddleProximal);
  978. hasThreeFingers = null != lthumb && null != lindex && null != lmiddle && null != rthumb && null != rindex && null != rmiddle;
  979. if (!hasThreeFingers)
  980. {
  981. // although its only a warning, we return here because the rest
  982. // of the analysis is for VRIK
  983. OnGUIWarning(ad, "Thumb, Index, and Middle finger bones are not mapped, Full-Body IK will be disabled.", delegate () { Selection.activeObject = anim.gameObject; }, null);
  984. status = false;
  985. }
  986. Transform pelvis = anim.GetBoneTransform(HumanBodyBones.Hips);
  987. Transform chest = anim.GetBoneTransform(HumanBodyBones.Chest);
  988. Transform upperchest = anim.GetBoneTransform(HumanBodyBones.UpperChest);
  989. Transform torso = anim.GetBoneTransform(HumanBodyBones.Spine);
  990. Transform neck = anim.GetBoneTransform(HumanBodyBones.Neck);
  991. Transform lclav = anim.GetBoneTransform(HumanBodyBones.LeftShoulder);
  992. Transform rclav = anim.GetBoneTransform(HumanBodyBones.RightShoulder);
  993. if (null == neck || null == lclav || null == rclav || null == pelvis || null == torso || null == chest)
  994. {
  995. OnGUIError(ad, "Spine hierarchy missing elements, make sure that Pelvis, Spine, Chest, Neck and Shoulders are mapped.", delegate () { Selection.activeObject = anim.gameObject; }, null);
  996. return false;
  997. }
  998. if (null != upperchest)
  999. correctSpineHierarchy = lclav.parent == upperchest && rclav.parent == upperchest && neck.parent == upperchest;
  1000. else
  1001. correctSpineHierarchy = lclav.parent == chest && rclav.parent == chest && neck.parent == chest;
  1002. if (!correctSpineHierarchy)
  1003. {
  1004. OnGUIError(ad, "Spine hierarchy incorrect. Make sure that the parent of both Shoulders and the Neck is the Chest (or UpperChest if set).", delegate () { Selection.activeObject = anim.gameObject; }, null);
  1005. return false;
  1006. }
  1007. Transform lshoulder = anim.GetBoneTransform(HumanBodyBones.LeftUpperArm);
  1008. Transform lelbow = anim.GetBoneTransform(HumanBodyBones.LeftLowerArm);
  1009. Transform rshoulder = anim.GetBoneTransform(HumanBodyBones.RightUpperArm);
  1010. Transform relbow = anim.GetBoneTransform(HumanBodyBones.RightLowerArm);
  1011. correctArmHierarchy = lshoulder.GetChild(0) == lelbow && lelbow.GetChild(0) == lhand &&
  1012. rshoulder.GetChild(0) == relbow && relbow.GetChild(0) == rhand;
  1013. if (!correctArmHierarchy)
  1014. {
  1015. OnGUIWarning(ad, "LowerArm is not first child of UpperArm or Hand is not first child of LowerArm: you may have problems with Forearm rotations.", delegate () { Selection.activeObject = anim.gameObject; }, null);
  1016. status = false;
  1017. }
  1018. Transform lhip = anim.GetBoneTransform(HumanBodyBones.LeftUpperLeg);
  1019. Transform lknee = anim.GetBoneTransform(HumanBodyBones.LeftLowerLeg);
  1020. Transform rhip = anim.GetBoneTransform(HumanBodyBones.RightUpperLeg);
  1021. Transform rknee = anim.GetBoneTransform(HumanBodyBones.RightLowerLeg);
  1022. correctLegHierarchy = lhip.GetChild(0) == lknee && lknee.GetChild(0) == lfoot &&
  1023. rhip.GetChild(0) == rknee && rknee.GetChild(0) == rfoot;
  1024. if (!correctLegHierarchy)
  1025. {
  1026. OnGUIWarning(ad, "LowerLeg is not first child of UpperLeg or Foot is not first child of LowerLeg: you may have problems with Shin rotations.", delegate () { Selection.activeObject = anim.gameObject; }, null);
  1027. status = false;
  1028. }
  1029. if (!(IsAncestor(pelvis, rfoot) && IsAncestor(pelvis, lfoot) && IsAncestor(pelvis, lhand) || IsAncestor(pelvis, rhand) || IsAncestor(pelvis, lhand)))
  1030. {
  1031. OnGUIWarning(ad, "This avatar has a split heirarchy (Hips bone is not the ancestor of all humanoid bones). IK may not work correctly.", delegate () { Selection.activeObject = anim.gameObject; }, null);
  1032. status = false;
  1033. }
  1034. // if thigh bone rotations diverge from 180 from hip bone rotations, full-body tracking/ik does not work well
  1035. Vector3 hipLocalUp = pelvis.InverseTransformVector(Vector3.up);
  1036. Vector3 legLDir = lhip.TransformVector(hipLocalUp);
  1037. Vector3 legRDir = rhip.TransformVector(hipLocalUp);
  1038. float angL = Vector3.Angle(Vector3.up, legLDir);
  1039. float angR = Vector3.Angle(Vector3.up, legRDir);
  1040. if (angL < 175f || angR < 175f)
  1041. {
  1042. string angle = string.Format("{0:F1}", Mathf.Min(angL, angR));
  1043. OnGUIWarning(ad, "The angle between pelvis and thigh bones should be close to 180 degrees (this avatar's angle is " + angle + "). Your avatar may not work well with full-body IK and Tracking.", delegate () { Selection.activeObject = anim.gameObject; }, null);
  1044. status = false;
  1045. }
  1046. return status;
  1047. }
  1048. void ShowRestrictedComponents(IEnumerable<Component> componentsToRemove)
  1049. {
  1050. List<GameObject> gos = new List<GameObject>();
  1051. foreach (var c in componentsToRemove)
  1052. gos.Add(c.gameObject);
  1053. Selection.objects = gos.ToArray();
  1054. }
  1055. void FixRestrictedComponents(IEnumerable<Component> componentsToRemove)
  1056. {
  1057. List<GameObject> gos = new List<GameObject>();
  1058. foreach (var c in componentsToRemove)
  1059. {
  1060. gos.Add(c.gameObject);
  1061. Destroy(c);
  1062. }
  1063. }
  1064. void OnGUIAvatarCheck(VRCSDK2.VRC_AvatarDescriptor avatar)
  1065. {
  1066. string vrcFilePath = WWW.UnEscapeURL(UnityEditor.EditorPrefs.GetString("currentBuildingAssetBundlePath"));
  1067. int fileSize = 0;
  1068. if (!string.IsNullOrEmpty(vrcFilePath) && ValidationHelpers.CheckIfAssetBundleFileTooLarge(ContentType.Avatar, vrcFilePath, out fileSize))
  1069. {
  1070. OnGUIWarning(avatar, ValidationHelpers.GetAssetBundleOverSizeLimitMessageSDKWarning(ContentType.Avatar, fileSize), delegate () { Selection.activeObject = avatar.gameObject; }, null);
  1071. }
  1072. AvatarPerformanceStats perfStats = new AvatarPerformanceStats();
  1073. AvatarPerformance.CalculatePerformanceStats(avatar.Name, avatar.gameObject, perfStats);
  1074. OnGUIPerformanceInfo(avatar, perfStats, AvatarPerformanceCategory.Overall);
  1075. OnGUIPerformanceInfo(avatar, perfStats, AvatarPerformanceCategory.PolyCount);
  1076. OnGUIPerformanceInfo(avatar, perfStats, AvatarPerformanceCategory.AABB);
  1077. var eventHandler = avatar.GetComponentInChildren<VRCSDK2.VRC_EventHandler>();
  1078. if (eventHandler != null)
  1079. {
  1080. OnGUIError(avatar, "This avatar contains an EventHandler, which is not currently supported in VRChat.", delegate () { Selection.activeObject = avatar.gameObject; }, null);
  1081. }
  1082. if (avatar.lipSync == VRCSDK2.VRC_AvatarDescriptor.LipSyncStyle.VisemeBlendShape && avatar.VisemeSkinnedMesh == null)
  1083. OnGUIError(avatar, "This avatar uses Visemes but the Face Mesh is not specified.", delegate () { Selection.activeObject = avatar.gameObject; }, null);
  1084. var anim = avatar.GetComponent<Animator>();
  1085. if (anim == null)
  1086. {
  1087. OnGUIWarning(avatar, "This avatar does not contain an animator, and will not animate in VRChat.", delegate () { Selection.activeObject = avatar.gameObject; }, null);
  1088. }
  1089. else if (anim.isHuman == false)
  1090. {
  1091. OnGUIWarning(avatar, "This avatar is not imported as a humanoid rig and will not play VRChat's provided animation set.", delegate () { Selection.activeObject = avatar.gameObject; }, null);
  1092. }
  1093. else if (avatar.gameObject.activeInHierarchy == false)
  1094. {
  1095. OnGUIError(avatar, "Your avatar is disabled in the scene hierarchy!", delegate () { Selection.activeObject = avatar.gameObject; }, null);
  1096. }
  1097. else
  1098. {
  1099. Transform foot = anim.GetBoneTransform(HumanBodyBones.LeftFoot);
  1100. Transform shoulder = anim.GetBoneTransform(HumanBodyBones.LeftUpperArm);
  1101. if (foot == null)
  1102. OnGUIError(avatar, "Your avatar is humanoid, but it's feet aren't specified!", delegate () { Selection.activeObject = avatar.gameObject; }, null);
  1103. if (shoulder == null)
  1104. OnGUIError(avatar, "Your avatar is humanoid, but it's upper arms aren't specified!", delegate () { Selection.activeObject = avatar.gameObject; }, null);
  1105. if (foot != null && shoulder != null)
  1106. {
  1107. Vector3 footPos = foot.position - avatar.transform.position;
  1108. if (footPos.y < 0)
  1109. OnGUIWarning(avatar, "Avatar feet are beneath the avatar's origin (the floor). That's probably not what you want.", delegate () { Selection.activeObject = avatar.gameObject; }, null);
  1110. Vector3 shoulderPosition = shoulder.position - avatar.transform.position;
  1111. if (shoulderPosition.y < 0.2f)
  1112. OnGUIError(avatar, "This avatar is too short. The minimum is 20cm shoulder height.", delegate () { Selection.activeObject = avatar.gameObject; }, null);
  1113. else if (shoulderPosition.y < 1.0f)
  1114. OnGUIWarning(avatar, "This avatar is short. This is probably shorter than you want.", delegate () { Selection.activeObject = avatar.gameObject; }, null);
  1115. else if (shoulderPosition.y > 5.0f)
  1116. OnGUIWarning(avatar, "This avatar is too tall. The maximum is 5m shoulder height.", delegate () { Selection.activeObject = avatar.gameObject; }, null);
  1117. else if (shoulderPosition.y > 2.5f)
  1118. OnGUIWarning(avatar, "This avatar is tall. This is probably taller than you want.", delegate () { Selection.activeObject = avatar.gameObject; }, null);
  1119. }
  1120. if (AnalyzeIK(avatar, avatar.gameObject, anim) == false)
  1121. OnGUILink(avatar, "See Avatar Rig Requirements for more information.", kAvatarRigRequirementsURL);
  1122. }
  1123. IEnumerable<Component> componentsToRemove = AvatarValidation.FindIllegalComponents(avatar.gameObject);
  1124. HashSet<string> componentsToRemoveNames = new HashSet<string>();
  1125. foreach (Component c in componentsToRemove)
  1126. {
  1127. if (componentsToRemoveNames.Contains(c.GetType().Name) == false)
  1128. componentsToRemoveNames.Add(c.GetType().Name);
  1129. }
  1130. if (componentsToRemoveNames.Count > 0)
  1131. OnGUIError(avatar, "The following component types are found on the Avatar and will be removed by the client: " + string.Join(", ", componentsToRemoveNames.ToArray()), delegate () { ShowRestrictedComponents(componentsToRemove); }, delegate () { FixRestrictedComponents(componentsToRemove); });
  1132. if (AvatarValidation.EnforceAudioSourceLimits(avatar.gameObject).Count > 0)
  1133. OnGUIWarning(avatar, "Audio sources found on Avatar, they will be adjusted to safe limits, if necessary.", delegate () { Selection.activeObject = avatar.gameObject; }, null);
  1134. if (AvatarValidation.EnforceAvatarStationLimits(avatar.gameObject).Count > 0)
  1135. OnGUIWarning(avatar, "Stations found on Avatar, they will be adjusted to safe limits, if necessary.", delegate () { Selection.activeObject = avatar.gameObject; }, null);
  1136. if (avatar.gameObject.GetComponentInChildren<Camera>() != null)
  1137. OnGUIWarning(avatar, "Cameras are removed from non-local avatars at runtime.", delegate () { Selection.activeObject = avatar.gameObject; }, null);
  1138. #if UNITY_ANDROID
  1139. IEnumerable<Shader> illegalShaders = AvatarValidation.FindIllegalShaders(avatar.gameObject);
  1140. foreach (Shader s in illegalShaders)
  1141. {
  1142. OnGUIError(avatar, "Avatar uses unsupported shader '" + s.name + "'. You can only use the shaders provided in 'VRChat/Mobile' for Quest avatars.", delegate () { Selection.activeObject = avatar.gameObject; }, null);
  1143. }
  1144. #endif
  1145. foreach (AvatarPerformanceCategory perfCategory in System.Enum.GetValues(typeof(AvatarPerformanceCategory)))
  1146. {
  1147. if (perfCategory == AvatarPerformanceCategory.Overall ||
  1148. perfCategory == AvatarPerformanceCategory.PolyCount ||
  1149. perfCategory == AvatarPerformanceCategory.AABB ||
  1150. perfCategory == AvatarPerformanceCategory.AvatarPerformanceCategoryCount)
  1151. {
  1152. continue;
  1153. }
  1154. OnGUIPerformanceInfo(avatar, perfStats, perfCategory);
  1155. }
  1156. OnGUILink(avatar, "Avatar Optimization Tips", kAvatarOptimizationTipsURL);
  1157. }
  1158. void OnGUIPerformanceInfo(VRCSDK2.VRC_AvatarDescriptor avatar, AvatarPerformanceStats perfStats, AvatarPerformanceCategory perfCategory)
  1159. {
  1160. string text;
  1161. PerformanceInfoDisplayLevel displayLevel;
  1162. PerformanceRating rating = perfStats.GetPerformanceRatingForCategory(perfCategory);
  1163. SDKPerformanceDisplay.GetSDKPerformanceInfoText(perfStats, perfCategory, out text, out displayLevel);
  1164. switch(displayLevel)
  1165. {
  1166. case PerformanceInfoDisplayLevel.None:
  1167. {
  1168. break;
  1169. }
  1170. case PerformanceInfoDisplayLevel.Verbose:
  1171. {
  1172. if(showAvatarPerformanceDetails)
  1173. {
  1174. OnGUIStat(avatar, text, rating);
  1175. }
  1176. break;
  1177. }
  1178. case PerformanceInfoDisplayLevel.Info:
  1179. {
  1180. OnGUIStat(avatar, text, rating);
  1181. break;
  1182. }
  1183. case PerformanceInfoDisplayLevel.Warning:
  1184. {
  1185. OnGUIStat(avatar, text, rating);
  1186. break;
  1187. }
  1188. case PerformanceInfoDisplayLevel.Error:
  1189. {
  1190. OnGUIStat(avatar, text, rating);
  1191. OnGUIError(avatar, text, delegate () { Selection.activeObject = avatar.gameObject; }, null);
  1192. break;
  1193. }
  1194. default:
  1195. {
  1196. OnGUIError(avatar, "Unknown performance display level.", delegate () { Selection.activeObject = avatar.gameObject; }, null);
  1197. break;
  1198. }
  1199. }
  1200. }
  1201. void OnGUIAvatar(VRCSDK2.VRC_AvatarDescriptor avatar)
  1202. {
  1203. EditorGUILayout.Space();
  1204. //GUILayout.BeginVertical(boxGuiStyle, GUILayout.Width(SdkWindowWidth));
  1205. GUILayout.BeginHorizontal();
  1206. GUILayout.BeginVertical(GUILayout.Width(300));
  1207. EditorGUILayout.Space();
  1208. GUILayout.Label("Online Publishing", infoGuiStyle);
  1209. GUILayout.Label("In order for other people to see your avatar in VRChat it must be built and published to our game servers.", infoGuiStyle);
  1210. GUILayout.EndVertical();
  1211. GUILayout.BeginVertical(GUILayout.Width(200));
  1212. EditorGUILayout.Space();
  1213. GUI.enabled = (GUIErrors.Count == 0 && checkedForIssues) || APIUser.CurrentUser.developerType == APIUser.DeveloperType.Internal;
  1214. if (GUILayout.Button("Build & Publish"))
  1215. {
  1216. if (APIUser.CurrentUser.canPublishAvatars)
  1217. {
  1218. VRC_SdkBuilder.shouldBuildUnityPackage = VRCSdkControlPanel.FutureProofPublishEnabled;
  1219. VRC_SdkBuilder.ExportAndUploadAvatarBlueprint(avatar.gameObject);
  1220. }
  1221. else
  1222. {
  1223. ShowContentPublishPermissionsDialog();
  1224. }
  1225. }
  1226. GUI.enabled = true;
  1227. GUILayout.EndVertical();
  1228. EditorGUILayout.EndHorizontal();
  1229. //GUILayout.EndVertical();
  1230. }
  1231. public static void ShowContentPublishPermissionsDialog()
  1232. {
  1233. if (!RemoteConfig.IsInitialized())
  1234. {
  1235. RemoteConfig.Init(() => ShowContentPublishPermissionsDialog());
  1236. return;
  1237. }
  1238. string message = RemoteConfig.GetString("sdkNotAllowedToPublishMessage");
  1239. int result = UnityEditor.EditorUtility.DisplayDialogComplex("VRChat SDK", message, "Developer FAQ", "VRChat Discord", "OK");
  1240. if (result == 0)
  1241. {
  1242. ShowDeveloperFAQ();
  1243. }
  1244. if (result == 1)
  1245. {
  1246. ShowVRChatDiscord();
  1247. }
  1248. }
  1249. }