using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.EventSystems; using Yarn.Compiler; using Yarn.Unity; using Yarn.Unity.Editor; public static class YarnDemoSetup { const string YarnProjectPath = "Assets/Dialogue/NightclubArcadia.yarnproject"; const string ScenePath = NightclubArcadia.EditorTools.ScenePaths.Sandbox; [MenuItem("Tools/Nightclub Arcadia/Set Up Yarn Sandbox Scene")] public static void SetUpYarnDialogueTest() { if (NightclubArcadia.EditorTools.LegacySceneSetup.Blocked("Set Up Yarn Sandbox Scene", "generate a throwaway scene whose dialogue runner duplicates the real one")) return; // This menu BUILDS A SCENE FROM SCRATCH and saves over ScenePath. Pointed at a // real level it silently destroys it, which is exactly what happened when it // was briefly retargeted at SC101_ConferenceHall. The guard is the fix; do not // remove it, and do not point ScenePath outside Scenes/Dev. if (!NightclubArcadia.EditorTools.ScenePaths.IsDisposable(ScenePath)) { Debug.LogError($"Refusing to run: '{ScenePath}' is not under Assets/Scenes/Dev/. " + "This menu overwrites its target scene wholesale."); return; } if (!AssetDatabase.IsValidFolder("Assets/Scenes/Dev")) { AssetDatabase.CreateFolder("Assets/Scenes", "Dev"); } if (!AssetDatabase.IsValidFolder("Assets/TextMesh Pro")) { Debug.LogWarning("TextMesh Pro essential resources are missing. Run Window → TextMeshPro → Import TMP Essential Resources."); } if (!AssetDatabase.IsValidFolder("Assets/Dialogue")) { AssetDatabase.CreateFolder("Assets", "Dialogue"); } if (!AssetDatabase.IsValidFolder("Assets/Scenes")) { AssetDatabase.CreateFolder("Assets", "Scenes"); } var yarnProject = AssetDatabase.LoadAssetAtPath(YarnProjectPath); if (yarnProject == null) { // YarnProjectUtility.CreateDefaultYarnProject is internal, so // construct the same default project here. var project = new Project { ExcludeFilePatterns = new[] { "**/*~/*", "./Samples/Yarn Spinner*/*" }, }; YarnEditorUtility.CreateYarnProject(YarnProjectPath, project); AssetDatabase.Refresh(); yarnProject = AssetDatabase.LoadAssetAtPath(YarnProjectPath); } var scene = EditorSceneManager.NewScene(NewSceneSetup.DefaultGameObjects, NewSceneMode.Single); var runner = CreateDialogueRunnerMenuItem.CreateDialogueRunner(); var so = new SerializedObject(runner); so.FindProperty("yarnProject").objectReferenceValue = yarnProject; so.ApplyModifiedPropertiesWithoutUndo(); // CreateDialogueRunner returns a component on a PREFAB INSTANCE of the package's // Dialogue System prefab. Applying a SerializedProperty on one of those does not // always record the override, and an unrecorded override is dropped on save — // which produced a sandbox whose runner had no project, auto-started, and // failed with "Can't start dialogue: no Yarn Project has been configured." if (PrefabUtility.IsPartOfPrefabInstance(runner)) { PrefabUtility.RecordPrefabInstancePropertyModifications(runner); } runner.autoStart = true; runner.startNode = "Start"; EditorUtility.SetDirty(runner); EditorSceneManager.MarkSceneDirty(scene); // Postcondition. This menu exists to produce a runnable scene; a scene whose // runner has no project is worse than no scene, because it fails at Play time // with an error that points nowhere near here. if (new SerializedObject(runner).FindProperty("yarnProject").objectReferenceValue == null) { Debug.LogError("Yarn sandbox setup failed: the DialogueRunner has no Yarn Project. " + "The generated scene will error on Play. Not saving."); return; } #if UNITY_2023_1_OR_NEWER var eventSystems = Object.FindObjectsByType(FindObjectsInactive.Include); #else var eventSystems = Object.FindObjectsOfType(true); #endif foreach (var eventSystem in eventSystems) { if (eventSystem.GetComponent() != null) { continue; } #if ENABLE_INPUT_SYSTEM eventSystem.gameObject.AddComponent(); #endif } EditorSceneManager.SaveScene(scene, ScenePath); AssetDatabase.SaveAssets(); Debug.Log($"Yarn dialogue test setup complete. Scene saved to {ScenePath}"); } }