t1k:cocos:playable:gameflow
| Field | Value |
|---|---|
| Module | playable |
| Version | 0.15.0 |
| Effort | high |
| Tools | — |
Keywords: ads, cocos, CTA, end card, game flow, gameflow, loading, playable, UI views
How to invoke
Section titled “How to invoke”/t1k:cocos:playable:gameflowCocos Playable Game Flow
Section titled “Cocos Playable Game Flow”This skill handles game states, views, loading screens, end cards, and CTA integration. Does NOT handle parameter definitions (use t1k-cocos-playable-parameter) or SDK adapters (use t1k-cocos-playable-sdk-core).
Game State Machine
Section titled “Game State Machine”LOADING -> FTUE -> GAMEPLAY -> WIN / LOSEexport enum GameState { LOADING, FTUE, GAMEPLAY, WIN, LOSE }Ads that chain several rounds add one interstitial state between rounds:
LOADING -> FTUE -> GAMEPLAY -> [CHAPTER_RESULT -> GAMEPLAY]* -> WIN / LOSERoute every round outcome — win, lose, and timeout alike — through a single onLevelFinished(won) handler that decides “interstitial vs end card”. A secondary path that calls onWin()/onLose() directly (a countdown timer is the usual culprit) skips the chain entirely and ends the ad on the first loss.
Core Components
Section titled “Core Components”- GameView (singleton) — State machine.
onWin(),onLose(),onTutorialComplete() - LoadingView — Progress bar, waits for
AllAsyncParametersReadySignal. Bound viaLoadingScreenParameter(replaces old scatteredLoadingBackgroundColor+LoadingIcon+LoadingGameNameparams). - EndCardView (abstract) — Base for end cards, plays audio, triggers CTA. Exposes
backgroundSprite,titleLabel,subtitleLabelforParameterBinder.bindEndCard(). - WinView / LoseView — Extend EndCardView, provide audio name. Bound via
EndCardParameter(replaces oldEndCardWinCTA/EndCardLoseCTAparams). - CTAService — Routes CTA click to correct store per SDK
- PlayableHelper — First touch -> BGM, optional redirect-after-N-clicks
Composite Parameter Binding for Views
Section titled “Composite Parameter Binding for Views”EndCardParameter and LoadingScreenParameter are composite types that bundle all view fields into one dashboard group. Use ParameterBinder to apply them:
// In ParameterController.SetUpOnUpdate():PlayableConfig.EndCardWin.onUpdate = (config) => { const p = ParameterBinder.bindEndCard(this.winView, config); if (p) this._spriteUpdatePromises.push(p);};
PlayableConfig.LoadingScreen.onUpdate = (config) => { const p = ParameterBinder.bindLoadingScreen(this.loadingView, config); if (p) this._spriteUpdatePromises.push(p);};See t1k-cocos-playable-parameter skill for composite type definitions and migration guide.
Workflow: Adding a New View/State
Section titled “Workflow: Adding a New View/State”- Create component extending
Componentinassets/scripts/UI/ - Add @property(Node) reference in
GameView.ts - Hide in
GameView.hideAllViews() - Add transition method (e.g.,
onMyState()) settingcurrentState+ activating view - Wire parameters if needed (use
t1k-cocos-playable-parameterskill) - Assign in Cocos Editor Inspector
Signal Flow
Section titled “Signal Flow”ParametersReadySignal -- sync params readyAllAsyncParametersReadySignal -- async params loaded (LoadingView waits for this)FirstInteractionSignal -- user first touch (InputService)TapSignal / SwipeSignal -- user input eventsImport Convention
Section titled “Import Convention”Always use db:// protocol for cross-submodule imports:
import { SignalBus } from "db://assets/PLAGameFoundation/signalBus/SignalBus";import { AudioService } from "db://assets/PLAGameFoundation/gameControl/utilities/AudioSystem/AudioService";import { SdkType } from "db://assets/PlayableParamterTool/GameConfig";import { GameView } from "db://assets/scripts/UI/GameView";Audio Integration
Section titled “Audio Integration”AudioService.instance.playMusic(Constant.AUDIO_NAME.BGM);AudioService.instance.playSFX(Constant.AUDIO_NAME.WIN);Audio files in resources/audio/. Register names in constant.ts -> AUDIO_NAME.
Store Links & CTA
Section titled “Store Links & CTA”// constant.ts: STORE_LINK = { ANDROID_LINK, IOS_LINK }// CTAService routes per SDK: THE_ONE -> gameEndHandler, VOODOO -> ParameterManager.redirect()Code Examples
Section titled “Code Examples”See references/gameflow-code-examples.md for complete implementation patterns.
Gotchas
Section titled “Gotchas”- NEVER extend
EndCardViewfor an interstitial (between-rounds) view.EndCardView.onEnable()callssetupCTA()unconditionally, and whenfullScreenCTAis true it bindsTOUCH_ENDon the whole node →CTAService.handleCTAClick(). A card shown mid-gameplay would redirect the player to the store on any tap. Write a plainComponent(~80 lines) instead; carving a “disable CTA” hole into the base class breaks the two real end cards that depend on it. Also give the interstitial acc.BlockInputEvents: Cocos dispatches a touch to the topmost node that registers a listener, so a bare dimmingSpriteoverlay does NOT stop taps from reaching the live board underneath. - A restartable level breaks everything written for a one-shot level. The moment a round can rebuild in place (chapter chains, retry-on-lose), audit for: state armed by a once-per-playthrough signal (a level-selected signal fires once, but per-round freezes fire every round);
scheduleOncecallbacks that outlive teardown and land on the next round; pooled visual state left dirty by the previous round; and values captured lazily on first use, which then inherit round N−1 instead of the editor default. Capture such defaults exactly once inonLoad(), and callunscheduleAllCallbacks()in teardown. - GameView lifecycle owns the FSM, not vice versa — destroying GameView while FSM holds a reference dangles the state’s
this. - Scene transitions during state.enter() are a footgun — the next state runs onLoad against an unmounted parent.
- Inspector-wired callback params must guard with
typeof param === 'function', never bare?.()or a truthy check. Any public method you wire from the Cocos Inspector via anEventHandler(ButtonclickEvents, viewonWin/onLose/CTA handlers, custom[EventHandler]arrays) is invoked by Cocos with the EventHandler’s CustomEventData string as its FIRST argument — NOTundefined. So an optional-callback param guarded withonComplete?.()(guards only null/undefined) orif (onComplete)(a non-empty string is truthy) throwsonComplete is not a functionwhen fired from the Inspector. Guard withif (typeof onComplete === 'function') onComplete();so the same handler works called from code OR from an Inspector EventHandler (string arg ignored, no crash). - Audio clips resolve ONLY from the in-scene
AudioContainer.audioList.AudioService.playMusic(name)/playSfx(name)look the clip up viaAudioContainer.instance.getAudioClip(name), andAudioContainer.buildAudioMap()keys clips byclip.name(the asset basename, no extension) built solely from the inspector-assignedaudioList: AudioClip[]. There is noresources/audiofallback — a name not present inaudioListreturns null and only logs a warning (silent no-sound). So a new BGM/SFX must be (1) added to the scene’sAudioContainercomponentaudioList, and (2) referenced by a name that exactly matches the asset basename (case- and space-sensitive).AUDIO_NAMEcomments like “place X.mp3 in resources/audio” are misleading — the resources path is not consulted.