@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,1111 @@
|
|
|
1
|
+
# CloudBehaviours Reference
|
|
2
|
+
|
|
3
|
+
Server-authoritative game logic authored as a C# class, with the client bindings — RPCs, synchronized
|
|
4
|
+
state, push events — generated for you. Part of `com.unity.services.cloudcode`, requiring Unity 6000.5+.
|
|
5
|
+
|
|
6
|
+
Read this when the goal needs cloud-owned state that clients read but cannot write: shared match state,
|
|
7
|
+
per-player secret state, server-owned timers. For request/response server logic with nothing to
|
|
8
|
+
synchronize, plain Cloud Code modules are generally available and the better default.
|
|
9
|
+
|
|
10
|
+
## Table of Contents
|
|
11
|
+
|
|
12
|
+
- [Prerequisites](#prerequisites)
|
|
13
|
+
- [Anti-Hallucination Reference](#anti-hallucination-reference)
|
|
14
|
+
- [The authoring model](#the-authoring-model)
|
|
15
|
+
- [What `public` means on the cloud class](#what-public-means-on-the-cloud-class)
|
|
16
|
+
- [Scopes](#scopes)
|
|
17
|
+
- [Deployed names](#deployed-names)
|
|
18
|
+
- [Generated code is not editable](#generated-code-is-not-editable)
|
|
19
|
+
- [Client bindings](#client-bindings)
|
|
20
|
+
- [Per-viewer state with `[SyncView]`](#per-viewer-state-with-syncview)
|
|
21
|
+
- [Calling another behaviour](#calling-another-behaviour)
|
|
22
|
+
- [Timers](#timers)
|
|
23
|
+
- [State, serialization, and model types](#state-serialization-and-model-types)
|
|
24
|
+
- [Endpoint access control](#endpoint-access-control)
|
|
25
|
+
- [Local development, debugging, and deployment](#local-development-debugging-and-deployment)
|
|
26
|
+
- [Generator diagnostics](#generator-diagnostics)
|
|
27
|
+
- [Silent failure modes](#silent-failure-modes)
|
|
28
|
+
|
|
29
|
+
## Prerequisites
|
|
30
|
+
|
|
31
|
+
| Requirement | Detail |
|
|
32
|
+
| :-- | :-- |
|
|
33
|
+
| `com.unity.services.cloudcode` **`3.1.0-exp.1`**+ | Confirm in the project with **Create > Services > Cloud Behaviour Script** in the create menu |
|
|
34
|
+
| Editor is 6000.5 or newer | every CloudBehaviours assembly is gated on `UNITY_6000_5_OR_NEWER` |
|
|
35
|
+
| Project is linked to a Unity Cloud project | required to deploy, and to authenticate at runtime |
|
|
36
|
+
|
|
37
|
+
`Scope.MultiplayerSession` additionally needs `com.unity.services.multiplayer` installed. Player-scoped
|
|
38
|
+
behaviours do not. One module can hold CloudBehaviours and native Cloud Code classes side by side.
|
|
39
|
+
|
|
40
|
+
If the create-menu entry is missing, say so and do not write CloudBehaviour code: the installed version
|
|
41
|
+
does not carry CloudBehaviours and no amount of correct code will compile against it. Fall back to a plain
|
|
42
|
+
stateful module unless the user asks for the experimental package.
|
|
43
|
+
|
|
44
|
+
## Anti-Hallucination Reference
|
|
45
|
+
|
|
46
|
+
| Correct | Incorrect (do NOT use) |
|
|
47
|
+
|---|---|
|
|
48
|
+
| `[StateScope(Scope.Player)]` / `[StateScope(Scope.MultiplayerSession)]` on the cloud class | any other class attribute to mark a behaviour -- `[StateScope]` is the only one |
|
|
49
|
+
| cloud class inherits `CloudBehaviour` | a plain class with only `[StateScope]` -- that is a native module, not a behaviour |
|
|
50
|
+
| `public int Round;`, `public int Round { get; set; }` or `public int Round { get; private set; }` -- all synchronize | a get-only or computed property -- ignored with warning `CCSG113`; expose a computed value with `[SyncView]` |
|
|
51
|
+
| `public` members reach clients; `private` ones never leave the cloud | assuming access control hides a `public` field, or that a `private` one is synchronized |
|
|
52
|
+
| `[CloudCodeService] ITimerService timers` as a method parameter | an unannotated `ITimerService` parameter -- build error `CCSG136` |
|
|
53
|
+
| `await self.EndTurn(arg).ScheduleAsync(delay)` on the injected `I{Behaviour}` | assembling an endpoint-name string by hand to schedule work |
|
|
54
|
+
| `[CloudCodeService] ITimerService` + `GetTimerAsync(id)` to cancel | expecting a cancel on `ICall` -- it has none |
|
|
55
|
+
| `await other.Endpoint(arg).ForScope(scopeId)` to invoke another scope instance now | `ScheduleAsync` for a cross-scope call -- it only ever targets the executing instance |
|
|
56
|
+
| `[CloudCodeAccess(Access.Service)]` on a timer target | leaving it callable by clients because "only the timer calls it" |
|
|
57
|
+
| `Access.Service` endpoints called only from behaviours, via `I{Behaviour}` | calling one from the client -- the generated client has no method for it |
|
|
58
|
+
| `Context.ServiceToken` inside a timer callback | `Context.PlayerId` -- it is `null`; a timer runs for no player |
|
|
59
|
+
| `[SyncView("Hand")] internal List<Card> ProjectHand(string playerId)` | a `private` projection -- build error `CCSG142` |
|
|
60
|
+
| client partial: `OnEnable` -> `EnableClient()`, `OnDisable` -> `DisableClient()` | hand-writing `EnableClient`, `Hydrate`, an RPC, or a synchronized property -- they are generated |
|
|
61
|
+
| client calls the generated method: `await behaviour.PlayTurn(move)` | `CallModuleEndpointAsync("Behaviour", "PlayTurn", args)` from a behaviour's client |
|
|
62
|
+
| `Synchronized`, `ScopeChanged`, `Fatal`, `Error`, `MessageReceived` on the generated bindings | any other event name for connection or state callbacks |
|
|
63
|
+
| read synchronized fields in a `ScopeChanged` handler -- the client hydrated before raising it | calling `Hydrate()` on connect or reconnect -- it already ran, and yours is a second RPC |
|
|
64
|
+
| `List<T>`, `T[]`, `Dictionary<,>` across the boundary | a custom `IEnumerable<T>` type -- compile-time error |
|
|
65
|
+
| model types carrying only fields and properties | methods or constructors on a model -- not regenerated client-side; a computed or get-only property is error `CCSG131` |
|
|
66
|
+
| behaviour class names unique within a module | two behaviours sharing a simple name across namespaces -- generator error |
|
|
67
|
+
|
|
68
|
+
## Creating the module
|
|
69
|
+
|
|
70
|
+
Always create a new module with **Create > Services > Cloud Behaviour Script**, because the creator writes
|
|
71
|
+
the module's `.ccmu` asset along with both assemblies, and the Deployment window lists only modules that
|
|
72
|
+
have a `.ccmu`. A module built from hand-written asmdefs alone compiles and generates bindings, but never
|
|
73
|
+
appears in the Deployment window, and nothing reports why. An agent without computer-use tools asks the
|
|
74
|
+
user to run this menu step; with them, it runs the step itself.
|
|
75
|
+
|
|
76
|
+
The first behaviour in a folder opens the Cloud Behaviour Module Creator, which writes
|
|
77
|
+
`<ModuleName>.ccmu` at the module root and scaffolds two assemblies:
|
|
78
|
+
|
|
79
|
+
| Folder | Assembly references | Platform |
|
|
80
|
+
| :-- | :-- | :-- |
|
|
81
|
+
| `Cloud/` | `Unity.Services.CloudCode.Core`, `Unity.Services.CloudCode.Apis`, `Unity.Services.CloudBehaviours.Editor`, `Unity.Services.CloudCode.SourceGenerator`, `Newtonsoft.Json.dll` | Editor only, `noEngineReferences` |
|
|
82
|
+
| `Client/` | `Unity.Services.CloudBehaviours.Runtime`, `Unity.Services.Core`, `Unity.Services.CloudCode`, `Unity.Services.CloudCode.SourceGenerator`, **the cloud assembly**, and for `Scope.MultiplayerSession` also `Unity.Services.CloudBehaviours.Multiplayer.Runtime` + `Unity.Services.Multiplayer` | all |
|
|
83
|
+
|
|
84
|
+
Two entries in that `Client/` row are easy to get wrong, and both fail as `CCSG127` plus a wall of
|
|
85
|
+
`CS0103` on the client partial:
|
|
86
|
+
|
|
87
|
+
- **The client assembly must reference the cloud assembly**, even though the cloud assembly is
|
|
88
|
+
Editor-only. This is correct and does not break player builds: the generator emits stubs of the cloud
|
|
89
|
+
types into the client assembly, so nothing Editor-only is carried into a player.
|
|
90
|
+
- **A session-scoped behaviour needs the two multiplayer assemblies on the *client* assembly**, because
|
|
91
|
+
the generated client addresses its state through a `MultiplayerScopeProvider`. Installing
|
|
92
|
+
`com.unity.services.multiplayer` is not sufficient on its own — the references have to be there too.
|
|
93
|
+
|
|
94
|
+
Use the table to check what the creator generated. The creator also wires the cloud reference, which a
|
|
95
|
+
hand-written client asmdef usually misses. The split matters: the cloud assembly has no engine
|
|
96
|
+
references, so `UnityEngine` types are unavailable server-side, and the generator needs both assemblies to
|
|
97
|
+
reference it.
|
|
98
|
+
|
|
99
|
+
If the user already has hand-written assemblies and the module is missing from the Deployment window, the
|
|
100
|
+
module is missing its `.ccmu`. Add one instead of rebuilding the module, because a new `.ccmu` leaves every
|
|
101
|
+
assembly name, GUID and script reference as it is:
|
|
102
|
+
|
|
103
|
+
1. Stop until the user confirms a rollback point, such as a commit in their version control or a backup
|
|
104
|
+
copy of the project, so a failed repair has a clean way back.
|
|
105
|
+
2. Check the cloud asmdef against the `Cloud/` row of the table above and add any missing references. The
|
|
106
|
+
create menu refuses to add behaviours to a module whose cloud assembly lacks them.
|
|
107
|
+
3. Write `<ModuleName>.ccmu` in the folder that holds both assembly folders, in the format and with the
|
|
108
|
+
GUID rules in the `.ccmu` File Format section of `references/cloud-code-modules.md`.
|
|
109
|
+
Take each GUID from the `guid:` line of the matching `.asmdef.meta` in this project, never from
|
|
110
|
+
anywhere else, because a GUID resolves only in the project that minted it.
|
|
111
|
+
4. Verify: the `.ccmu` imports with zero console errors, and the module is listed in
|
|
112
|
+
**Services > Deployment** under the cloud asmdef's name. If either check fails, restore the rollback
|
|
113
|
+
point and read the console error before trying again.
|
|
114
|
+
|
|
115
|
+
## The authoring model
|
|
116
|
+
|
|
117
|
+
What you write, and what the generator writes for you:
|
|
118
|
+
|
|
119
|
+
1. **The creation flow scaffolds two assemblies** — a `Cloud/` assembly (Editor-only, no engine
|
|
120
|
+
references) and a `Client/` assembly, each referencing `Unity.Services.CloudCode.SourceGenerator`.
|
|
121
|
+
2. **You implement the behaviour on the cloud side**: a class inheriting `CloudBehaviour` with a
|
|
122
|
+
`[StateScope]`, public methods for the operations, public fields for the state clients should see, and
|
|
123
|
+
private members for everything that stays server-side.
|
|
124
|
+
3. **The source generators run on every compile** and emit two halves from that one class:
|
|
125
|
+
- the **Cloud Code module** itself — a `[CloudCodeFunction("Behaviour__Method")]` per public method,
|
|
126
|
+
the `Behaviour_Hydrate` endpoint, and the synchronization that pushes state to clients in scope.
|
|
127
|
+
This is what gets deployed and what the service runs.
|
|
128
|
+
- the **client bindings** — the other half of your `partial` client class, carrying the RPC methods
|
|
129
|
+
(except `Access.Service` endpoints, which clients cannot call),
|
|
130
|
+
the read-only synchronized properties and the `UnityEvent`s your game code calls and subscribes to.
|
|
131
|
+
4. **Your game client uses the bindings** like any other component: enable it, call the generated
|
|
132
|
+
methods, react to `Synchronized` and to your own push events.
|
|
133
|
+
|
|
134
|
+
Neither generated half appears under `Assets/`; both are compiler output, regenerated on each compile.
|
|
135
|
+
|
|
136
|
+
```csharp
|
|
137
|
+
// Cloud/MatchState.cs — cloud assembly, runs in Cloud Code
|
|
138
|
+
using System;
|
|
139
|
+
using System.Collections.Generic;
|
|
140
|
+
using Microsoft.Extensions.Logging;
|
|
141
|
+
using Unity.Services.CloudBehaviours;
|
|
142
|
+
using Unity.Services.CloudCode.Core;
|
|
143
|
+
|
|
144
|
+
[StateScope(Scope.MultiplayerSession)]
|
|
145
|
+
public class MatchState : CloudBehaviour
|
|
146
|
+
{
|
|
147
|
+
public int Round; // synchronized to every client in scope
|
|
148
|
+
private List<string> _deck; // stays in the cloud
|
|
149
|
+
|
|
150
|
+
public event Action<string> RoundEnded; // push channel
|
|
151
|
+
|
|
152
|
+
public string PlayTurn(string move) // RPC
|
|
153
|
+
{
|
|
154
|
+
Round++;
|
|
155
|
+
Logger.LogDebug("Round {Round}", Round); // Logger comes from the base class
|
|
156
|
+
RoundEnded?.Invoke(Context.PlayerId); // Context is IExecutionContext
|
|
157
|
+
return "ok";
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
```csharp
|
|
163
|
+
// Client/MatchStateClient.cs — client assembly
|
|
164
|
+
using Unity.Services.CloudBehaviours;
|
|
165
|
+
using UnityEngine;
|
|
166
|
+
|
|
167
|
+
[CloudBehaviour(typeof(MatchState))]
|
|
168
|
+
public partial class MatchStateClient : MonoBehaviour
|
|
169
|
+
{
|
|
170
|
+
protected virtual void OnEnable() => EnableClient();
|
|
171
|
+
protected virtual void OnDisable() => DisableClient();
|
|
172
|
+
|
|
173
|
+
async void OnMoveButton()
|
|
174
|
+
{
|
|
175
|
+
var result = await PlayTurn("rock"); // generated RPC
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Write only `OnEnable`/`OnDisable` and your own logic. `EnableClient`, `DisableClient`, `Hydrate`, the
|
|
181
|
+
RPC methods, the synchronized properties, and the `UnityEvent`s are all generated — implementing any of
|
|
182
|
+
them by hand collides with the generated partial.
|
|
183
|
+
|
|
184
|
+
## What `public` means on the cloud class
|
|
185
|
+
|
|
186
|
+
Accessibility is the opt-in mechanism, which is why secrets simply stay `private`:
|
|
187
|
+
|
|
188
|
+
| Cloud class member | Becomes | On the client |
|
|
189
|
+
| :-- | :-- | :-- |
|
|
190
|
+
| `public` method | RPC | `async Task` / `async Task<T>` method |
|
|
191
|
+
| `public` field (non-static, non-const, non-readonly), or `public` property with a public getter and a setter of any accessibility | synchronized variable | `public T Name { get; private set; }` |
|
|
192
|
+
| `public event Action`, `Action<…>`, `EventHandler`, `EventHandler<T>` | push channel | `UnityEvent` / `UnityEvent<…>` |
|
|
193
|
+
| method with `[SyncView("name")]` | per-viewer projection | read-only property named `name` |
|
|
194
|
+
| parameter with `[CloudCodeService]` | injected service | omitted from every caller-facing signature — the client RPC and `I{Behaviour}` alike |
|
|
195
|
+
|
|
196
|
+
Non-public members are invisible to clients and to the generator.
|
|
197
|
+
|
|
198
|
+
Two traps live in that table:
|
|
199
|
+
|
|
200
|
+
- **A service parameter needs `[CloudCodeService]`.** `Task StartTurn(ITimerService timers)` fails the build with `CCSG136`, because a Cloud Code service or generated `I{Behaviour}` can't arrive in a request payload. Annotate every injected service.
|
|
201
|
+
- **Read-only members don't synchronize.** A get-only or computed property, or a `readonly` field, is ignored with warning `CCSG113`; a `public static` or `const` member is skipped with warning `CCSG158`. Declare synchronized state as a public field or a property with a setter (`{ get; private set; }` is fine), expose a computed value through a `[SyncView]` method, and mutate a collection's contents rather than reassigning a read-only one. The client still sees it as a read-only property.
|
|
202
|
+
|
|
203
|
+
## Scopes
|
|
204
|
+
|
|
205
|
+
`[StateScope(...)]` binds the instance's state and decides who may call it.
|
|
206
|
+
|
|
207
|
+
| Attribute | State | Default access | Use for |
|
|
208
|
+
| :-- | :-- | :-- | :-- |
|
|
209
|
+
| `[StateScope(Scope.Player)]` | per player, persistent | `Access.Player` | inventory, progression, inboxes |
|
|
210
|
+
| `[StateScope(Scope.MultiplayerSession)]` | per session, shared by all members | `Access.SessionMember` | turn state, shared match state |
|
|
211
|
+
|
|
212
|
+
`[StateScope]` is **mandatory**. A behaviour without one is `CCSG126`, an error that aborts generation
|
|
213
|
+
for the whole module — there is no stateless or non-scoped CloudBehaviour. (Plain Cloud Code functions
|
|
214
|
+
do default to `Access.Global` when unscoped; behaviours do not work that way.) For stateless
|
|
215
|
+
request/response work, use a plain Cloud Code module instead.
|
|
216
|
+
|
|
217
|
+
New scripts are scaffolded as `Scope.Player`. Persistent private state → `Player`; shared volatile
|
|
218
|
+
state → `MultiplayerSession`. Override per endpoint with `[CloudCodeAccess(...)]` — see
|
|
219
|
+
[Endpoint access control](#endpoint-access-control).
|
|
220
|
+
|
|
221
|
+
## Deployed names
|
|
222
|
+
|
|
223
|
+
The generator emits one Cloud Code function per endpoint, and these strings appear in logs, in the
|
|
224
|
+
Deployment window, and anywhere a function name is passed as a string (timers, for instance):
|
|
225
|
+
|
|
226
|
+
| Thing | Deployed name |
|
|
227
|
+
| :-- | :-- |
|
|
228
|
+
| RPC `Method` on behaviour `B` | `B__Method` |
|
|
229
|
+
| Hydration | `B_Hydrate` (single underscore) |
|
|
230
|
+
| State sync push | `B__Synchronize` |
|
|
231
|
+
| Push event `Ev` | `B__Ev` |
|
|
232
|
+
|
|
233
|
+
Two behaviours in one module may declare the same *method* name — bindings resolve by full type identity.
|
|
234
|
+
Their **class** names must differ, though, even across namespaces: the generated `{module}Extensions`
|
|
235
|
+
aggregator derives `Add{Name}Client` and short-name type references from the simple name, so duplicates
|
|
236
|
+
are reported as an error.
|
|
237
|
+
|
|
238
|
+
Names must also be unique *within* a behaviour, across kinds. An endpoint, a synchronized member, an
|
|
239
|
+
event, and a `[SyncView]` projection all share one wire key namespace, and the comparison is
|
|
240
|
+
case-insensitive, so `Score` and `score` collide. Overloads collide for the same reason — one wire key,
|
|
241
|
+
two methods. A same-kind duplicate keeps the first declaration with warning `CCSG117`, which shows up as a member
|
|
242
|
+
that mysteriously does not exist on the client. Members of different kinds sharing a name have no
|
|
243
|
+
survivor, so all of them are dropped and the build fails with error `CCSG118`. Event payloads are
|
|
244
|
+
limited to four parameters.
|
|
245
|
+
|
|
246
|
+
## Generated code is not editable
|
|
247
|
+
|
|
248
|
+
The generator runs on every compilation and emits sources the compiler holds in memory — they never
|
|
249
|
+
appear under `Assets/` and are not checked in. To regenerate, recompile. When generated code looks
|
|
250
|
+
wrong, fix the cloud class or the bindings class; there is nothing to edit in the output.
|
|
251
|
+
|
|
252
|
+
### Reading the generated sources
|
|
253
|
+
|
|
254
|
+
Not editable is not the same as not inspectable, and reading them is often the fastest way to close the
|
|
255
|
+
loop on "the client says that member does not exist". Roslyn can dump every generator's output to disk,
|
|
256
|
+
but the setup has three traps, so follow it exactly.
|
|
257
|
+
|
|
258
|
+
**1. Put the `csc.rsp` next to the `.asmdef`, never at the `Assets/` root.** A root `csc.rsp` is applied
|
|
259
|
+
to every assembly in the project, packages included. Roslyn names output files by generator, not by the
|
|
260
|
+
assembly being compiled, so dozens of parallel compiles write the same paths and fail with `CS0016`. Add
|
|
261
|
+
one file per assembly you want to read, each naming its own output folder:
|
|
262
|
+
|
|
263
|
+
```
|
|
264
|
+
# Assets/MyGame/Client/csc.rsp (beside MyGame.Client.asmdef)
|
|
265
|
+
/generatedfilesout:Generated/MyGame.Client
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
**2. Create that folder before you compile.** The path is relative to the project root, not `Assets/`.
|
|
269
|
+
Neither Unity nor Roslyn creates it; Roslyn only creates the per-generator subfolders beneath it. Without
|
|
270
|
+
it, every assembly using the rsp fails with:
|
|
271
|
+
|
|
272
|
+
```
|
|
273
|
+
error CS0016: Could not write to output file '<project>\Generated\...\AssemblyMonoScriptTypes.generated.cs'
|
|
274
|
+
-- 'Could not find a part of the path ...'
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
The message names a generated file, so it reads like a generator bug. It is a missing folder. If the rsp
|
|
278
|
+
was global, `Unity.Pipeline.CodeGen` is among the casualties and `unity status` reports no Pipeline
|
|
279
|
+
instance while the Editor is open in front of you; delete the rsp to recover.
|
|
280
|
+
|
|
281
|
+
**3. Keep the folder outside `Assets/`.** Unity would import the dumped `.cs` files and compile them again.
|
|
282
|
+
|
|
283
|
+
Recompile, and the output lands under the folder you named:
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
Generated/
|
|
287
|
+
MyGame.Client/
|
|
288
|
+
CloudCodeSourceGenerator/
|
|
289
|
+
SourceGenerator.Generators.BehaviourBindingsGenerator/ <-- client partial + its inspector
|
|
290
|
+
MyGame.Client.MatchStateClient.g.cs
|
|
291
|
+
MyGame.Client.MatchStateClient_Inspector.g.cs
|
|
292
|
+
MyGame.Client.MatchState_Sync.g.cs
|
|
293
|
+
MyGame.Cloud/
|
|
294
|
+
CloudCodeSourceGenerator/
|
|
295
|
+
SourceGenerator.Generators.BehaviourManifestGenerator/ <-- the deployed cloud module
|
|
296
|
+
MyGame.Cloud.IMatchState.g.cs
|
|
297
|
+
MyGame.Cloud.MatchState_System.g.cs
|
|
298
|
+
MyGame.Cloud.MatchState_Sync.g.cs
|
|
299
|
+
MyGame.Cloud.__MatchStateCcClient.g.cs
|
|
300
|
+
MyGameExtensions.g.cs
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
The file name is the hint name — `{Namespace}.{Type}.g.cs` — so a member you expected on the client is
|
|
304
|
+
either in `…MatchStateClient.g.cs` or it was never generated, which is the whole diagnosis. Read the
|
|
305
|
+
bindings file to see what the client actually got, and the manifest file to see the endpoint names and
|
|
306
|
+
the `_Sync` payload the cloud will push.
|
|
307
|
+
|
|
308
|
+
This is build output: it is rewritten on every compile, editing it changes nothing, and it does not
|
|
309
|
+
belong in source control. It is also a trap for everyone else: if the `csc.rsp` is committed and
|
|
310
|
+
`Generated/` is ignored, the folder does not exist on a fresh clone and the project fails to compile for
|
|
311
|
+
anyone but its author. Either keep the `csc.rsp` files uncommitted, or commit a `.gitkeep` in each output
|
|
312
|
+
folder and ignore only the contents:
|
|
313
|
+
|
|
314
|
+
```
|
|
315
|
+
Generated/**/*.cs
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
## Client bindings
|
|
319
|
+
|
|
320
|
+
### What the generator emits
|
|
321
|
+
|
|
322
|
+
For `[CloudBehaviour(typeof(TCloud))] public partial class MyClient : MonoBehaviour`, the generated half
|
|
323
|
+
of the partial contains:
|
|
324
|
+
|
|
325
|
+
```csharp
|
|
326
|
+
[field: SerializeField] internal string TargetSessionType { get; private set; } // MultiplayerSession scope — or:
|
|
327
|
+
[field: SerializeField] internal string TargetPlayerId { get; private set; } // Player scope
|
|
328
|
+
|
|
329
|
+
public CloudCodeScope Scope { get; } // the scope provider's current scope
|
|
330
|
+
public bool IsClientEnabled { get; }
|
|
331
|
+
|
|
332
|
+
public UnityEvent ScopeChanged;
|
|
333
|
+
public UnityEvent<CloudBehaviourException> Fatal;
|
|
334
|
+
public UnityEvent<CloudBehaviourError> Error;
|
|
335
|
+
public UnityEvent<IMessageReceivedEvent> MessageReceived;
|
|
336
|
+
public UnityEvent Synchronized;
|
|
337
|
+
|
|
338
|
+
public void EnableClient();
|
|
339
|
+
// MultiplayerSession scope also: EnableClient(string sessionType), EnableClient(ISession),
|
|
340
|
+
// EnableClient(SessionObserver), EnableClient(MultiplayerSession)
|
|
341
|
+
// Player scope also: EnableClient(string playerId)
|
|
342
|
+
public void DisableClient();
|
|
343
|
+
public async Task Hydrate();
|
|
344
|
+
|
|
345
|
+
// one read-only property per synchronized member: public int Round { get; private set; }
|
|
346
|
+
// one UnityEvent per push channel: public UnityEvent<string> RoundEnded;
|
|
347
|
+
// one async method per client-callable RPC: public async Task<string> PlayTurn(string move);
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
The binding creates a `PlayerScopeProvider` or `MultiplayerScopeProvider` according to the cloud
|
|
351
|
+
class's `[StateScope]`, so the common case needs no wiring. The provider isn't exposed; choose what to
|
|
352
|
+
bind to with the `EnableClient` overload instead.
|
|
353
|
+
|
|
354
|
+
### Lifecycle
|
|
355
|
+
|
|
356
|
+
```csharp
|
|
357
|
+
protected virtual void OnEnable() => EnableClient();
|
|
358
|
+
protected virtual void OnDisable() => DisableClient();
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
`EnableClient()` subscribes to the provider and starts resolving the scope; `DisableClient()` tears that
|
|
362
|
+
down. Calling any `EnableClient` overload twice without an intervening `DisableClient()` logs a warning and
|
|
363
|
+
does nothing. To rebind to a different target, call `DisableClient()` first.
|
|
364
|
+
|
|
365
|
+
RPCs need a resolved scope. Rather than firing calls immediately after enabling, drive them from user
|
|
366
|
+
input or from `ScopeChanged`, which raises once the provider has a scope and the client has tried to
|
|
367
|
+
hydrate against it. Gate state-dependent work on `Synchronized`, or on the absence of
|
|
368
|
+
`HydrationFailedOnConnect` (see [Hydration](#hydration)), because `ScopeChanged` also follows a failed hydrate. `Scope` itself goes non-null earlier — the moment the provider resolves — so polling
|
|
369
|
+
`Scope != null` hands you a scope whose synchronized fields are not populated yet.
|
|
370
|
+
|
|
371
|
+
### Choosing what to bind to
|
|
372
|
+
|
|
373
|
+
`TargetSessionType` and `TargetPlayerId` are `internal` properties with a serialized backing field, so
|
|
374
|
+
they are editable in the Inspector (**Target Session Type**, **Target Player ID**) but not settable from
|
|
375
|
+
other code. Leaving them empty is the useful default:
|
|
376
|
+
|
|
377
|
+
| Scope | Target | Empty means | Set means |
|
|
378
|
+
| :-- | :-- | :-- | :-- |
|
|
379
|
+
| `MultiplayerSession` | `TargetSessionType` | adopt the single active session; if more than one is active the binding reports `CloudBehaviourExceptionReason.AmbiguousSessions` | watch only for sessions of that type — must match `SessionOptions.Type` used at create/join/matchmaking |
|
|
380
|
+
| `Player` / none | `TargetPlayerId` | follow the signed-in player | bind to that specific player id |
|
|
381
|
+
|
|
382
|
+
To choose the target from code, call the matching overload instead: `EnableClient(sessionType)`,
|
|
383
|
+
`EnableClient(ISession)`, `EnableClient(SessionObserver)` or `EnableClient(MultiplayerSession)` for
|
|
384
|
+
session scope, and `EnableClient(playerId)` for player scope. An overload replaces any target set in the
|
|
385
|
+
Inspector.
|
|
386
|
+
|
|
387
|
+
### Reacting to state and events
|
|
388
|
+
|
|
389
|
+
Synchronized members arrive as read-only properties; `Synchronized` raises after each applied update
|
|
390
|
+
(an RPC return, an incoming push, or the hydrate that runs when the scope resolves). Refreshing UI
|
|
391
|
+
from `Synchronized` decouples the view from individual RPCs:
|
|
392
|
+
|
|
393
|
+
```csharp
|
|
394
|
+
protected virtual void OnEnable()
|
|
395
|
+
{
|
|
396
|
+
EnableClient();
|
|
397
|
+
Synchronized.AddListener(RefreshUi);
|
|
398
|
+
RoundEnded.AddListener(OnRoundEnded);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
protected virtual void OnDisable()
|
|
402
|
+
{
|
|
403
|
+
Synchronized.RemoveListener(RefreshUi);
|
|
404
|
+
RoundEnded.RemoveListener(OnRoundEnded);
|
|
405
|
+
DisableClient();
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
void OnRoundEnded(string playerId) => Debug.Log($"round ended by {playerId}");
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Remove runtime listeners in `OnDisable`: `UnityEvent` keeps them across disable and enable, so re-enabling would add duplicates.
|
|
412
|
+
|
|
413
|
+
Because these are `UnityEvent`s, listeners can also be wired in the Inspector.
|
|
414
|
+
|
|
415
|
+
### Hydration
|
|
416
|
+
|
|
417
|
+
A client hydrates itself when its scope resolves: it pulls current state through the `B_Hydrate`
|
|
418
|
+
endpoint, applies it, and only then raises `ScopeChanged`, with `Synchronized` immediately after. A
|
|
419
|
+
`ScopeChanged` handler can therefore read synchronized fields directly — they are already current,
|
|
420
|
+
provided the hydrate succeeded (see below) — and a client that connects and does nothing else still holds server state.
|
|
421
|
+
|
|
422
|
+
If that first hydrate fails, `Error` is raised with `HydrationFailedOnConnect` *before*
|
|
423
|
+
`ScopeChanged`; `ScopeChanged` still follows, no `Synchronized` does, and the fields keep their
|
|
424
|
+
previous or default values. An `Error` handler therefore runs on a client that has not yet announced
|
|
425
|
+
its scope — `Scope` is already set, so calling `Hydrate()` from that handler is legal.
|
|
426
|
+
|
|
427
|
+
Every RPC return also carries the caller's fresh view of synchronized state, so a client that calls
|
|
428
|
+
an RPC does not need to hydrate either.
|
|
429
|
+
|
|
430
|
+
That leaves `Hydrate()` for two cases: catching up inside a live scope after a period of not
|
|
431
|
+
listening, and recovering from `HydrationFailedOnConnect`. A reconnect is not one of them — it
|
|
432
|
+
re-resolves the scope, which hydrates.
|
|
433
|
+
|
|
434
|
+
Connecting costs one hydrate RPC per scope resolution, per client. A pooled client toggled
|
|
435
|
+
repeatedly, or many behaviours on one player, pays it each time.
|
|
436
|
+
|
|
437
|
+
### Failures
|
|
438
|
+
|
|
439
|
+
Two events, split by whether the client survives.
|
|
440
|
+
|
|
441
|
+
`Fatal` carries a `CloudBehaviourException` whose `Reason` is one of `Unknown`, `SubscriptionError`,
|
|
442
|
+
`Kicked`, or `AmbiguousSessions`. The client is **already disabled** by the time handlers run —
|
|
443
|
+
`IsClientEnabled` is false and nothing further arrives until `EnableClient()` is called again. Do not
|
|
444
|
+
call it from the handler: a failure that reproduces immediately would recurse. `AmbiguousSessions` in
|
|
445
|
+
particular means the project has multiple active sessions and the binding cannot guess — set
|
|
446
|
+
**Target Session Type** in the Inspector, or call `EnableClient(sessionType)`.
|
|
447
|
+
|
|
448
|
+
`Error` carries a `CloudBehaviourError` and reports a failure the client **carries on from**: it keeps
|
|
449
|
+
its `Scope` and stays enabled. Read `Reason` to decide whether to retry. Today the only value besides
|
|
450
|
+
`Unknown` is `HydrationFailedOnConnect` — the client could not fetch server state when its scope
|
|
451
|
+
resolved, so synchronized fields still hold their previous or default values. Call `Hydrate()` to try
|
|
452
|
+
again.
|
|
453
|
+
|
|
454
|
+
```csharp
|
|
455
|
+
Fatal.AddListener(e => Debug.LogError($"client stopped: {e.Reason}"));
|
|
456
|
+
Error.AddListener(e =>
|
|
457
|
+
{
|
|
458
|
+
if (e.Reason == CloudBehaviourErrorReason.HydrationFailedOnConnect)
|
|
459
|
+
_ = Hydrate();
|
|
460
|
+
});
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
Both also log to the console, so an unhandled failure is not silent.
|
|
464
|
+
|
|
465
|
+
## Per-viewer state with `[SyncView]`
|
|
466
|
+
|
|
467
|
+
A plain synchronized field sends the same value to everyone in scope. When each player must see a
|
|
468
|
+
*different* value — their own hand, their own fog of war, their own private balance — express it as a
|
|
469
|
+
projection instead.
|
|
470
|
+
|
|
471
|
+
`[SyncView("wireName")]` marks a method as the synced member. The method **is** the state on the wire:
|
|
472
|
+
the generator emits one member named `wireName`, typed as the method's return type, and fills it per
|
|
473
|
+
recipient by calling the method with that player's id.
|
|
474
|
+
|
|
475
|
+
```csharp
|
|
476
|
+
[StateScope(Scope.MultiplayerSession)]
|
|
477
|
+
public class CardGame : CloudBehaviour
|
|
478
|
+
{
|
|
479
|
+
[CloudCodeSerializeProperty]
|
|
480
|
+
private Dictionary<string, List<Card>> _hands = new(); // every hand: persisted, cloud-only
|
|
481
|
+
|
|
482
|
+
[SyncView("Hand")]
|
|
483
|
+
public List<Card> ProjectHand(string playerId) =>
|
|
484
|
+
_hands.TryGetValue(playerId, out var hand) ? hand : new List<Card>();
|
|
485
|
+
}
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
The client sees `public List<Card> Hand { get; private set; }` holding only that player's cards. The
|
|
489
|
+
unfiltered dictionary never leaves the cloud, which is the point: filtering client-side would ship every
|
|
490
|
+
hand to every player.
|
|
491
|
+
|
|
492
|
+
### Requirements
|
|
493
|
+
|
|
494
|
+
- Signature is `TWire Method(string playerId)` — one `string` parameter, non-`void` return.
|
|
495
|
+
- The method must be `public` or `internal`. A `private` projection is build error `CCSG142`, so
|
|
496
|
+
`internal` is the tidy choice: visible to the generator, invisible outside the assembly. A `[SyncView]` method never becomes an RPC even when public, because
|
|
497
|
+
projections are partitioned out before endpoints are collected.
|
|
498
|
+
- It must be synchronous and non-generic: `async`, `Task`-returning, generic or `static` methods are not
|
|
499
|
+
captured.
|
|
500
|
+
- The `name` argument is required and non-empty; it is the member name clients see, so it should read
|
|
501
|
+
like a field (`"Hand"`, not `"ProjectHand"`).
|
|
502
|
+
- The `playerId` passed in is always the id of the player the payload is being built for — the viewer
|
|
503
|
+
about to receive this value, not whoever triggered the call. On an RPC return that is the caller, on a
|
|
504
|
+
push it is each recipient in turn, and on `Hydrate()` it is the player asking. Build the value for
|
|
505
|
+
*that* player and nothing else.
|
|
506
|
+
- When the invocation has no calling player — a timer callback, or a cross-behaviour call — the
|
|
507
|
+
**return value** carries an empty envelope rather than calling projections with an empty id. This is
|
|
508
|
+
the RPC return only. Pushes are unaffected: they are built per recipient, so each one still has a real
|
|
509
|
+
player id and projections resolve normally. See
|
|
510
|
+
[What a timer callback still pushes](#what-a-timer-callback-still-pushes).
|
|
511
|
+
- Keep the method side-effect free. It runs once per recipient on every synchronization, so mutating
|
|
512
|
+
state there produces surprising repeated writes.
|
|
513
|
+
- The projection runs for the calling player's own view as well, so a caller sees their projected value
|
|
514
|
+
in the RPC return, not the raw state.
|
|
515
|
+
|
|
516
|
+
### Sending to specific players
|
|
517
|
+
|
|
518
|
+
Per-viewer control applies to synchronized state. A declared event is pushed to everyone in scope, and a
|
|
519
|
+
check inside the method decides only whether the event fires, not who receives it. When only some players
|
|
520
|
+
should hear about something, push to each of them with
|
|
521
|
+
`Push.SendPlayerMessageAsync(Context, message, messageType, playerId)` instead of invoking an event.
|
|
522
|
+
|
|
523
|
+
## Calling another behaviour
|
|
524
|
+
|
|
525
|
+
The generator emits an `I{Behaviour}` interface for every behaviour in the module, with one member per
|
|
526
|
+
endpoint. A member does not invoke anything — it returns an unexecuted **`ICall`** (or `ICall<T>` when
|
|
527
|
+
the endpoint returns a value) describing the invocation. Inject the interface with `[CloudCodeService]`,
|
|
528
|
+
name the endpoint with its real arguments, then choose how to run it:
|
|
529
|
+
|
|
530
|
+
| Execution mode | Runs | Against | Returns |
|
|
531
|
+
|---|---|---|---|
|
|
532
|
+
| `ScheduleAsync(delay)` | later, after `delay` | the scope instance that is **executing** | `Task<string>` — the timer's id |
|
|
533
|
+
| `ForScope(scopeId)` | immediately | another instance of the **target's** scope | `Task`, or `Task<T>` carrying the endpoint's return value |
|
|
534
|
+
|
|
535
|
+
The two are alternatives. A described call handed to neither fails the build with `CCSG133`.
|
|
536
|
+
|
|
537
|
+
Use `ScheduleAsync` for timers ([Timers](#timers)). Use `ForScope` for everything else: one behaviour
|
|
538
|
+
invoking another right now, including across different scopes.
|
|
539
|
+
|
|
540
|
+
> **Do not call `ScheduleAsync` on another behaviour's `I{Behaviour}`**: it fails the build with `CCSG132`.
|
|
541
|
+
> `ScheduleAsync` is only for deferring **your own** behaviour's endpoints.
|
|
542
|
+
>
|
|
543
|
+
> To defer work against another behaviour, schedule one of your own endpoints and do the cross-scope
|
|
544
|
+
> call from inside it:
|
|
545
|
+
>
|
|
546
|
+
> ```csharp
|
|
547
|
+
> // in MatchRoom
|
|
548
|
+
> public async Task EndMatchSoon([CloudCodeService] IMatchRoom self) =>
|
|
549
|
+
> await self.FlushScores().ScheduleAsync(TimeSpan.FromSeconds(30)); // own endpoint: fine
|
|
550
|
+
>
|
|
551
|
+
> [CloudCodeAccess(Access.Service)]
|
|
552
|
+
> public async Task FlushScores([CloudCodeService] ILeaderboard leaderboard)
|
|
553
|
+
> {
|
|
554
|
+
> foreach (var entry in _scores)
|
|
555
|
+
> await leaderboard.SubmitScore(entry.Value).ForScope(entry.Key); // cross-scope: ForScope
|
|
556
|
+
> }
|
|
557
|
+
> ```
|
|
558
|
+
|
|
559
|
+
### Cross-scope invocation with `ForScope`
|
|
560
|
+
|
|
561
|
+
`ForScope(scopeId)` targets a specific instance of the callee's scope. The `scopeId` is a player id or a
|
|
562
|
+
multiplayer session id, matching the target behaviour's own `[StateScope]` — not the caller's.
|
|
563
|
+
|
|
564
|
+
```csharp
|
|
565
|
+
using System.Collections.Generic;
|
|
566
|
+
using System.Threading.Tasks;
|
|
567
|
+
using Unity.Services.CloudBehaviours;
|
|
568
|
+
using Unity.Services.CloudCode.Core;
|
|
569
|
+
|
|
570
|
+
[StateScope(Scope.Player)]
|
|
571
|
+
public class Leaderboard : CloudBehaviour
|
|
572
|
+
{
|
|
573
|
+
public int BestScore;
|
|
574
|
+
|
|
575
|
+
[CloudCodeAccess(Access.Service)] // no client method is generated; a behaviour can call it
|
|
576
|
+
public int SubmitScore(int score)
|
|
577
|
+
{
|
|
578
|
+
if (score > BestScore) BestScore = score;
|
|
579
|
+
return BestScore;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
[StateScope(Scope.MultiplayerSession)]
|
|
584
|
+
public class MatchRoom : CloudBehaviour
|
|
585
|
+
{
|
|
586
|
+
[CloudCodeSerializeProperty] private Dictionary<string, int> _scores = new();
|
|
587
|
+
|
|
588
|
+
[CloudCodeAccess(Access.SessionHost)]
|
|
589
|
+
public async Task EndMatch([CloudCodeService] ILeaderboard leaderboard)
|
|
590
|
+
{
|
|
591
|
+
foreach (var entry in _scores)
|
|
592
|
+
{
|
|
593
|
+
// entry.Key is a player id: the scope id of that player's Leaderboard instance
|
|
594
|
+
var best = await leaderboard.SubmitScore(entry.Value).ForScope(entry.Key);
|
|
595
|
+
Logger.LogInformation($"{entry.Key} best is now {best}");
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
`MatchRoom` is session-scoped and `Leaderboard` is player-scoped, so each call names the player whose
|
|
602
|
+
instance it should land on. Awaiting an `ICall<T>.ForScope(...)` gives that endpoint's return value
|
|
603
|
+
directly — the callee's synchronized members are not part of what the caller receives.
|
|
604
|
+
|
|
605
|
+
### Requirements
|
|
606
|
+
|
|
607
|
+
- **Both behaviours must live in the same module** (the same cloud assembly). `I{Behaviour}` and the
|
|
608
|
+
`{module}Extensions` registrations are generated per module. If the target is in another module, this
|
|
609
|
+
route does not exist — call it as a plain Cloud Code module instead.
|
|
610
|
+
- **`[CloudCodeService]` on the injected parameter is mandatory.** Without it the build fails with
|
|
611
|
+
`CCSG136`.
|
|
612
|
+
- **Injected parameters are not part of the interface.** The generator strips every
|
|
613
|
+
`[CloudCodeService]` parameter from `I{Behaviour}`, exactly as it does from the client bindings, so
|
|
614
|
+
name only the endpoint's real arguments. Do not pass `null` as a placeholder for an injected slot —
|
|
615
|
+
there is no slot. An endpoint declared
|
|
616
|
+
`Task FlushScores(int round, [CloudCodeService] ILeaderboard leaderboard)` is called as
|
|
617
|
+
`self.FlushScores(round)`.
|
|
618
|
+
- **Lock the target down.** A callable endpoint is callable by clients too. Anything only a behaviour
|
|
619
|
+
should invoke wants `[CloudCodeAccess(Access.Service)]` — a cross-behaviour call arrives as a service
|
|
620
|
+
call, so the restriction blocks clients while letting the caller through, and the generated client has
|
|
621
|
+
no method for it. See
|
|
622
|
+
[Endpoint access control](#endpoint-access-control).
|
|
623
|
+
- **Expect no calling player.** A service invocation has no player behind it: `Context.PlayerId` is
|
|
624
|
+
`null` and there is no player access token. Pass what the endpoint needs as arguments, and use
|
|
625
|
+
`Context.ServiceToken` for outbound service calls.
|
|
626
|
+
- **Class names must be unique within the module**, even across namespaces.
|
|
627
|
+
|
|
628
|
+
## Timers
|
|
629
|
+
|
|
630
|
+
Timers are server-authoritative: the cloud schedules a call back into one of the behaviour's own
|
|
631
|
+
endpoints, so a player who closes the game cannot dodge a turn clock.
|
|
632
|
+
|
|
633
|
+
Scheduling uses the same generated `I{Behaviour}` interface described in
|
|
634
|
+
[Calling another behaviour](#calling-another-behaviour): inject it, name the endpoint with its real
|
|
635
|
+
arguments, and run the resulting `ICall` with `ScheduleAsync(delay)`. That defers the invocation on the
|
|
636
|
+
scope instance that is executing.
|
|
637
|
+
|
|
638
|
+
```csharp
|
|
639
|
+
using System;
|
|
640
|
+
using System.Threading.Tasks;
|
|
641
|
+
using Unity.Services.CloudBehaviours;
|
|
642
|
+
using Unity.Services.CloudCode.Core;
|
|
643
|
+
|
|
644
|
+
[StateScope(Scope.MultiplayerSession)]
|
|
645
|
+
public class TurnClock : CloudBehaviour
|
|
646
|
+
{
|
|
647
|
+
public string CurrentTimerId; // persisted, so it survives to the next call
|
|
648
|
+
public int TurnNumber;
|
|
649
|
+
|
|
650
|
+
[CloudCodeAccess(Access.SessionHost)]
|
|
651
|
+
public async Task StartTurn([CloudCodeService] ITurnClock self)
|
|
652
|
+
{
|
|
653
|
+
CurrentTimerId = await self.EndTurn(TurnNumber).ScheduleAsync(TimeSpan.FromSeconds(30));
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
[CloudCodeAccess(Access.Service)]
|
|
657
|
+
public void EndTurn(int turnNumber) // the scheduled endpoint
|
|
658
|
+
{
|
|
659
|
+
if (turnNumber != TurnNumber) return; // a stale timer fires against an old turn
|
|
660
|
+
TurnNumber++;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
[CloudCodeAccess(Access.SessionHost)]
|
|
664
|
+
public async Task CancelTurn([CloudCodeService] ITimerService timers)
|
|
665
|
+
{
|
|
666
|
+
var timer = await timers.GetTimerAsync(CurrentTimerId);
|
|
667
|
+
timer.Cancel();
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
Arguments are ordinary typed parameters on the endpoint — no dictionary of loosely-typed values, and no
|
|
673
|
+
function-name string to assemble. Under the hood `ScheduleAsync` registers a timer against the deployed
|
|
674
|
+
endpoint for you.
|
|
675
|
+
|
|
676
|
+
### Cancelling
|
|
677
|
+
|
|
678
|
+
`ScheduleAsync` returns the timer's id. `ICall` has no cancel of its own, so keep injecting
|
|
679
|
+
`[CloudCodeService] ITimerService` for that: `GetTimerAsync(id)` returns the timer, which exposes
|
|
680
|
+
`RemainingTime()` and `Cancel()`. Persist the id if you will ever need it — a later invocation gets a
|
|
681
|
+
fresh instance.
|
|
682
|
+
|
|
683
|
+
### Lock the target down, and don't expect a player
|
|
684
|
+
|
|
685
|
+
A scheduled endpoint is still an endpoint, so a client can call it directly. Restrict it with
|
|
686
|
+
`[CloudCodeAccess(Access.Service)]`: a timed invocation arrives as a service call, so the timer still
|
|
687
|
+
fires while clients are blocked. The generated client has no method for it, so a stray client call is a
|
|
688
|
+
compile error.
|
|
689
|
+
|
|
690
|
+
That same identity difference shapes what the endpoint can assume when a timer runs it:
|
|
691
|
+
|
|
692
|
+
- `Context.PlayerId` is `null` — the invocation is not on behalf of anyone, so branching on the "calling
|
|
693
|
+
player" does not work. Pass what you need as arguments, or read it from state.
|
|
694
|
+
- there is no player access token; use `Context.ServiceToken` for outbound service calls.
|
|
695
|
+
- session data is refreshed before it runs, so `Context.Session.PlayerIds` reflects who is in the session
|
|
696
|
+
at that moment — handy for skipping players who left.
|
|
697
|
+
|
|
698
|
+
### What a timer callback still pushes
|
|
699
|
+
|
|
700
|
+
A timer runs for no player, but clients still see what it changes. A timer-triggered invocation pushes
|
|
701
|
+
to the same recipients as any other call: every session member for `Scope.MultiplayerSession`, and the
|
|
702
|
+
owner for `Scope.Player`.
|
|
703
|
+
|
|
704
|
+
- **Synchronized state is pushed** to each recipient, with each payload built against that recipient's
|
|
705
|
+
id, so `[SyncView]` projections resolve per viewer as they do on a client call. The service fills
|
|
706
|
+
`Session.PlayerIds` for a session-scoped behaviour whatever the endpoint's access level, so a
|
|
707
|
+
service-token invocation still reaches every member.
|
|
708
|
+
- **Declared events are pushed** to the same recipients, carrying a full synchronization envelope.
|
|
709
|
+
- **Only the RPC return value is empty**, and only when the behaviour has projections. Nothing reads the
|
|
710
|
+
return of a timer invocation anyway.
|
|
711
|
+
|
|
712
|
+
A value that fails to arrive after a timer fired is not explained by the timer.
|
|
713
|
+
|
|
714
|
+
### Limits and patterns
|
|
715
|
+
|
|
716
|
+
- The delay must be **greater than zero**.
|
|
717
|
+
- Each scope instance (one player, or one session) may hold at most **10 active timers**; registering
|
|
718
|
+
an eleventh throws. Cancelling frees the slot, so cancel the ones you no longer need rather than
|
|
719
|
+
letting them pile up.
|
|
720
|
+
- The delays of all active timers on a scope instance may add up to at most **24 hours** in
|
|
721
|
+
production, so no single timer can be longer than that.
|
|
722
|
+
- There is no recurring timer. For a repeating clock, have the scheduled endpoint schedule the next one,
|
|
723
|
+
keeping exactly one alive at a time.
|
|
724
|
+
- **Only schedule your own behaviour's endpoints.** Calling `ScheduleAsync` on another behaviour's
|
|
725
|
+
`I{Behaviour}` fails the build with `CCSG132`. See [Calling another behaviour](#calling-another-behaviour) for the
|
|
726
|
+
pattern that works.
|
|
727
|
+
|
|
728
|
+
## State, serialization, and model types
|
|
729
|
+
|
|
730
|
+
### Two separate rules: sync and persistence
|
|
731
|
+
|
|
732
|
+
Whether a member reaches clients and whether it survives to the next invocation are decided
|
|
733
|
+
independently:
|
|
734
|
+
|
|
735
|
+
- **Reaches clients** — determined purely by `public` accessibility.
|
|
736
|
+
- **Persists between invocations** — public members persist by default; two attributes override it, and
|
|
737
|
+
both apply to fields and properties:
|
|
738
|
+
|
|
739
|
+
| Attribute | Effect |
|
|
740
|
+
| :-- | :-- |
|
|
741
|
+
| `[CloudCodeSerializeProperty]` | persists a non-public member — durable state the client never sees |
|
|
742
|
+
| `[CloudCodeIgnorePropertyAttribute]` (`[CloudCodeIgnoreProperty]`) | keeps a public member out of persistence while it still synchronizes |
|
|
743
|
+
|
|
744
|
+
```csharp
|
|
745
|
+
[CloudCodeSerializeProperty]
|
|
746
|
+
private string _correctAnswer; // survives invocations, never sent to clients
|
|
747
|
+
|
|
748
|
+
[CloudCodeIgnoreProperty]
|
|
749
|
+
public int FramesSinceStart; // synced for display, not worth persisting
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
Only serialized members survive: the runtime may rehydrate the instance between calls, so anything held
|
|
753
|
+
in a plain private field is gone by the next invocation.
|
|
754
|
+
|
|
755
|
+
Custom serialization with `IStateSerializer` isn't supported on a CloudBehaviour yet, and implementing it
|
|
756
|
+
is error `CCSG157`. Don't suggest it for state migration; use the attributes above and keep saved members
|
|
757
|
+
backward compatible.
|
|
758
|
+
|
|
759
|
+
A persisted data type can't hide an inherited member unless both are properties of the same type. The
|
|
760
|
+
server can't save two members with one name, so that pair is error `CCSG156`. Rename one, or add
|
|
761
|
+
`[CloudCodeIgnoreProperty]` to the one that doesn't need saving.
|
|
762
|
+
|
|
763
|
+
`CCSG156` can't see non-public members of a type compiled in another assembly, such as a shared asmdef.
|
|
764
|
+
There, an `internal` or `private` member that hides a public one isn't reported, and every save of the
|
|
765
|
+
behaviour fails at runtime. Don't hide inherited members in shared data types.
|
|
766
|
+
|
|
767
|
+
Synchronized state is a public field or a public property with a public getter and a setter of any
|
|
768
|
+
accessibility, the same accessor shapes that persist. A `readonly` field or a get-only or computed property is
|
|
769
|
+
ignored with warning `CCSG113`; use a `[SyncView]` method for a computed value. A `public static` or
|
|
770
|
+
`const` member is skipped with warning `CCSG158`. Data **types** crossing the
|
|
771
|
+
boundary are stricter: a member that can be read but not assigned there is error `CCSG131`.
|
|
772
|
+
`[CloudCodeSerializeProperty]` and `[CloudCodeIgnoreProperty]` still apply to both fields and properties
|
|
773
|
+
for persistence purposes.
|
|
774
|
+
|
|
775
|
+
### Manual synchronization
|
|
776
|
+
|
|
777
|
+
`AutoSynchronize` is `true` by default. After each endpoint returns, Cloud Code pushes the updated
|
|
778
|
+
synchronized state to the other clients in the scope, once per call however many fields changed, so a
|
|
779
|
+
single endpoint never needs to turn it off.
|
|
780
|
+
|
|
781
|
+
Turn it off to batch changes from several calls into one push. `AutoSynchronize` is saved with the
|
|
782
|
+
behaviour's state, so setting it to `false` keeps it off for every later call in that scope, from any
|
|
783
|
+
player, until an endpoint sets it back to `true`. End the batch by setting it to `true` in the last call;
|
|
784
|
+
that call's normal push then carries everything:
|
|
785
|
+
|
|
786
|
+
```csharp
|
|
787
|
+
public void BeginPlacement()
|
|
788
|
+
{
|
|
789
|
+
AutoSynchronize = false; // later calls change state without pushing
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
public void PlaceShip(int x, int y)
|
|
793
|
+
{
|
|
794
|
+
Ships.Add(new Ship { X = x, Y = y });
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
public void LockPlacement()
|
|
798
|
+
{
|
|
799
|
+
Locked = true;
|
|
800
|
+
AutoSynchronize = true; // this call's push carries the whole batch
|
|
801
|
+
}
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
Rules:
|
|
805
|
+
|
|
806
|
+
- Write the endpoint that sets `AutoSynchronize` back to `true` in the same change that sets it to
|
|
807
|
+
`false`. Left off, state still saves and each caller still gets it in the RPC return, but no other
|
|
808
|
+
client receives a synchronization push, so the game looks frozen to everyone else.
|
|
809
|
+
- Events still push during a batch, and each one carries the full synchronized state, so raising an
|
|
810
|
+
event mid-batch sends the batch's changes early.
|
|
811
|
+
- Re-enable it on paths that abandon the batch too, such as a player leaving mid-placement or a round
|
|
812
|
+
timing out. The endpoint that starts the next round, or a timer callback, can do it.
|
|
813
|
+
- To push partway through a call, for example before a slow step, call `SynchronizeAsync()` and leave
|
|
814
|
+
`AutoSynchronize` on. The end-of-call push still follows.
|
|
815
|
+
- Don't initialise the field to `false`. That makes the behaviour fully manual, and every endpoint that
|
|
816
|
+
should reach other clients has to call `SynchronizeAsync()` itself.
|
|
817
|
+
|
|
818
|
+
The calling client always receives current state in the RPC return, whatever `AutoSynchronize` is set to.
|
|
819
|
+
|
|
820
|
+
Risks:
|
|
821
|
+
|
|
822
|
+
- The flag belongs to the scope instance, not to the call or the player. One player's endpoint turning it
|
|
823
|
+
off stops the end-of-call push of every endpoint of that behaviour, for every player in the scope, and it
|
|
824
|
+
stays off across restarts of the scope because it is saved with the rest of the state. Only events and
|
|
825
|
+
explicit `SynchronizeAsync()` calls still push.
|
|
826
|
+
- Nothing warns when it is left off. The symptom is other clients no longer receiving updates from
|
|
827
|
+
endpoints unrelated to the batch, while each caller still sees current state in its own RPC return. When
|
|
828
|
+
pushes stop, look for an endpoint that set `AutoSynchronize = false` and a path that never set it back.
|
|
829
|
+
- Read-only endpoints cannot opt out of synchronization. An endpoint that changes nothing still pushes the
|
|
830
|
+
full synchronized state to every push recipient other than the caller: every other member of a
|
|
831
|
+
`Scope.MultiplayerSession`, or the owner of a `Scope.Player` scope when someone else calls it. An owner
|
|
832
|
+
calling its own player scope sends nothing. Don't turn `AutoSynchronize` off to avoid the push: it is the
|
|
833
|
+
same permanent switch. Accept it, and keep read-only session endpoints off hot paths, for example by
|
|
834
|
+
rate-limiting how often the client calls them.
|
|
835
|
+
|
|
836
|
+
Who receives the push depends on scope. For `Scope.MultiplayerSession` the synchronization message goes
|
|
837
|
+
to `Context.Session.PlayerIds`, so session members see each other's changes without calling anything.
|
|
838
|
+
The caller is left out because the RPC return already gave it the state.
|
|
839
|
+
|
|
840
|
+
A player-scoped behaviour's only client is its owner, so every push goes to the owner. When the owner
|
|
841
|
+
calls, the RPC return carries the state, so no synchronization push is sent, and events still reach the
|
|
842
|
+
owner. When a timer, a service call or another player's `Global` call changes the state, the owner gets
|
|
843
|
+
the synchronization push and the events. A caller who isn't the owner gets only the RPC return. Don't
|
|
844
|
+
design a player-scoped behaviour as though other clients will be pushed to.
|
|
845
|
+
|
|
846
|
+
For a raw push outside synchronization, `SendMessageAsync(messageType, payload)` sends to every session
|
|
847
|
+
member for `Scope.MultiplayerSession`, and to the owner for `Scope.Player`. Declared events are usually
|
|
848
|
+
clearer than raw messages.
|
|
849
|
+
|
|
850
|
+
### Model types
|
|
851
|
+
|
|
852
|
+
Custom classes, structs, and enums referenced from public fields, properties, endpoint parameters/returns,
|
|
853
|
+
or event payloads are regenerated in the client bindings assembly. The regenerated version carries the
|
|
854
|
+
public fields and properties (as `{ get; set; }`) and enum members — nothing else.
|
|
855
|
+
|
|
856
|
+
```csharp
|
|
857
|
+
// Good — data only
|
|
858
|
+
public class AttackResult
|
|
859
|
+
{
|
|
860
|
+
public string TargetPlayerId;
|
|
861
|
+
public int X;
|
|
862
|
+
public bool WasHit;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// Compiles, but the method is missing on the client
|
|
866
|
+
public class AttackResult
|
|
867
|
+
{
|
|
868
|
+
public bool WasHit;
|
|
869
|
+
public bool IsSunk() => /* ... */; // methods aren't regenerated
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// Fails the build with CCSG131
|
|
873
|
+
public class AttackResult
|
|
874
|
+
{
|
|
875
|
+
public bool WasHit;
|
|
876
|
+
public string Summary => $"{WasHit}"; // readable but not assignable
|
|
877
|
+
}
|
|
878
|
+
```
|
|
879
|
+
|
|
880
|
+
A method is only dropped from the client stub. A member that can be read but not assigned, such as a
|
|
881
|
+
computed or get-only property, can't round-trip, so the build fails with `CCSG131`.
|
|
882
|
+
|
|
883
|
+
Nested types referenced by a model are generated recursively and stay nested inside their containing
|
|
884
|
+
type. Enum bindings preserve the underlying type (`byte`, `long`, …), so values outside `int` range are
|
|
885
|
+
safe. Types that share a simple name across different namespaces are generated separately.
|
|
886
|
+
|
|
887
|
+
A model type cannot inherit from a `[CloudCodeService]` type — service objects hold server-only state
|
|
888
|
+
that cannot be serialized.
|
|
889
|
+
|
|
890
|
+
### Unsupported types
|
|
891
|
+
|
|
892
|
+
A custom collection type — anything implementing `IEnumerable<T>` that is not `List<T>`, an array, or
|
|
893
|
+
`Dictionary<,>` — is a **compile-time error** when used as an endpoint parameter, an endpoint return, or a
|
|
894
|
+
data-type field, because it cannot round-trip through JSON. Use `List<T>` or `T[]`.
|
|
895
|
+
|
|
896
|
+
Everything crossing the boundary must be JSON-serializable: primitives, `string`, enums, plain
|
|
897
|
+
classes/structs, and `List<T>`/arrays/dictionaries of those.
|
|
898
|
+
|
|
899
|
+
## Endpoint access control
|
|
900
|
+
|
|
901
|
+
Every CloudBehaviour endpoint has an access level. Left alone it is derived from the class scope, which
|
|
902
|
+
is the right answer most of the time:
|
|
903
|
+
|
|
904
|
+
| Class scope | Resolved access |
|
|
905
|
+
| :-- | :-- |
|
|
906
|
+
| `[StateScope(Scope.Player)]` | `Access.Player` |
|
|
907
|
+
| `[StateScope(Scope.MultiplayerSession)]` | `Access.SessionMember` |
|
|
908
|
+
|
|
909
|
+
Resolution happens when the module loads, so the attribute is only needed where an endpoint should differ
|
|
910
|
+
from its class default.
|
|
911
|
+
|
|
912
|
+
```csharp
|
|
913
|
+
using Unity.Services.CloudBehaviours;
|
|
914
|
+
using Unity.Services.CloudCode.Core;
|
|
915
|
+
|
|
916
|
+
[StateScope(Scope.MultiplayerSession)]
|
|
917
|
+
public class Match : CloudBehaviour
|
|
918
|
+
{
|
|
919
|
+
public void SubmitMove(string move) { } // Access.SessionMember (default)
|
|
920
|
+
|
|
921
|
+
[CloudCodeAccess(Access.SessionHost)]
|
|
922
|
+
public void StartMatch() { } // only the session host
|
|
923
|
+
|
|
924
|
+
[CloudCodeAccess(Access.Service)]
|
|
925
|
+
public void GrantReward(string playerId, int amount) { } // only service-token callers
|
|
926
|
+
}
|
|
927
|
+
```
|
|
928
|
+
|
|
929
|
+
An `Access.Service` endpoint gets no method on the generated client binding, so calling it from game
|
|
930
|
+
code is a compile error rather than a runtime `403`. Behaviours still reach it through `I{Behaviour}`
|
|
931
|
+
(`ScheduleAsync`, `ForScope`).
|
|
932
|
+
|
|
933
|
+
### The `Access` enum
|
|
934
|
+
|
|
935
|
+
Which values are legal depends on the class's `[StateScope]`, so the enum groups by scope:
|
|
936
|
+
|
|
937
|
+
| Valid on | Value | Who may call |
|
|
938
|
+
| :-- | :-- | :-- |
|
|
939
|
+
| **any scope** | `Unspecified` | resolve from the class scope at load time (the default) |
|
|
940
|
+
| | `Global` | any authenticated caller |
|
|
941
|
+
| | `Service` | service-token callers only — other services, trusted backends, timer invocations; never a game client |
|
|
942
|
+
| **`Scope.Player` only** | `Player` | the player who owns the scope |
|
|
943
|
+
| **`Scope.MultiplayerSession` only** | `SessionMember` | any member of the session |
|
|
944
|
+
| | `SessionHost` | the host of the session |
|
|
945
|
+
|
|
946
|
+
An `Access` value the module cannot honour throws when the module loads rather than failing quietly, so
|
|
947
|
+
pass enum members and avoid casting integers into `Access`.
|
|
948
|
+
|
|
949
|
+
### Choosing a level
|
|
950
|
+
|
|
951
|
+
Reach for `Access.Service` on anything that mints value — granting currency, awarding items, adjusting
|
|
952
|
+
another player's state. `SessionHost` suits match flow control (start, force-advance, kick) where one
|
|
953
|
+
client is authoritative. Beyond that, the scope defaults are usually correct, and adding attributes that
|
|
954
|
+
merely restate them is noise.
|
|
955
|
+
|
|
956
|
+
Access control is not a substitute for keeping state private: an endpoint restricted to `Service` still
|
|
957
|
+
exposes any `public` field to every client in scope.
|
|
958
|
+
|
|
959
|
+
## Local development, debugging, and deployment
|
|
960
|
+
|
|
961
|
+
### The local Cloud Code server
|
|
962
|
+
|
|
963
|
+
Deploying to the local server is the fast loop: it runs the cloud assembly in a local process, supports
|
|
964
|
+
breakpoints, and lets Play mode talk to real cloud logic without a round trip to UGS.
|
|
965
|
+
|
|
966
|
+
- Configuration lives on the `CloudCodeLocalServerSettings` asset — the listening **port** (default
|
|
967
|
+
**14750**) and an optional **secrets** `TextAsset`. The settings UI can create and assign a placeholder
|
|
968
|
+
secrets JSON file and open it in an external editor; invalid JSON surfaces an error dialog rather than
|
|
969
|
+
failing at runtime.
|
|
970
|
+
- A module whose source changed is **redeployed automatically** onto the running server — no stop/start
|
|
971
|
+
cycle. Redeploy happens after a recompile, outside Play mode only. **Nothing deploys to the local server
|
|
972
|
+
during Play mode**, not even an explicit `services_deployment_item_deploy`, which is refused. A
|
|
973
|
+
`404 function-not-found` for an endpoint added while playing means the module isn't deployed yet: exit
|
|
974
|
+
Play mode, wait for `Local Cloud Code server reloaded` in the Console, then re-enter. Same known limitation
|
|
975
|
+
as modules: see
|
|
976
|
+
the Deploying via the Deployment Window section of `references/cloud-code-modules.md`.
|
|
977
|
+
- The server won't start without an active services environment: see
|
|
978
|
+
the Running the Local Cloud Code Server section of `references/cloud-code-modules.md`.
|
|
979
|
+
- On startup the server checks for port conflicts and a usable .NET runtime, and a server that dies during
|
|
980
|
+
startup reports why.
|
|
981
|
+
|
|
982
|
+
### The binding Inspector
|
|
983
|
+
|
|
984
|
+
The generator also emits a custom Inspector for each bindings component, which is the fastest way to see
|
|
985
|
+
what a binding is actually doing at runtime. In Play mode it shows:
|
|
986
|
+
|
|
987
|
+
- a live connection-status indicator and the resolved player or session identifier — so a binding that
|
|
988
|
+
never resolved a scope is visible without adding a single log line;
|
|
989
|
+
- the current value of every synchronized member and `[SyncView]` projection, read-only;
|
|
990
|
+
- `UnityEvent` slots for `ScopeChanged`, `Fatal`, `Error`, `MessageReceived`, `Synchronized`, and each
|
|
991
|
+
push event, so listeners can be wired without code.
|
|
992
|
+
|
|
993
|
+
Check it before instrumenting anything. A missing member points at generation (see
|
|
994
|
+
[Generator diagnostics](#generator-diagnostics)); a resolved scope with stale values points at the push
|
|
995
|
+
path; no scope at all points at the lifecycle.
|
|
996
|
+
|
|
997
|
+
### Logging
|
|
998
|
+
|
|
999
|
+
Two independent switches:
|
|
1000
|
+
|
|
1001
|
+
| Where | How |
|
|
1002
|
+
| :-- | :-- |
|
|
1003
|
+
| Server-side, inside a behaviour | `Logger` on the base class (`Microsoft.Extensions.Logging.ILogger`) |
|
|
1004
|
+
| Cloud Code's own Editor/authoring logging, including local server operation | **Verbose Logging** in **Project Settings > Services > Cloud Code**, backed by the `ENABLE_UNITY_CLOUD_CODE_AUTHORING_VERBOSE_LOGGING` define |
|
|
1005
|
+
| Client-side binding traffic — scope resolution, pushes, RPC calls | add the `ENABLE_CLOUDBEHAVIOUR_LOGS` scripting define; the generated bindings' log calls are `[Conditional]` on it, so they compile out without it |
|
|
1006
|
+
|
|
1007
|
+
Local server infrastructure logging defaults to non-verbose, so turn Verbose Logging on before chasing a
|
|
1008
|
+
deployment or connection problem.
|
|
1009
|
+
|
|
1010
|
+
### Deployment
|
|
1011
|
+
|
|
1012
|
+
CloudBehaviour modules appear in the **Deployment** window as Cloud Code modules and deploy from there to
|
|
1013
|
+
the linked UGS project. A module asset is a `.ccmu` file; its inspector offers deployment actions and a
|
|
1014
|
+
summary of the last successful deployment. The window finds modules only through their `.ccmu`, so a module
|
|
1015
|
+
that is missing from it has no `.ccmu`. Repair it by adding one (see
|
|
1016
|
+
[Creating the module](#creating-the-module)).
|
|
1017
|
+
|
|
1018
|
+
Names come from the **cloud assembly (asmdef) name**, not the `.ccmu` asset name. While the local server
|
|
1019
|
+
runs, an explicit deploy goes to it instead of the environment, so stop it before deploying remotely.
|
|
1020
|
+
|
|
1021
|
+
A deployed behaviour runs in the same hosting model as any other Cloud Code module, so **cold start
|
|
1022
|
+
applies**: after roughly 15 minutes without traffic the worker shuts down and the next call pays a
|
|
1023
|
+
latency spike while it starts again. Budget for it in the UX — do not put a tight timeout on the first
|
|
1024
|
+
call after an idle period, and do not treat that first slow response as a failure.
|
|
1025
|
+
|
|
1026
|
+
### When the generated bindings do not compile
|
|
1027
|
+
|
|
1028
|
+
Work through it in this order, since the later steps are rarer:
|
|
1029
|
+
|
|
1030
|
+
1. Recompile. Generated sources are produced per compilation; a stale error can survive an interrupted
|
|
1031
|
+
one.
|
|
1032
|
+
2. Check both assemblies reference `Unity.Services.CloudCode.SourceGenerator`, and that the cloud
|
|
1033
|
+
assembly references `Unity.Services.CloudBehaviours.Editor`. A missing reference is the most common
|
|
1034
|
+
cause, and the generator's diagnostic names it.
|
|
1035
|
+
3. Check the console for generator diagnostics — collisions between behaviour members, read-only members
|
|
1036
|
+
skipped for synchronization, unsupported collection types, and duplicate names are all reported there
|
|
1037
|
+
rather than as mystery type errors.
|
|
1038
|
+
4. Confirm no generated member has been implemented by hand in the partial (`EnableClient`,
|
|
1039
|
+
`DisableClient`, `Hydrate`, an RPC, a synchronized property).
|
|
1040
|
+
5. Confirm the editor is 6000.5+ — both scaffolded assemblies are gated behind `UNITY_6000_5_OR_NEWER`,
|
|
1041
|
+
so on an older editor the types simply do not exist and every reference to them fails.
|
|
1042
|
+
|
|
1043
|
+
## Generator diagnostics
|
|
1044
|
+
|
|
1045
|
+
The generator reports problems as compiler diagnostics with ids of the form `CCSG1xx` (CloudBehaviours;
|
|
1046
|
+
`CCSG2xx` is native modules, `CCSG3xx` bindings). They surface in the Console and in the IDE like any
|
|
1047
|
+
other compiler message, and severities can be adjusted through `.editorconfig`.
|
|
1048
|
+
|
|
1049
|
+
**Read them before assuming the generator is broken.** Most CloudBehaviour problems are reported, name
|
|
1050
|
+
the offending member, and say what was kept and what was dropped.
|
|
1051
|
+
|
|
1052
|
+
**An error that aborts generation takes every generated member with it.** The client partial then has no
|
|
1053
|
+
generated half, so a single `CCSG1xx` error surfaces as a pile of `CS0103 - does not exist in the current
|
|
1054
|
+
context` on `EnableClient`, `DisableClient`, `Synchronized`, the RPCs and the synchronized properties.
|
|
1055
|
+
Those are symptoms. Fix the `CCSG1xx` and they all go away together — editing the client partial to chase
|
|
1056
|
+
them is wasted work.
|
|
1057
|
+
|
|
1058
|
+
Common ones, by what they tell you:
|
|
1059
|
+
|
|
1060
|
+
| Id | Situation | Severity | Fix |
|
|
1061
|
+
| :-- | :-- | :-- | :-- |
|
|
1062
|
+
| `CCSG126` | Behaviour class has no `[StateScope]` | error | Add `[StateScope]` to the class |
|
|
1063
|
+
| `CCSG101` | Two behaviours in a module share a simple class name | error | Rename one behaviour so simple names are unique in the module |
|
|
1064
|
+
| `CCSG120` / `CCSG122` | Name reserved by a generated type or framework type | error | Rename the member or class away from the reserved name |
|
|
1065
|
+
| `CCSG115` | `Access` value incompatible with the class scope | error | Use an `Access` value valid for the class scope |
|
|
1066
|
+
| `CCSG116` | `[CloudCodeAccess]` on something that is not an endpoint | error | Remove `[CloudCodeAccess]` or put it on an endpoint |
|
|
1067
|
+
| `CCSG109` | `ref` / `out` parameter | error | Remove the `ref` / `out` modifier |
|
|
1068
|
+
| `CCSG112` / `CCSG125` | Generic behaviour class, or a nested one | error | Make the behaviour class non-generic and top-level |
|
|
1069
|
+
| `CCSG110` / `CCSG111` | Unsupported awaitable return, or `async void` | error | Return `Task`, `Task<T>` or a plain value, and never `async void` |
|
|
1070
|
+
| `CCSG128` / `CCSG129` | Custom collection type in a signature or data-type field | error | Use `List<T>`, arrays or dictionaries instead of a custom collection |
|
|
1071
|
+
| `CCSG131` | Data-type member that is readable but not settable, including a `readonly` field | error | Give the member a setter, or remove it from the data type |
|
|
1072
|
+
| `CCSG132` | `ScheduleAsync` on another behaviour's `I{Behaviour}` — a timer only runs on the behaviour that scheduled it | error | Call `ScheduleAsync` only on the behaviour that owns the timer |
|
|
1073
|
+
| `CCSG133` | A described `ICall` is never given `ScheduleAsync` or `ForScope` | error | Call `ScheduleAsync` or `ForScope` on the described `ICall` |
|
|
1074
|
+
| `CCSG136` | Cloud Code service or generated `I{Behaviour}` parameter without `[CloudCodeService]` | error | Mark the parameter `[CloudCodeService]` |
|
|
1075
|
+
| `CCSG142` | `[SyncView]` method that isn't `public` or `internal` | error | Make the `[SyncView]` method `public` or `internal` |
|
|
1076
|
+
| `CCSG127` | `Scope.MultiplayerSession` client assembly missing `Unity.Services.CloudBehaviours.Multiplayer.Runtime` / `Unity.Services.Multiplayer` references — the message names both. Fires even with the package installed | error | Add the two assembly references the message names to the client assembly |
|
|
1077
|
+
| `CCSG118` | Members of different kinds sharing a name — all of them dropped | error | Rename the members so no two kinds share a name |
|
|
1078
|
+
| `CCSG117` | Duplicate member name within a behaviour — first declaration kept | **warning** | Rename the duplicate member |
|
|
1079
|
+
| `CCSG119` | Member name reserved by the generated bindings class | **warning** | Rename the member away from the bindings class name |
|
|
1080
|
+
| `CCSG113` | Read-only synchronized member ignored: a `readonly` field, or a get-only or computed property | **warning** | Give the member a setter if it must synchronize |
|
|
1081
|
+
| `CCSG158` | `public static` or `const` member on a behaviour — not synchronized | **warning** | Move the value to a synchronized instance member if clients need it |
|
|
1082
|
+
| `CCSG114` | Unsupported event handler type, including more than four payload parameters | **warning** | Use a supported handler type with at most four payload parameters |
|
|
1083
|
+
|
|
1084
|
+
The warnings matter as much as the errors: each one means a member you wrote is **not** on the wire, and
|
|
1085
|
+
the build still succeeds. If a client member is missing, search the Console for `CCSG1` before changing
|
|
1086
|
+
any code.
|
|
1087
|
+
|
|
1088
|
+
## Silent failure modes
|
|
1089
|
+
|
|
1090
|
+
These compile and then do nothing, which is what makes them expensive:
|
|
1091
|
+
|
|
1092
|
+
- **Read-only and static members don't synchronize.** Public fields and public properties with a
|
|
1093
|
+
setter of any accessibility sync. A `readonly` field or a get-only or computed property raises warning
|
|
1094
|
+
`CCSG113`; a `public static` or `const` member raises warning `CCSG158`. Either way the client property never appears.
|
|
1095
|
+
- **Names collide across kinds, case-insensitively.** Endpoints, synchronized fields, events and
|
|
1096
|
+
projections share one wire-key namespace, and overloads collide for the same reason. A same-kind
|
|
1097
|
+
duplicate keeps the first declaration with **warning** `CCSG117`, so the build stays green while the
|
|
1098
|
+
dropped member is missing on the client. A cross-kind clash drops all of them and fails with error `CCSG118`.
|
|
1099
|
+
- **Push events are never replayed**, so a late joiner sees nothing; lasting state belongs in a
|
|
1100
|
+
synchronized field.
|
|
1101
|
+
- **Player-scoped behaviours push to no other clients.** State reaches that one client through RPC
|
|
1102
|
+
returns, the hydrate on connect, and `Hydrate()`.
|
|
1103
|
+
- **A module without a `.ccmu` never reaches the Deployment window.** Hand-written asmdefs compile and
|
|
1104
|
+
generate bindings, but nothing lists the module for deployment and nothing warns. Create new modules
|
|
1105
|
+
through the menu, and repair an existing one by adding its `.ccmu` (see
|
|
1106
|
+
[Creating the module](#creating-the-module)).
|
|
1107
|
+
- **Model types are data only.** Methods and constructors are not regenerated on the client — the stub
|
|
1108
|
+
carries the public fields and properties, nothing else. A computed or get-only property is error
|
|
1109
|
+
`CCSG131`.
|
|
1110
|
+
|
|
1111
|
+
When a member is missing on the client, read the console for generator diagnostics before changing code.
|