> CCFish, a Cocos Creator 2.4.15 fishing shooter deployed as a Telegram Mini App, uses a fully automated GitHub Actions pipeline to rebuild playable ad variants from game balance changes and feed platform-level CPI, install, and ROAS data back into game design decisions — creating a closed loop between user acquisition and product development. ## The Problem — Manual Ad Variant Creation Doesn't Scale Every mobile gaming UA team knows the drill: a new event launches, a new fish species drops, or difficulty is rebalanced, and someone needs to manually create updated playable ads for Meta, TikTok, and Google. Each platform requires different specs — MRAID-compliant HTML for Google, specific aspect ratios for Meta, TikTok's unique orientation requirements. The result is a bottleneck that throttles iteration speed. For a fishing shooter like CCFish, where the core loop revolves around weapon power, fish spawn rates, and reward curves, even a small balance tweak can dramatically change how a playable ad performs. A 10% reduction in the catch rate of rare fish might make the ad feel "too hard" (low install conversion), while a 5% increase in base ammo might make it "too easy" (low retention quality). With manual ad creation, it could take a team of designers and engineers days — or weeks — to produce a handful of variants, test them, and iterate. The fundamental problem is one of feedback latency. By the time a manual ad variant is live and generating install data, the game balance it was based on may already have moved on. The advertiser and the product team operate in separate cycles, each on their own clock. ## The Solution — Automated Pipeline Overview CCFish solved this by building an entirely automated ad generation pipeline. The core insight: since CCFish is built in Cocos Creator, the same engine that renders the game can also render playable ad variants. If the build process is automated, then every game balance change can automatically produce a corresponding set of ad variants. The pipeline works in five stages: 1. **Trigger** — A game balance change (e.g., fish health values, weapon damage, spawn intervals) is committed to the repository. 2. **Build** — GitHub Actions triggers a Cocos Creator web-mobile build. 3. **Generate** — The build script produces multiple MRAID-compliant playable HTML variants, each configured with different game parameters (easy mode, hard mode, specific weapon focus, etc.). 4. **Deploy** — All variants are deployed to Cloudflare Pages, giving each a unique URL. 5. **Ingest** — Ad networks (Meta, TikTok, Google) ingest the URLs as playable ad creatives. Once ads are live, each platform's API streams install, CPI, and ROAS data back into a central analytics store. That data is then queried by the game design team to inform the next round of balance changes — closing the loop. ## Architecture — GitHub Actions, Cocos Creator, MRAID, Cloudflare ### GitHub Actions CI/CD The pipeline is orchestrated by a GitHub Actions workflow defined in `.github/workflows/playable-ads.yml`. It triggers on pushes to the `main` branch that modify balance configuration files (typically JSON or Lua scripts under `assets/Resources/config/`). ```yaml name: Generate Playable Ads on: push: branches: - main paths: - 'assets/Resources/config/balance/**' - 'assets/Resources/config/fish/**' - 'assets/Resources/config/weapons/**' jobs: build-and-deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Cocos uses: ./.github/actions/setup-cocos with: version: '2.4.15' - name: Build Game run: | cocos compile -p web-mobile - name: Generate Ad Variants run: | python scripts/generate_ads.py \ --build-dir build/web-mobile \ --output-dir build/ads \ --config variants.yaml - name: Deploy to Cloudflare Pages uses: cloudflare/wrangler-action@v3 with: apiToken: ${{ secrets.CF_API_TOKEN }} command: pages deploy build/ads --project-name=ccfish-ads ``` ### Cocos Creator Build Pipeline The `cocos compile -p web-mobile` command produces a standard Cocos HTML5 build. The key architectural decision is that CCFish's game logic is data-driven: fish behavior, weapon stats, and difficulty curves are loaded from external configuration files at runtime. This means a playable ad variant is simply a Cocos web-mobile build with a different config file injected. ### MRAID Compliance Google, Meta, and certain other networks require MRAID (Mobile Rich Media Ad Interface Definitions) compliance. The ad generation script wraps each Cocos build in an MRAID container: ```html <!DOCTYPE html> <html> <head> <meta name="ad.orientation" content="portrait"> <script src="mraid.js"></script> <script> // Wait for MRAID ready event before initializing game if (typeof mraid !== 'undefined') { mraid.addEventListener('ready', function() { initGame(); }); } else { // Fallback for non-MRAID environments window.addEventListener('load', initGame); } function initGame() { // Bootstrap Cocos game with variant-specific config var variant = window.location.pathname.split('/')[2] || 'easy'; window.COCOS_CONFIG_OVERRIDE = variant; var script = document.createElement('script'); script.src = 'main.js'; document.body.appendChild(script); } </script> </head> <body style="margin:0;overflow:hidden;"> <div id="Cocos2dGameContainer"></div> </body> </html> ``` ### Cloudflare Pages Deployment Cloudflare Pages serves as the CDN and ad hosting layer. Each variant gets its own sub-path: | Variant | URL Path | Description | |---------|----------|-------------| | Easy | `/ads/easy/` | Low fish health, abundant ammo | | Hard | `/ads/hard/` | High fish health, scarce ammo | | Boss Rush | `/ads/boss/` | Only boss fish spawn | | Tutorial | `/ads/tutorial/` | Guided first 30 seconds | | Weapon Focus | `/ads/shotgun/` | Shotgun-only variant | Cloudflare's global edge network ensures low-latency ad loading across all regions, which matters for playable ads — a slow load means a dropped impression before the user even sees the game. ## Implementation — Step-by-Step from Build to Ad Network ### Step 1: Variant Definition The variant configuration lives in `scripts/variants.yaml`: ```yaml variants: - id: easy label: "Easy Catch" overrides: fish.health_multiplier: 0.5 player.ammo_per_spawn: 20 fish.spawn_interval: 1.5 ui.show_tutorial: true - id: hard label: "Pro Fisher" overrides: fish.health_multiplier: 2.0 player.ammo_per_spawn: 5 fish.spawn_interval: 3.0 fish.rare_spawn_chance: 0.01 - id: boss label: "Boss Rush" overrides: fish.types: ["boss_bass", "boss_tuna"] player.ammo_per_spawn: 30 fish.spawn_interval: 0.8 ``` ### Step 2: Ad Generation Script The `generate_ads.py` script does the heavy lifting: 1. Clones the base web-mobile build. 2. For each variant, writes a variant-specific `config.json` into the build directory. 3. Generates the MRAID wrapper HTML. 4. Creates a platform-specific manifest (e.g., `meta-manifest.json`, `google-manifest.json`) that ad networks use for automatic ingestion. 5. Outputs everything to `build/ads/{variant_id}/`. ### Step 3: Ad Network Registration Each platform has a different ingestion mechanism: - **Meta (Facebook Ads)**: Uses the Facebook Marketing API to register playable ad assets. A GitHub Actions step calls `POST /act_{ad_account_id}/adcreatives` with the Cloudflare URL. - **TikTok (Pangle)**: TikTok supports third-party playable ad URLs. The script generates a ZIP of each variant and uploads it via the TikTok Ads API. - **Google (AdMob / Campaign Manager 360)**: Google requires a MRAID-compliant HTML file served over HTTPS. The Cloudflare Pages URL satisfies this requirement directly. ### Step 4: Data Collection Each platform's API streams performance data back: ```python # Example: Polling Meta Ads API for ad performance def poll_ad_performance(ad_creative_id): return facebook_api.get( f"/{ad_creative_id}/insights", params={ "fields": "impressions,clicks,installs,cpi,spend", "date_preset": "last_7_days" } ) ``` Data lands in a PostgreSQL database with a schema like: | Column | Type | Description | |--------|------|-------------| | variant_id | TEXT | Which variant (easy, hard, boss, etc.) | | platform | TEXT | Meta, TikTok, or Google | | impressions | INTEGER | Total impressions | | clicks | INTEGER | Total clicks | | installs | INTEGER | Post-click installs | | cpi | FLOAT | Cost per install (USD) | | roas_7d | FLOAT | 7-day return on ad spend | | balance_version | TEXT | Git SHA of the balance config used | | created_at | TIMESTAMP | Data pull timestamp | ### Step 5: The Feedback Loop A dashboard (built with Streamlit) surfaces the data to the game design team: ```sql -- Query: Which variant has the best CPI on each platform? SELECT variant_id, platform, AVG(cpi) as avg_cpi, SUM(installs) as total_installs FROM ad_performance WHERE created_at >= NOW() - INTERVAL '7 days' GROUP BY variant_id, platform ORDER BY avg_cpi ASC; ``` If the "Easy Catch" variant on TikTok has a CPI of $0.32 while "Pro Fisher" on Meta has $1.10, the team knows which segment to target and which difficulty curve resonates with each audience. ## Results — Time Saved, CPI Improvements, Iteration Speed Since implementing the automated pipeline, CCFish has seen measurable results: | Metric | Before (Manual) | After (Automated) | Improvement | |--------|----------------|-------------------|-------------| | Time to create ad variants | 3-5 days | 12 minutes | ~500x faster | | Number of active variants | 2-3 | 8-12 | 4x more variants | | CPI for top-performing variant | $0.85 | $0.

Key Takeaways

- **Automate the full loop** — From game build to ad network to performance feedback, every step should be scripted

- **Playable ads outperform static** — Interactive ads drive 3-4x higher conversion rates; automation makes them viable at scale

- **Cloudflare Pages + MRAID** — URL-hosted playable ads are the fastest path from build to ad network delivery

- **Feed data back to game design** — Ad performance is a signal for game balance; close the loop with automated dashboards