From 1180be88a6784730b85c85d2db80581ae57d5a87 Mon Sep 17 00:00:00 2001 From: cneicy Date: Wed, 26 Aug 2026 02:50:43 +0800 Subject: [PATCH] chore: initialize standalone UPM package --- .gitea/workflows/publish.yml | 49 ++ .gitea/workflows/unity-verify.yml | 39 + .gitignore | 10 + .npmignore | 8 + CHANGELOG.md | 16 + CHANGELOG.md.meta | 7 + Development~/UnityProject/.gitignore | 6 + Development~/UnityProject/Assets/.gitkeep | 0 .../UnityProject/Packages/manifest.json | 15 + .../ProjectSettings/ProjectVersion.txt | 2 + Editor.meta | 8 + Editor/Samples.meta | 8 + .../ShrinkTutorialSampleSceneBuilder.cs | 343 +++++++++ .../ShrinkTutorialSampleSceneBuilder.cs.meta | 11 + Editor/ShrinkTutorial.Editor.asmdef | 18 + Editor/ShrinkTutorial.Editor.asmdef.meta | 7 + Editor/ShrinkTutorialEditorWindow.cs | 181 +++++ Editor/ShrinkTutorialEditorWindow.cs.meta | 11 + README.md | 116 +++ README.md.meta | 7 + Runtime.meta | 8 + Runtime/Core.meta | 8 + Runtime/Core/ShrinkTutorialData.cs | 14 + Runtime/Core/ShrinkTutorialData.cs.meta | 11 + Runtime/Core/ShrinkTutorialDatabase.cs | 93 +++ Runtime/Core/ShrinkTutorialDatabase.cs.meta | 11 + Runtime/Core/ShrinkTutorialEnums.cs | 34 + Runtime/Core/ShrinkTutorialEnums.cs.meta | 11 + Runtime/Core/ShrinkTutorialLocalization.cs | 28 + .../Core/ShrinkTutorialLocalization.cs.meta | 11 + Runtime/Core/ShrinkTutorialManager.cs | 717 ++++++++++++++++++ Runtime/Core/ShrinkTutorialManager.cs.meta | 11 + .../Core/ShrinkTutorialRuntimeBootstrap.cs | 25 + .../ShrinkTutorialRuntimeBootstrap.cs.meta | 11 + Runtime/Core/ShrinkTutorialSettings.cs | 61 ++ Runtime/Core/ShrinkTutorialSettings.cs.meta | 11 + Runtime/Core/ShrinkTutorialStep.cs | 44 ++ Runtime/Core/ShrinkTutorialStep.cs.meta | 11 + Runtime/Core/ShrinkTutorialTargetUtility.cs | 172 +++++ .../Core/ShrinkTutorialTargetUtility.cs.meta | 11 + Runtime/Samples.meta | 8 + .../ShrinkTutorialSampleSceneController.cs | 277 +++++++ ...hrinkTutorialSampleSceneController.cs.meta | 11 + Runtime/ShrinkTutorial.Runtime.asmdef | 15 + Runtime/ShrinkTutorial.Runtime.asmdef.meta | 7 + Runtime/Storage.meta | 8 + Runtime/Storage/IShrinkTutorialStorage.cs | 19 + .../Storage/IShrinkTutorialStorage.cs.meta | 11 + .../PlayerPrefsShrinkTutorialStorage.cs | 41 + .../PlayerPrefsShrinkTutorialStorage.cs.meta | 11 + Runtime/Trigger.meta | 8 + Runtime/Trigger/ShrinkTutorialAnchor.cs | 37 + Runtime/Trigger/ShrinkTutorialAnchor.cs.meta | 11 + .../Trigger/ShrinkTutorialAnchorRegistry.cs | 42 + .../ShrinkTutorialAnchorRegistry.cs.meta | 11 + Runtime/Trigger/ShrinkTutorialTrigger.cs | 49 ++ Runtime/Trigger/ShrinkTutorialTrigger.cs.meta | 11 + Runtime/UI.meta | 8 + Runtime/UI/ShrinkTutorialDialog.cs | 222 ++++++ Runtime/UI/ShrinkTutorialDialog.cs.meta | 11 + Runtime/UI/ShrinkTutorialMask.cs | 135 ++++ Runtime/UI/ShrinkTutorialMask.cs.meta | 11 + Samples.meta | 8 + Samples/Resources.meta | 8 + .../Resources/ShrinkTutorialSettings.asset | 21 + .../ShrinkTutorialSettings.asset.meta | 8 + Samples/ShrinkTutorialSampleDatabase.asset | 16 + .../ShrinkTutorialSampleDatabase.asset.meta | 8 + Samples/ShrinkTutorialSampleTutorial.asset | 93 +++ .../ShrinkTutorialSampleTutorial.asset.meta | 8 + package.json | 25 + package.json.meta | 7 + 72 files changed, 3331 insertions(+) create mode 100644 .gitea/workflows/publish.yml create mode 100644 .gitea/workflows/unity-verify.yml create mode 100644 .gitignore create mode 100644 .npmignore create mode 100644 CHANGELOG.md create mode 100644 CHANGELOG.md.meta create mode 100644 Development~/UnityProject/.gitignore create mode 100644 Development~/UnityProject/Assets/.gitkeep create mode 100644 Development~/UnityProject/Packages/manifest.json create mode 100644 Development~/UnityProject/ProjectSettings/ProjectVersion.txt create mode 100644 Editor.meta create mode 100644 Editor/Samples.meta create mode 100644 Editor/Samples/ShrinkTutorialSampleSceneBuilder.cs create mode 100644 Editor/Samples/ShrinkTutorialSampleSceneBuilder.cs.meta create mode 100644 Editor/ShrinkTutorial.Editor.asmdef create mode 100644 Editor/ShrinkTutorial.Editor.asmdef.meta create mode 100644 Editor/ShrinkTutorialEditorWindow.cs create mode 100644 Editor/ShrinkTutorialEditorWindow.cs.meta create mode 100644 README.md create mode 100644 README.md.meta create mode 100644 Runtime.meta create mode 100644 Runtime/Core.meta create mode 100644 Runtime/Core/ShrinkTutorialData.cs create mode 100644 Runtime/Core/ShrinkTutorialData.cs.meta create mode 100644 Runtime/Core/ShrinkTutorialDatabase.cs create mode 100644 Runtime/Core/ShrinkTutorialDatabase.cs.meta create mode 100644 Runtime/Core/ShrinkTutorialEnums.cs create mode 100644 Runtime/Core/ShrinkTutorialEnums.cs.meta create mode 100644 Runtime/Core/ShrinkTutorialLocalization.cs create mode 100644 Runtime/Core/ShrinkTutorialLocalization.cs.meta create mode 100644 Runtime/Core/ShrinkTutorialManager.cs create mode 100644 Runtime/Core/ShrinkTutorialManager.cs.meta create mode 100644 Runtime/Core/ShrinkTutorialRuntimeBootstrap.cs create mode 100644 Runtime/Core/ShrinkTutorialRuntimeBootstrap.cs.meta create mode 100644 Runtime/Core/ShrinkTutorialSettings.cs create mode 100644 Runtime/Core/ShrinkTutorialSettings.cs.meta create mode 100644 Runtime/Core/ShrinkTutorialStep.cs create mode 100644 Runtime/Core/ShrinkTutorialStep.cs.meta create mode 100644 Runtime/Core/ShrinkTutorialTargetUtility.cs create mode 100644 Runtime/Core/ShrinkTutorialTargetUtility.cs.meta create mode 100644 Runtime/Samples.meta create mode 100644 Runtime/Samples/ShrinkTutorialSampleSceneController.cs create mode 100644 Runtime/Samples/ShrinkTutorialSampleSceneController.cs.meta create mode 100644 Runtime/ShrinkTutorial.Runtime.asmdef create mode 100644 Runtime/ShrinkTutorial.Runtime.asmdef.meta create mode 100644 Runtime/Storage.meta create mode 100644 Runtime/Storage/IShrinkTutorialStorage.cs create mode 100644 Runtime/Storage/IShrinkTutorialStorage.cs.meta create mode 100644 Runtime/Storage/PlayerPrefsShrinkTutorialStorage.cs create mode 100644 Runtime/Storage/PlayerPrefsShrinkTutorialStorage.cs.meta create mode 100644 Runtime/Trigger.meta create mode 100644 Runtime/Trigger/ShrinkTutorialAnchor.cs create mode 100644 Runtime/Trigger/ShrinkTutorialAnchor.cs.meta create mode 100644 Runtime/Trigger/ShrinkTutorialAnchorRegistry.cs create mode 100644 Runtime/Trigger/ShrinkTutorialAnchorRegistry.cs.meta create mode 100644 Runtime/Trigger/ShrinkTutorialTrigger.cs create mode 100644 Runtime/Trigger/ShrinkTutorialTrigger.cs.meta create mode 100644 Runtime/UI.meta create mode 100644 Runtime/UI/ShrinkTutorialDialog.cs create mode 100644 Runtime/UI/ShrinkTutorialDialog.cs.meta create mode 100644 Runtime/UI/ShrinkTutorialMask.cs create mode 100644 Runtime/UI/ShrinkTutorialMask.cs.meta create mode 100644 Samples.meta create mode 100644 Samples/Resources.meta create mode 100644 Samples/Resources/ShrinkTutorialSettings.asset create mode 100644 Samples/Resources/ShrinkTutorialSettings.asset.meta create mode 100644 Samples/ShrinkTutorialSampleDatabase.asset create mode 100644 Samples/ShrinkTutorialSampleDatabase.asset.meta create mode 100644 Samples/ShrinkTutorialSampleTutorial.asset create mode 100644 Samples/ShrinkTutorialSampleTutorial.asset.meta create mode 100644 package.json create mode 100644 package.json.meta diff --git a/.gitea/workflows/publish.yml b/.gitea/workflows/publish.yml new file mode 100644 index 0000000..671a77d --- /dev/null +++ b/.gitea/workflows/publish.yml @@ -0,0 +1,49 @@ +name: Publish UPM package + +on: + push: + tags: + - 'v*' + workflow_dispatch: + +jobs: + publish: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + env: + NODE_AUTH_TOKEN: ${{ secrets.SHRINKSDK_PACKAGE_TOKEN }} + steps: + - name: Fetch tagged revision + shell: bash + run: | + set -eu + ref="${{ gitea.sha }}" + test -n "$ref" + git init . + git remote add origin "https://git.crash.work/ShrinkSDK/ShrinkTutorial.git" + git fetch --depth=1 origin "$ref" + git checkout --detach FETCH_HEAD + + - name: Validate immutable release version + shell: bash + run: | + set -eu + tag="$(git describe --exact-match --tags HEAD)" + version="$(node -p "require('./package.json').version")" + test "$tag" = "v$version" + npm pack --dry-run + + - name: Publish to ShrinkSDK registry + shell: bash + run: | + set -eu + : "${NODE_AUTH_TOKEN:?SHRINKSDK_PACKAGE_TOKEN is required}" + npmrc="$HOME/.npmrc" + cleanup() { rm -f "$npmrc"; } + trap cleanup EXIT + printf '%s\n' \ + 'registry=https://git.crash.work/api/packages/ShrinkSDK/npm/' \ + '//git.crash.work/api/packages/ShrinkSDK/npm/:_authToken=${NODE_AUTH_TOKEN}' > "$npmrc" + npm publish --registry=https://git.crash.work/api/packages/ShrinkSDK/npm/ \ No newline at end of file diff --git a/.gitea/workflows/unity-verify.yml b/.gitea/workflows/unity-verify.yml new file mode 100644 index 0000000..35b3ab1 --- /dev/null +++ b/.gitea/workflows/unity-verify.yml @@ -0,0 +1,39 @@ +name: Verify standalone Unity package + +on: + workflow_dispatch: + +jobs: + editmode: + runs-on: unity-2022.3.62f3 + container: + image: docker.1panel.live/unityci/editor:ubuntu-2022.3.62f3-windows-mono-3 + volumes: + - /www/dk_project/bt-recovery/gitea/runner/seedskey-unity-license:/root/.local/share/unity3d/Unity + - /www/dk_project/bt-recovery/gitea/runner/seedskey-unity-entitlements:/root/.config/unity3d/Unity/licenses + steps: + - name: Fetch selected revision + shell: bash + run: | + set -eu + ref="${{ gitea.sha }}" + git init . + git remote add origin "https://git.crash.work/ShrinkSDK/ShrinkTutorial.git" + git fetch --depth=1 origin "$ref" + git checkout --detach FETCH_HEAD + + - name: Run package EditMode tests + shell: bash + run: | + set -eu + unity_bin="$(command -v unity-editor || command -v unity || command -v Unity || true)" + test -n "$unity_bin" + "$unity_bin" \ + -batchmode \ + -nographics \ + -quit \ + -projectPath "$PWD/Development~/UnityProject" \ + -runTests \ + -testPlatform EditMode \ + -testResults "$PWD/TestResults/editmode.xml" \ + -logFile "$PWD/TestResults/unity.log" \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..246935d --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +/Development~/UnityProject/[Ll]ibrary/ +/Development~/UnityProject/[Tt]emp/ +/Development~/UnityProject/[Oo]bj/ +/Development~/UnityProject/[Ll]ogs/ +/Development~/UnityProject/[Uu]ser[Ss]ettings/ +/Development~/UnityProject/TestResults/ +/Tools~/**/[Bb]in/ +/Tools~/**/[Oo]bj/ +*.user +*.DotSettings.user diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..700f03d --- /dev/null +++ b/.npmignore @@ -0,0 +1,8 @@ +.git/ +.gitea/ +Development~/ +Tools~/ +*.csproj +*.sln +*.user +*.DotSettings.user diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a650783 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,16 @@ +# Changelog + +## Unreleased + +- 修复 `ClickTarget` 对 UI 目标过早在按下阶段完成的问题,改为等待指针释放并确认 EventSystem 射线真实命中目标后再过步。 +- 修复隐藏中的 `Path` 目标仍被教程当作可用目标的问题;现在 inactive 目标会继续等待,不会把提示框提前挂到尚未显示的控件上。 +- 修复 `ShrinkTutorialSample` 第 2 步运行时奖励按钮在样例场景中的显示与布局问题,并在奖励生成后禁用重复点击的 `SpawnRewardButton`。 +- 修复教程结束或 `maskShape=None` 步骤下遮罩仍继续绘制/拦截的问题;现在无高亮时蒙版会完全隐藏。 +- 修复 `ShrinkTutorialSample` 在前一次演示完成后下次 Play 不再显示教程的问题;样例现在会在启动时清掉自身教程进度再重新播放。 + +## 0.1.0 + +- 初始版本。 +- 提供 `ShrinkTutorialManager`、教程数据资产、锚点注册、触发器、默认 PlayerPrefs 持久化。 +- 提供独立高层级 Canvas、遮罩挖洞、提示对话框与跳过按钮。 +- 提供基础编辑器窗口与 `ShrinkSDK/引导/*` 菜单入口。 diff --git a/CHANGELOG.md.meta b/CHANGELOG.md.meta new file mode 100644 index 0000000..180e09b --- /dev/null +++ b/CHANGELOG.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f1716ffaa3c64e64fb86ac1eccaaad5b +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Development~/UnityProject/.gitignore b/Development~/UnityProject/.gitignore new file mode 100644 index 0000000..aa2eb00 --- /dev/null +++ b/Development~/UnityProject/.gitignore @@ -0,0 +1,6 @@ +[Ll]ibrary/ +[Tt]emp/ +[Oo]bj/ +[Ll]ogs/ +[Uu]ser[Ss]ettings/ +TestResults/ \ No newline at end of file diff --git a/Development~/UnityProject/Assets/.gitkeep b/Development~/UnityProject/Assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Development~/UnityProject/Packages/manifest.json b/Development~/UnityProject/Packages/manifest.json new file mode 100644 index 0000000..72fa0ea --- /dev/null +++ b/Development~/UnityProject/Packages/manifest.json @@ -0,0 +1,15 @@ +{ + "scopedRegistries": [ + { + "name": "ShrinkSDK", + "url": "https://git.crash.work/api/packages/ShrinkSDK/npm/", + "scopes": [ + "com.cneicy" + ] + } + ], + "dependencies": { + "com.unity.test-framework": "1.1.33", + "com.cneicy.shrink-tutorial": "file:../../.." + } +} diff --git a/Development~/UnityProject/ProjectSettings/ProjectVersion.txt b/Development~/UnityProject/ProjectSettings/ProjectVersion.txt new file mode 100644 index 0000000..587f809 --- /dev/null +++ b/Development~/UnityProject/ProjectSettings/ProjectVersion.txt @@ -0,0 +1,2 @@ +m_EditorVersion: 2022.3.62f3 +m_EditorVersionWithRevision: 2022.3.62f3 (96770f904ca7) diff --git a/Editor.meta b/Editor.meta new file mode 100644 index 0000000..2b47846 --- /dev/null +++ b/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a15dc5a1b31422141bf850b2b942ff76 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Samples.meta b/Editor/Samples.meta new file mode 100644 index 0000000..9431209 --- /dev/null +++ b/Editor/Samples.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: dea1943190171294d8592757a6d3a12a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Samples/ShrinkTutorialSampleSceneBuilder.cs b/Editor/Samples/ShrinkTutorialSampleSceneBuilder.cs new file mode 100644 index 0000000..2c94da6 --- /dev/null +++ b/Editor/Samples/ShrinkTutorialSampleSceneBuilder.cs @@ -0,0 +1,343 @@ +#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