Files
ShrinkTutorial/Editor/Samples/ShrinkTutorialSampleSceneBuilder.cs
T
cneicy 1180be88a6
Publish UPM package / publish (push) Failing after 1s
chore: initialize standalone UPM package
2026-08-26 02:50:43 +08:00

344 lines
16 KiB
C#

#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<ShrinkTutorialSampleSceneController>();
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<RectTransform>();
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<VerticalLayoutGroup>();
layoutGroup.childAlignment = TextAnchor.UpperCenter;
layoutGroup.childControlWidth = true;
layoutGroup.childControlHeight = true;
layoutGroup.childForceExpandHeight = false;
layoutGroup.childForceExpandWidth = true;
layoutGroup.spacing = 12f;
var fitter = rewardContainer.GetComponent<ContentSizeFitter>();
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<SceneAsset>(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<ShrinkTutorialSettings>(SettingsPath);
if (settings)
return settings;
settings = ScriptableObject.CreateInstance<ShrinkTutorialSettings>();
settings.autoCreateManager = true;
settings.dontDestroyOnLoad = true;
settings.showSkipButton = true;
AssetDatabase.CreateAsset(settings, SettingsPath);
return settings;
}
private static ShrinkTutorialDatabase EnsureDatabase()
{
var database = AssetDatabase.LoadAssetAtPath<ShrinkTutorialDatabase>(DatabasePath);
if (database)
return database;
database = ScriptableObject.CreateInstance<ShrinkTutorialDatabase>();
AssetDatabase.CreateAsset(database, DatabasePath);
return database;
}
private static ShrinkTutorialData EnsureTutorial()
{
var tutorial = AssetDatabase.LoadAssetAtPath<ShrinkTutorialData>(TutorialPath);
if (tutorial)
return tutorial;
tutorial = ScriptableObject.CreateInstance<ShrinkTutorialData>();
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<ShrinkTutorialStep>
{
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>();
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>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
var scaler = canvasObject.GetComponent<CanvasScaler>();
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>();
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>();
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>();
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>();
image.color = new Color(0.2f, 0.42f, 0.72f, 1f);
var button = buttonObject.GetComponent<Button>();
button.targetGraphic = image;
var text = CreateText(buttonObject.transform, "Label", label, 28f,
Vector2.zero, Vector2.one, new Vector2(0.5f, 0.5f), Vector2.zero, Vector2.zero);
text.alignment = TextAlignmentOptions.Center;
return button;
}
private static TextMeshProUGUI CreateText(
Transform parent,
string name,
string text,
float fontSize,
Vector2 anchorMin,
Vector2 anchorMax,
Vector2 pivot,
Vector2 anchoredPosition,
Vector2 sizeDelta)
{
var textObject = new GameObject(name, typeof(RectTransform), typeof(TextMeshProUGUI));
var rectTransform = textObject.GetComponent<RectTransform>();
rectTransform.SetParent(parent, false);
rectTransform.anchorMin = anchorMin;
rectTransform.anchorMax = anchorMax;
rectTransform.pivot = pivot;
rectTransform.anchoredPosition = anchoredPosition;
rectTransform.sizeDelta = sizeDelta;
var tmp = textObject.GetComponent<TextMeshProUGUI>();
tmp.text = text;
tmp.fontSize = fontSize;
tmp.color = Color.white;
tmp.enableWordWrapping = true;
tmp.font = TMP_Settings.defaultFontAsset;
return tmp;
}
private static void Stretch(RectTransform rectTransform)
{
rectTransform.anchorMin = Vector2.zero;
rectTransform.anchorMax = Vector2.one;
rectTransform.offsetMin = Vector2.zero;
rectTransform.offsetMax = Vector2.zero;
rectTransform.pivot = new Vector2(0.5f, 0.5f);
}
private static void EnsureFolder(string path)
{
if (AssetDatabase.IsValidFolder(path))
return;
var parent = Path.GetDirectoryName(path)?.Replace("\\", "/");
var name = Path.GetFileName(path);
if (!string.IsNullOrEmpty(parent) && !AssetDatabase.IsValidFolder(parent))
EnsureFolder(parent);
AssetDatabase.CreateFolder(parent, name);
}
}
}
#endif