t1k:cocos:playable:juice
| Field | Value |
|---|---|
| Module | playable |
| Version | 0.15.0 |
| Effort | high |
| Tools | — |
Keywords: @playablelabs/motion, ads, animation, binding, cocos, disable, enable, enhance, game feel, hide, juice, motion, MotionBindingPlayer, MotionPresetPlayer, playable, preset, show, tween
How to invoke
Section titled “How to invoke”/t1k:cocos:playable:juiceMotion — Game-Feel Enhancement Pass
Section titled “Motion — Game-Feel Enhancement Pass”Scan the project’s visual code and make it feel better with @playablelabs/motion: improve existing animations and add motion to bare visual state-changes (enable/disable, show/hide). Apply effects by attaching a config-driven Player component to the node through Cocos MCP — never by hand-tweening or editing raw properties in the editor.
Prefer
@playablelabs/motionfor new game-feel work. Drive effects through the config-driven Player components rather than introducing hand-rolledJuiceKit/ScreenEffectsstatic tweens (db://assets/.../game-foundation/juice/*). When you touch an existing JuiceKit call-site, migrate it to a motion preset/binding; leave untouched call-sites alone.
Decision Tree
Section titled “Decision Tree”| Intent | Path |
|---|---|
| ”Add juice / suggest motion for this project” | Full Workflow — scan → classify → vibe-gate → apply |
| ”This element just appears/disappears, give it life” | Goal 2 (bare show/hide → preset) |
| “This tween is flat, make it punchier” | Goal 1 (improve existing) |
| “Apply preset X to node Y” | Skip to Apply via MCP |
Two goals
Section titled “Two goals”- Improve existing animation — a hand-rolled
tween()/ flat scale / linear fade gets a tuned@playablelabs/motionpreset or binding with better easing, overshoot, or stagger. - Add new motion to bare visual updates — a node that only flips
active/enabledor sets opacity (no animation) gets an Entrance/Exit (one-shot) or Attention/Tutorial (looping) preset.
Both decisions consider the game vibe. When the element’s intent is unclear, you MUST ask (see the gate in the Workflow).
Reference
Section titled “Reference”Working examples ship with the @playablelabs/motion package: API in assets/packages/@playablelabs/motion/README.md; demo Players (MotionDemo.ts, MotionPresetPlayer.ts, MotionBindingPlayer.ts) under the package’s demo directory. A project may also vendor the demo Players as example components elsewhere under assets/ — attach whichever copy the editor has compiled.
Barrel import (registers built-in adapters + bindToX helpers):
import { Motion, Ease, LoopType, LSequence, MotionPresets } from "db://assets/packages/@playablelabs/motion/index";Two Player components (config-object driven)
Section titled “Two Player components (config-object driven)”Both assemble their effect from inspector fields via buildConfig() — you set config, never raw transform. Attach the one that fits:
| Component | Use when | Config surface |
|---|---|---|
MotionPresetPlayer | A named, designed effect fits (entrance, attention loop, reward, celebration, label) | preset enum + tuning fields (duration, scale, offsetY, arcHeight, turns, colorA/B, …). Leave a numeric field at 0 to use the preset’s own default. playOnStart, replayInterval. |
MotionBindingPlayer | You need ONE raw property animated (Position/Scale/Angle/Color/Opacity/ContentSize) with custom from→to + ease + loop | target enum, from*/to*, ease, loopType, loops (-1 = forever), delay. Stack several on one node for concurrent multi-property motion. |
Preset catalog (MotionPresetPlayer.preset)
Section titled “Preset catalog (MotionPresetPlayer.preset)”One-shot (fire once; set replayInterval to re-fire):
| Category | Presets |
|---|---|
| Entrance | popIn fadeIn scaleIn slideIn dropIn flyIn zoomIn spinIn |
| Exit | popOut fadeOut scaleOut slideOut flyOut |
| Feedback | punch shake squashStretch punchRotation flash nudge |
| Reward | collect arcCollect coinPop countUp scorePop |
| Celebration | victoryBounce starPop confettiRise trophyRaise |
| Label (per-char) | popInChars fadeInChars slideInChars popOutChars fadeOutChars slideOutChars |
Looping (play forever; replayInterval ignored):
| Category | Presets |
|---|---|
| Attention | pulse breathe heartbeat float swing wobble tilt glow jiggle |
| Tutorial | tapHint dragHint ripple pointBob tapPulse |
| Label loop | waveChars |
Workflow
Section titled “Workflow”- Scan
CocosPlayableAdsTemplate/assets/scripts(delegate toExplorefor breadth) for visual-update sites on a label / sprite / node / UI view / node-with-UI-children:- existing animation:
tween(,Tween,JuiceKit.,.to(,setScale,UIOpacity, manual easing. - bare state-change:
node.active =,.enabled =,.opacity =,setSiblingIndex,removeFromParentwith no animation.
- existing animation:
- Classify each site → Goal 1 (improve) or Goal 2 (add). Note the element’s role (CTA, score, tutorial hand, reward, panel, end-card title…).
- Game-vibe gate (MANDATORY when intent is unclear). Use
AskUserQuestion— never guess silently. The core question per ambiguous element: should it be catchy all the time (a loop) or show/move quickly then settle (one-shot)? Map answers: “catchy/persistent/draw-the-eye” → Attention/Tutorial looping preset; “quick info/appears then done” → Entrance (+Exit) one-shot. Batch up to 4 elements per call. - Map vibe → component + preset using the catalog. CTA/idle-prompt → looping
pulse/breathe/tapHint. Reward/score →coinPop/scorePop/countUp. End-card title →popIn/victoryBounce. Bare panel show →scaleIn/fadeIn; hide →fadeOut/scaleOut. - Apply via MCP (config object only — see below).
- Verify — re-
get_infothe component, confirmpreset/config fields landed; report each node + chosen preset + vibe rationale.
Apply via MCP
Section titled “Apply via MCP”Cocos MCP server cocos-creator (port 3000). Everything goes through the config object — set component properties, never raw node transform in the editor.
manage_node { action: "find_by_name", name: "<NodeName>" } → nodeUuidmanage_component { action: "attach_script", nodeUuid, scriptPath: "db://assets/<motion-demo-path>/MotionPresetPlayer.ts" }manage_component { action: "set_property", nodeUuid, componentType: "MotionPresetPlayer", property: "preset", propertyType: "number", value: <enum index> }manage_component { action: "set_property", nodeUuid, componentType: "MotionPresetPlayer", property: "duration", propertyType: "number", value: 0.5 }manage_component { action: "set_property", nodeUuid, componentType: "MotionPresetPlayer", property: "playOnStart", propertyType: "boolean", value: true }scriptPathpoints at wherever the demo Players are compiled in the project (package demo dir or a vendoredassets/copy).presetis a numeric enum index = position in the catalog order above (Entrance first:popIn=0,fadeIn=1, …). Resolve viamanage_component get_infoif unsure.- Numeric tuning fields left at
0fall back to the preset’s built-in default — only set the ones you intend to override. - For
MotionBindingPlayer: settarget(enum: Position=0, PositionX=1, PositionY=2, PositionZ=3, Scale=4, Angle=5, EulerAngles=6, Color=7, Opacity=8, ContentSize=9),fromScalar/toScalarorfromVec/toVec,ease,loopType,loops.
Config-object thumb rule
Section titled “Config-object thumb rule”The Player components exist so animation is data, not code: every effect is the MotionPresetConfig / MotionBindingConfig assembled from inspector fields. Always drive motion by setting those config properties through MCP. Do NOT: hand-write Motion.create(...).bindToX() at call-sites for these nodes, tween raw worldPosition in editor scripts, or set node.scale/position/opacity directly to fake an animation.
Gotchas
Section titled “Gotchas”- Duplicate demo-Player copies collide. If the demo Players exist in more than one place (e.g. the package under
assets/packages/@playablelabs/motion/plus a project-vendored copy elsewhere underassets/), both register the same@ccclass('MotionPresetPlayer')name. A duplicate-ccclass error or “missing or invalid” component means only ONE copy compiled — attach the Player from the copy Cocos actually loaded (confirm withmanage_component get_available). Import the library API from the package barrel. - Demo Players are example components excluded from the package’s shipped
filesmanifest. They are the intended attach targets for this pass; if the project later needs its own, copy the pattern intoassets/scripts(keep the config-drivenbuildConfigshape). - Inspector
Enum()must be a LOCAL static literal — the demo Players build their dropdowns from local literals (PRESET_NAMES,BindingTargetEnum), NOTEnum(importedLibEnum). A barrel value can beundefinedduring bundle module-eval; the load-order throw de-registers every@ccclassin the package. Preserve this if you fork a Player. - Call
Motion.cancelAll()on scene teardown/reload — the dispatcher is director-level and survives scene loads; leaked handles to destroyed nodes are avoided by node bindings (they guardisValidper tick), butcancelAll()is the teardown safety valve. - Looping vs one-shot is a vibe decision, not a default — never silently pick a loop for a CTA or a one-shot for an idle prompt. If unsure, the gate (
AskUserQuestion) is mandatory. - Accessibility: screen shake amplifies motion-sickness (gate behind a toggle); hit-stop > 100ms reads as a freeze; ~50 particles is the 60fps mid-tier Android ceiling.
- No CLI build/test — verification is manual in the Cocos Editor; confirm the component + config landed via MCP
get_info.