@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,1276 @@
|
|
|
1
|
+
# Cloud Code Scripts and Modules Reference
|
|
2
|
+
|
|
3
|
+
Covers **Cloud Code modules** (`.ccmu`, authored natively in the Editor), **legacy modules** (`.ccmr`
|
|
4
|
+
references to a .NET solution built outside Unity) and JavaScript **scripts**. For CloudBehaviours, see the CloudBehaviours reference.
|
|
5
|
+
|
|
6
|
+
> **On Unity 6000.5+, create new C# modules as `.ccmu`.** They are authored inside the Unity project, compile with it, and
|
|
7
|
+
> generate their client bindings at compile time — no separate solution, no `dotnet publish`. `.ccmr` is
|
|
8
|
+
> legacy: on 6000.5+ maintain the ones a project already has and do not create new ones; on an older
|
|
9
|
+
> Editor, new modules use `.ccmr`. On 6000.5+, if you are about to write a
|
|
10
|
+
> `.sln`, a `.csproj` or a `FolderProfile.pubxml`, you are on the wrong path — see
|
|
11
|
+
> [Creating a Cloud Code Module](#creating-a-cloud-code-module-ccmu) instead.
|
|
12
|
+
|
|
13
|
+
> **The signatures, enums and diagnostics below are a convenience snapshot and can lag the package.**
|
|
14
|
+
> Read the [Cloud Code documentation](https://docs.unity.com/en-us/cloud-code/modules/overview.md) for
|
|
15
|
+
> the project's installed `com.unity.services.cloudcode` version before acting, and again whenever a
|
|
16
|
+
> step fails or a symbol does not resolve.
|
|
17
|
+
|
|
18
|
+
## Table of Contents
|
|
19
|
+
|
|
20
|
+
- [Anti-Hallucination Reference](#anti-hallucination-reference)
|
|
21
|
+
- [ICloudCodeService Methods](#icloudcodeservice-methods)
|
|
22
|
+
- [Module Runtime Constraints](#module-runtime-constraints)
|
|
23
|
+
- [Subscriptions](#subscriptions)
|
|
24
|
+
- [Triggers](#triggers)
|
|
25
|
+
- [Creating a Cloud Code Module (.ccmu)](#creating-a-cloud-code-module-ccmu)
|
|
26
|
+
- [Injected Services](#injected-services-cloudcodeservice)
|
|
27
|
+
- [Registering Services](#registering-services-cloudcloudsetupcs)
|
|
28
|
+
- [Module State and Persistence](#module-state-and-persistence)
|
|
29
|
+
- [Evolving a persisted class](#evolving-a-persisted-class)
|
|
30
|
+
- [Generated Client Bindings](#generated-client-bindings)
|
|
31
|
+
- [Legacy Modules (.ccmr) - Maintenance Only](#legacy-modules-ccmr---maintenance-only)
|
|
32
|
+
- [Code Templates](#code-templates)
|
|
33
|
+
- [Deploying via the Deployment Window](#deploying-via-the-deployment-window)
|
|
34
|
+
- [Running the Local Cloud Code Server](#running-the-local-cloud-code-server)
|
|
35
|
+
- [Error Handling](#error-handling)
|
|
36
|
+
- [Error Messages](#error-messages)
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Anti-Hallucination Reference
|
|
41
|
+
|
|
42
|
+
| Correct | Incorrect (do NOT use) |
|
|
43
|
+
|---|---|
|
|
44
|
+
| For a new C# module, author a `.ccmu` in the Editor | Scaffolding a `.sln`/`.csproj`/`.pubxml` for a new module -- that is legacy `.ccmr` |
|
|
45
|
+
| Create a `.ccmu` module with **Create > Services > Cloud Code Module Script** | Hand-writing a new module's `.asmdef` files -- a module without a `.ccmu` never appears in the Deployment window |
|
|
46
|
+
| A `.ccmu` module's name is its **cloud asmdef** `name` | The `.ccmu` filename -- it is only a fallback |
|
|
47
|
+
| `.ccmu` client bindings are generated at **compile time** from `[CloudModuleClient]` | Clicking **Generate Bindings** -- that is the `.ccmr` flow |
|
|
48
|
+
| `[CloudCodeService]` on an injected parameter of a `.ccmu` endpoint | A bare one -- the server still injects it, but it leaks into the generated client binding |
|
|
49
|
+
| `config.AddGameApiClient()` in `ICloudCodeSetup` | `config.Dependencies.AddSingleton(GameApiClient.Create())` -- `Create()` is `[Obsolete]` |
|
|
50
|
+
| The generated client for a `.ccmu` module; `CallModuleEndpointAsync("Module", "Function", args)` for a legacy `.ccmr` module | `CallModuleEndpointAsync` on a `.ccmu` module, or `CallEndpointAsync` for modules |
|
|
51
|
+
| `CallEndpointAsync("scriptName", args)` | `CallEndpointAsync` for modules -- scripts only |
|
|
52
|
+
| `SubscribeToPlayerMessagesAsync()`, or the `(callbacks)` overload | `SubscribeToGeneralMessagesAsync` -- no such method |
|
|
53
|
+
| Pass a `SubscriptionEventCallbacks` to the subscribe call, or attach to `subscription.Callbacks` afterwards | Callbacks as a constructor argument -- there is no such constructor |
|
|
54
|
+
| Non-generic `CallEndpointAsync` returns `Task<string>` | Returns `Task` (void) -- it returns a string |
|
|
55
|
+
| Non-generic `CallModuleEndpointAsync` returns `Task<string>` | Returns `Task` (void) -- it returns a string |
|
|
56
|
+
| `EventConnectionState` enum | `SubscriptionEventConnectionState` |
|
|
57
|
+
| `Error` callback is `Action<string>` | `Action<EventConnectionState>` |
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## ICloudCodeService Methods
|
|
62
|
+
|
|
63
|
+
```csharp
|
|
64
|
+
// Call a Cloud Code Script endpoint. Returns the script's return value as string.
|
|
65
|
+
Task<string> CallEndpointAsync(string function, Dictionary<string, object> args = default)
|
|
66
|
+
|
|
67
|
+
// Call a Cloud Code Script endpoint with typed deserialization.
|
|
68
|
+
Task<TResult> CallEndpointAsync<TResult>(string function, Dictionary<string, object> args = default)
|
|
69
|
+
|
|
70
|
+
// Call a Cloud Code Module endpoint. Returns the result as string.
|
|
71
|
+
// module = module name, function = method name.
|
|
72
|
+
// Legacy .ccmr modules only: module = the .ccmr filename without extension.
|
|
73
|
+
// A .ccmu module is called through its generated client, not with this method.
|
|
74
|
+
Task<string> CallModuleEndpointAsync(string module, string function,
|
|
75
|
+
Dictionary<string, object> args = default, CloudCodeScope scope = default)
|
|
76
|
+
|
|
77
|
+
// Call a Cloud Code Module endpoint with typed deserialization.
|
|
78
|
+
Task<TResult> CallModuleEndpointAsync<TResult>(string module, string function,
|
|
79
|
+
Dictionary<string, object> args = default, CloudCodeScope scope = default)
|
|
80
|
+
|
|
81
|
+
// Subscribe to real-time messages sent to the current player. Requires Wire.
|
|
82
|
+
// Either wire callbacks up afterwards on the returned .Callbacks, or pass them in.
|
|
83
|
+
Task<ISubscriptionEvents> SubscribeToPlayerMessagesAsync()
|
|
84
|
+
Task<ISubscriptionEvents> SubscribeToPlayerMessagesAsync(SubscriptionEventCallbacks callbacks)
|
|
85
|
+
|
|
86
|
+
// Subscribe to real-time project-wide messages. Requires Wire. Same two forms.
|
|
87
|
+
Task<ISubscriptionEvents> SubscribeToProjectMessagesAsync()
|
|
88
|
+
Task<ISubscriptionEvents> SubscribeToProjectMessagesAsync(SubscriptionEventCallbacks callbacks)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**CRITICAL:** The first argument to `CallModuleEndpointAsync` is the **module name**, the second is the **function name**. Do NOT confuse the parameter order.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Module Runtime Constraints
|
|
96
|
+
|
|
97
|
+
- **Cold start:** An idle module's worker is eventually shut down, and the next call after that pays a latency spike while it spins back up. The idle window is a service-side setting rather than a documented guarantee, so design the UX to tolerate a slow first call rather than coding to a specific number.
|
|
98
|
+
- **No UnityEngine:** Modules run on standard .NET, not in the player. The `UnityEngine` namespace is NOT
|
|
99
|
+
available. A `.ccmu` cloud assembly is Editor-only and compiled by Unity, so its dependencies must be
|
|
100
|
+
available to the Editor; a `.ccmr` solution can pull NuGet packages directly.
|
|
101
|
+
- **Push messages:** Server-to-client push messages can only be sent from **modules**, not from JavaScript scripts.
|
|
102
|
+
- **.NET version:** modules target .NET 8 on Unity before 6.7 and on the local Cloud Code server; on
|
|
103
|
+
Unity 6.7+ they can target .NET 10. Legacy `.ccmr` solutions support up to .NET 10.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Subscriptions
|
|
108
|
+
|
|
109
|
+
Cloud Code Subscriptions require `com.unity.services.wire`. Subscribe to real-time server-push messages.
|
|
110
|
+
|
|
111
|
+
### `ISubscriptionEvents`
|
|
112
|
+
|
|
113
|
+
```csharp
|
|
114
|
+
public interface ISubscriptionEvents
|
|
115
|
+
{
|
|
116
|
+
SubscriptionEventCallbacks Callbacks { get; } // Wire up handlers here
|
|
117
|
+
Task SubscribeAsync(); // Re-subscribe after unsubscribing
|
|
118
|
+
Task UnsubscribeAsync(); // Stop receiving messages
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### `SubscriptionEventCallbacks`
|
|
123
|
+
|
|
124
|
+
There are two forms. **Prefer building the callbacks first and passing them in**, so every handler is
|
|
125
|
+
attached before the subscription goes live:
|
|
126
|
+
|
|
127
|
+
```csharp
|
|
128
|
+
var callbacks = new SubscriptionEventCallbacks();
|
|
129
|
+
callbacks.MessageReceived += OnMessageReceived; // Action<IMessageReceivedEvent>
|
|
130
|
+
callbacks.MessageBytesReceived += OnBytesReceived; // Action<byte[]>
|
|
131
|
+
callbacks.ConnectionStateChanged += OnStateChanged; // Action<EventConnectionState>
|
|
132
|
+
callbacks.Kicked += OnKicked; // Action
|
|
133
|
+
callbacks.Error += OnError; // Action<string>
|
|
134
|
+
|
|
135
|
+
var subscription = await CloudCodeService.Instance.SubscribeToPlayerMessagesAsync(callbacks);
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The parameterless overload is a convenience wrapper that calls the same method with a fresh, empty
|
|
139
|
+
`SubscriptionEventCallbacks`. You then attach handlers to the returned object's `Callbacks` property —
|
|
140
|
+
which means a message arriving between the subscription going live and your `+=` running has no handler to
|
|
141
|
+
reach:
|
|
142
|
+
|
|
143
|
+
```csharp
|
|
144
|
+
var subscription = await CloudCodeService.Instance.SubscribeToPlayerMessagesAsync();
|
|
145
|
+
subscription.Callbacks.MessageReceived += OnMessageReceived; // anything before this line is missed
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Both overloads exist for `SubscribeToProjectMessagesAsync` too.
|
|
149
|
+
|
|
150
|
+
### `IMessageReceivedEvent` Properties
|
|
151
|
+
|
|
152
|
+
| Property | Type | Description |
|
|
153
|
+
|---|---|---|
|
|
154
|
+
| `MessageType` | `string` | Message type identifier set by the server |
|
|
155
|
+
| `Message` | `string` | Raw message payload (typically JSON) |
|
|
156
|
+
| `Id` | `string` | Unique message ID |
|
|
157
|
+
| `CorrelationId` | `string` | Correlation ID for tracing |
|
|
158
|
+
| `Source` | `string` | Source of the message |
|
|
159
|
+
| `Type` | `string` | CloudEvents type |
|
|
160
|
+
| `SpecVersion` | `string` | CloudEvents spec version |
|
|
161
|
+
| `Time` | `DateTime` | Timestamp |
|
|
162
|
+
| `ProjectId` | `string` | UGS project ID |
|
|
163
|
+
| `EnvironmentId` | `string` | UGS environment ID |
|
|
164
|
+
|
|
165
|
+
### `EventConnectionState`
|
|
166
|
+
|
|
167
|
+
Namespace: `Unity.Services.CloudCode.Subscriptions`.
|
|
168
|
+
|
|
169
|
+
| State | Value | Meaning |
|
|
170
|
+
|---|---|---|
|
|
171
|
+
| `Unknown` | 0 | Initial state |
|
|
172
|
+
| `Unsubscribed` | 1 | Not connected |
|
|
173
|
+
| `Subscribing` | 2 | Connection in progress |
|
|
174
|
+
| `Subscribed` | 3 | Connected and receiving messages |
|
|
175
|
+
| `Unsynced` | 4 | Temporarily disconnected, will reconnect |
|
|
176
|
+
| `Error` | 5 | Connection error |
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Triggers
|
|
181
|
+
|
|
182
|
+
Triggers let Cloud Code endpoints run automatically in response to events from other UGS services, without any client call. Configured in the Unity Dashboard, the Deployment Window (`.tr` files, via `com.unity.services.tooling`), or `unity ugs tr` (see `references/ugs-cli.md`) -- not in client code.
|
|
183
|
+
|
|
184
|
+
Which services emit trigger events, and under what event names, is service-side configuration rather than
|
|
185
|
+
anything in this SDK — it changes independently of the package. Treat the following as illustration and
|
|
186
|
+
confirm the current set in the Dashboard's trigger editor, which lists what is actually available to your
|
|
187
|
+
project:
|
|
188
|
+
|
|
189
|
+
| Emitter | Example trigger events |
|
|
190
|
+
|---|---|
|
|
191
|
+
| Authentication | Player signed in, account deleted |
|
|
192
|
+
| Scheduler | Cron-based or one-shot scheduled events |
|
|
193
|
+
| Leaderboards | Score submitted, leaderboard reset |
|
|
194
|
+
| Cloud Save | Data saved, data deleted |
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Creating a Cloud Code Module (`.ccmu`)
|
|
199
|
+
|
|
200
|
+
A Cloud Code module is authored **inside the Unity project**. Its cloud code lives in an Editor-only
|
|
201
|
+
assembly that Unity compiles with the rest of the project, and the client bindings are generated at
|
|
202
|
+
compile time. There is no solution to build, nothing to publish, and no NuGet restore.
|
|
203
|
+
|
|
204
|
+
### Requirements
|
|
205
|
+
|
|
206
|
+
- Unity **6000.5** or newer.
|
|
207
|
+
- `com.unity.services.cloudcode` **3.0.0** or newer.
|
|
208
|
+
- **Api Compatibility Level set to `.NET Standard 2.1`** in Player Settings. Any other level makes the
|
|
209
|
+
module compile fine and then fail to deploy with "Unsupported API Compatibility Level".
|
|
210
|
+
|
|
211
|
+
### Create it
|
|
212
|
+
|
|
213
|
+
Right-click a folder under `Assets/` -> **Create > Services > Cloud Code Module Script** and fill in the
|
|
214
|
+
creation window. An agent without computer-use tools asks the user to run this menu step; with them, it
|
|
215
|
+
runs the step itself. The rest of this section describes what the Editor generates for you, so you can
|
|
216
|
+
recognise and check the result. It is not a list of files to write by hand.
|
|
217
|
+
|
|
218
|
+
Always use the menu for a new module, because it writes the module's `.ccmu` asset along with both
|
|
219
|
+
assemblies, and the Deployment window lists only modules that have a `.ccmu`. A module built from
|
|
220
|
+
hand-written asmdefs alone compiles and generates bindings, but never appears in the Deployment window,
|
|
221
|
+
and nothing reports why.
|
|
222
|
+
|
|
223
|
+
If an existing module is missing from the Deployment window, check for its `.ccmu`. When there is none,
|
|
224
|
+
repair the module by adding one rather than rebuilding it, because that keeps every assembly name, GUID
|
|
225
|
+
and script reference:
|
|
226
|
+
|
|
227
|
+
1. Stop until the user confirms a rollback point, such as a commit in their version control or a backup
|
|
228
|
+
copy of the project.
|
|
229
|
+
2. Write `<ModuleName>.ccmu` in the folder that holds both assembly folders, following
|
|
230
|
+
[`.ccmu` File Format](#ccmu-file-format).
|
|
231
|
+
3. Verify: the `.ccmu` imports with zero console errors, and the module is listed in
|
|
232
|
+
**Services > Deployment** under the cloud asmdef's name. If either check fails, restore the rollback
|
|
233
|
+
point and read the console error before trying again.
|
|
234
|
+
|
|
235
|
+
### Directory Structure
|
|
236
|
+
|
|
237
|
+
The module is a folder named after the module. The `.ccmu` sits at its root, one level **above** both
|
|
238
|
+
assemblies:
|
|
239
|
+
|
|
240
|
+
```
|
|
241
|
+
Assets/<AnyFolder>/
|
|
242
|
+
MyModule/
|
|
243
|
+
MyModule.ccmu <-- the module asset
|
|
244
|
+
Cloud/
|
|
245
|
+
MyModule.asmdef <-- Editor-only cloud assembly; its name IS the module name
|
|
246
|
+
MyCloudCodeScript.cs <-- [CloudCodeFunction] entry points
|
|
247
|
+
CloudSetup.cs <-- you add this; only if an endpoint injects IGameApiClient
|
|
248
|
+
Client/
|
|
249
|
+
MyModuleClient.asmdef <-- ships in the player; must reference the cloud assembly
|
|
250
|
+
MyCloudCodeScriptClient.cs <-- [CloudModuleClient] partial; bindings generated into it
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
The module folder can live anywhere under `Assets/`. **No `~` suffix** -- unlike `.ccmr`, the cloud
|
|
254
|
+
assembly is meant to be compiled by Unity, so hiding it from the importer would break the module.
|
|
255
|
+
|
|
256
|
+
This is the layout the Editor creates. Keep it, and in particular **leave the `.ccmu` in the folder it was
|
|
257
|
+
created in**. The Editor treats a folder holding a `.ccmu` as one that already contains a module, so moving
|
|
258
|
+
the file can allow a second module to be created on top of this one. If a project already keeps its `.ccmu`
|
|
259
|
+
somewhere else, leave that alone rather than "fixing" it.
|
|
260
|
+
|
|
261
|
+
### `.ccmu` File Format
|
|
262
|
+
|
|
263
|
+
Plain JSON, two GUIDs, nothing else. It carries no code, no name and no path -- it exists only to pair the
|
|
264
|
+
two assemblies. The create menu writes it for a new module; write it by hand only to repair an existing
|
|
265
|
+
module that lacks one:
|
|
266
|
+
|
|
267
|
+
```json
|
|
268
|
+
{
|
|
269
|
+
"cloudAssemblyDefinitionGuid": "<guid from Cloud/MyModule.asmdef.meta>",
|
|
270
|
+
"clientAssemblyDefinitionGuid": "<guid from Client/MyModuleClient.asmdef.meta>"
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**Never invent these values.** They are Unity asset GUIDs: Unity mints one per asset on import and stores
|
|
275
|
+
it in that asset's `.meta` file. Copy each one out of the `guid:` line of the matching `.asmdef.meta` --
|
|
276
|
+
the cloud assembly's for `cloudAssemblyDefinitionGuid`, the client assembly's for
|
|
277
|
+
`clientAssemblyDefinitionGuid`:
|
|
278
|
+
|
|
279
|
+
```yaml
|
|
280
|
+
# Cloud/MyModule.asmdef.meta
|
|
281
|
+
fileFormatVersion: 2
|
|
282
|
+
guid: <this value>
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
A GUID identifies an asset in one project only, so a value taken from anywhere else -- another project,
|
|
286
|
+
another module, a sample, an example in documentation -- resolves to nothing. Both keys are required; a
|
|
287
|
+
`.ccmu` naming a GUID Unity cannot resolve fails to import and logs an error.
|
|
288
|
+
|
|
289
|
+
Creating the module through the Editor writes the file with the right values already in it.
|
|
290
|
+
|
|
291
|
+
### Deployed Module Name
|
|
292
|
+
|
|
293
|
+
**The module name is the cloud assembly's `name` field, not the `.ccmu` filename.** That is the name the
|
|
294
|
+
generated bindings pass to `CallModuleEndpointAsync` and the name the module deploys under. The filename
|
|
295
|
+
is used only as a fallback, when the asmdef cannot be read.
|
|
296
|
+
|
|
297
|
+
The creation window keeps the three in sync: module name `MyModule` produces `MyModule.ccmu`,
|
|
298
|
+
`MyModule.asmdef` (cloud) and `MyModuleClient.asmdef` (client). **Leave those names as generated.** Renaming
|
|
299
|
+
a module means renaming the cloud assembly, and that changes the deployed module name and every call site
|
|
300
|
+
along with it.
|
|
301
|
+
|
|
302
|
+
### Cloud Assembly (`Cloud/MyModule.asmdef`)
|
|
303
|
+
|
|
304
|
+
Editor-only, no engine references, not auto-referenced. It must reference Cloud Code Core and Apis; the
|
|
305
|
+
source generator reference is what emits the client bindings.
|
|
306
|
+
|
|
307
|
+
```json
|
|
308
|
+
{
|
|
309
|
+
"name": "MyModule",
|
|
310
|
+
"rootNamespace": "",
|
|
311
|
+
"references": [
|
|
312
|
+
"Unity.Services.CloudCode.Apis",
|
|
313
|
+
"Unity.Services.CloudCode.Core",
|
|
314
|
+
"Unity.Services.CloudCode.SourceGenerator"
|
|
315
|
+
],
|
|
316
|
+
"includePlatforms": [ "Editor" ],
|
|
317
|
+
"excludePlatforms": [],
|
|
318
|
+
"allowUnsafeCode": false,
|
|
319
|
+
"overrideReferences": true,
|
|
320
|
+
"precompiledReferences": [],
|
|
321
|
+
"autoReferenced": false,
|
|
322
|
+
"defineConstraints": [ "UNITY_6000_5_OR_NEWER" ],
|
|
323
|
+
"versionDefines": [],
|
|
324
|
+
"noEngineReferences": true
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
`includePlatforms: ["Editor"]`, `noEngineReferences: true` and `autoReferenced: false` all matter: they keep
|
|
329
|
+
server-only code out of player builds, out of every other assembly's global scope, and away from
|
|
330
|
+
`UnityEngine` types that do not exist on the Cloud Code host.
|
|
331
|
+
|
|
332
|
+
**Nothing validates them at import or deploy time, though.** Get one wrong and the module still imports,
|
|
333
|
+
still compiles, and still deploys — it is simply wrong in ways you find out later. The checks that do exist:
|
|
334
|
+
|
|
335
|
+
| When | What is checked | How it surfaces |
|
|
336
|
+
| :-- | :-- | :-- |
|
|
337
|
+
| `.ccmu` import | only that both asmdef GUIDs resolve | console error, asset fails to import |
|
|
338
|
+
| **Create > Services > Cloud Code Module Script** into an existing module's `Cloud/` folder | Editor-only, no engine references, not auto-referenced, references Core + Apis | modal dialog: *"This Cloud Code module's server Assembly Definition (.asmdef) is missing or misconfigured..."*, and the script is not created |
|
|
339
|
+
| Deploy | nothing about the asmdef flags; the only structural check is that two modules do not share a cloud assembly | per-item error status on each offending module in the Deployment window, logged as *"Assembly Failure -- Multiple CCMs reference the same assembly definition"* |
|
|
340
|
+
|
|
341
|
+
So the asmdef flags are effectively enforced only when you add a script through the menu. A hand-edited
|
|
342
|
+
module is on the honour system — worth checking by eye, because nothing else will. (Deploy
|
|
343
|
+
does make other checks unrelated to the asmdef — Api Compatibility Level and whether the project compiles —
|
|
344
|
+
covered under [Deploying via the Deployment Window](#deploying-via-the-deployment-window).)
|
|
345
|
+
|
|
346
|
+
### Client Assembly (`Client/MyModuleClient.asmdef`)
|
|
347
|
+
|
|
348
|
+
This is the assembly that ships in your game. It **must reference the cloud assembly**, which looks wrong at
|
|
349
|
+
first glance: the cloud assembly is Editor-only, so how can a runtime assembly that gets compiled into a
|
|
350
|
+
player depend on it?
|
|
351
|
+
|
|
352
|
+
- **In the Editor**, the reference is what lets the source generator see the cloud class and read its
|
|
353
|
+
`[CloudCodeFunction]` methods, and what makes `[CloudModuleClient(typeof(MyCloudCodeScript))]` resolve.
|
|
354
|
+
Without it there is nothing to generate bindings from.
|
|
355
|
+
- **In a player build**, the Editor-only cloud assembly is not linked — so the generator emits an **empty
|
|
356
|
+
stub type** for each bound cloud class, purely so the `typeof(...)` in the attribute still resolves. The
|
|
357
|
+
stub carries no members.
|
|
358
|
+
|
|
359
|
+
The upshot: your server code never ships to players. What ships is the generated bindings plus an empty
|
|
360
|
+
shell of the cloud class name. You do not write or manage the stubs; they exist only in non-Editor
|
|
361
|
+
compilation.
|
|
362
|
+
|
|
363
|
+
The asmdef itself is an ordinary runtime one — no `includePlatforms`, engine references allowed:
|
|
364
|
+
|
|
365
|
+
```json
|
|
366
|
+
{
|
|
367
|
+
"name": "MyModuleClient",
|
|
368
|
+
"rootNamespace": "",
|
|
369
|
+
"references": [
|
|
370
|
+
"MyModule",
|
|
371
|
+
"Unity.Services.CloudCode",
|
|
372
|
+
"Unity.Services.CloudCode.SourceGenerator"
|
|
373
|
+
],
|
|
374
|
+
"includePlatforms": [],
|
|
375
|
+
"excludePlatforms": [],
|
|
376
|
+
"autoReferenced": true,
|
|
377
|
+
"defineConstraints": [],
|
|
378
|
+
"versionDefines": [],
|
|
379
|
+
"noEngineReferences": false
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Cloud Script (`Cloud/MyCloudCodeScript.cs`)
|
|
384
|
+
|
|
385
|
+
A plain class — no `.csproj`, no publish profile.
|
|
386
|
+
|
|
387
|
+
```csharp
|
|
388
|
+
using System.Threading.Tasks;
|
|
389
|
+
using Unity.Services.CloudCode.Core;
|
|
390
|
+
|
|
391
|
+
namespace MyModule.Cloud
|
|
392
|
+
{
|
|
393
|
+
// The scope determines how state is persisted. See Module State and Persistence below.
|
|
394
|
+
[StateScope(Scope.Player)]
|
|
395
|
+
public class MyCloudCodeScript
|
|
396
|
+
{
|
|
397
|
+
[CloudCodeFunction(nameof(SayHello))]
|
|
398
|
+
public string SayHello(string name) => $"Hello, {name}!";
|
|
399
|
+
|
|
400
|
+
[CloudCodeFunction(nameof(GetScoreAsync))]
|
|
401
|
+
public async Task<int> GetScoreAsync() => await Task.FromResult(1);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
The generated namespaces derive from the **cloud** assembly name: `<CloudAsmName>.Cloud` for cloud scripts
|
|
407
|
+
and `<CloudAsmName>.Client` for client scripts.
|
|
408
|
+
|
|
409
|
+
### Injected Services (`[CloudCodeService]`)
|
|
410
|
+
|
|
411
|
+
A module gets its services in one of two ways — constructor injection and marked method parameters. Both
|
|
412
|
+
are supported and both generate correct client bindings; the one exception is `IExecutionContext`, which
|
|
413
|
+
must always be a method parameter (see below).
|
|
414
|
+
|
|
415
|
+
**Constructor injection.** The class takes its services in a constructor; the runtime resolves them from the
|
|
416
|
+
DI container. The class must have **exactly one public constructor**, or instantiation fails with
|
|
417
|
+
`Invalid constructor: type X must have exactly one public constructor`. Constructor parameters are not
|
|
418
|
+
endpoint parameters, so they never reach the generated client:
|
|
419
|
+
|
|
420
|
+
```csharp
|
|
421
|
+
public class Rewards
|
|
422
|
+
{
|
|
423
|
+
readonly IGameApiClient _client;
|
|
424
|
+
public Rewards(IGameApiClient client) => _client = client;
|
|
425
|
+
|
|
426
|
+
[CloudCodeFunction(nameof(Grant))]
|
|
427
|
+
public Task<int> Grant(string rewardId) { ... } // client sees: Grant(string rewardId)
|
|
428
|
+
}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
**Method-parameter injection.** The service is a parameter on the endpoint itself. Here — and *only* here —
|
|
432
|
+
it must carry `[CloudCodeService]`:
|
|
433
|
+
|
|
434
|
+
```csharp
|
|
435
|
+
[CloudCodeFunction(nameof(Grant))]
|
|
436
|
+
public Task<int> Grant([CloudCodeService] IGameApiClient client, string rewardId) { ... }
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
The attribute tells the generator "this is injected, not caller data". It has no bearing on constructor
|
|
440
|
+
parameters.
|
|
441
|
+
|
|
442
|
+
**Mark every injected parameter.** An unmarked parameter is treated as caller data: it lands in the
|
|
443
|
+
generated client as an argument the caller has to supply, and is sent in the request payload. That is
|
|
444
|
+
almost never what you want for a service — `IExecutionContext` in particular is a server-side interface
|
|
445
|
+
the client cannot construct, so the generated client is unusable. No diagnostic fires; an unexplained
|
|
446
|
+
error inside a generated `.g.cs` is the signal.
|
|
447
|
+
|
|
448
|
+
**`IExecutionContext` must be a `[CloudCodeService]` method parameter** — a constructor asking for it
|
|
449
|
+
fails. Other services are fine in the constructor, and that is the tidier form when every endpoint uses
|
|
450
|
+
them. Keep per-invocation values out of the constructor: the instance is cached per scope, so whatever it
|
|
451
|
+
captures outlives a single call.
|
|
452
|
+
|
|
453
|
+
The runtime provides these without any registration:
|
|
454
|
+
|
|
455
|
+
| Service | Gives you |
|
|
456
|
+
| :-- | :-- |
|
|
457
|
+
| `IExecutionContext` | player ID, project ID, environment, service token, session |
|
|
458
|
+
| `ILogger<T>` | logging |
|
|
459
|
+
| `ITimerService` | register and cancel server-owned timers that call back into the module |
|
|
460
|
+
| `IPushClient` | send push messages to a player or the whole project |
|
|
461
|
+
|
|
462
|
+
Anything else — `IGameApiClient` above all — must be registered in `CloudSetup.cs` (below) before it can be
|
|
463
|
+
injected. Private helper methods are ordinary C# and need no attribute; only `[CloudCodeFunction]`
|
|
464
|
+
endpoints do.
|
|
465
|
+
|
|
466
|
+
### Registering Services (`Cloud/CloudSetup.cs`)
|
|
467
|
+
|
|
468
|
+
A class implementing `ICloudCodeSetup` registers what `[CloudCodeService]` can inject. Required once per
|
|
469
|
+
module if any endpoint injects `IGameApiClient`; omit the file entirely if none do.
|
|
470
|
+
|
|
471
|
+
```csharp
|
|
472
|
+
using Unity.Services.CloudCode.Apis.Extensions;
|
|
473
|
+
using Unity.Services.CloudCode.Core;
|
|
474
|
+
|
|
475
|
+
namespace MyModule.Cloud
|
|
476
|
+
{
|
|
477
|
+
public class CloudSetup : ICloudCodeSetup
|
|
478
|
+
{
|
|
479
|
+
public void Setup(ICloudCodeConfig config)
|
|
480
|
+
{
|
|
481
|
+
config.AddGameApiClient();
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
Use the `config.AddGameApiClient()` extension. The older
|
|
488
|
+
`config.Dependencies.AddSingleton(GameApiClient.Create())` still appears in legacy `.ccmr` modules, but
|
|
489
|
+
`GameApiClient.Create()` is `[Obsolete]` — its own message directs you to `AddGameApiClient()`.
|
|
490
|
+
|
|
491
|
+
### Client Script (`Client/MyCloudCodeScriptClient.cs`)
|
|
492
|
+
|
|
493
|
+
An empty `partial` class carrying `[CloudModuleClient(typeof(CloudClass))]`. The source generator fills in
|
|
494
|
+
the other half at compile time. Do not add members here -- they will collide with the generated partial.
|
|
495
|
+
|
|
496
|
+
```csharp
|
|
497
|
+
using Unity.Services.CloudCode;
|
|
498
|
+
|
|
499
|
+
namespace MyModule.Client
|
|
500
|
+
{
|
|
501
|
+
[CloudModuleClient(typeof(global::MyModule.Cloud.MyCloudCodeScript))]
|
|
502
|
+
public partial class MyCloudCodeScriptClient
|
|
503
|
+
{
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### Adding Another Script to an Existing Module
|
|
509
|
+
|
|
510
|
+
Right-click the module's **`Cloud/` folder** -> **Create > Services > Cloud Code Module Script**. The same
|
|
511
|
+
menu item detects the existing module and creates the cloud/client pair inside it instead of starting a
|
|
512
|
+
new module. Creating a script in the `Client/` folder is rejected -- always start from `Cloud/`.
|
|
513
|
+
|
|
514
|
+
---
|
|
515
|
+
|
|
516
|
+
## Module State and Persistence
|
|
517
|
+
|
|
518
|
+
A module class annotated `[StateScope]` keeps state between calls. The runtime holds one instance per
|
|
519
|
+
scope — per player, or per multiplayer session — and every change to a persisted member is saved for you.
|
|
520
|
+
Saving is guaranteed, but not necessarily complete by the time a call returns, so do not treat "the call
|
|
521
|
+
finished" as "the state is on disk".
|
|
522
|
+
|
|
523
|
+
```csharp
|
|
524
|
+
using System.Collections.Generic;
|
|
525
|
+
using Microsoft.Extensions.Logging;
|
|
526
|
+
using Unity.Services.CloudCode.Core;
|
|
527
|
+
|
|
528
|
+
[StateScope(Scope.MultiplayerSession)]
|
|
529
|
+
public class TurnTimer
|
|
530
|
+
{
|
|
531
|
+
public bool GameOver; // public members persist
|
|
532
|
+
public int TurnCount;
|
|
533
|
+
public List<string> History = new();
|
|
534
|
+
|
|
535
|
+
[CloudCodeSerializeProperty]
|
|
536
|
+
private string _secretAnswer; // private, but persisted
|
|
537
|
+
|
|
538
|
+
[CloudCodeIgnoreProperty]
|
|
539
|
+
public int CallsThisSession; // public, but not persisted
|
|
540
|
+
|
|
541
|
+
readonly ILogger<TurnTimer> _logger; // not persisted; injected by the constructor below
|
|
542
|
+
|
|
543
|
+
// Services are injected through the constructor or a [CloudCodeService] parameter -- never into a
|
|
544
|
+
// bare field. A field nothing assigns stays null.
|
|
545
|
+
public TurnTimer(ILogger<TurnTimer> logger) => _logger = logger;
|
|
546
|
+
|
|
547
|
+
[CloudCodeFunction("PlayTurn")]
|
|
548
|
+
public void PlayTurn(string move) { TurnCount++; }
|
|
549
|
+
}
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
| Scope | Instance per | Typical use |
|
|
553
|
+
| :-- | :-- | :-- |
|
|
554
|
+
| `Scope.Player` | player | inventory, progression, per-player counters |
|
|
555
|
+
| `Scope.MultiplayerSession` | session | turn order, shared board, match state |
|
|
556
|
+
| *(no `[StateScope]`)* | none — stateless | validation, rewards, calls to other services |
|
|
557
|
+
|
|
558
|
+
### Targeting an instance from the client
|
|
559
|
+
|
|
560
|
+
A `.ccmu` client targets an instance through its constructor: the generated client takes the player ID or the session ID that its cloud class's `[StateScope]` needs (see [Generated Client Bindings](#generated-client-bindings)), and passes the matching `CloudCodeScope` for you.
|
|
561
|
+
|
|
562
|
+
```csharp
|
|
563
|
+
// Scope to a multiplayer session: the generated client takes the session ID
|
|
564
|
+
var client = new TurnTimerClient(sessionId);
|
|
565
|
+
await client.PlayTurn(move);
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
`CloudCodeScope` and `ScopeType` (namespace `Unity.Services.CloudCode`) are what the generated client sends on your behalf.
|
|
569
|
+
|
|
570
|
+
#### `ScopeType` Enum
|
|
571
|
+
|
|
572
|
+
| Scope Type | Value | Description |
|
|
573
|
+
|---|---|---|
|
|
574
|
+
| `MultiplayerSession` | 1 | Scoped to a multiplayer session ID |
|
|
575
|
+
| `Player` | 2 | Scoped to the calling player |
|
|
576
|
+
|
|
577
|
+
**There is no default scope.** The `scope` argument defaults to `null`, which is sent as "no scope", not as
|
|
578
|
+
`Player`. A function on a `[StateScope]` class called without a scope is rejected server-side with
|
|
579
|
+
*"Function's ... class in module ... requires a scope and must be called with a scope"*. Conversely a
|
|
580
|
+
non-scoped function called *with* a scope is rejected too, and a mismatched scope
|
|
581
|
+
(`Player` against a `[StateScope(Scope.MultiplayerSession)]` class) fails the same way. The generated client
|
|
582
|
+
gets this right on its own — it passes the scope its cloud class declares — which is one more reason to always call a `.ccmu` module through it.
|
|
583
|
+
|
|
584
|
+
---
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
### What persists
|
|
588
|
+
|
|
589
|
+
- **public members persist** — fields and properties alike
|
|
590
|
+
- `[CloudCodeSerializeProperty]` persists a non-public member
|
|
591
|
+
- `[CloudCodeIgnoreProperty]` excludes a public one
|
|
592
|
+
- anything else — plain private fields — is not persisted
|
|
593
|
+
|
|
594
|
+
**Accessibility decides this, not what the member holds.** The serializer asks only: is it ignored, is it
|
|
595
|
+
explicitly marked, is it public? There is no exemption for service types. So a service or logger assigned to
|
|
596
|
+
a **public** field *is* persisted, with two consequences worth avoiding:
|
|
597
|
+
|
|
598
|
+
- Its object graph is walked and written into your scope state — which for `IExecutionContext` means the
|
|
599
|
+
service token, and for `IGameApiClient` means client configuration.
|
|
600
|
+
- Persisted members are written back when state is loaded, so the value the constructor injected is overwritten by
|
|
601
|
+
whatever came out of storage — and an interface cannot be reconstructed from the stored JSON.
|
|
602
|
+
|
|
603
|
+
Keep injected services and loggers in **private** fields, which are excluded by default:
|
|
604
|
+
|
|
605
|
+
```csharp
|
|
606
|
+
readonly IGameApiClient _client; // private: not persisted
|
|
607
|
+
readonly ILogger<MyModule> _logger;
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
If one genuinely has to be public, exclude it explicitly:
|
|
611
|
+
|
|
612
|
+
```csharp
|
|
613
|
+
[CloudCodeIgnoreProperty]
|
|
614
|
+
public IGameApiClient Client; // public, but excluded
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
`[CloudCodeIgnoreProperty]` is checked before `[CloudCodeSerializeProperty]`, so ignore always wins.
|
|
618
|
+
|
|
619
|
+
"Not persisted" is not the same as "reset every call". The runtime keeps one instance per scope and can
|
|
620
|
+
reuse it across invocations. Do not build on that: a non-persisted field may hold its value across
|
|
621
|
+
invocations but can be discarded at any point. Keep anything that must survive in a persisted member.
|
|
622
|
+
|
|
623
|
+
### Custom serialization
|
|
624
|
+
|
|
625
|
+
Implementing `IStateSerializer` on the module class replaces the default serialization entirely:
|
|
626
|
+
|
|
627
|
+
```csharp
|
|
628
|
+
public byte[] OnSerialize();
|
|
629
|
+
public void OnDeserialize(byte[] input);
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
The runtime checks the module instance for the interface and uses it when present, so this works because
|
|
633
|
+
the class you author is the class that gets deployed. Reach for it when the default serialization is
|
|
634
|
+
unworkable — large binary blobs, custom compression, or a schema you want to version by hand.
|
|
635
|
+
|
|
636
|
+
### Evolving a persisted class
|
|
637
|
+
|
|
638
|
+
Persisted state outlives the code that wrote it. A deployed module reads records written by every
|
|
639
|
+
earlier version of itself, so the shape of a `[StateScope]` class is a storage contract, not just a
|
|
640
|
+
class declaration. Nothing warns you: the module compiles, deploys and runs, and the damage shows up
|
|
641
|
+
later as a value that is gone, a list that keeps growing, or a scope that no longer loads.
|
|
642
|
+
|
|
643
|
+
| Change to a persisted class | What happens to already-persisted records |
|
|
644
|
+
| :-- | :-- |
|
|
645
|
+
| **Add** a member | Keeps its field initializer (or the value the constructor set). Loading only overwrites members that are present in the stored record. |
|
|
646
|
+
| **Rename** a member | Treated as a remove plus an add: the stored value is stranded and the new member keeps its initializer. |
|
|
647
|
+
| **Remove** a member | Value is dropped on the next write. |
|
|
648
|
+
| **Retype** a member | A compatible stored value converts (a number into a `string` field). An incompatible one (`"abc"` into an `int`, an object into a number) throws on load, and every call for that scope fails with `CloudCodeExecutionScopeStateException` until the state is fixed. |
|
|
649
|
+
| **Rename the class** | Instance state is keyed on the class name, so a rename orphans that scope's entire state — every field, not one. |
|
|
650
|
+
| **Change the `Scope`** | Different instance key. The old state is unreachable. |
|
|
651
|
+
|
|
652
|
+
**Give a new member the initializer you want; it applies to old records.** A new `bool AllowSpectators = true;`
|
|
653
|
+
reads `true` for every player or session that existed before the change, because the instance is
|
|
654
|
+
constructed first and the stored record only overwrites the members it contains.
|
|
655
|
+
|
|
656
|
+
**Initialize persisted collections empty.** Loading adds stored items to the collection the
|
|
657
|
+
initializer already created, so `public List<string> Tags = new() { "starter" };` gains another
|
|
658
|
+
`"starter"` on every load and save. A `HashSet` or `Dictionary` does not duplicate, but its
|
|
659
|
+
initializer entries come back after the player's state removed them. Declare persisted collections
|
|
660
|
+
as `= new();` and add starting items from code the first time the state is set up.
|
|
661
|
+
|
|
662
|
+
**Design the state so migration stays cheap:**
|
|
663
|
+
|
|
664
|
+
- Keep the persisted surface small and explicit. Private fields plus `[CloudCodeIgnoreProperty]` on
|
|
665
|
+
anything derived or cached means fewer members that have to be migrated later.
|
|
666
|
+
- Put game state in one payload class, versioned as a whole, rather than a wide set of loose fields
|
|
667
|
+
on the module class.
|
|
668
|
+
- Never reuse a member name for a new meaning, and never renumber or reorder an enum that is
|
|
669
|
+
persisted — the stored value is what comes back.
|
|
670
|
+
- Do not hide the real schema inside a hand-serialized string. A single opaque `string` field keeps
|
|
671
|
+
every one of these problems and moves them into your own serializer.
|
|
672
|
+
|
|
673
|
+
**Migrate with a version envelope and a chain of forms.** Implement `IStateSerializer`, stamp a
|
|
674
|
+
schema version alongside the payload, and let each version own the single hop to the next. A module
|
|
675
|
+
never writes a migration between two arbitrary versions: it deserializes whichever form was
|
|
676
|
+
persisted and calls `Migrate` until it reaches the latest.
|
|
677
|
+
|
|
678
|
+
```csharp
|
|
679
|
+
using System;
|
|
680
|
+
using System.Text;
|
|
681
|
+
using Newtonsoft.Json;
|
|
682
|
+
using Newtonsoft.Json.Linq;
|
|
683
|
+
using Unity.Services.CloudCode.Core;
|
|
684
|
+
|
|
685
|
+
public interface ISchema
|
|
686
|
+
{
|
|
687
|
+
ISchema Migrate(); // next form, or null when this is the latest
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
public class SchemaV1 : ISchema
|
|
691
|
+
{
|
|
692
|
+
public string Name = "";
|
|
693
|
+
public ISchema Migrate() => new SchemaV2 { CharacterName = Name };
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
public class SchemaV2 : ISchema
|
|
697
|
+
{
|
|
698
|
+
public string CharacterName = "";
|
|
699
|
+
public ISchema Migrate() => null;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
[StateScope(Scope.Player)]
|
|
703
|
+
public class PlayerProfile : IStateSerializer
|
|
704
|
+
{
|
|
705
|
+
const int SchemaVersion = 2;
|
|
706
|
+
|
|
707
|
+
public SchemaV2 Data = new();
|
|
708
|
+
|
|
709
|
+
public byte[] OnSerialize()
|
|
710
|
+
{
|
|
711
|
+
var envelope = new JObject
|
|
712
|
+
{
|
|
713
|
+
["SchemaVersion"] = SchemaVersion,
|
|
714
|
+
["Data"] = JObject.FromObject(Data),
|
|
715
|
+
};
|
|
716
|
+
return Encoding.UTF8.GetBytes(envelope.ToString(Formatting.None));
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
public void OnDeserialize(byte[] input)
|
|
720
|
+
{
|
|
721
|
+
// Nothing stored for this scope -- keep the instance as constructed.
|
|
722
|
+
if (input == null || input.Length == 0)
|
|
723
|
+
return;
|
|
724
|
+
|
|
725
|
+
var envelope = JObject.Parse(Encoding.UTF8.GetString(input));
|
|
726
|
+
var version = envelope["SchemaVersion"]?.Value<int>() ?? 1;
|
|
727
|
+
// A record written before this serializer existed is the bare v1 member set, with no envelope.
|
|
728
|
+
var payload = envelope["SchemaVersion"] == null ? envelope : (JObject)envelope["Data"];
|
|
729
|
+
|
|
730
|
+
ISchema schema = version switch
|
|
731
|
+
{
|
|
732
|
+
1 => payload.ToObject<SchemaV1>(),
|
|
733
|
+
2 => payload.ToObject<SchemaV2>(),
|
|
734
|
+
// State written by a newer version cannot be migrated backwards, and running on a
|
|
735
|
+
// default payload would silently discard the player's data.
|
|
736
|
+
_ => throw new Exception($"cannot read schema version {version}, this module writes {SchemaVersion}"),
|
|
737
|
+
};
|
|
738
|
+
|
|
739
|
+
while (schema?.Migrate() is { } next) schema = next;
|
|
740
|
+
|
|
741
|
+
Data = schema as SchemaV2 ?? throw new Exception($"chain from version {version} did not end at SchemaV2");
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
`JObject` needs `Newtonsoft.Json.dll` in the **cloud** asmdef's `precompiledReferences`. The generated
|
|
747
|
+
asmdef ships that array empty with `overrideReferences: true`, so the reference has to be added by hand —
|
|
748
|
+
without it the `Newtonsoft.Json` namespace does not resolve in the module assembly at all:
|
|
749
|
+
|
|
750
|
+
```json
|
|
751
|
+
"overrideReferences": true,
|
|
752
|
+
"precompiledReferences": [ "Newtonsoft.Json.dll" ],
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
Adding a `SchemaV3` later means writing `SchemaV2.Migrate`, bumping `SchemaVersion`, adding a `3` case
|
|
756
|
+
to the switch and changing both the `Data` field type and the final type check to `SchemaV3`. The
|
|
757
|
+
earlier hops do not change. The default serializer stores the persisted members as a JSON object
|
|
758
|
+
keyed by member name, so a record written before the envelope existed is that bare v1 payload. Keep the old
|
|
759
|
+
`SchemaV*` classes — deleting one breaks every record still stored in that form. A hop is also where
|
|
760
|
+
a value is repaired rather than defaulted: a list that did not exist in v1, a cosmetic an older
|
|
761
|
+
record claims the player owns.
|
|
762
|
+
|
|
763
|
+
**Verify against what is actually stored, not against the class.** Before changing a deployed
|
|
764
|
+
`[StateScope]` class, look at what real records hold. There is no API or CLI that reads a module's
|
|
765
|
+
server-side state, so add a temporary read-only `[CloudCodeFunction]` that returns only the members
|
|
766
|
+
you need to check, deploy it to a non-production environment, call it once for a known player or
|
|
767
|
+
session, and remove it afterwards. A deployed function is callable by clients, so never return
|
|
768
|
+
secret state such as `_secretAnswer`. This shows the values after deserialization, not the stored
|
|
769
|
+
format.
|
|
770
|
+
|
|
771
|
+
## Generated Client Bindings
|
|
772
|
+
|
|
773
|
+
`.ccmu` and `.ccmr` both give you a strongly-typed client instead of raw `CallModuleEndpointAsync`, but
|
|
774
|
+
they generate it in completely different ways. Do not mix the two flows.
|
|
775
|
+
|
|
776
|
+
### `.ccmu` -- generated at compile time
|
|
777
|
+
|
|
778
|
+
You do not write either half of this. The creation flow generates the cloud script and its client partial,
|
|
779
|
+
and the client partial already carries `[CloudModuleClient(typeof(CloudClass))]`. From there the source
|
|
780
|
+
generator emits `<ClientClass>.g.cs` on every compile. Nothing to click, nothing to deploy first, no
|
|
781
|
+
`GeneratedModuleBindings/` folder.
|
|
782
|
+
|
|
783
|
+
The constructor depends on the cloud class's `[StateScope]`:
|
|
784
|
+
|
|
785
|
+
| Cloud class | Constructor |
|
|
786
|
+
| :-- | :-- |
|
|
787
|
+
| `[StateScope(Scope.Player)]` | `new MyScriptClient(playerId)` |
|
|
788
|
+
| `[StateScope(Scope.MultiplayerSession)]` | `new MyScriptClient(sessionId)` |
|
|
789
|
+
| *(no `[StateScope]`)* | `new MyScriptClient()` |
|
|
790
|
+
|
|
791
|
+
Each also has an overload taking an `ICloudCodeService`, for tests or a non-default instance.
|
|
792
|
+
|
|
793
|
+
```csharp
|
|
794
|
+
var client = new MyCloudCodeScriptClient(AuthenticationService.Instance.PlayerId);
|
|
795
|
+
var score = await client.GetScoreAsync();
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
The generated methods return `Task`/`Task<T>` and pass the **cloud assembly name** as the module name, so
|
|
799
|
+
a rename of that assembly changes the deployed endpoint the client calls.
|
|
800
|
+
An `Access.Service` function gets no client method, since only Cloud Code itself (timers, for example)
|
|
801
|
+
can call it; calling it from the client is a compile error, not a runtime `403`.
|
|
802
|
+
|
|
803
|
+
**Types in endpoint signatures.** Declare a class or enum that an endpoint takes or returns once, in the cloud
|
|
804
|
+
assembly, and use that type from client code. The generator emits a data-only stub of it, with the same name and
|
|
805
|
+
namespace, into the client assembly, in the Editor and in player builds. The game assembly sees the stub through the client assembly and never references the Editor-only cloud assembly. A class is reduced to its public instance fields and properties that have both a public getter and a public setter, and each one is emitted on the stub as a `{ get; set; }` property. Get-only, computed, `readonly` and private-setter members, non-public members, indexers, statics, consts, methods, constructors and initializers are not copied. The generator warns only about public members it leaves out because they are read-only (CCSG216) or computed (CCSG229); non-public, static, const and indexer members are skipped silently. Declare a model type as plain data with public settable members, and do not declare a second copy on the client. A type from any other assembly is referenced directly and is not stubbed.
|
|
806
|
+
|
|
807
|
+
### `.ccmr` -- generated on demand from the deployed module
|
|
808
|
+
|
|
809
|
+
The generator reads the *deployed* module's metadata and writes one file per class under
|
|
810
|
+
`Assets/CloudCode/GeneratedModuleBindings/`. Deploy first, then generate. Do not edit those files -- they
|
|
811
|
+
are overwritten on regeneration.
|
|
812
|
+
|
|
813
|
+
- Select the `.ccmr` in the **Project window**, then click **Generate Bindings** in the Inspector, or
|
|
814
|
+
- **Services > CloudCode > Generate All Modules Bindings** to regenerate every `.ccmr` at once.
|
|
815
|
+
|
|
816
|
+
```csharp
|
|
817
|
+
// Assets/CloudCode/GeneratedModuleBindings/MyModuleBindings.cs
|
|
818
|
+
// This file was generated by Cloud Code Bindings Generator. Modifications will be lost upon regeneration.
|
|
819
|
+
public class MyModuleBindings
|
|
820
|
+
{
|
|
821
|
+
readonly ICloudCodeService k_Service;
|
|
822
|
+
public MyModuleBindings(ICloudCodeService service) { k_Service = service; }
|
|
823
|
+
|
|
824
|
+
public async Task<string> SayHello(string name)
|
|
825
|
+
=> await k_Service.CallModuleEndpointAsync<string>("MyModule", "SayHello",
|
|
826
|
+
new Dictionary<string, object> { { "name", name } });
|
|
827
|
+
}
|
|
828
|
+
```
|
|
829
|
+
|
|
830
|
+
```csharp
|
|
831
|
+
var bindings = new MyModuleBindings(CloudCodeService.Instance);
|
|
832
|
+
var greeting = await bindings.SayHello("World");
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
**Known limitations (`.ccmr`):** no distinction between required/optional parameters; tuples are not
|
|
836
|
+
supported.
|
|
837
|
+
|
|
838
|
+
---
|
|
839
|
+
|
|
840
|
+
## Legacy Modules (`.ccmr`) - Maintenance Only
|
|
841
|
+
|
|
842
|
+
**On Unity 6000.5+, do not create new `.ccmr` modules**; see
|
|
843
|
+
[Creating a Cloud Code Module](#creating-a-cloud-code-module-ccmu) instead. This section covers reading,
|
|
844
|
+
fixing and redeploying the `.ccmr` modules a project already has, and creating a new one on an older Editor,
|
|
845
|
+
which cannot author `.ccmu`.
|
|
846
|
+
|
|
847
|
+
A legacy module is a .NET class library (.NET 8, up to .NET 10) living outside `Assets/`, built and published by the Deployment
|
|
848
|
+
Window, and referenced from Unity by a `.ccmr` file.
|
|
849
|
+
|
|
850
|
+
### Directory Structure
|
|
851
|
+
|
|
852
|
+
Place the module at the **project root** (alongside `Assets/`). Unity does not scan the project root, so no `~` suffix is needed.
|
|
853
|
+
|
|
854
|
+
> **Tilde rule:** Only add `~` to a folder name (e.g. `MyModule~/`) when the module lives **inside `Assets/`** -- the suffix tells Unity to skip that folder during compilation. At the project root, omit it.
|
|
855
|
+
|
|
856
|
+
```
|
|
857
|
+
<ProjectRoot>/
|
|
858
|
+
Assets/
|
|
859
|
+
CloudCode/
|
|
860
|
+
MyModule.ccmr <-- Unity asset pointing to the .sln
|
|
861
|
+
MyModuleCCM/
|
|
862
|
+
MyModule.sln
|
|
863
|
+
MyModule/
|
|
864
|
+
MyModule.csproj
|
|
865
|
+
MyModule.cs <-- [CloudCodeFunction] entry points
|
|
866
|
+
ModuleSetup.cs <-- registers IGameApiClient via DI
|
|
867
|
+
Properties/
|
|
868
|
+
PublishProfiles/
|
|
869
|
+
FolderProfile.pubxml <-- publish to linux-x64 (required)
|
|
870
|
+
MyModule.Tests/
|
|
871
|
+
MyModule.Tests.csproj <-- IsPublishable=false, never deployed
|
|
872
|
+
MyModuleTests.cs
|
|
873
|
+
```
|
|
874
|
+
|
|
875
|
+
### `.sln` File Template
|
|
876
|
+
|
|
877
|
+
Generate fresh GUIDs for `{MAIN-GUID}`, `{TEST-GUID}`, and `{SLN-GUID}` using `[System.Guid]::NewGuid()` in PowerShell or any GUID generator. The test project intentionally has no `Release Build.0` entry to exclude it from Release builds.
|
|
878
|
+
|
|
879
|
+
```
|
|
880
|
+
Microsoft Visual Studio Solution File, Format Version 12.00
|
|
881
|
+
# Visual Studio Version 16
|
|
882
|
+
VisualStudioVersion = 16.0.30114.105
|
|
883
|
+
MinimumVisualStudioVersion = 10.0.40219.1
|
|
884
|
+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyModule", "MyModule\MyModule.csproj", "{MAIN-GUID}"
|
|
885
|
+
EndProject
|
|
886
|
+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyModule.Tests", "MyModule.Tests\MyModule.Tests.csproj", "{TEST-GUID}"
|
|
887
|
+
EndProject
|
|
888
|
+
Global
|
|
889
|
+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
|
890
|
+
Debug|Any CPU = Debug|Any CPU
|
|
891
|
+
Release|Any CPU = Release|Any CPU
|
|
892
|
+
EndGlobalSection
|
|
893
|
+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
|
894
|
+
{MAIN-GUID}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
895
|
+
{MAIN-GUID}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
896
|
+
{MAIN-GUID}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
897
|
+
{MAIN-GUID}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
898
|
+
{TEST-GUID}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
899
|
+
{TEST-GUID}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
900
|
+
{TEST-GUID}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
901
|
+
EndGlobalSection
|
|
902
|
+
GlobalSection(SolutionProperties) = preSolution
|
|
903
|
+
HideSolutionNode = FALSE
|
|
904
|
+
EndGlobalSection
|
|
905
|
+
GlobalSection(ExtensibilityGlobals) = postSolution
|
|
906
|
+
SolutionGuid = {SLN-GUID}
|
|
907
|
+
EndGlobalSection
|
|
908
|
+
EndGlobal
|
|
909
|
+
```
|
|
910
|
+
|
|
911
|
+
### `.csproj` (net8.0, CloudCode.Apis 0.0.27, CloudCode.Core 0.0.7)
|
|
912
|
+
|
|
913
|
+
```xml
|
|
914
|
+
<Project Sdk="Microsoft.NET.Sdk">
|
|
915
|
+
|
|
916
|
+
<PropertyGroup>
|
|
917
|
+
<TargetFramework>net8.0</TargetFramework>
|
|
918
|
+
<Nullable>enable</Nullable>
|
|
919
|
+
<Configurations>Debug;Release</Configurations>
|
|
920
|
+
</PropertyGroup>
|
|
921
|
+
|
|
922
|
+
<ItemGroup>
|
|
923
|
+
<PackageReference Include="Com.Unity.Services.CloudCode.Apis" Version="0.0.27" />
|
|
924
|
+
<PackageReference Include="Com.Unity.Services.CloudCode.Core" Version="0.0.7" />
|
|
925
|
+
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.1" />
|
|
926
|
+
</ItemGroup>
|
|
927
|
+
|
|
928
|
+
<PropertyGroup>
|
|
929
|
+
<PublishReadyToRunComposite>true</PublishReadyToRunComposite>
|
|
930
|
+
</PropertyGroup>
|
|
931
|
+
|
|
932
|
+
</Project>
|
|
933
|
+
```
|
|
934
|
+
|
|
935
|
+
### `FolderProfile.pubxml` (linux-x64, SelfContained=false)
|
|
936
|
+
|
|
937
|
+
Cloud Code runs on Linux. `SelfContained=false` keeps the output small -- the runtime is provided by the
|
|
938
|
+
Cloud Code host.
|
|
939
|
+
|
|
940
|
+
```xml
|
|
941
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
942
|
+
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
|
943
|
+
<PropertyGroup>
|
|
944
|
+
<Configuration>Release</Configuration>
|
|
945
|
+
<Platform>Any CPU</Platform>
|
|
946
|
+
<PublishDir>bin\Release\net8.0\publish\</PublishDir>
|
|
947
|
+
<PublishProtocol>FileSystem</PublishProtocol>
|
|
948
|
+
<TargetFramework>net8.0</TargetFramework>
|
|
949
|
+
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
|
|
950
|
+
<SelfContained>false</SelfContained>
|
|
951
|
+
<PublishSingleFile>False</PublishSingleFile>
|
|
952
|
+
</PropertyGroup>
|
|
953
|
+
</Project>
|
|
954
|
+
```
|
|
955
|
+
|
|
956
|
+
### `ModuleSetup.cs` (registers GameApiClient)
|
|
957
|
+
|
|
958
|
+
Required once per module if any endpoint needs `IGameApiClient`. This is what the Editor's `.ccmr`
|
|
959
|
+
template generates:
|
|
960
|
+
|
|
961
|
+
```csharp
|
|
962
|
+
using Unity.Services.CloudCode.Apis.Extensions;
|
|
963
|
+
using Unity.Services.CloudCode.Core;
|
|
964
|
+
|
|
965
|
+
public class ModuleSetup : ICloudCodeSetup
|
|
966
|
+
{
|
|
967
|
+
public void Setup(ICloudCodeConfig config)
|
|
968
|
+
{
|
|
969
|
+
config.AddGameApiClient();
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
```
|
|
973
|
+
|
|
974
|
+
An older module may instead contain `config.Dependencies.AddSingleton(GameApiClient.Create())`. That still
|
|
975
|
+
works, but `GameApiClient.Create()` is `[Obsolete]` and its own message points at `AddGameApiClient()`, so
|
|
976
|
+
prefer the form above when you touch the file.
|
|
977
|
+
|
|
978
|
+
### `MyModule.cs` ([CloudCodeFunction], IExecutionContext, IGameApiClient)
|
|
979
|
+
|
|
980
|
+
```csharp
|
|
981
|
+
using System;
|
|
982
|
+
using System.Globalization;
|
|
983
|
+
using Microsoft.Extensions.Logging;
|
|
984
|
+
using Unity.Services.CloudCode.Apis;
|
|
985
|
+
using Unity.Services.CloudCode.Core;
|
|
986
|
+
|
|
987
|
+
public class MyModule
|
|
988
|
+
{
|
|
989
|
+
readonly IGameApiClient m_GameApiClient;
|
|
990
|
+
readonly ILogger<MyModule> m_Logger;
|
|
991
|
+
|
|
992
|
+
public MyModule(IGameApiClient gameApiClient, ILogger<MyModule> logger)
|
|
993
|
+
{
|
|
994
|
+
m_GameApiClient = gameApiClient;
|
|
995
|
+
m_Logger = logger;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// No context needed -- pure computation.
|
|
999
|
+
[CloudCodeFunction("SayHello")]
|
|
1000
|
+
public string SayHello(string name) => $"Hello, {name}!";
|
|
1001
|
+
|
|
1002
|
+
// Add IExecutionContext as a method parameter only when you need player/project info.
|
|
1003
|
+
[CloudCodeFunction("GetServerTime")]
|
|
1004
|
+
public string GetServerTime(IExecutionContext context)
|
|
1005
|
+
=> DateTime.UtcNow.ToString(CultureInfo.InvariantCulture);
|
|
1006
|
+
}
|
|
1007
|
+
```
|
|
1008
|
+
|
|
1009
|
+
**Key concepts:**
|
|
1010
|
+
|
|
1011
|
+
| File / Concept | Purpose |
|
|
1012
|
+
|---|---|
|
|
1013
|
+
| `[CloudCodeFunction]` | Marks a public method as a callable endpoint |
|
|
1014
|
+
| `IExecutionContext` | Optional method parameter -- player ID, project ID, service token |
|
|
1015
|
+
| `IGameApiClient` | Constructor-injected via DI -- typed clients for UGS APIs |
|
|
1016
|
+
| `ModuleSetup.cs` | Registers `GameApiClient` via `ICloudCodeSetup` -- required once per module |
|
|
1017
|
+
| `FolderProfile.pubxml` | Publish profile targeting `linux-x64` -- Cloud Code runs on Linux |
|
|
1018
|
+
| `IsPublishable=false` | Prevents test project from being included in `dotnet publish` |
|
|
1019
|
+
|
|
1020
|
+
**Dependency injection:** `IGameApiClient` and `ILogger<T>` are constructor-injected. `IExecutionContext` is a method parameter (only add it when the function needs player/project info).
|
|
1021
|
+
|
|
1022
|
+
### Test Project `.csproj` (IsPublishable=false)
|
|
1023
|
+
|
|
1024
|
+
`IsPublishable=false` is enforced in two ways: this property, and the omission of `Release Build.0` in the `.sln`. Both are intentional.
|
|
1025
|
+
|
|
1026
|
+
```xml
|
|
1027
|
+
<Project Sdk="Microsoft.NET.Sdk">
|
|
1028
|
+
|
|
1029
|
+
<PropertyGroup>
|
|
1030
|
+
<TargetFramework>net8.0</TargetFramework>
|
|
1031
|
+
<LangVersion>latest</LangVersion>
|
|
1032
|
+
<ImplicitUsings>enable</ImplicitUsings>
|
|
1033
|
+
<Nullable>enable</Nullable>
|
|
1034
|
+
<IsPackable>false</IsPackable>
|
|
1035
|
+
<IsPublishable>false</IsPublishable>
|
|
1036
|
+
</PropertyGroup>
|
|
1037
|
+
|
|
1038
|
+
<ItemGroup>
|
|
1039
|
+
<PackageReference Include="coverlet.collector" Version="6.0.2" />
|
|
1040
|
+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
|
1041
|
+
<PackageReference Include="NUnit" Version="4.2.2" />
|
|
1042
|
+
<PackageReference Include="NUnit.Analyzers" Version="4.4.0" />
|
|
1043
|
+
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
|
1044
|
+
</ItemGroup>
|
|
1045
|
+
|
|
1046
|
+
<ItemGroup>
|
|
1047
|
+
<Using Include="NUnit.Framework" />
|
|
1048
|
+
</ItemGroup>
|
|
1049
|
+
|
|
1050
|
+
<ItemGroup>
|
|
1051
|
+
<ProjectReference Include="..\MyModule\MyModule.csproj" />
|
|
1052
|
+
</ItemGroup>
|
|
1053
|
+
|
|
1054
|
+
</Project>
|
|
1055
|
+
```
|
|
1056
|
+
|
|
1057
|
+
### `.ccmr` File Format
|
|
1058
|
+
|
|
1059
|
+
```json
|
|
1060
|
+
{
|
|
1061
|
+
"modulePath": "../../MyModuleCCM/MyModule.sln"
|
|
1062
|
+
}
|
|
1063
|
+
```
|
|
1064
|
+
|
|
1065
|
+
Path is relative to the `.ccmr` file. `../../` goes up from `Assets/CloudCode/` to the project root, then into `MyModuleCCM/`. The filename without `.ccmr` (`MyModule`) is the module name used in `CallModuleEndpointAsync`.
|
|
1066
|
+
|
|
1067
|
+
### Migrating a `.ccmr` to `.ccmu`
|
|
1068
|
+
|
|
1069
|
+
Create a `.ccmu` module and move each `[CloudCodeFunction]` class into its `Cloud/` folder. `[StateScope]`
|
|
1070
|
+
and the function bodies carry over unchanged, and so does constructor injection — with one exception. What
|
|
1071
|
+
changes:
|
|
1072
|
+
|
|
1073
|
+
| `.ccmr` | becomes |
|
|
1074
|
+
| :-- | :-- |
|
|
1075
|
+
| `.sln`, `.csproj`, `.pubxml`, test project | deleted -- Unity compiles the cloud assembly |
|
|
1076
|
+
| constructor-injected `IGameApiClient` / `ILogger<T>` | works as-is; the class needs exactly one public constructor |
|
|
1077
|
+
| constructor-injected `IExecutionContext` | **must move** to a `[CloudCodeService]` method parameter -- it is a scoped service and cannot be constructor-injected here |
|
|
1078
|
+
| bare `IExecutionContext` **method parameter** | same parameter, now marked `[CloudCodeService]` |
|
|
1079
|
+
| `ModuleSetup.cs` with `GameApiClient.Create()` | `CloudSetup.cs` with `config.AddGameApiClient()` |
|
|
1080
|
+
|
|
1081
|
+
NuGet `PackageReference`s do not survive either: a `.ccmu` compiles under Unity, so a dependency has to be
|
|
1082
|
+
available to the Editor. Keep the module name stable (name the cloud asmdef after the old `.ccmr`) so the deployed module keeps its name, and replace every
|
|
1083
|
+
`CallModuleEndpointAsync` call site with the new module's generated client.
|
|
1084
|
+
|
|
1085
|
+
---
|
|
1086
|
+
|
|
1087
|
+
## Code Templates
|
|
1088
|
+
|
|
1089
|
+
JavaScript scripts: maintain, deploy and call existing ones; write new server logic as a C# module.
|
|
1090
|
+
|
|
1091
|
+
### Call a Script Endpoint
|
|
1092
|
+
|
|
1093
|
+
```csharp
|
|
1094
|
+
using Unity.Services.CloudCode;
|
|
1095
|
+
using System.Collections.Generic;
|
|
1096
|
+
|
|
1097
|
+
var args = new Dictionary<string, object>
|
|
1098
|
+
{
|
|
1099
|
+
{ "level", 5 },
|
|
1100
|
+
{ "difficulty", "hard" }
|
|
1101
|
+
};
|
|
1102
|
+
|
|
1103
|
+
int reward = await CloudCodeService.Instance.CallEndpointAsync<int>("CalculateReward", args);
|
|
1104
|
+
Debug.Log($"Reward: {reward}");
|
|
1105
|
+
```
|
|
1106
|
+
|
|
1107
|
+
### Call a Cloud Code Module (`.ccmu`, generated client)
|
|
1108
|
+
|
|
1109
|
+
```csharp
|
|
1110
|
+
// Cloud class PlayerProgress is [StateScope(Scope.Player)], so the client takes a player ID.
|
|
1111
|
+
var progress = new PlayerProgressClient(AuthenticationService.Instance.PlayerId);
|
|
1112
|
+
var result = await progress.AwardXp(50);
|
|
1113
|
+
|
|
1114
|
+
Debug.Log($"Total XP: {result.TotalXp}");
|
|
1115
|
+
```
|
|
1116
|
+
|
|
1117
|
+
### Call a Module Endpoint (Typed Result, raw)
|
|
1118
|
+
|
|
1119
|
+
For legacy `.ccmr` modules only. A `.ccmu` module is always called through its generated client, as above.
|
|
1120
|
+
|
|
1121
|
+
```csharp
|
|
1122
|
+
// Module class name: "ProgressionModule", method: "AwardXp"
|
|
1123
|
+
var result = await CloudCodeService.Instance.CallModuleEndpointAsync<ProgressResult>(
|
|
1124
|
+
"ProgressionModule", "AwardXp",
|
|
1125
|
+
new Dictionary<string, object> { { "amount", 50 } });
|
|
1126
|
+
Debug.Log($"Total XP: {result.TotalXp}");
|
|
1127
|
+
```
|
|
1128
|
+
|
|
1129
|
+
### Subscribe to Player Messages (Full Example)
|
|
1130
|
+
|
|
1131
|
+
```csharp
|
|
1132
|
+
ISubscriptionEvents subscription;
|
|
1133
|
+
|
|
1134
|
+
async Task SubscribeToMessages()
|
|
1135
|
+
{
|
|
1136
|
+
// Attach every handler before subscribing, so nothing can arrive unhandled.
|
|
1137
|
+
var callbacks = new SubscriptionEventCallbacks();
|
|
1138
|
+
callbacks.MessageReceived += OnMessageReceived;
|
|
1139
|
+
callbacks.ConnectionStateChanged += state =>
|
|
1140
|
+
Debug.Log($"Subscription state: {state}");
|
|
1141
|
+
callbacks.Error += error =>
|
|
1142
|
+
Debug.LogError($"Subscription error: {error}");
|
|
1143
|
+
callbacks.Kicked += () =>
|
|
1144
|
+
Debug.LogWarning("Kicked from subscription");
|
|
1145
|
+
|
|
1146
|
+
subscription = await CloudCodeService.Instance.SubscribeToPlayerMessagesAsync(callbacks);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
void OnMessageReceived(IMessageReceivedEvent evt)
|
|
1150
|
+
{
|
|
1151
|
+
Debug.Log($"[{evt.MessageType}] {evt.Message}");
|
|
1152
|
+
// var data = JsonUtility.FromJson<MyPayload>(evt.Message);
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
async Task Unsubscribe()
|
|
1156
|
+
{
|
|
1157
|
+
await subscription.UnsubscribeAsync();
|
|
1158
|
+
subscription = null;
|
|
1159
|
+
}
|
|
1160
|
+
```
|
|
1161
|
+
|
|
1162
|
+
---
|
|
1163
|
+
|
|
1164
|
+
## Deploying via the Deployment Window
|
|
1165
|
+
|
|
1166
|
+
1. Open **Services > Deployment** in the Unity Editor.
|
|
1167
|
+
2. All `.js`, `.ccmu` and `.ccmr` files under `Assets/` are listed. **Select only what you mean to deploy**
|
|
1168
|
+
-- deploying everything can overwrite modules that are not ready to go out.
|
|
1169
|
+
3. A `.ccmu` deploys the compiled cloud assembly, under the **cloud asmdef's name**. Its client bindings
|
|
1170
|
+
already exist -- they were generated when Unity compiled the module, so there is nothing to regenerate.
|
|
1171
|
+
4. A `.ccmr` builds and publishes its solution. After it deploys, generate or regenerate its bindings
|
|
1172
|
+
(see [Generated Client Bindings](#generated-client-bindings)).
|
|
1173
|
+
|
|
1174
|
+
**The local Cloud Code server never deploys during play mode.** Entering play mode does not deploy modules to
|
|
1175
|
+
it, and nothing is deployed to it while the Editor is in play mode. Edit and deploy in edit mode, then
|
|
1176
|
+
enter play mode. Changes made during play mode do not reach the local server until you exit play mode and
|
|
1177
|
+
deploy again.
|
|
1178
|
+
|
|
1179
|
+
A `.ccmu` deploy fails with **"Unsupported API Compatibility Level"** unless the project's Player Settings
|
|
1180
|
+
have Api Compatibility Level on `.NET Standard 2.1`. It also fails if the project has compile errors --
|
|
1181
|
+
the module is deployed from its compiled assembly, so it has to build first.
|
|
1182
|
+
|
|
1183
|
+
**Deploying a `.ccmu` needs a running Editor.** Nothing packages a `.ccmu` outside the Editor, and
|
|
1184
|
+
`unity ugs deploy` skips `.ccmu` (see `references/ugs-cli.md`), so a CI pipeline that deploys `.ccmu` modules has
|
|
1185
|
+
to run the Editor. Only legacy `.ccmr` modules deploy without one, through their `.sln`; do not create a
|
|
1186
|
+
`.ccmr` to get around this.
|
|
1187
|
+
|
|
1188
|
+
**Local development needs no deploy.** While the local Cloud Code server runs (Cloud Code button on the
|
|
1189
|
+
Editor toolbar > **Start Local Server**; it needs an active environment), each successful compile redeploys
|
|
1190
|
+
changed `.ccmu` modules onto it, outside Play mode. **Nothing deploys to the local server during Play mode**,
|
|
1191
|
+
not even an explicit deploy, which is refused: exit Play mode before recompiling and deploying. A
|
|
1192
|
+
`404 function-not-found` for an endpoint added while playing means exactly this; exiting Play mode redeploys
|
|
1193
|
+
it and logs `Local Cloud Code server reloaded`.
|
|
1194
|
+
|
|
1195
|
+
**Known limitation.** An unfocused Editor doesn't notice file edits or run the local auto-deploy until it
|
|
1196
|
+
regains focus. After editing module code without focusing the Editor, run `unity command recompile`, then
|
|
1197
|
+
deploy with the `Deploy.cs` script from
|
|
1198
|
+
`references/deployment.md`: while the local
|
|
1199
|
+
server runs, the deploy targets it, not the environment. Stop the server before deploying remotely.
|
|
1200
|
+
|
|
1201
|
+
To deploy without the window, run the deploy script in
|
|
1202
|
+
the Trigger Deployment Programmatically section of `references/deployment.md`.
|
|
1203
|
+
|
|
1204
|
+
---
|
|
1205
|
+
|
|
1206
|
+
## Running the Local Cloud Code Server
|
|
1207
|
+
|
|
1208
|
+
The local server runs the project's modules in a process on this machine, and the SDK routes
|
|
1209
|
+
`CallModuleEndpointAsync` to it instead of the cloud. Start and stop it with the Cloud Code button on the
|
|
1210
|
+
Editor toolbar (**Start Local Server** / **Stop Local Server**), outside Play mode; it needs Unity 6000.3+.
|
|
1211
|
+
Redeploy on compile, and what to do when the Editor is unfocused, are covered in
|
|
1212
|
+
[Deploying via the Deployment Window](#deploying-via-the-deployment-window).
|
|
1213
|
+
|
|
1214
|
+
The server listens on port **14750** by default. To run several Editors, each with its own local server,
|
|
1215
|
+
give each project a distinct port in **Edit > Project Settings > Services > Cloud Code > Local Debugging >
|
|
1216
|
+
Port** while its server is stopped; a port already in use fails startup with *"Server Port <port> is not
|
|
1217
|
+
available."*
|
|
1218
|
+
Play mode reads the port and whether a server is running from Editor preferences shared by every Editor on
|
|
1219
|
+
the machine, so calls go to the most recently started server, and stopping any Editor's server sends the
|
|
1220
|
+
others' calls to the cloud.
|
|
1221
|
+
|
|
1222
|
+
### It will not start until an active environment is set
|
|
1223
|
+
|
|
1224
|
+
Startup validates the environment first, and a project with no active environment fails with:
|
|
1225
|
+
|
|
1226
|
+
> Cannot start the local Cloud Code server. The environment has not been set. Please use the Environment
|
|
1227
|
+
> Selector, found under Edit → Project Settings → Services → Environments, to set it.
|
|
1228
|
+
|
|
1229
|
+
A fresh clone or a CI checkout has no active environment, so this is usually the first thing that fails.
|
|
1230
|
+
Set one in **Edit > Project Settings > Services > Environments** (ask the user, or drive the Editor UI).
|
|
1231
|
+
|
|
1232
|
+
---
|
|
1233
|
+
|
|
1234
|
+
## Error Handling
|
|
1235
|
+
|
|
1236
|
+
```csharp
|
|
1237
|
+
try
|
|
1238
|
+
{
|
|
1239
|
+
var result = await client.MyFunction(arg);
|
|
1240
|
+
}
|
|
1241
|
+
catch (CloudCodeRateLimitedException ex)
|
|
1242
|
+
{
|
|
1243
|
+
Debug.LogError($"Rate limited. Retry after {ex.RetryAfter}s");
|
|
1244
|
+
}
|
|
1245
|
+
catch (CloudCodeException ex)
|
|
1246
|
+
{
|
|
1247
|
+
Debug.LogError($"Cloud Code error: {ex.Message} (reason: {ex.Reason})");
|
|
1248
|
+
}
|
|
1249
|
+
```
|
|
1250
|
+
|
|
1251
|
+
### `CloudCodeExceptionReason` Enum
|
|
1252
|
+
|
|
1253
|
+
Read the `CloudCodeExceptionReason` API reference for the installed package before writing a handler that
|
|
1254
|
+
branches on `ex.Reason`, because the set of reasons changes between package versions:
|
|
1255
|
+
|
|
1256
|
+
`https://docs.unity3d.com/Packages/com.unity.services.cloudcode@<VERSION>/api/Unity.Services.CloudCode.CloudCodeExceptionReason.html`
|
|
1257
|
+
|
|
1258
|
+
Take `<VERSION>` from the installed `com.unity.services.cloudcode` version in
|
|
1259
|
+
`Packages/packages-lock.json` (for `3.0.0`, use `3.0`). That file lists the package even when it arrives as
|
|
1260
|
+
a dependency of another package, which `Packages/manifest.json` does not. If a reason the code needs is missing from that page, the installed version does not
|
|
1261
|
+
have it.
|
|
1262
|
+
|
|
1263
|
+
---
|
|
1264
|
+
|
|
1265
|
+
## Error Messages
|
|
1266
|
+
|
|
1267
|
+
| Error string | Cause | Fix |
|
|
1268
|
+
| :-- | :-- | :-- |
|
|
1269
|
+
| `Unsupported API Compatibility Level` | The project's Api Compatibility Level is not `.NET Standard 2.1` | Set it in Player Settings, then redeploy the `.ccmu` |
|
|
1270
|
+
| `Invalid constructor: type X must have exactly one public constructor` | The module class has no public constructor or several | Keep exactly one public constructor |
|
|
1271
|
+
| `Function's ... class in module ... requires a scope and must be called with a scope` | A `[StateScope]` function was called without a scope, or with the wrong one | Pass a `CloudCodeScope` that matches the class's `[StateScope]`, or use the generated client |
|
|
1272
|
+
| `This Cloud Code module's server Assembly Definition (.asmdef) is missing or misconfigured...` | The cloud asmdef is not Editor-only, has engine references, is auto-referenced, or lacks the Core and Apis references | Fix the asmdef, then create the script again |
|
|
1273
|
+
| `Assembly Failure -- Multiple CCMs reference the same assembly definition` | Two modules share one cloud assembly | Give each module its own cloud assembly |
|
|
1274
|
+
| `Server Port <port> is not available.` | Another process, often another Editor's local server, holds the port | Set a distinct port under Local Debugging > Port while the server is stopped |
|
|
1275
|
+
| `Cannot start the local Cloud Code server. The environment has not been set...` | The project has no active environment | Set one in **Edit > Project Settings > Services > Environments** |
|
|
1276
|
+
| `CloudCodeExecutionScopeStateException` | Persisted state no longer deserializes after an incompatible member change | Restore a compatible member type, or migrate with `IStateSerializer` |
|