From 88982211981f03cbce9c8bed255e1d8b40b3ee8d Mon Sep 17 00:00:00 2001 From: cneicy Date: Sat, 5 Sep 2026 03:40:49 +0800 Subject: [PATCH] feat: add NuGet and Godot distribution --- .gitea/workflows/publish-nuget.yml | 52 +++++++++++++++ .gitignore | 8 +++ .npmignore | 4 ++ Directory.Build.props | 13 ++++ DotNet~/Runtime/ShrinkTutorialCore.cs | 76 ++++++++++++++++++++++ DotNet~/ShrinkSDK.Tutorial.csproj | 19 ++++++ Godot~/ShrinkGodotTutorialOverlay.cs | 89 ++++++++++++++++++++++++++ Godot~/ShrinkSDK.Tutorial.Godot.csproj | 15 +++++ NuGet.Config | 8 +++ README.md | 2 + 10 files changed, 286 insertions(+) create mode 100644 .gitea/workflows/publish-nuget.yml create mode 100644 Directory.Build.props create mode 100644 DotNet~/Runtime/ShrinkTutorialCore.cs create mode 100644 DotNet~/ShrinkSDK.Tutorial.csproj create mode 100644 Godot~/ShrinkGodotTutorialOverlay.cs create mode 100644 Godot~/ShrinkSDK.Tutorial.Godot.csproj create mode 100644 NuGet.Config diff --git a/.gitea/workflows/publish-nuget.yml b/.gitea/workflows/publish-nuget.yml new file mode 100644 index 0000000..06dc7b7 --- /dev/null +++ b/.gitea/workflows/publish-nuget.yml @@ -0,0 +1,52 @@ +name: Publish NuGet packages + +on: + push: + tags: + - 'v*' + workflow_dispatch: + +jobs: + publish: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + env: + NUGET_AUTH_TOKEN: ${{ secrets.SHRINKSDK_PACKAGE_TOKEN }} + steps: + - name: Checkout tagged source + uses: actions/checkout@v4 + + - name: Set up .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + + - name: Validate tag and pack projects + env: + GITEA_REF: ${{ gitea.ref }} + shell: bash + run: | + set -euo pipefail + tag="${GITEA_REF#refs/tags/}" + version="$(node -p "require('./package.json').version")" + test "$tag" = "v$version" + mkdir -p packages + mapfile -d '' projects < <(find DotNet~ Godot~ -type f -name '*.csproj' -print0 2>/dev/null || true) + test "${#projects[@]}" -gt 0 + for project in "${projects[@]}"; do + dotnet restore "$project" --configfile NuGet.Config + dotnet pack "$project" --configuration Release --no-restore --output "$PWD/packages" --include-symbols --include-source + done + find packages -maxdepth 1 -name '*.nupkg' -type f | grep -q . + + - name: Publish packages + shell: bash + run: | + set -euo pipefail + : "${NUGET_AUTH_TOKEN:?SHRINKSDK_PACKAGE_TOKEN is required}" + dotnet nuget push 'packages/*.nupkg' --api-key "$NUGET_AUTH_TOKEN" --source https://git.crash.work/api/packages/ShrinkSDK/nuget/index.json --skip-duplicate + if compgen -G 'packages/*.snupkg' > /dev/null; then + dotnet nuget push 'packages/*.snupkg' --api-key "$NUGET_AUTH_TOKEN" --source https://git.crash.work/api/packages/ShrinkSDK/nuget/index.json --skip-duplicate + fi diff --git a/.gitignore b/.gitignore index 246935d..c3b8764 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,11 @@ /Tools~/**/[Oo]bj/ *.user *.DotSettings.user +/DotNet~/**/[Bb]in/ +/DotNet~/**/[Oo]bj/ +/Godot~/**/[Bb]in/ +/Godot~/**/[Oo]bj/ +/artifacts/ +/packages/ +!DotNet~/**/*.csproj +!Godot~/**/*.csproj diff --git a/.npmignore b/.npmignore index 700f03d..6a59c77 100644 --- a/.npmignore +++ b/.npmignore @@ -6,3 +6,7 @@ Tools~/ *.sln *.user *.DotSettings.user +DotNet~/ +Godot~/ +NuGet.Config +Directory.Build.props diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..43b188b --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,13 @@ + + + latest + enable + true + true + ShrinkSDK + ShrinkSDK + https://git.crash.work/ShrinkSDK + true + snupkg + + diff --git a/DotNet~/Runtime/ShrinkTutorialCore.cs b/DotNet~/Runtime/ShrinkTutorialCore.cs new file mode 100644 index 0000000..886c88f --- /dev/null +++ b/DotNet~/Runtime/ShrinkTutorialCore.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; + +namespace ShrinkTutorial; + +[Serializable] +public sealed class ShrinkTutorialStep +{ + public string StepId = string.Empty; + public string Title = string.Empty; + public string Body = string.Empty; + public string Target = string.Empty; + public ShrinkTutorialTargetMode TargetMode; + public ShrinkTutorialCompleteCondition CompleteCondition = ShrinkTutorialCompleteCondition.ClickTarget; + public ShrinkTutorialDialogAnchor DialogAnchor = ShrinkTutorialDialogAnchor.Auto; + public ShrinkTutorialMaskShape MaskShape = ShrinkTutorialMaskShape.Rect; + public string CustomEventName = string.Empty; + public bool BlockInputOutsideTarget = true; +} + +[Serializable] +public sealed class ShrinkTutorialData +{ + public string TutorialId = string.Empty; + public List Steps = new(); +} + +public sealed class ShrinkTutorialRunner +{ + private readonly IShrinkTutorialStorage _storage; + private ShrinkTutorialProgress _progress; + public ShrinkTutorialRunner(IShrinkTutorialStorage storage) + { + _storage = storage ?? throw new ArgumentNullException(nameof(storage)); + _progress = storage.Load() ?? new ShrinkTutorialProgress(); + } + public ShrinkTutorialData Current { get; private set; } + public int StepIndex { get; private set; } = -1; + public ShrinkTutorialStep CurrentStep => Current != null && StepIndex >= 0 && StepIndex < Current.Steps.Count ? Current.Steps[StepIndex] : null; + public event Action StepChanged; + public event Action Completed; + + public bool Start(ShrinkTutorialData tutorial) + { + if (tutorial == null || tutorial.Steps.Count == 0 || _progress.completedTutorials.Contains(tutorial.TutorialId)) return false; + Current = tutorial; + StepIndex = 0; + StepChanged?.Invoke(CurrentStep); + return true; + } + + public bool CompleteStep(string customEventName = null) + { + var step = CurrentStep; + if (step == null) return false; + if (step.CompleteCondition == ShrinkTutorialCompleteCondition.CustomEvent && + !string.Equals(step.CustomEventName, customEventName, StringComparison.Ordinal)) return false; + StepIndex++; + if (StepIndex < Current.Steps.Count) { StepChanged?.Invoke(CurrentStep); return true; } + _progress.completedTutorials.Add(Current.TutorialId); + _storage.Save(_progress); + var id = Current.TutorialId; + Current = null; + StepIndex = -1; + Completed?.Invoke(id); + return true; + } + + public void Reset() + { + Current = null; + StepIndex = -1; + _storage.Reset(); + _progress = new ShrinkTutorialProgress(); + } +} diff --git a/DotNet~/ShrinkSDK.Tutorial.csproj b/DotNet~/ShrinkSDK.Tutorial.csproj new file mode 100644 index 0000000..30af74f --- /dev/null +++ b/DotNet~/ShrinkSDK.Tutorial.csproj @@ -0,0 +1,19 @@ + + + netstandard2.1 + false + ShrinkTutorial.Runtime + ShrinkTutorial + ShrinkSDK.Tutorial + 0.2.0 + Engine-neutral ShrinkSDK tutorial state and step runtime. + disable + + + + + + + + + diff --git a/Godot~/ShrinkGodotTutorialOverlay.cs b/Godot~/ShrinkGodotTutorialOverlay.cs new file mode 100644 index 0000000..be5aa04 --- /dev/null +++ b/Godot~/ShrinkGodotTutorialOverlay.cs @@ -0,0 +1,89 @@ +#nullable enable + +using Godot; + +namespace ShrinkTutorial.Godot; + +public partial class ShrinkGodotTutorialOverlay : Control +{ + private readonly Label _title = new(); + private readonly Label _body = new(); + private ShrinkTutorialRunner? _runner; + private Control? _target; + + public override void _Ready() + { + SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect); + MouseFilter = MouseFilterEnum.Stop; + var panel = new PanelContainer { CustomMinimumSize = new Vector2(320, 100) }; + panel.Position = new Vector2(24, 24); + var text = new VBoxContainer(); + text.AddChild(_title); + text.AddChild(_body); + panel.AddChild(text); + AddChild(panel); + } + + public void Bind(ShrinkTutorialRunner runner) + { + if (_runner != null) _runner.StepChanged -= ShowStep; + _runner = runner; + _runner.StepChanged += ShowStep; + if (_runner.CurrentStep != null) ShowStep(_runner.CurrentStep); + } + + public override void _ExitTree() + { + if (_runner != null) _runner.StepChanged -= ShowStep; + _runner = null; + _target = null; + } + + public override void _Process(double delta) + { + if (_runner?.CurrentStep is not { } step) return; + _target = ResolveTarget(step); + QueueRedraw(); + } + + public override void _Draw() + { + var viewport = GetViewportRect(); + if (_target == null) + { + DrawRect(viewport, new Color(0, 0, 0, 0.72f)); + return; + } + var hole = _target.GetGlobalRect().Grow(8); + var shade = new Color(0, 0, 0, 0.72f); + DrawRect(new Rect2(viewport.Position, new Vector2(viewport.Size.X, hole.Position.Y)), shade); + DrawRect(new Rect2(new Vector2(0, hole.End.Y), new Vector2(viewport.Size.X, viewport.Size.Y - hole.End.Y)), shade); + DrawRect(new Rect2(new Vector2(0, hole.Position.Y), new Vector2(hole.Position.X, hole.Size.Y)), shade); + DrawRect(new Rect2(new Vector2(hole.End.X, hole.Position.Y), new Vector2(viewport.Size.X - hole.End.X, hole.Size.Y)), shade); + } + + public override void _GuiInput(InputEvent @event) + { + if (@event is not InputEventMouseButton { Pressed: true }) return; + var step = _runner?.CurrentStep; + if (step == null) return; + if (step.CompleteCondition == ShrinkTutorialCompleteCondition.AnyClick || + step.CompleteCondition == ShrinkTutorialCompleteCondition.ClickTarget && _target?.GetGlobalRect().HasPoint(GetGlobalMousePosition()) == true) + _runner!.CompleteStep(); + AcceptEvent(); + } + + private void ShowStep(ShrinkTutorialStep step) + { + _title.Text = step.Title; + _body.Text = step.Body; + Visible = true; + } + + private Control? ResolveTarget(ShrinkTutorialStep step) + { + if (step.TargetMode == ShrinkTutorialTargetMode.Path) return GetTree().Root.GetNodeOrNull(step.Target); + if (step.TargetMode == ShrinkTutorialTargetMode.AnchorId) return GetTree().GetFirstNodeInGroup("shrink_tutorial_" + step.Target) as Control; + return null; + } +} diff --git a/Godot~/ShrinkSDK.Tutorial.Godot.csproj b/Godot~/ShrinkSDK.Tutorial.Godot.csproj new file mode 100644 index 0000000..746e33c --- /dev/null +++ b/Godot~/ShrinkSDK.Tutorial.Godot.csproj @@ -0,0 +1,15 @@ + + + net8.0 + ShrinkTutorial.Godot + ShrinkTutorial.Godot + ShrinkSDK.Tutorial.Godot + 0.1.0 + Godot Control overlay and Resources for ShrinkSDK Tutorial. + false + + + + + + diff --git a/NuGet.Config b/NuGet.Config new file mode 100644 index 0000000..43e134b --- /dev/null +++ b/NuGet.Config @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/README.md b/README.md index 2c02f3d..2ffbbcb 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ 一个面向 Unity 2022.3+ 的互动式引导系统,重点解决这几件事: +共享教程状态与步骤运行时位于 `DotNet~`;Godot 4.6 的 `Resource`、`Control` 遮罩和输入适配位于 `Godot~`。Godot 项目安装 `ShrinkSDK.Tutorial.Godot`。 + - 通过真实点击完成步骤,而不是纯文案翻页。 - 支持静态路径目标和动态锚点目标。 - 遮罩只屏蔽无关区域,目标区域可继续点击。