CCFish launched its playable ad platform in under 8 weeks by combining Cocos Creator for lightweight ad rendering, Cloudflare Workers for serverless backend orchestration, and TensorFlow Lite for on-device ad optimization — resulting in a 40% lift in user engagement and a 60% reduction in infrastructure costs versus traditional HTML5 ad solutions.
The Problem
Playable ads deliver click-through rates 3-5x higher than video or banner ads, but building them at scale is difficult. Existing solutions fall into two camps: heavy SDK-based engines (Unity, Unreal) that produce bundles exceeding 8 MB — far above the 3 MB ceiling ad networks enforce — and simplified drag-and-drop tools that offer speed but zero flexibility.
CCFish's clients wanted rich, interactive playable ads that felt like real games, but they couldn't afford Unity's infrastructure bill. They needed ads loading in under 2 seconds on mid-range Android devices across Southeast Asia, where bandwidth and storage are at a premium. The core question: can you build playable ads that feel native but cost a fraction of the infrastructure?
The Solution
CCFish built a lightweight playable ad engine using Cocos Creator 2.4.x as the rendering layer and Cloudflare Workers as the serverless backbone. The key insight: separate ad logic from ad assets, keeping bundles under 2.5 MB while supporting complex game mechanics.
| Component | Technology | Role |
|---|---|---|
| Rendering Engine | Cocos Creator 2.4.x | 2D sprite rendering, animations, particle effects |
| Backend API | Cloudflare Workers | Ad delivery, A/B testing, analytics ingestion |
| Data Layer | Cloudflare D1 | Session storage, CTR tracking, user segmentation |
| AI Optimization | TensorFlow Lite (on-device) | Real-time ad personalization |
| CI/CD | GitHub Actions + Wrangler | Automated builds, deployments to Cloudflare's edge |
Cocos Creator was a strategic choice. It holds over 50% market share in China, making it natural for CCFish's target audience. Its WebGL renderer produces bundles 60-70% smaller than Unity WebGL builds, and its TypeScript-first API enables type sharing between the ad engine and the backend.
Architecture
CCFish's platform follows a three-tier edge architecture on Cloudflare's global network:
```
+---------------------+
| User Device |
| +---------------+ |
| | Cocos Creator | | <- Ad rendering (client-side)
| | WebGL Runtime | |
| +-------+-------+ |
+----------+----------+
|
+------+------+
| Cloudflare | <- Edge Workers (100+ locations)
| Workers |
+------+------+
|
+------+------+
| Cloudflare | <- Serverless SQL
| D1 DB |
+-------------+
```
**Ad Delivery Pipeline:** A user visits a publisher's app, the ad SDK requests a playable from the Worker endpoint, the Worker queries D1 for the optimal ad variant based on geo and device, a minimal HTML shell loads the Cocos Creator WebGL build, the ad renders in under 1.5 seconds, interactions stream back via fetch beacons, and TensorFlow Lite on the client scores variants for the next request.
Implementation
Cocos Creator Ad Component
Every playable ad extends a base `PlayableAd` component:
```typescript
// BasePlayableAd.ts
import { Component, _decorator } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('BasePlayableAd')
export class BasePlayableAd extends Component {
@property({ type: Number })
private maxDuration: number = 15;
onLoad() {
navigator.sendBeacon('/api/track',
JSON.stringify({ event: 'impression', ts: Date.now() }));
}
trackInteraction(type: string, data?: Record<string, unknown>) {
navigator.sendBeacon('/api/track',
JSON.stringify({ event: 'interaction', type, ts: Date.now(), data }));
}
completeAd() {
this.trackInteraction('complete');
(window as any).MRAID?.close();
}
update(dt: number) {
if (Date.now() - this.startTime > this.maxDuration * 1000)
this.completeAd();
}
}
```
Cloudflare Worker Endpoint
A single fetch handler routes to the right D1 query:
```typescript
// src/index.ts
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
const path = url.pathname;
if (path === '/api/ad') {
const geo = request.cf?.country || 'US';
const result = await env.DB.prepare(
`SELECT ad_id, bundle_url FROM ad_variants
WHERE region = ? ORDER BY ctr DESC LIMIT 1`
).bind(geo).first();
if (!result) return Response.redirect('/ads/default.html', 302);
const ad = await env.AD_BUCKET.get(result.bundle_url);
return new Response(ad.body, {
headers: { 'Content-Type': 'text/html', 'Cache-Control': 'max-age=60' }
});
}
if (path === '/api/track' && request.method === 'POST') {
const data = await request.json();
await env.DB.prepare(
`INSERT INTO ad_events (ad_id, variant, event, ts, metadata)
VALUES (?, ?, ?, ?, ?)`
).bind(data.ad_id, data.variant, data.event, data.ts,
JSON.stringify(data)).run();
return new Response('ok', { status: 200 });
}
return new Response('Not Found', { status: 404 });
}
};
```
D1 Schema
```sql
CREATE TABLE ad_variants (
ad_id TEXT PRIMARY KEY, variant TEXT NOT NULL,
bundle_url TEXT NOT NULL, region TEXT NOT NULL,
device_class TEXT NOT NULL, ctr REAL DEFAULT 0.0,
impressions INTEGER DEFAULT 0,
created_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE ad_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ad_id TEXT NOT NULL, variant TEXT NOT NULL,
event TEXT NOT NULL, ts INTEGER NOT NULL,
metadata TEXT,
FOREIGN KEY (ad_id) REFERENCES ad_variants(ad_id)
);
CREATE INDEX idx_ad_variants_region ON ad_variants(region, device_class);
```
CI/CD with Wrangler
```yaml
.github/workflows/deploy.yml
name: Deploy Playable Ads
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
- run: npm ci
- run: npx wrangler deploy
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
- run: npx wrangler d1 migrations apply CCFISH_DB
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
```
Results
After three months in production (Q2 2026):
| Metric | Before (Legacy) | After (CCFish) | Improvement |
|---|---|---|---|
| Average load time | 3.8s | 1.4s | 63% faster |
| Bundle size | 6.2 MB | 2.1 MB | 66% smaller |
| CTR | 2.1% | 4.5% | +114% |
| Completion rate | 48% | 72% | +50% |
| Infrastructure/month | $12,400 | $4,900 | -60% |
| Ad variants in rotation | 8 | 47 | 5.8x more |
The cost savings are driven by Cloudflare Workers' free tier covering the first 100,000 daily requests, with D1 at $0.75/GB/month and R2 egress at $0.011/GB. At 3.5 million daily ad impressions, the monthly bill came to $4,900 versus $12,400 on AWS EC2 + CloudFront.
Developer velocity also improved: the Cocos Creator visual scene builder let the creative team prototype ads in hours instead of days, and shared TypeScript types caught backend errors at compile time. The team shipped 7 major campaigns in 90 days versus 2 with the legacy pipeline.
Key Takeaways
1. **Cocos Creator is the right engine for playable ads.** Its WebGL renderer produces bundles under 2.5 MB while supporting full 2D game mechanics — animations, particle effects, physics, and user input.
2. **Separate ad logic from assets.** Serving a minimal HTML shell from Workers and loading game assets from R2 achieves sub-2-second loads. Each ad variant is a self-contained build, making A/B testing straightforward.
3. **Serverless is a competitive advantage.** Workers + D1 eliminated server management while providing global edge distribution. The 60% cost reduction is a direct result of paying only for what you use.
4. **On-device AI works for optimization.** TensorFlow Lite scores ad variants on the client, feeding aggregate signals back through D1. No user data leaves the device for inference, keeping the system privacy-compliant.
5. **Design backwards from ad network constraints.** The 3 MB bundle limit, 2-second load target, and Android 7+ requirement drove every decision — from engine choice to edge deployment — producing a faster, cheaper system than any unbounded approach.
CCFish's playable ad platform is now live and accepting campaigns. The full SDK and documentation are available to qualified partners.