#if UNITY_EDITOR using System.Collections.Generic; using System.IO; using TMPro; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; namespace ShrinkTutorial.Editor { public static class ShrinkTutorialSampleSceneBuilder { private const string ScenePath = "Assets/Scenes/ShrinkTutorialSample.unity"; private const string SettingsFolder = "Assets/Modules/ShrinkTutorial/Samples/Resources"; private const string SettingsPath = SettingsFolder + "/ShrinkTutorialSettings.asset"; private const string DatabasePath = "Assets/Modules/ShrinkTutorial/Samples/ShrinkTutorialSampleDatabase.asset"; private const string TutorialPath = "Assets/Modules/ShrinkTutorial/Samples/ShrinkTutorialSampleTutorial.asset"; [MenuItem("ShrinkSDK/引导/创建示例场景")] public static void BuildSampleScene() { EnsureFolder("Assets/Modules/ShrinkTutorial"); EnsureFolder("Assets/Modules/ShrinkTutorial/Samples"); EnsureFolder(SettingsFolder); EnsureFolder("Assets/Scenes"); var settings = EnsureSettings(); var database = EnsureDatabase(); var tutorial = EnsureTutorial(); database.hideFlags = HideFlags.None; settings.database = database; EditorUtility.SetDirty(settings); UpdateTutorialData(tutorial); SetDatabaseContent(database, tutorial); var scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single); scene.name = "ShrinkTutorialSample"; CreateCamera(); var canvas = CreateCanvas(); var root = new GameObject("Tutorial Sample Root"); var controller = root.AddComponent(); var openButton = CreateButton(canvas.transform, "OpenInventoryButton", "Open Inventory", new Vector2(0f, 110f), new Vector2(240f, 64f)); var statusText = CreateText(canvas.transform, "StatusText", "Waiting for tutorial start.", 26f, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 42f), new Vector2(900f, 48f)); statusText.alignment = TextAlignmentOptions.Center; statusText.color = new Color(0.92f, 0.92f, 0.92f, 1f); CreateText(canvas.transform, "TitleText", "ShrinkTutorial Sample Scene", 42f, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -70f), new Vector2(900f, 60f)) .alignment = TextAlignmentOptions.Center; CreateText(canvas.transform, "DescText", "This scene demonstrates static targets, dynamic anchors, and custom-event completion.", 24f, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -120f), new Vector2(1100f, 50f)) .alignment = TextAlignmentOptions.Center; var inventoryPanel = CreatePanel(canvas.transform, "InventoryPanel", new Vector2(0f, -60f), new Vector2(720f, 360f), new Color(0.12f, 0.16f, 0.22f, 0.96f)); inventoryPanel.gameObject.SetActive(false); CreateText(inventoryPanel, "InventoryTitle", "Inventory Panel", 34f, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -34f), new Vector2(300f, 48f)) .alignment = TextAlignmentOptions.Center; CreateText(inventoryPanel, "InventoryHint", "Create the runtime reward first, then click the spawned item.", 22f, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -74f), new Vector2(520f, 40f)) .alignment = TextAlignmentOptions.Center; var spawnButton = CreateButton(inventoryPanel, "SpawnRewardButton", "Spawn Reward Item", new Vector2(0f, 88f), new Vector2(250f, 58f)); var rewardContainer = new GameObject("RewardContainer", typeof(RectTransform), typeof(VerticalLayoutGroup), typeof(ContentSizeFitter)); var rewardContainerRect = rewardContainer.GetComponent(); rewardContainerRect.SetParent(inventoryPanel, false); rewardContainerRect.anchorMin = new Vector2(0.5f, 0f); rewardContainerRect.anchorMax = new Vector2(0.5f, 0f); rewardContainerRect.pivot = new Vector2(0.5f, 0f); rewardContainerRect.anchoredPosition = new Vector2(0f, 24f); rewardContainerRect.sizeDelta = new Vector2(480f, 120f); var layoutGroup = rewardContainer.GetComponent(); layoutGroup.childAlignment = TextAnchor.UpperCenter; layoutGroup.childControlWidth = true; layoutGroup.childControlHeight = true; layoutGroup.childForceExpandHeight = false; layoutGroup.childForceExpandWidth = true; layoutGroup.spacing = 12f; var fitter = rewardContainer.GetComponent(); fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize; AssignControllerReferences(controller, openButton, spawnButton, inventoryPanel.gameObject, rewardContainerRect, statusText); EditorSceneManager.SaveScene(scene, ScenePath); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); EditorSceneManager.OpenScene(ScenePath); Selection.activeObject = AssetDatabase.LoadAssetAtPath(ScenePath); Debug.Log("[ShrinkTutorial] Sample scene created: Assets/Scenes/ShrinkTutorialSample.unity"); } [MenuItem("ShrinkSDK/Tutorial/Create Sample Scene")] public static void BuildSampleSceneAlias() { BuildSampleScene(); } private static ShrinkTutorialSettings EnsureSettings() { var settings = AssetDatabase.LoadAssetAtPath(SettingsPath); if (settings) return settings; settings = ScriptableObject.CreateInstance(); settings.autoCreateManager = true; settings.dontDestroyOnLoad = true; settings.showSkipButton = true; AssetDatabase.CreateAsset(settings, SettingsPath); return settings; } private static ShrinkTutorialDatabase EnsureDatabase() { var database = AssetDatabase.LoadAssetAtPath(DatabasePath); if (database) return database; database = ScriptableObject.CreateInstance(); AssetDatabase.CreateAsset(database, DatabasePath); return database; } private static ShrinkTutorialData EnsureTutorial() { var tutorial = AssetDatabase.LoadAssetAtPath(TutorialPath); if (tutorial) return tutorial; tutorial = ScriptableObject.CreateInstance(); AssetDatabase.CreateAsset(tutorial, TutorialPath); return tutorial; } private static void UpdateTutorialData(ShrinkTutorialData tutorial) { tutorial.tutorialId = ShrinkTutorialSampleSceneController.TutorialId; tutorial.priority = 100; tutorial.prerequisiteIds = new string[0]; tutorial.steps = new List { new() { targetMode = ShrinkTutorialTargetMode.Path, targetPath = "Tutorial Sample Canvas/OpenInventoryButton", completeCondition = ShrinkTutorialCompleteCondition.ClickTarget, fallbackText = "Click here to open the inventory panel.", dialogAnchor = ShrinkTutorialDialogAnchor.Bottom }, new() { targetMode = ShrinkTutorialTargetMode.Path, targetPath = "Tutorial Sample Canvas/InventoryPanel/SpawnRewardButton", completeCondition = ShrinkTutorialCompleteCondition.ClickTarget, fallbackText = "The inventory is open. Click 'Spawn Reward Item' to demonstrate dynamic-anchor waiting.", dialogAnchor = ShrinkTutorialDialogAnchor.Top }, new() { targetMode = ShrinkTutorialTargetMode.AnchorId, anchorId = ShrinkTutorialSampleSceneController.RewardAnchorId, waitForTarget = true, waitTimeout = 10f, completeCondition = ShrinkTutorialCompleteCondition.CustomEvent, customEventName = ShrinkTutorialSampleSceneController.RewardClaimEventName, fallbackText = "This button was created at runtime. Click it and the step will complete via a custom event.", dialogAnchor = ShrinkTutorialDialogAnchor.Top }, new() { targetMode = ShrinkTutorialTargetMode.None, completeCondition = ShrinkTutorialCompleteCondition.AnyClick, fallbackText = "Demo finished. You completed the static-target, dynamic-anchor, and custom-event flow. Click anywhere to close.", dialogAnchor = ShrinkTutorialDialogAnchor.Auto, showArrow = false, maskShape = ShrinkTutorialMaskShape.None } }; EditorUtility.SetDirty(tutorial); } private static void SetDatabaseContent(ShrinkTutorialDatabase database, ShrinkTutorialData tutorial) { var serializedObject = new SerializedObject(database); var tutorialsProperty = serializedObject.FindProperty("tutorials"); tutorialsProperty.arraySize = 1; tutorialsProperty.GetArrayElementAtIndex(0).objectReferenceValue = tutorial; serializedObject.ApplyModifiedPropertiesWithoutUndo(); EditorUtility.SetDirty(database); } private static void AssignControllerReferences( ShrinkTutorialSampleSceneController controller, Button openButton, Button spawnButton, GameObject inventoryPanel, RectTransform rewardContainer, TextMeshProUGUI statusText) { var serializedObject = new SerializedObject(controller); serializedObject.FindProperty("openInventoryButton").objectReferenceValue = openButton; serializedObject.FindProperty("spawnRewardButton").objectReferenceValue = spawnButton; serializedObject.FindProperty("inventoryPanel").objectReferenceValue = inventoryPanel; serializedObject.FindProperty("rewardContainer").objectReferenceValue = rewardContainer; serializedObject.FindProperty("statusText").objectReferenceValue = statusText; serializedObject.ApplyModifiedPropertiesWithoutUndo(); EditorUtility.SetDirty(controller); } private static void CreateCamera() { var cameraObject = new GameObject("Main Camera", typeof(Camera)); cameraObject.tag = "MainCamera"; var camera = cameraObject.GetComponent(); camera.clearFlags = CameraClearFlags.SolidColor; camera.backgroundColor = new Color(0.06f, 0.08f, 0.12f, 1f); camera.orthographic = true; } private static Canvas CreateCanvas() { var canvasObject = new GameObject("Tutorial Sample Canvas", typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster)); var canvas = canvasObject.GetComponent(); canvas.renderMode = RenderMode.ScreenSpaceOverlay; var scaler = canvasObject.GetComponent(); scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; scaler.referenceResolution = new Vector2(1920f, 1080f); scaler.matchWidthOrHeight = 0.5f; var background = CreatePanel(canvasObject.transform, "Background", Vector2.zero, new Vector2(0f, 0f), new Color(0.06f, 0.08f, 0.12f, 1f)); Stretch(background); background.SetAsFirstSibling(); return canvas; } private static RectTransform CreatePanel(Transform parent, string name, Vector2 anchoredPosition, Vector2 size, Color color) { var panelObject = new GameObject(name, typeof(RectTransform), typeof(Image)); var rectTransform = panelObject.GetComponent(); rectTransform.SetParent(parent, false); rectTransform.anchorMin = new Vector2(0.5f, 0.5f); rectTransform.anchorMax = new Vector2(0.5f, 0.5f); rectTransform.pivot = new Vector2(0.5f, 0.5f); rectTransform.anchoredPosition = anchoredPosition; rectTransform.sizeDelta = size; var image = panelObject.GetComponent(); image.color = color; return rectTransform; } private static Button CreateButton(Transform parent, string name, string label, Vector2 anchoredPosition, Vector2 size) { var buttonObject = new GameObject(name, typeof(RectTransform), typeof(Image), typeof(Button)); var rectTransform = buttonObject.GetComponent(); rectTransform.SetParent(parent, false); rectTransform.anchorMin = new Vector2(0.5f, 0.5f); rectTransform.anchorMax = new Vector2(0.5f, 0.5f); rectTransform.pivot = new Vector2(0.5f, 0.5f); rectTransform.anchoredPosition = anchoredPosition; rectTransform.sizeDelta = size; var image = buttonObject.GetComponent(); image.color = new Color(0.2f, 0.42f, 0.72f, 1f); var button = buttonObject.GetComponent