@aqian0/dsh-unity 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +53 -0
- package/LICENSE-UNITY-COMPANION.md +122 -0
- package/LICENSE.md +31 -0
- package/NOTICE.md +90 -0
- package/README.md +273 -2
- package/cordis.patch.yml +22 -0
- package/docs/design.md +297 -0
- package/icon.svg +16 -0
- package/lib/frontmatter.js +392 -0
- package/lib/gating.js +105 -0
- package/lib/harness.js +16 -0
- package/lib/index.js +171 -0
- package/lib/project.js +210 -0
- package/lib/registry.js +268 -0
- package/lib/requirements.js +214 -0
- package/lib/skills.js +191 -0
- package/lib/types/frontmatter.d.ts +84 -0
- package/lib/types/gating.d.ts +80 -0
- package/lib/types/harness.d.ts +152 -0
- package/lib/types/index.d.ts +75 -0
- package/lib/types/project.d.ts +93 -0
- package/lib/types/registry.d.ts +96 -0
- package/lib/types/requirements.d.ts +67 -0
- package/lib/types/skills.d.ts +54 -0
- package/lib/types/watch.d.ts +34 -0
- package/lib/watch.js +84 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +70 -4
- package/skills/2d-pixel-perfect/SKILL.md +208 -0
- package/skills/2d-pixel-perfect/references/api-reference.md +81 -0
- package/skills/2d-pixel-perfect/references/camera-setup-builtin.cs +40 -0
- package/skills/2d-pixel-perfect/references/camera-setup-urp.cs +49 -0
- package/skills/2d-pixel-perfect/references/pipeline-detection.cs +50 -0
- package/skills/2d-pixel-perfect/references/sprite-settings.cs +37 -0
- package/skills/asset-transformer-toolkit/SECURITY.md +15 -0
- package/skills/asset-transformer-toolkit/SKILL.md +39 -0
- package/skills/asset-transformer-toolkit/references/create-importer.md +121 -0
- package/skills/asset-transformer-toolkit/references/lods.md +61 -0
- package/skills/asset-transformer-toolkit/references/rule-api.md +154 -0
- package/skills/asset-transformer-toolkit/references/ruleblock-api.md +37 -0
- package/skills/asset-transformer-toolkit/references/ruleset-api.md +138 -0
- package/skills/asset-transformer-toolkit/references/rulesets-and-actions.md +227 -0
- package/skills/audio-setup-mixers/SECURITY.md +16 -0
- package/skills/audio-setup-mixers/SKILL.md +127 -0
- package/skills/audio-setup-mixers/references/api.md +162 -0
- package/skills/build-gtk/SECURITY.md +16 -0
- package/skills/build-gtk/SKILL.md +200 -0
- package/skills/build-gtk/references/api-reference.md +33 -0
- package/skills/build-gtk/references/graph-api-gaps.md +96 -0
- package/skills/build-gtk/references/runtime-and-visualization.md +191 -0
- package/skills/build-gtk/references/state-machine-api.md +270 -0
- package/skills/build-gtk/references/where-the-docs-stop.md +49 -0
- package/skills/build-live-game/SKILL.md +159 -0
- package/skills/build-live-game/references/achievements.md +757 -0
- package/skills/build-live-game/references/apis.md +271 -0
- package/skills/build-live-game/references/authentication.md +431 -0
- package/skills/build-live-game/references/battlepass.md +904 -0
- package/skills/build-live-game/references/blueprints-and-samples.md +75 -0
- package/skills/build-live-game/references/cloud-code-cloudbehaviours.md +1111 -0
- package/skills/build-live-game/references/cloud-code-modules.md +1276 -0
- package/skills/build-live-game/references/cloud-code.md +58 -0
- package/skills/build-live-game/references/cloud-save.md +464 -0
- package/skills/build-live-game/references/deployment.md +231 -0
- package/skills/build-live-game/references/orientation.md +30 -0
- package/skills/build-live-game/references/player-account.md +821 -0
- package/skills/build-live-game/references/remote-config.md +96 -0
- package/skills/build-live-game/references/services-overview.md +76 -0
- package/skills/build-live-game/references/tooling.md +375 -0
- package/skills/build-live-game/references/ugs-cli.md +86 -0
- package/skills/craft-game-floors/SECURITY.md +23 -0
- package/skills/craft-game-floors/SKILL.md +134 -0
- package/skills/craft-game-floors/references/art-direction.md +85 -0
- package/skills/craft-game-floors/references/camera-rigs.md +51 -0
- package/skills/craft-game-floors/references/character-motion.md +35 -0
- package/skills/craft-game-floors/references/game-audio.md +47 -0
- package/skills/craft-game-floors/references/game-feel.md +68 -0
- package/skills/craft-game-floors/references/game-shell.md +45 -0
- package/skills/craft-game-floors/references/live-floor.md +35 -0
- package/skills/craft-game-floors/references/placeholder-assets.md +35 -0
- package/skills/craft-game-floors/references/scene-lighting.md +52 -0
- package/skills/craft-game-floors/references/toon-shading.md +34 -0
- package/skills/craft-game-floors/references/vfx.md +40 -0
- package/skills/craft-game-floors/references/visual-scorecard.md +43 -0
- package/skills/create-2d-physics/SECURITY.md +16 -0
- package/skills/create-2d-physics/SKILL.md +128 -0
- package/skills/create-2d-physics/references/2d-physics-core.md +155 -0
- package/skills/create-2d-physics/references/legacy-2d-physics.md +45 -0
- package/skills/create-tile-palette-or-rule-tiles/SKILL.md +95 -0
- package/skills/create-tile-palette-or-rule-tiles/references/hexagonalruletile.md +71 -0
- package/skills/create-tile-palette-or-rule-tiles/references/palette-create.md +38 -0
- package/skills/create-tile-palette-or-rule-tiles/references/ruletile-createempty.md +37 -0
- package/skills/create-tile-palette-or-rule-tiles/references/ruletile-createfromsegment.md +219 -0
- package/skills/create-tile-palette-or-rule-tiles/references/ruletile.md +95 -0
- package/skills/create-tile-palette-or-rule-tiles/scripts/CreatePaletteTemplate.cs +148 -0
- package/skills/create-tile-palette-or-rule-tiles/scripts/ManualWorkflowExample.cs +63 -0
- package/skills/create-tile-palette-or-rule-tiles/scripts/RuleTileGenerator.cs +61 -0
- package/skills/create-tile-palette-or-rule-tiles/scripts/TilemapRuleTileCreateFromSegment.cs +225 -0
- package/skills/fix-3d-physics-collisions/SECURITY.md +17 -0
- package/skills/fix-3d-physics-collisions/SKILL.md +139 -0
- package/skills/fix-3d-physics-collisions/references/collision-checklist.md +80 -0
- package/skills/fix-3d-physics-collisions/references/collision-debugger.md +18 -0
- package/skills/fix-3d-physics-collisions/references/console-errors-table.md +9 -0
- package/skills/fix-3d-physics-collisions/references/kinematic-trigger-facts.md +18 -0
- package/skills/fix-3d-physics-collisions/references/other-symptoms.md +29 -0
- package/skills/fix-3d-physics-collisions/references/raycast-checklist.md +8 -0
- package/skills/fix-3d-physics-collisions/references/topic-map.md +42 -0
- package/skills/fix-3d-physics-collisions/references/trigger-checklist.md +9 -0
- package/skills/fix-3d-physics-collisions/scripts/CollisionDebugger.cs +561 -0
- package/skills/generate-editor-search-query/SECURITY.md +17 -0
- package/skills/generate-editor-search-query/SKILL.md +117 -0
- package/skills/generate-editor-search-query/references/open-search-window.md +70 -0
- package/skills/generate-editor-search-query/references/query-patterns.md +124 -0
- package/skills/implement-in-app-purchases/SKILL.md +82 -0
- package/skills/implement-in-app-purchases/references/api-details.md +41 -0
- package/skills/implement-in-app-purchases/references/api-notes.md +105 -0
- package/skills/implement-in-app-purchases/references/codeless-catalog.md +197 -0
- package/skills/implement-in-app-purchases/references/convert-adapty.md +270 -0
- package/skills/implement-in-app-purchases/references/convert-essentialkit.md +239 -0
- package/skills/implement-in-app-purchases/references/convert-revenuecat.md +277 -0
- package/skills/implement-in-app-purchases/references/convert-unipay.md +145 -0
- package/skills/implement-in-app-purchases/references/migration-v4-to-v5.md +46 -0
- package/skills/implement-in-app-purchases/references/path-add-iap-to-new-project.md +197 -0
- package/skills/implement-in-app-purchases/references/path-convert-native-google-billing.md +302 -0
- package/skills/implement-in-app-purchases/references/path-convert-native-storekit.md +357 -0
- package/skills/implement-in-app-purchases/references/path-implement-iap-d2c.md +253 -0
- package/skills/implement-in-app-purchases/references/platform-notes.md +55 -0
- package/skills/implement-in-app-purchases/references/pre-check.md +207 -0
- package/skills/implement-in-app-purchases/references/topic-map.md +46 -0
- package/skills/initialize-ai-navigation/SKILL.md +109 -0
- package/skills/initialize-ai-navigation/references/navigation-system.md +793 -0
- package/skills/initialize-ai-navigation/references/topic-map.md +66 -0
- package/skills/levelplay-unity-integration/SKILL.md +122 -0
- package/skills/levelplay-unity-integration/references/adding-more-ad-formats.md +15 -0
- package/skills/levelplay-unity-integration/references/banner-api.md +927 -0
- package/skills/levelplay-unity-integration/references/best-practices.md +604 -0
- package/skills/levelplay-unity-integration/references/dependency-resolution.md +94 -0
- package/skills/levelplay-unity-integration/references/examples.md +10 -0
- package/skills/levelplay-unity-integration/references/ilrd-api.md +337 -0
- package/skills/levelplay-unity-integration/references/initialization-api.md +851 -0
- package/skills/levelplay-unity-integration/references/interstitial-api.md +907 -0
- package/skills/levelplay-unity-integration/references/ios-setup.md +463 -0
- package/skills/levelplay-unity-integration/references/migration-sdk-9.md +666 -0
- package/skills/levelplay-unity-integration/references/privacy-settings.md +593 -0
- package/skills/levelplay-unity-integration/references/production-release-checklist.md +22 -0
- package/skills/levelplay-unity-integration/references/rewarded-api.md +918 -0
- package/skills/levelplay-unity-integration/references/step-0-new-or-migration.md +8 -0
- package/skills/levelplay-unity-integration/references/step-1-verify-environment.md +4 -0
- package/skills/levelplay-unity-integration/references/step-10-testing.md +15 -0
- package/skills/levelplay-unity-integration/references/step-2-business-goals.md +12 -0
- package/skills/levelplay-unity-integration/references/step-3-install-sdk.md +55 -0
- package/skills/levelplay-unity-integration/references/step-4-native-dependencies.md +22 -0
- package/skills/levelplay-unity-integration/references/step-5-app-key-and-ad-units.md +18 -0
- package/skills/levelplay-unity-integration/references/step-6-5-privacy-and-regulation.md +51 -0
- package/skills/levelplay-unity-integration/references/step-6-admob-keys.md +16 -0
- package/skills/levelplay-unity-integration/references/step-7-initialize.md +50 -0
- package/skills/levelplay-unity-integration/references/step-8-ad-unit-strategy.md +16 -0
- package/skills/levelplay-unity-integration/references/step-9-implement-ad-units.md +66 -0
- package/skills/levelplay-unity-integration/references/testing-and-validation.md +222 -0
- package/skills/levelplay-unity-integration/references/troubleshooting.md +99 -0
- package/skills/localize-unity-project/SKILL.md +159 -0
- package/skills/localize-unity-project/references/api-notes.md +15 -0
- package/skills/localize-unity-project/references/cjk-fonts.md +83 -0
- package/skills/localize-unity-project/references/docs-map.md +42 -0
- package/skills/localize-unity-project/references/documentation-lookup.md +33 -0
- package/skills/localize-unity-project/references/locale-switching.md +17 -0
- package/skills/localize-unity-project/references/table-completeness-check.md +47 -0
- package/skills/localize-unity-project/references/translating-existing-project.md +47 -0
- package/skills/localize-unity-project/references/translation-strategy.md +41 -0
- package/skills/localize-unity-project/references/troubleshooting.md +12 -0
- package/skills/localize-unity-project/scripts/L10nBatchProcessor.cs +191 -0
- package/skills/localize-unity-project/scripts/LocalizedFontAsset.cs +18 -0
- package/skills/manage-sprite-atlas/SKILL.md +202 -0
- package/skills/manage-sprite-atlas/references/addressables-delivery.md +58 -0
- package/skills/manage-sprite-atlas/references/advanced-manual-authoring.md +19 -0
- package/skills/manage-sprite-atlas/references/api.md +382 -0
- package/skills/manage-sprite-atlas/references/best-practices.md +183 -0
- package/skills/manage-sprite-atlas/references/common-errors.md +123 -0
- package/skills/manage-sprite-atlas/references/complete-workflow.md +61 -0
- package/skills/manage-sprite-atlas/references/custom-packing.md +147 -0
- package/skills/manage-sprite-atlas/references/delivery-mechanism.md +30 -0
- package/skills/manage-sprite-atlas/references/key-requirements.md +21 -0
- package/skills/manage-sprite-atlas/references/manual-authoring.md +39 -0
- package/skills/manage-sprite-atlas/references/namespaces.md +15 -0
- package/skills/manage-sprite-atlas/references/v2-architecture.md +21 -0
- package/skills/manage-sprite-atlas/resources/authoringvsruntime.cs +176 -0
- package/skills/manage-sprite-atlas/resources/buildaddressablespostprocess.cs +59 -0
- package/skills/manage-sprite-atlas/resources/cacheloadedatlases.cs +145 -0
- package/skills/manage-sprite-atlas/resources/cleanupbatchoperations.cs +119 -0
- package/skills/manage-sprite-atlas/resources/createandconfiguremasteratlas.cs +57 -0
- package/skills/manage-sprite-atlas/resources/custompackersetup.cs +88 -0
- package/skills/manage-sprite-atlas/resources/deprecatedmethods.cs +142 -0
- package/skills/manage-sprite-atlas/resources/dontmodifywithoutsaving.cs +120 -0
- package/skills/manage-sprite-atlas/resources/dontpackinruntimebuilds.cs +190 -0
- package/skills/manage-sprite-atlas/resources/dontscriptspriteatlasineditor.cs +114 -0
- package/skills/manage-sprite-atlas/resources/editorconfiguration.cs +95 -0
- package/skills/manage-sprite-atlas/resources/enablespritepacking.cs +172 -0
- package/skills/manage-sprite-atlas/resources/gridspritepacker.cs +50 -0
- package/skills/manage-sprite-atlas/resources/handlelatebinding.cs +147 -0
- package/skills/manage-sprite-atlas/resources/handlenullcases.cs +141 -0
- package/skills/manage-sprite-atlas/resources/migratev1tov2.cs +335 -0
- package/skills/manage-sprite-atlas/resources/multipagepacker.cs +80 -0
- package/skills/manage-sprite-atlas/resources/optimizetexturesettings.cs +191 -0
- package/skills/manage-sprite-atlas/resources/packerdebugging.cs +158 -0
- package/skills/manage-sprite-atlas/resources/packtransform.cs +51 -0
- package/skills/manage-sprite-atlas/resources/platformnames.cs +127 -0
- package/skills/manage-sprite-atlas/resources/queryspritesefficiently.cs +222 -0
- package/skills/manage-sprite-atlas/resources/registercallbacks.cs +152 -0
- package/skills/manage-sprite-atlas/resources/runtimeatlasloader.cs +15 -0
- package/skills/manage-sprite-atlas/resources/runtimespriteaccess.cs +19 -0
- package/skills/manage-sprite-atlas/resources/savespriteatlasasset.cs +107 -0
- package/skills/manage-sprite-atlas/resources/scriptablepacker.cs +81 -0
- package/skills/manage-sprite-atlas/resources/setincludeinbuild.cs +164 -0
- package/skills/manage-sprite-atlas/resources/sizeoptimizedpacker.cs +88 -0
- package/skills/manage-sprite-atlas/resources/spriteatlaslatebinding.cs +123 -0
- package/skills/manage-sprite-atlas/resources/spriteatlasprebuildgenerator.cs +219 -0
- package/skills/manage-sprite-atlas/resources/spritedata.cs +35 -0
- package/skills/manage-sprite-atlas/resources/spritepack.cs +39 -0
- package/skills/manage-sprite-atlas/resources/texturedata.cs +15 -0
- package/skills/manage-sprite-atlas/resources/updatebuildscripts.cs +357 -0
- package/skills/manage-sprite-atlas/resources/updatesettingsconfiguration.cs +135 -0
- package/skills/manage-sprite-atlas/resources/usevariantswisely.cs +179 -0
- package/skills/manage-sprite-atlas/resources/variantatlas.cs +39 -0
- package/skills/migrate-birp-to-urp/SECURITY.md +16 -0
- package/skills/migrate-birp-to-urp/SKILL.md +167 -0
- package/skills/migrate-birp-to-urp/references/capturing-the-editor.md +66 -0
- package/skills/migrate-birp-to-urp/references/complex-shader-situations.md +272 -0
- package/skills/migrate-birp-to-urp/references/conversion-and-rendering-triage.md +113 -0
- package/skills/migrate-birp-to-urp/references/custom-shader-triage.md +123 -0
- package/skills/migrate-birp-to-urp/references/implementation-patterns.md +529 -0
- package/skills/migrate-birp-to-urp/references/migration-workflow.md +265 -0
- package/skills/migrate-birp-to-urp/references/parity-validation-and-troubleshooting.md +114 -0
- package/skills/migrate-birp-to-urp/references/preflight-and-urp-setup.md +195 -0
- package/skills/migrate-birp-to-urp/references/quality-settings-map.md +69 -0
- package/skills/new-unity-project/SECURITY.md +26 -0
- package/skills/new-unity-project/SKILL.md +171 -0
- package/skills/new-unity-project/references/checklist.md +18 -0
- package/skills/new-unity-project/references/common-mistakes.md +15 -0
- package/skills/new-unity-project/references/skill-setup.md +66 -0
- package/skills/new-unity-project/references/step-1-concept.md +13 -0
- package/skills/new-unity-project/references/step-2-platforms-and-create.md +31 -0
- package/skills/new-unity-project/references/step-3-join-and-reconcile.md +16 -0
- package/skills/new-unity-project/references/step-4-source-control.md +9 -0
- package/skills/new-unity-project/references/step-5-packages.md +18 -0
- package/skills/new-unity-project/references/step-6-identity-and-floors.md +20 -0
- package/skills/new-unity-project/references/step-7-visual-baseline.md +75 -0
- package/skills/new-unity-project/references/step-8-first-commit.md +21 -0
- package/skills/new-unity-project/references/step-9-handoff.md +23 -0
- package/skills/optimize-audio/SECURITY.md +16 -0
- package/skills/optimize-audio/SKILL.md +120 -0
- package/skills/optimize-audio/references/diagnostics.md +42 -0
- package/skills/optimize-audio/references/troubleshooting.md +41 -0
- package/skills/optimize-audio/resources/audio-import-api.md +149 -0
- package/skills/optimize-audio/resources/platform-settings.md +48 -0
- package/skills/optimize-text-mesh-pro/SKILL.md +181 -0
- package/skills/optimize-text-mesh-pro/references/dynamic-atlas-and-profiling.md +28 -0
- package/skills/optimize-text-mesh-pro/references/topic-map.md +36 -0
- package/skills/optimize-web/SECURITY.md +16 -0
- package/skills/optimize-web/SKILL.md +157 -0
- package/skills/optimize-web/references/editor-csharp.md +120 -0
- package/skills/optimize-web/references/topic-map.md +49 -0
- package/skills/optimize-web/references/troubleshooting.md +45 -0
- package/skills/optimize-web/references/understand-request.md +19 -0
- package/skills/optimize-web/references/web-assets-quality.md +59 -0
- package/skills/optimize-web/references/web-player-settings.md +39 -0
- package/skills/optimize-web/references/web-profiling-memory.md +71 -0
- package/skills/optimize-web/scripts/WebOptimizer.cs +46 -0
- package/skills/optimize-web/scripts/toktx-examples.sh +11 -0
- package/skills/setup-multiplayer-services/SKILL.md +36 -0
- package/skills/setup-multiplayer-services/references/dgs-entrypoint.md +79 -0
- package/skills/setup-multiplayer-services/references/entrypoints.md +213 -0
- package/skills/setup-multiplayer-services/references/examples.md +33 -0
- package/skills/setup-multiplayer-services/references/implementation-fit.md +30 -0
- package/skills/setup-multiplayer-services/references/underlying-services.md +11 -0
- package/skills/setup-multiplayer-services/references/workflows-prerequisites.md +17 -0
- package/skills/setup-vivox-voice-chat/SECURITY.md +16 -0
- package/skills/setup-vivox-voice-chat/SKILL.md +166 -0
- package/skills/setup-vivox-voice-chat/references/api-reference-lookup.md +28 -0
- package/skills/setup-vivox-voice-chat/references/docs-topic-map.md +13 -0
- package/skills/setup-vivox-voice-chat/references/events-and-participants.md +79 -0
- package/skills/setup-vivox-voice-chat/references/init-and-login.md +92 -0
- package/skills/setup-vivox-voice-chat/references/text-chat.md +93 -0
- package/skills/setup-vivox-voice-chat/references/troubleshooting.md +47 -0
- package/skills/setup-vivox-voice-chat/references/voice-channels.md +92 -0
- package/skills/shader-graph-create-custom-node/SKILL.md +25 -0
- package/skills/shader-graph-create-custom-node/resources/all_hints.hlsl +182 -0
- package/skills/sprite-editor/SECURITY.md +16 -0
- package/skills/sprite-editor/SKILL.md +66 -0
- package/skills/sprite-editor/references/api_reference.md +151 -0
- package/skills/sprite-editor/references/background.md +112 -0
- package/skills/sprite-editor/references/templates.md +72 -0
- package/skills/sprite-editor/scripts/AutomaticSliceTexture.cs +40 -0
- package/skills/sprite-editor/scripts/GenerateNewSpriteRects.cs +200 -0
- package/skills/sprite-editor/scripts/GetTextureSourceImageSize.cs +32 -0
- package/skills/sprite-editor/scripts/GetTextureToSlice.cs +55 -0
- package/skills/sprite-editor/scripts/GridSliceTexture.cs +40 -0
- package/skills/sprite-editor/scripts/IsometricSliceTexture.cs +141 -0
- package/skills/sprite-editor/scripts/README.md +134 -0
- package/skills/sprite-editor/scripts/SetPivotExample.cs +58 -0
- package/skills/sprite-editor/scripts/SpriteToPng.cs +88 -0
- package/skills/sprite-segment-3x3grid/SKILL.md +177 -0
- package/skills/sprite-segment-3x3grid/references/topic-map.md +37 -0
- package/skills/sprite-segment-3x3grid/scripts/SpriteGridAnalysis.cs +119 -0
- package/skills/ui/SKILL.md +142 -0
- package/skills/ui-imgui/SKILL.md +180 -0
- package/skills/ui-imgui/references/gui-elements.md +156 -0
- package/skills/ui-imgui/references/templates.md +141 -0
- package/skills/ui-ugui/SKILL.md +217 -0
- package/skills/ui-ugui/references/layout-and-anchors.md +61 -0
- package/skills/ui-ugui/references/scrollview-setup.md +29 -0
- package/skills/ui-uitk/SKILL.md +229 -0
- package/skills/ui-uitk/references/common-issues.md +74 -0
- package/skills/ui-uitk/references/custom-elements.md +241 -0
- package/skills/ui-uitk/references/painter2d.md +282 -0
- package/skills/ui-uitk/references/pointermanipulator-guide.md +94 -0
- package/skills/ui-uitk/references/svg-icons.md +136 -0
- package/skills/ui-uitk/references/ui-runtime-binding.md +234 -0
- package/skills/ui-uitk/references/uss-guide.md +138 -0
- package/skills/unity-cli/CHANGELOG.md +524 -0
- package/skills/unity-cli/SECURITY.md +68 -0
- package/skills/unity-cli/SKILL.md +160 -0
- package/skills/unity-cli/references/auth-license-cloud.md +318 -0
- package/skills/unity-cli/references/bootstrap-project.md +177 -0
- package/skills/unity-cli/references/build-run-test.md +555 -0
- package/skills/unity-cli/references/catalog-plugins.md +86 -0
- package/skills/unity-cli/references/ci-recipes.md +76 -0
- package/skills/unity-cli/references/cloud-automation.md +819 -0
- package/skills/unity-cli/references/cloud-workflows.md +19 -0
- package/skills/unity-cli/references/collaboration.md +533 -0
- package/skills/unity-cli/references/config-hub.md +239 -0
- package/skills/unity-cli/references/diagnostics-maintenance.md +718 -0
- package/skills/unity-cli/references/docs-workflow.md +179 -0
- package/skills/unity-cli/references/editor-targeting.md +29 -0
- package/skills/unity-cli/references/editors-install.md +490 -0
- package/skills/unity-cli/references/environment-variables.md +42 -0
- package/skills/unity-cli/references/exit-codes.md +47 -0
- package/skills/unity-cli/references/global-flags.md +32 -0
- package/skills/unity-cli/references/integration-advanced.md +1130 -0
- package/skills/unity-cli/references/notes.md +20 -0
- package/skills/unity-cli/references/notifications.md +39 -0
- package/skills/unity-cli/references/playmode-verification-loop.md +279 -0
- package/skills/unity-cli/references/projects-templates.md +997 -0
- package/skills/unity-cli/references/scene-editing-preflight.md +25 -0
- package/skills/unity-cli/references/setup-and-help.md +54 -0
- package/skills/unity-cli/references/version-control.md +612 -0
- package/skills/unity-package-management/SKILL.md +169 -0
- package/skills/unity-package-management/references/discovering-packages.md +66 -0
- package/skills/unity-package-management/references/headless-run.md +31 -0
- package/skills/unity-package-management/references/installer-script.md +92 -0
- package/skills/unity-package-management/references/select-packages.md +126 -0
- package/skills/unity-pipeline/SECURITY.md +17 -0
- package/skills/unity-pipeline/SKILL.md +205 -0
- package/skills/unity-pipeline/references/csharp-eval.md +23 -0
- package/skills/unity-pipeline/references/multiplayer-play-mode.md +103 -0
- package/skills/urp-postprocessing/SECURITY.md +16 -0
- package/skills/urp-postprocessing/SKILL.md +188 -0
- package/skills/urp-postprocessing/references/code-templates.md +150 -0
- package/skills/urp-postprocessing/references/effect-reference.md +86 -0
- package/skills/validate-urp-render-graph-renderer-feature/SKILL.md +92 -0
- package/skills/validate-urp-render-graph-renderer-feature/references/validation-checklist.md +179 -0
|
@@ -0,0 +1,757 @@
|
|
|
1
|
+
# Achievements Reference
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Architecture Overview](#architecture-overview)
|
|
6
|
+
- [Data Models](#data-models)
|
|
7
|
+
- [API Notes](#api-notes)
|
|
8
|
+
- [Namespace Aliases](#namespace-aliases-required)
|
|
9
|
+
- [Access Class Options](#access-class-options-reference)
|
|
10
|
+
- [Bucket Selection](#bucket-selection)
|
|
11
|
+
- [Error Handling](#error-handling)
|
|
12
|
+
- [Load Definitions from Remote Config](#load-definitions-from-remote-config)
|
|
13
|
+
- [Load Player Records from Cloud Save](#load-player-records-from-cloud-save)
|
|
14
|
+
- [Save Player Records (Direct Write)](#save-player-records-direct-write)
|
|
15
|
+
- [Server-Authoritative Writes via Cloud Code](#server-authoritative-writes-via-cloud-code)
|
|
16
|
+
- [Full Client Implementation](#full-client-implementation)
|
|
17
|
+
- [Cloud Code Module](#cloud-code-module)
|
|
18
|
+
- [AchievementModule.cs](#achievementmodulecs)
|
|
19
|
+
- [Server-Side Model](#server-side-model)
|
|
20
|
+
- [Cloud Resources](#cloud-resources)
|
|
21
|
+
- [Deployment Checklist](#deployment-checklist)
|
|
22
|
+
- [Official Unity Reference](#official-unity-reference)
|
|
23
|
+
- [Validation](#validation)
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Architecture Overview
|
|
28
|
+
|
|
29
|
+
Achievements combine Remote Config (static definitions), Cloud Save (per-player progress), and Cloud Code (server-authoritative writes). An Access Control policy locks down direct client writes in production.
|
|
30
|
+
|
|
31
|
+
### Service Mapping
|
|
32
|
+
|
|
33
|
+
| Concern | Service | Key / Endpoint |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| Achievement definitions | Remote Config | `"achievements"` key (JSON array) |
|
|
36
|
+
| Player records | Cloud Save (Protected or Public) | `"achievements"` key (JSON array) |
|
|
37
|
+
| Server-authoritative writes | Cloud Code Module | `AchievementModule` |
|
|
38
|
+
| Block direct writes | Access Control | `.ac` policy denying player Cloud Save writes |
|
|
39
|
+
|
|
40
|
+
### Data Flow
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
Remote Config Cloud Save
|
|
44
|
+
"achievements" key "achievements" key
|
|
45
|
+
┌─────────────────┐ ┌─────────────────┐
|
|
46
|
+
│ AchievementDef- │ client │ AchievementRe- │
|
|
47
|
+
│ inition[] │───reads────────>│ cord[] │
|
|
48
|
+
│ │ │ │
|
|
49
|
+
│ Id │ │ Id │
|
|
50
|
+
│ Title │ │ Unlocked │
|
|
51
|
+
│ Description │ │ ProgressCount │
|
|
52
|
+
│ IsHidden │ │ │
|
|
53
|
+
│ ProgressTarget │ └────────┬────────┘
|
|
54
|
+
└─────────────────┘ │
|
|
55
|
+
two write paths
|
|
56
|
+
┌──────────────┴──────────────┐
|
|
57
|
+
│ │
|
|
58
|
+
Direct Write Trusted Write
|
|
59
|
+
(dev / prototype) (production)
|
|
60
|
+
│ │
|
|
61
|
+
▼ ▼
|
|
62
|
+
Cloud Save Cloud Code
|
|
63
|
+
PublicWriteAccess- "AchievementModule"
|
|
64
|
+
ClassOptions │
|
|
65
|
+
▼
|
|
66
|
+
Cloud Save
|
|
67
|
+
SetProtectedItemAsync
|
|
68
|
+
(server token bypass)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Key principle:** In production, route all writes through the Cloud Code module. The Access Control `.ac` file denies direct player writes to the Protected bucket, so only the server can mutate achievement records.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Data Models
|
|
76
|
+
|
|
77
|
+
### AchievementDefinition (client-side)
|
|
78
|
+
|
|
79
|
+
Stored in Remote Config as a JSON array under the `"achievements"` key. Read-only from the client.
|
|
80
|
+
|
|
81
|
+
```csharp
|
|
82
|
+
using System;
|
|
83
|
+
|
|
84
|
+
[Serializable]
|
|
85
|
+
public class AchievementDefinition
|
|
86
|
+
{
|
|
87
|
+
public string Id;
|
|
88
|
+
public string Title;
|
|
89
|
+
public string Description;
|
|
90
|
+
public bool IsHidden;
|
|
91
|
+
public int ProgressTarget; // <= 1 = single unlock, > 1 = multi-stage
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
| Field | Type | Description |
|
|
96
|
+
|---|---|---|
|
|
97
|
+
| `Id` | `string` | Unique identifier (e.g. `"first_win"`) |
|
|
98
|
+
| `Title` | `string` | Display name shown to the player |
|
|
99
|
+
| `Description` | `string` | What the player must do |
|
|
100
|
+
| `IsHidden` | `bool` | If true, hide until unlocked (secret achievements) |
|
|
101
|
+
| `ProgressTarget` | `int` | Target count for multi-stage achievements; 0 or 1 = single unlock |
|
|
102
|
+
|
|
103
|
+
### AchievementRecord (defined once, in the cloud assembly)
|
|
104
|
+
|
|
105
|
+
Stored in Cloud Save as a JSON array under the `"achievements"` key. Define it once, in `AchievementModule.cs` (see [Server-Side Model](#server-side-model)), as a class of public settable members. The source generator emits a data-only stub of the same name and namespace, `AchievementModule.Cloud.AchievementRecord`, into the client assembly, so client code uses that type and never declares its own copy. `Id` matches `AchievementDefinition.Id`; `ProgressCount` is the current progress toward `ProgressTarget`.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## API Notes
|
|
110
|
+
|
|
111
|
+
### Namespace Aliases (Required)
|
|
112
|
+
|
|
113
|
+
Cloud Save has identically named option classes at different namespace levels. Always use these
|
|
114
|
+
aliases to avoid ambiguity:
|
|
115
|
+
|
|
116
|
+
```csharp
|
|
117
|
+
using PlayerLoadOptions = Unity.Services.CloudSave.Models.Data.Player.LoadOptions;
|
|
118
|
+
using PlayerSaveOptions = Unity.Services.CloudSave.Models.Data.Player.SaveOptions;
|
|
119
|
+
using PlayerDeleteOptions = Unity.Services.CloudSave.Models.Data.Player.DeleteOptions;
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Without these aliases, the compiler will report ambiguous type references.
|
|
123
|
+
|
|
124
|
+
### Access Class Options Reference
|
|
125
|
+
|
|
126
|
+
| Class | Read | Write | Use case |
|
|
127
|
+
|---|---|---|---|
|
|
128
|
+
| `DefaultWriteAccessClassOptions` / `DefaultReadAccessClassOptions` | Owner only | Owner only | Private player data |
|
|
129
|
+
| `PublicReadAccessClassOptions(playerId)` / `PublicWriteAccessClassOptions` | Anyone | Owner + server | Achievements, scores, public profiles |
|
|
130
|
+
| `ProtectedReadAccessClassOptions` | Owner only | Server only (Cloud Code) | Anti-cheat, server-awarded state |
|
|
131
|
+
|
|
132
|
+
### Bucket Selection
|
|
133
|
+
|
|
134
|
+
The read access class **must** match the bucket that was written to. Cloud Save has three
|
|
135
|
+
separate buckets — Default, Public, and Protected. Data written to one bucket is **invisible**
|
|
136
|
+
when reading from another.
|
|
137
|
+
|
|
138
|
+
- **Trusted path** (Cloud Code `SetProtectedItemAsync`) — data lands in the **Protected** bucket — read with `ProtectedReadAccessClassOptions()`
|
|
139
|
+
- **Direct path** (`PublicWriteAccessClassOptions`) — data lands in the **Public** bucket — read with `PublicReadAccessClassOptions(playerId)`
|
|
140
|
+
|
|
141
|
+
> **Common mistake:** Switching from direct writes to trusted writes (or vice versa) without
|
|
142
|
+
> updating the read access class. The data appears to vanish — it's still there, just in
|
|
143
|
+
> the other bucket.
|
|
144
|
+
|
|
145
|
+
### Error Handling
|
|
146
|
+
|
|
147
|
+
Cloud Code and Cloud Save errors surface as typed exceptions on the client:
|
|
148
|
+
|
|
149
|
+
```csharp
|
|
150
|
+
try
|
|
151
|
+
{
|
|
152
|
+
await m_Client.UnlockAchievement("first_win");
|
|
153
|
+
}
|
|
154
|
+
catch (CloudCodeException ex)
|
|
155
|
+
{
|
|
156
|
+
// ex.Reason: CloudCodeExceptionReason (e.g. ScriptError for module-thrown exceptions)
|
|
157
|
+
Debug.LogError($"[Achievements] Cloud Code error: {ex.Reason} — {ex.Message}");
|
|
158
|
+
}
|
|
159
|
+
catch (CloudSaveException ex)
|
|
160
|
+
{
|
|
161
|
+
// ex.Reason: CloudSaveExceptionReason
|
|
162
|
+
// A 403 on the Protected bucket means the client attempted a direct write —
|
|
163
|
+
// all writes must go through the Cloud Code module when Access Control is deployed.
|
|
164
|
+
Debug.LogError($"[Achievements] Cloud Save error: {ex.Reason} — {ex.Message}");
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
| Error | Cause | Fix |
|
|
169
|
+
| :-- | :-- | :-- |
|
|
170
|
+
| `CloudCodeException` with reason `ScriptError` | The module threw, for example on a failed validation | Read `ex.Message`, then correct the arguments or the player's state |
|
|
171
|
+
| `CloudSaveException` with a `403` | The client attempted a direct write to the Protected bucket | Route the write through the Cloud Code module |
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Load Definitions from Remote Config
|
|
176
|
+
|
|
177
|
+
Fetch the `"achievements"` Remote Config key and deserialize the JSON array into a list of definitions.
|
|
178
|
+
|
|
179
|
+
```csharp
|
|
180
|
+
using Unity.Services.RemoteConfig;
|
|
181
|
+
using Newtonsoft.Json.Linq;
|
|
182
|
+
using System.Collections.Generic;
|
|
183
|
+
using System.Threading.Tasks;
|
|
184
|
+
|
|
185
|
+
struct UserAttributes {}
|
|
186
|
+
struct AppAttributes {}
|
|
187
|
+
|
|
188
|
+
async Task<List<AchievementDefinition>> LoadDefinitionsAsync()
|
|
189
|
+
{
|
|
190
|
+
var result = await RemoteConfigService.Instance.FetchConfigsAsync(
|
|
191
|
+
new UserAttributes(), new AppAttributes());
|
|
192
|
+
|
|
193
|
+
var token = result.config["achievements"];
|
|
194
|
+
return token.ToObject<List<AchievementDefinition>>();
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Load Player Records from Cloud Save
|
|
201
|
+
|
|
202
|
+
**CRITICAL -- bucket selection:** The read access class must match the bucket that was written to.
|
|
203
|
+
|
|
204
|
+
- **Trusted path (production):** The Cloud Code module writes to the **Protected** bucket. The client reads with `ProtectedReadAccessClassOptions()`.
|
|
205
|
+
- **Direct path (dev only):** The client writes to the **Public** bucket. The client reads with `PublicReadAccessClassOptions(playerId)`.
|
|
206
|
+
|
|
207
|
+
If you read from the wrong bucket, the data will appear empty.
|
|
208
|
+
|
|
209
|
+
```csharp
|
|
210
|
+
using System.Collections.Generic;
|
|
211
|
+
using System.Threading.Tasks;
|
|
212
|
+
using Newtonsoft.Json;
|
|
213
|
+
using Unity.Services.CloudSave;
|
|
214
|
+
using Unity.Services.CloudSave.Models.Data.Player;
|
|
215
|
+
using PlayerLoadOptions = Unity.Services.CloudSave.Models.Data.Player.LoadOptions;
|
|
216
|
+
|
|
217
|
+
async Task<List<AchievementRecord>> LoadRecordsAsync(string playerId, bool useTrustedWrites)
|
|
218
|
+
{
|
|
219
|
+
// Read from whichever bucket was written to -- Protected (trusted) or Public (direct).
|
|
220
|
+
ReadAccessClassOptions accessClass = useTrustedWrites
|
|
221
|
+
? new ProtectedReadAccessClassOptions()
|
|
222
|
+
: new PublicReadAccessClassOptions(playerId);
|
|
223
|
+
|
|
224
|
+
var result = await CloudSaveService.Instance.Data.Player.LoadAsync(
|
|
225
|
+
new HashSet<string> { "achievements" },
|
|
226
|
+
new PlayerLoadOptions(accessClass));
|
|
227
|
+
|
|
228
|
+
if (!result.TryGetValue("achievements", out var item))
|
|
229
|
+
return new List<AchievementRecord>();
|
|
230
|
+
|
|
231
|
+
return JsonConvert.DeserializeObject<List<AchievementRecord>>(item.Value.GetAsString());
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**`ProtectedReadAccessClassOptions`** — reads the current player's own Protected data (no player ID parameter; always reads self).
|
|
236
|
+
|
|
237
|
+
**`PublicReadAccessClassOptions(playerId)`** — reads another player's Public data. Pass `AuthenticationService.Instance.PlayerId` for the current player.
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Save Player Records (Direct Write)
|
|
242
|
+
|
|
243
|
+
Direct writes use the **Public** access class. This path is for development and prototyping only -- in production, deploy an Access Control policy to block direct player writes and route everything through Cloud Code.
|
|
244
|
+
|
|
245
|
+
```csharp
|
|
246
|
+
using System.Collections.Generic;
|
|
247
|
+
using System.Threading.Tasks;
|
|
248
|
+
using Newtonsoft.Json;
|
|
249
|
+
using Unity.Services.CloudSave;
|
|
250
|
+
using Unity.Services.CloudSave.Models;
|
|
251
|
+
using Unity.Services.CloudSave.Models.Data.Player;
|
|
252
|
+
using PlayerSaveOptions = Unity.Services.CloudSave.Models.Data.Player.SaveOptions;
|
|
253
|
+
|
|
254
|
+
async Task SaveRecordsAsync(List<AchievementRecord> records)
|
|
255
|
+
{
|
|
256
|
+
var json = JsonConvert.SerializeObject(records);
|
|
257
|
+
await CloudSaveService.Instance.Data.Player.SaveAsync(
|
|
258
|
+
new Dictionary<string, SaveItem>
|
|
259
|
+
{
|
|
260
|
+
{ "achievements", new SaveItem(json, null) }
|
|
261
|
+
},
|
|
262
|
+
new PlayerSaveOptions(new PublicWriteAccessClassOptions()));
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Server-Authoritative Writes via Cloud Code
|
|
269
|
+
|
|
270
|
+
In production, route all mutations through Cloud Code module endpoints. The module uses its service token to write to the Protected bucket, bypassing Access Control restrictions that block the player client.
|
|
271
|
+
|
|
272
|
+
The module's endpoints are `UnlockAchievement(achievementId)`, `UpdateAchievementProgress(achievementId, count)` and `ResetAchievements()`. The client calls them through the generated `AchievementModuleClient`, as in the client implementation below: `await m_Client.UnlockAchievement(id)`. `count` is the amount to add, not the new total.
|
|
273
|
+
|
|
274
|
+
### Delete / Reset Player Records (Direct)
|
|
275
|
+
|
|
276
|
+
```csharp
|
|
277
|
+
await CloudSaveService.Instance.Data.Player.DeleteAsync(
|
|
278
|
+
"achievements",
|
|
279
|
+
new PlayerDeleteOptions(new PublicWriteAccessClassOptions()));
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Full Client Implementation
|
|
285
|
+
|
|
286
|
+
Complete `AchievementManager` MonoBehaviour with dual-path support (trusted vs direct writes).
|
|
287
|
+
|
|
288
|
+
```csharp
|
|
289
|
+
using System;
|
|
290
|
+
using System.Collections.Generic;
|
|
291
|
+
using System.Linq;
|
|
292
|
+
using System.Threading.Tasks;
|
|
293
|
+
using AchievementModule.Client;
|
|
294
|
+
using AchievementModule.Cloud;
|
|
295
|
+
using Newtonsoft.Json;
|
|
296
|
+
using Unity.Services.Authentication;
|
|
297
|
+
using Unity.Services.CloudSave;
|
|
298
|
+
using Unity.Services.CloudSave.Models;
|
|
299
|
+
using Unity.Services.CloudSave.Models.Data.Player;
|
|
300
|
+
using PlayerSaveOptions = Unity.Services.CloudSave.Models.Data.Player.SaveOptions;
|
|
301
|
+
using PlayerLoadOptions = Unity.Services.CloudSave.Models.Data.Player.LoadOptions;
|
|
302
|
+
using PlayerDeleteOptions = Unity.Services.CloudSave.Models.Data.Player.DeleteOptions;
|
|
303
|
+
using Unity.Services.Core;
|
|
304
|
+
using Unity.Services.RemoteConfig;
|
|
305
|
+
using UnityEngine;
|
|
306
|
+
|
|
307
|
+
public class AchievementManager : MonoBehaviour
|
|
308
|
+
{
|
|
309
|
+
// Set to true to route writes through Cloud Code (required if Access Control is deployed).
|
|
310
|
+
// Set to false for direct Cloud Save writes (development only).
|
|
311
|
+
[SerializeField] bool m_UseTrustedWrites = true;
|
|
312
|
+
|
|
313
|
+
AchievementModuleClient m_Client;
|
|
314
|
+
|
|
315
|
+
public List<AchievementDefinition> Definitions { get; private set; } = new();
|
|
316
|
+
public List<AchievementRecord> Records { get; private set; } = new();
|
|
317
|
+
|
|
318
|
+
public event Action<AchievementDefinition> AchievementUnlocked;
|
|
319
|
+
public event Action Loaded;
|
|
320
|
+
|
|
321
|
+
async void Start()
|
|
322
|
+
{
|
|
323
|
+
await UnityServices.InitializeAsync();
|
|
324
|
+
await AuthenticationService.Instance.SignInAnonymouslyAsync();
|
|
325
|
+
m_Client = new AchievementModuleClient();
|
|
326
|
+
await LoadAsync();
|
|
327
|
+
Loaded?.Invoke();
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// -- Load ------------------------------------------------------------------
|
|
331
|
+
|
|
332
|
+
public async Task LoadAsync()
|
|
333
|
+
{
|
|
334
|
+
Definitions = await LoadDefinitionsAsync();
|
|
335
|
+
Records = await LoadRecordsAsync(AuthenticationService.Instance.PlayerId);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
struct UserAttributes {}
|
|
339
|
+
struct AppAttributes {}
|
|
340
|
+
|
|
341
|
+
async Task<List<AchievementDefinition>> LoadDefinitionsAsync()
|
|
342
|
+
{
|
|
343
|
+
var result = await RemoteConfigService.Instance.FetchConfigsAsync(
|
|
344
|
+
new UserAttributes(), new AppAttributes());
|
|
345
|
+
|
|
346
|
+
var token = result.config["achievements"];
|
|
347
|
+
return token.ToObject<List<AchievementDefinition>>();
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async Task<List<AchievementRecord>> LoadRecordsAsync(string playerId)
|
|
351
|
+
{
|
|
352
|
+
// Read from whichever bucket was written to -- Protected (trusted) or Public (direct).
|
|
353
|
+
ReadAccessClassOptions accessClass = m_UseTrustedWrites
|
|
354
|
+
? new ProtectedReadAccessClassOptions()
|
|
355
|
+
: new PublicReadAccessClassOptions(playerId);
|
|
356
|
+
|
|
357
|
+
var result = await CloudSaveService.Instance.Data.Player.LoadAsync(
|
|
358
|
+
new HashSet<string> { "achievements" },
|
|
359
|
+
new PlayerLoadOptions(accessClass));
|
|
360
|
+
|
|
361
|
+
if (!result.TryGetValue("achievements", out var item))
|
|
362
|
+
return new List<AchievementRecord>();
|
|
363
|
+
|
|
364
|
+
return JsonConvert.DeserializeObject<List<AchievementRecord>>(item.Value.GetAsString());
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// -- Unlock ----------------------------------------------------------------
|
|
368
|
+
|
|
369
|
+
public async Task UnlockAsync(string achievementId)
|
|
370
|
+
{
|
|
371
|
+
var def = Definitions.FirstOrDefault(d => d.Id == achievementId);
|
|
372
|
+
if (def == null)
|
|
373
|
+
{
|
|
374
|
+
Debug.LogWarning($"[Achievements] Unknown achievement: {achievementId}");
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
var record = GetOrCreateRecord(achievementId);
|
|
379
|
+
if (record.Unlocked)
|
|
380
|
+
return;
|
|
381
|
+
|
|
382
|
+
if (m_UseTrustedWrites)
|
|
383
|
+
{
|
|
384
|
+
// Server validates and writes -- bypasses Access Control restrictions.
|
|
385
|
+
await m_Client.UnlockAchievement(achievementId);
|
|
386
|
+
}
|
|
387
|
+
else
|
|
388
|
+
{
|
|
389
|
+
record.Unlocked = true;
|
|
390
|
+
await SaveRecordsAsync(Records);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
record.Unlocked = true;
|
|
394
|
+
AchievementUnlocked?.Invoke(def);
|
|
395
|
+
Debug.Log($"[Achievements] Unlocked: {def.Title}");
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// -- Progress (multi-stage) ------------------------------------------------
|
|
399
|
+
|
|
400
|
+
public async Task AddProgressAsync(string achievementId, int amount)
|
|
401
|
+
{
|
|
402
|
+
var def = Definitions.FirstOrDefault(d => d.Id == achievementId);
|
|
403
|
+
if (def == null || def.ProgressTarget <= 1)
|
|
404
|
+
return;
|
|
405
|
+
|
|
406
|
+
var record = GetOrCreateRecord(achievementId);
|
|
407
|
+
if (record.Unlocked)
|
|
408
|
+
return;
|
|
409
|
+
|
|
410
|
+
var newCount = record.ProgressCount + amount;
|
|
411
|
+
|
|
412
|
+
if (m_UseTrustedWrites)
|
|
413
|
+
{
|
|
414
|
+
await m_Client.UpdateAchievementProgress(achievementId, amount);
|
|
415
|
+
}
|
|
416
|
+
else
|
|
417
|
+
{
|
|
418
|
+
record.ProgressCount = newCount;
|
|
419
|
+
await SaveRecordsAsync(Records);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
record.ProgressCount = newCount;
|
|
423
|
+
|
|
424
|
+
if (record.ProgressCount >= def.ProgressTarget)
|
|
425
|
+
await UnlockAsync(achievementId);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// -- Reset -----------------------------------------------------------------
|
|
429
|
+
|
|
430
|
+
public async Task ResetAsync()
|
|
431
|
+
{
|
|
432
|
+
await CloudSaveService.Instance.Data.Player.DeleteAsync(
|
|
433
|
+
"achievements",
|
|
434
|
+
new PlayerDeleteOptions(new PublicWriteAccessClassOptions()));
|
|
435
|
+
|
|
436
|
+
Records.Clear();
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// -- Helpers ---------------------------------------------------------------
|
|
440
|
+
|
|
441
|
+
AchievementRecord GetOrCreateRecord(string id)
|
|
442
|
+
{
|
|
443
|
+
var record = Records.FirstOrDefault(r => r.Id == id);
|
|
444
|
+
if (record == null)
|
|
445
|
+
{
|
|
446
|
+
record = new AchievementRecord { Id = id };
|
|
447
|
+
Records.Add(record);
|
|
448
|
+
}
|
|
449
|
+
return record;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
async Task SaveRecordsAsync(List<AchievementRecord> records)
|
|
453
|
+
{
|
|
454
|
+
var json = JsonConvert.SerializeObject(records);
|
|
455
|
+
await CloudSaveService.Instance.Data.Player.SaveAsync(
|
|
456
|
+
new Dictionary<string, SaveItem>
|
|
457
|
+
{
|
|
458
|
+
{ "achievements", new SaveItem(json, null) }
|
|
459
|
+
},
|
|
460
|
+
new PlayerSaveOptions(new PublicWriteAccessClassOptions()));
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
## Cloud Code Module
|
|
468
|
+
|
|
469
|
+
The `AchievementModule` reads and writes to the **Protected** Cloud Save bucket using its service token, bypassing Access Control restrictions imposed on the player client.
|
|
470
|
+
|
|
471
|
+
> **Create the module with the Editor, not by hand.** Right-click a folder under `Assets/` ->
|
|
472
|
+
> **Create > Services > Cloud Code Module Script** and name it `AchievementModule`. See
|
|
473
|
+
> the Creating a Cloud Code Module section of `references/cloud-code-modules.md`.
|
|
474
|
+
>
|
|
475
|
+
> The Editor generates the following. Use the list to check what you got — there is nothing here to
|
|
476
|
+
> type out, and no `.sln`, `.csproj`, `.pubxml` or test project:
|
|
477
|
+
>
|
|
478
|
+
> - [ ] `Assets/CloudCode/AchievementModule/AchievementModule.ccmu` (holds the two asmdef GUIDs)
|
|
479
|
+
> - [ ] `Cloud/AchievementModule.asmdef` — Editor-only, `noEngineReferences`, `autoReferenced: false`
|
|
480
|
+
> - [ ] `Cloud/AchievementModule.cs` — rename/replace with the `[CloudCodeFunction]` endpoints below
|
|
481
|
+
> - [ ] `Client/AchievementModuleClient.asmdef` — references the cloud assembly
|
|
482
|
+
> - [ ] `Client/AchievementModuleClient.cs` — `[CloudModuleClient(typeof(AchievementModule))]` partial
|
|
483
|
+
>
|
|
484
|
+
> One file the Editor does **not** generate, which this module needs because its endpoints inject
|
|
485
|
+
> `IGameApiClient`:
|
|
486
|
+
>
|
|
487
|
+
> - [ ] `Cloud/CloudSetup.cs` — an `ICloudCodeSetup` calling `config.AddGameApiClient()`
|
|
488
|
+
> - [ ] `Newtonsoft.Json.dll` added to `precompiledReferences` in `Cloud/AchievementModule.asmdef` — the generated asmdef ships that array empty, and this module's code uses `JsonConvert` (see `references/cloud-code-modules.md`)
|
|
489
|
+
>
|
|
490
|
+
> The deployed module name is the **cloud asmdef's name** (`AchievementModule`), not the `.ccmu` filename.
|
|
491
|
+
>
|
|
492
|
+
> The client calls the module through the generated `AchievementModuleClient`. The assembly that holds `AchievementManager` must reference the module's client assembly; the Editor creates it auto-referenced, so the default game assembly already does.
|
|
493
|
+
|
|
494
|
+
### AchievementModule.cs
|
|
495
|
+
|
|
496
|
+
```csharp
|
|
497
|
+
using System.Collections.Generic;
|
|
498
|
+
using System.Linq;
|
|
499
|
+
using System.Threading.Tasks;
|
|
500
|
+
using Newtonsoft.Json;
|
|
501
|
+
using Unity.Services.CloudCode.Apis;
|
|
502
|
+
using Unity.Services.CloudCode.Core;
|
|
503
|
+
using Unity.Services.CloudSave.Model;
|
|
504
|
+
|
|
505
|
+
namespace AchievementModule.Cloud
|
|
506
|
+
{
|
|
507
|
+
// Each public method decorated with [CloudCodeFunction] becomes a callable endpoint.
|
|
508
|
+
// Injected services are method parameters marked [CloudCodeService]. Without the attribute a
|
|
509
|
+
// parameter leaks into the generated client binding as an argument the caller must supply.
|
|
510
|
+
public class AchievementModule
|
|
511
|
+
{
|
|
512
|
+
const string AchievementsKey = "achievements";
|
|
513
|
+
|
|
514
|
+
[CloudCodeFunction("UnlockAchievement")]
|
|
515
|
+
public async Task<AchievementRecord> UnlockAchievement(
|
|
516
|
+
[CloudCodeService] IExecutionContext context, [CloudCodeService] IGameApiClient client, string achievementId)
|
|
517
|
+
{
|
|
518
|
+
var records = await LoadRecordsAsync(context, client);
|
|
519
|
+
|
|
520
|
+
var record = records.FirstOrDefault(r => r.Id == achievementId);
|
|
521
|
+
if (record == null)
|
|
522
|
+
{
|
|
523
|
+
record = new AchievementRecord { Id = achievementId, Unlocked = true };
|
|
524
|
+
records.Add(record);
|
|
525
|
+
}
|
|
526
|
+
else
|
|
527
|
+
{
|
|
528
|
+
record.Unlocked = true;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
await SaveRecordsAsync(context, client, records);
|
|
532
|
+
return record;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
[CloudCodeFunction("UpdateAchievementProgress")]
|
|
536
|
+
public async Task<AchievementRecord> UpdateAchievementProgress(
|
|
537
|
+
[CloudCodeService] IExecutionContext context, [CloudCodeService] IGameApiClient client, string achievementId, int count)
|
|
538
|
+
{
|
|
539
|
+
var records = await LoadRecordsAsync(context, client);
|
|
540
|
+
|
|
541
|
+
var record = records.FirstOrDefault(r => r.Id == achievementId);
|
|
542
|
+
if (record == null)
|
|
543
|
+
{
|
|
544
|
+
record = new AchievementRecord { Id = achievementId, ProgressCount = count };
|
|
545
|
+
records.Add(record);
|
|
546
|
+
}
|
|
547
|
+
else
|
|
548
|
+
{
|
|
549
|
+
record.ProgressCount += count;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
await SaveRecordsAsync(context, client, records);
|
|
553
|
+
return record;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
[CloudCodeFunction("ResetAchievements")]
|
|
557
|
+
public async Task ResetAchievements([CloudCodeService] IExecutionContext context, [CloudCodeService] IGameApiClient client)
|
|
558
|
+
{
|
|
559
|
+
// Uses ServiceToken so the write bypasses Access Control restrictions on the player client.
|
|
560
|
+
await client.CloudSaveData.DeleteProtectedItemAsync(
|
|
561
|
+
context, context.ServiceToken, AchievementsKey,
|
|
562
|
+
context.ProjectId, context.PlayerId!);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
async Task<List<AchievementRecord>> LoadRecordsAsync(
|
|
566
|
+
IExecutionContext context, IGameApiClient client)
|
|
567
|
+
{
|
|
568
|
+
var result = await client.CloudSaveData.GetProtectedItemsAsync(
|
|
569
|
+
context, context.ServiceToken, context.ProjectId,
|
|
570
|
+
context.PlayerId, new List<string> { AchievementsKey });
|
|
571
|
+
|
|
572
|
+
var value = result.Data?.Results?.FirstOrDefault()?.Value?.ToString();
|
|
573
|
+
if (string.IsNullOrEmpty(value))
|
|
574
|
+
return new List<AchievementRecord>();
|
|
575
|
+
|
|
576
|
+
return JsonConvert.DeserializeObject<List<AchievementRecord>>(value);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
async Task SaveRecordsAsync(
|
|
580
|
+
IExecutionContext context, IGameApiClient client, List<AchievementRecord> records)
|
|
581
|
+
{
|
|
582
|
+
var json = JsonConvert.SerializeObject(records);
|
|
583
|
+
var body = new SetItemBody(AchievementsKey, json);
|
|
584
|
+
await client.CloudSaveData.SetProtectedItemAsync(
|
|
585
|
+
context, context.ServiceToken, context.ProjectId, context.PlayerId, body);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
### Server-Side Model
|
|
592
|
+
|
|
593
|
+
The single definition of the record, in `AchievementModule.cs`. Use public members with a public getter and setter, as the auto-properties below: the generator copies only those into the client stub, as `{ get; set; }` properties, and `Newtonsoft.Json` serializes them for the Cloud Save round-trip. Keep it in the module's `Cloud` namespace, and do not declare a second `AchievementRecord` in client code.
|
|
594
|
+
|
|
595
|
+
```csharp
|
|
596
|
+
namespace AchievementModule.Cloud
|
|
597
|
+
{
|
|
598
|
+
public class AchievementRecord
|
|
599
|
+
{
|
|
600
|
+
public string Id { get; set; }
|
|
601
|
+
public bool Unlocked { get; set; }
|
|
602
|
+
public int ProgressCount { get; set; }
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
---
|
|
608
|
+
|
|
609
|
+
## Cloud Resources
|
|
610
|
+
|
|
611
|
+
### Package Setup -- `manifest.json`
|
|
612
|
+
|
|
613
|
+
Add these entries to your Unity project's `Packages/manifest.json` dependencies:
|
|
614
|
+
|
|
615
|
+
```json
|
|
616
|
+
{
|
|
617
|
+
"dependencies": {
|
|
618
|
+
"com.unity.services.authentication": "3.6.1",
|
|
619
|
+
"com.unity.services.cloudcode": "3.0.0",
|
|
620
|
+
"com.unity.services.cloudsave": "3.4.0",
|
|
621
|
+
"com.unity.services.core": "1.16.0",
|
|
622
|
+
"com.unity.services.deployment": "1.7.2",
|
|
623
|
+
"com.unity.remote-config": "4.2.5",
|
|
624
|
+
"com.unity.services.tooling": "1.4.1"
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
> `com.unity.services.cloudcode` **`3.0.0`** is the native-authoring line, which the `.ccmu` module
|
|
630
|
+
> below requires — see the Requirements section of `references/cloud-code-modules.md`. The `2.10.x` line
|
|
631
|
+
> deploys legacy `.ccmr` modules only; use it when maintaining one, not for new work.
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
The `com.unity.services.tooling` package is required for `.ac` file deployment. The `com.unity.services.deployment` package provides the Deployment Window. `Newtonsoft.Json` is provided as a transitive dependency and does not need an explicit entry.
|
|
635
|
+
|
|
636
|
+
### Remote Config -- `Assets/Config/Achievements.rc`
|
|
637
|
+
|
|
638
|
+
Right-click in the Project window → **Assets > Create > Services > Remote Config**. Name the file `Achievements`. Replace the generated contents with:
|
|
639
|
+
|
|
640
|
+
```json
|
|
641
|
+
{
|
|
642
|
+
"$schema": "https://ugs-config-schemas.unity3d.com/v1/remote-config.schema.json",
|
|
643
|
+
"entries": {
|
|
644
|
+
"achievements": [
|
|
645
|
+
{
|
|
646
|
+
"Id": "first_win",
|
|
647
|
+
"Title": "First Win",
|
|
648
|
+
"Description": "Win your first match.",
|
|
649
|
+
"IsHidden": false,
|
|
650
|
+
"ProgressTarget": 0
|
|
651
|
+
},
|
|
652
|
+
{
|
|
653
|
+
"Id": "kill_100_enemies",
|
|
654
|
+
"Title": "Centurion",
|
|
655
|
+
"Description": "Defeat 100 enemies.",
|
|
656
|
+
"IsHidden": false,
|
|
657
|
+
"ProgressTarget": 100
|
|
658
|
+
},
|
|
659
|
+
{
|
|
660
|
+
"Id": "secret_ending",
|
|
661
|
+
"Title": "???",
|
|
662
|
+
"Description": "Find the secret ending.",
|
|
663
|
+
"IsHidden": true,
|
|
664
|
+
"ProgressTarget": 0
|
|
665
|
+
}
|
|
666
|
+
]
|
|
667
|
+
},
|
|
668
|
+
"types": {
|
|
669
|
+
"achievements": "JSON"
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
**Rules:**
|
|
675
|
+
|
|
676
|
+
- `Id` must be unique across all achievements.
|
|
677
|
+
- `ProgressTarget <= 1` means single-unlock (call `UnlockAsync`).
|
|
678
|
+
- `ProgressTarget > 1` means multi-stage (call `AddProgressAsync`).
|
|
679
|
+
- `IsHidden = true` — show obfuscated title/description until unlocked.
|
|
680
|
+
|
|
681
|
+
Deploy via **Services > Deployment** — the Deployment Window auto-discovers `.rc` files under `Assets/`.
|
|
682
|
+
|
|
683
|
+
### Cloud Code Module
|
|
684
|
+
|
|
685
|
+
> Create the module files listed in [Cloud Code Module](#cloud-code-module) above.
|
|
686
|
+
> See the Creating a Cloud Code Module section of `references/cloud-code-modules.md`
|
|
687
|
+
> for the generic templates; replace `MyModule` with `AchievementModule`.
|
|
688
|
+
>
|
|
689
|
+
> The `.ccmu` is deployed by the Deployment Window like any other cloud asset, but its code is compiled by
|
|
690
|
+
> Unity — a project that does not compile has nothing to deploy.
|
|
691
|
+
|
|
692
|
+
### Access Control -- `Assets/Config/DenyPlayerCloudSaveWrites.ac`
|
|
693
|
+
|
|
694
|
+
Right-click in the Project window → **Assets > Create > Services > Access Control**. Name the file `DenyPlayerCloudSaveWrites`. Replace the generated contents with:
|
|
695
|
+
|
|
696
|
+
```json
|
|
697
|
+
{
|
|
698
|
+
"$schema": "https://ugs-config-schemas.unity3d.com/v1/project-access-policy.schema.json",
|
|
699
|
+
"Statements": [
|
|
700
|
+
{
|
|
701
|
+
"Sid": "DenyPlayerCloudSaveWrites",
|
|
702
|
+
"Action": [
|
|
703
|
+
"Write"
|
|
704
|
+
],
|
|
705
|
+
"Effect": "Deny",
|
|
706
|
+
"Principal": "Player",
|
|
707
|
+
"Resource": "urn:ugs:cloud-save:*",
|
|
708
|
+
"Version": "1.0.0"
|
|
709
|
+
}
|
|
710
|
+
]
|
|
711
|
+
}
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
**Effect:** With this policy active, `AchievementManager` must have `m_UseTrustedWrites = true`. Direct Cloud Save writes from the player client will return `403`.
|
|
715
|
+
|
|
716
|
+
**Without this policy:** `m_UseTrustedWrites = false` works fine — suitable for development or games where anti-cheat is not a concern.
|
|
717
|
+
|
|
718
|
+
Deploy via **Services > Deployment** alongside your other files.
|
|
719
|
+
|
|
720
|
+
---
|
|
721
|
+
|
|
722
|
+
## Deployment Checklist
|
|
723
|
+
|
|
724
|
+
Deploy all cloud resources via **Services > Deployment** before testing:
|
|
725
|
+
|
|
726
|
+
- [ ] All packages added to `Packages/manifest.json` with correct minimum versions
|
|
727
|
+
- [ ] `Assets/Config/Achievements.rc` -- Remote Config achievement definitions
|
|
728
|
+
- [ ] `Assets/Config/DenyPlayerCloudSaveWrites.ac` -- Access Control policy blocking direct player writes
|
|
729
|
+
- [ ] Cloud Code module authored as a `.ccmu`: module asset, `Cloud/` + `Client/` asmdefs, endpoint class, `CloudSetup.cs` (see file list in [Cloud Code Module](#cloud-code-module))
|
|
730
|
+
- [ ] `AchievementModule.cs` added to the module project with `UnlockAchievement`, `UpdateAchievementProgress`, `ResetAchievements` functions
|
|
731
|
+
- [ ] Environment selected in the Deployment Window dropdown
|
|
732
|
+
- [ ] All resources deployed via the Deployment Window
|
|
733
|
+
- [ ] Project linked in **Edit > Project Settings > Services**
|
|
734
|
+
|
|
735
|
+
---
|
|
736
|
+
|
|
737
|
+
## Official Unity Reference
|
|
738
|
+
|
|
739
|
+
- **Unity Cloud Save documentation:** https://docs.unity.com/en-us/cloud-save.md
|
|
740
|
+
- **Unity Remote Config documentation:** https://docs.unity.com/en-us/remote-config.md
|
|
741
|
+
- **Unity Cloud Code documentation:** https://docs.unity.com/en-us/cloud-code.md
|
|
742
|
+
- **Access Control documentation:** https://docs.unity.com/en-us/cloud-code/modules/how-to-guides/access-control.md
|
|
743
|
+
|
|
744
|
+
---
|
|
745
|
+
|
|
746
|
+
## Validation
|
|
747
|
+
|
|
748
|
+
After implementing achievements, verify:
|
|
749
|
+
|
|
750
|
+
1. **Compilation:** The project compiles without errors. Ensure namespace aliases (`PlayerLoadOptions`, `PlayerSaveOptions`, `PlayerDeleteOptions`) are present.
|
|
751
|
+
2. **Initialization order:** `UnityServices.InitializeAsync()` then `AuthenticationService.Instance.SignInAnonymouslyAsync()` then service calls.
|
|
752
|
+
3. **Access class match:** If `m_UseTrustedWrites` is true, reads use `ProtectedReadAccessClassOptions()`. If false, reads use `PublicReadAccessClassOptions(playerId)`. Mismatched access classes return empty data.
|
|
753
|
+
4. **Cloud Code module builds:** `AchievementModule` compiles as part of the Unity project — the Editor-only cloud assembly has no compile errors, and the project's Api Compatibility Level is `.NET Standard 2.1`.
|
|
754
|
+
5. **One record definition:** `AchievementRecord` is declared only in the Cloud Code module, as `{ get; set; }` properties, and the client uses the generated stub of that type.
|
|
755
|
+
6. **Access Control deployed:** The `.ac` file denies direct player writes to Cloud Save. Without this, the trusted-write path has no enforcement advantage.
|
|
756
|
+
7. **Remote Config key matches:** The `.rc` file key (`"achievements"`) matches the key used in `result.config["achievements"]` on the client and `AchievementsKey` in the module.
|
|
757
|
+
8. **All cloud resources deployable:** The `.rc`, `.ac`, and `.ccmu` files are in the `Assets/` folder hierarchy and appear in the Deployment Window.
|