| name | cocos-analytics-specialist |
| description | Use when working on analytics implementation, KPI tracking, A/B testing, and data-driven optimization. |
Cocos Analytics Specialist
Overview
This Codex skill adapts the upstream Cocos Creator Claude agent guidance for use in Codex.
Expert in game analytics, data tracking, and performance monitoring for Cocos Creator games. Use this skill for analytics implementation, KPI tracking, A/B testing, and data-driven optimization.
Expertise
- Game analytics architecture and implementation
- Player behavior tracking and analysis
- Monetization and retention metrics
- A/B testing frameworks and statistical analysis
- Performance monitoring and alerting
- Business intelligence and reporting
- Privacy-compliant data collection
- Real-time analytics and dashboards
Usage Examples
Example 1: Analytics Implementation
Context: Need comprehensive game analytics
User: "Implement analytics tracking for player progression and monetization"
Assistant: "I will use $cocos-analytics-specialist"
Commentary: Creates privacy-compliant tracking with actionable insights
Example 2: A/B Testing Framework
Context: Need to optimize onboarding flow
User: "Set up A/B testing for tutorial completion rates"
Assistant: "I will use $cocos-analytics-specialist"
Commentary: Implements statistical framework with proper test design
Example 3: Performance Monitoring
Context: Need to monitor game performance in production
User: "Create real-time monitoring for crashes and performance issues"
Assistant: "I will use $cocos-analytics-specialist"
Commentary: Sets up alerting and automated issue detection
Analytics Architecture
Core Analytics System
export interface AnalyticsEvent {
eventName: string;
parameters: { [key: string]: any };
timestamp: number;
sessionId: string;
userId?: string;
platform: string;
version: string;
}
export interface AnalyticsConfig {
enableTracking: boolean;
batchSize: number;
flushInterval: number;
maxRetries: number;
debugMode: boolean;
privacyCompliant: boolean;
providers: AnalyticsProvider[];
}
@ccclass('AnalyticsManager')
export class AnalyticsManager extends Component {
@property
config: AnalyticsConfig = {
enableTracking: true,
batchSize: 20,
flushInterval: 30000,
maxRetries: 3,
debugMode: false,
privacyCompliant: true,
providers: []
};
private _eventQueue: AnalyticsEvent[] = [];
private _sessionId: string = '';
private _userId: string = '';
private _sessionStartTime: number = 0;
private _isInitialized: boolean = false;
onLoad() {
this.initializeAnalytics();
this.startSession();
this.setupEventListeners();
this.schedule(this.flushEvents, this.config.flushInterval / 1000);
}
private initializeAnalytics() {
this._sessionId = this.generateSessionId();
this._sessionStartTime = Date.now();
for (const provider of this.config.providers) {
this.initializeProvider(provider);
}
this._isInitialized = true;
this.trackEvent('analytics_initialized', {
providers: this.config.providers.map(p => p.name),
privacy_compliant: this.config.privacyCompliant
});
}
trackEvent(eventName: string, parameters: any = {}) {
if (!this._isInitialized || !this.config.enableTracking) {
return;
}
if (this.config.privacyCompliant && !this.hasUserConsent()) {
return;
}
const event: AnalyticsEvent = {
eventName,
parameters: {
...parameters,
session_duration: Date.now() - this._sessionStartTime,
game_version: this.getGameVersion(),
device_info: this.getDeviceInfo()
},
timestamp: Date.now(),
sessionId: this._sessionId,
userId: this._userId,
platform: sys.platform,
version: this.getAnalyticsVersion()
};
this._eventQueue.push(event);
if (this.config.debugMode) {
console.log('Analytics Event:', event);
}
if (this.isCriticalEvent(eventName)) {
this.flushEvents();
} else if (this._eventQueue.length >= this.config.batchSize) {
this.flushEvents();
}
}
private async flushEvents() {
if (this._eventQueue.length === 0) return;
const events = [...this._eventQueue];
this._eventQueue = [];
for (const provider of this.config.providers) {
try {
await this.sendEventsToProvider(provider, events);
} catch (error) {
console.error(`Failed to send events to ${provider.name}:`, error);
this._eventQueue.unshift(...events);
}
}
}
trackPlayerAction(action: string, context: any = {}) {
this.trackEvent('player_action', {
action,
...context,
screen: this.getCurrentScreen(),
level: this.getCurrentLevel(),
playtime: this.getSessionDuration()
});
}
trackLevelEvent(eventType: 'start' | 'complete' | 'fail', levelId: string, data: any = {}) {
this.trackEvent(`level_${eventType}`, {
level_id: levelId,
level_name: data.levelName,
difficulty: data.difficulty,
attempt_number: data.attemptNumber,
duration: data.duration,
score: data.score,
...data
});
}
trackProgressionEvent(progressionType: string, progressionId: string, status: string, data: any = {}) {
this.trackEvent('progression', {
progression_type: progressionType,
progression_id: progressionId,
progression_status: status,
player_level: data.playerLevel,
experience_gained: data.experienceGained,
currency_earned: data.currencyEarned,
...data
});
}
trackPurchaseEvent(productId: string, currency: string, amount: number, success: boolean, data: any = {}) {
const eventName = success ? 'purchase_completed' : 'purchase_failed';
this.trackEvent(eventName, {
product_id: productId,
currency,
amount,
transaction_id: data.transactionId,
payment_method: data.paymentMethod,
first_purchase: data.firstPurchase,
player_level: this.getPlayerLevel(),
session_number: this.getSessionNumber(),
...data
});
}
trackAdEvent(adType: 'banner' | 'interstitial' | 'rewarded', adProvider: string, eventType: string, data: any = {}) {
this.trackEvent('ad_event', {
ad_type: adType,
ad_provider: adProvider,
ad_event_type: eventType,
ad_placement: data.placement,
ad_network: data.network,
revenue: data.revenue,
...data
});
}
trackPerformanceMetric(metricName: string, value: number, context: any = {}) {
this.trackEvent('performance_metric', {
metric_name: metricName,
metric_value: value,
device_tier: this.getDeviceTier(),
memory_usage: this.getMemoryUsage(),
fps: this.getCurrentFPS(),
...context
});
}
trackCrash(error: Error, context: any = {}) {
this.trackEvent('crash', {
error_message: error.message,
error_stack: error.stack,
error_type: error.name,
game_state: this.getGameState(),
memory_usage: this.getMemoryUsage(),
device_info: this.getDeviceInfo(),
...context
});
this.flushEvents();
}
}
A/B Testing Framework
export interface ABTest {
id: string;
name: string;
hypothesis: string;
variants: ABVariant[];
trafficAllocation: number;
startDate: Date;
endDate: Date;
targetMetric: string;
minimumSampleSize: number;
confidenceLevel: number;
status: 'draft' | 'running' | 'completed' | 'paused';
}
export interface ABVariant {
id: string;
name: string;
trafficSplit: number;
parameters: { [key: string]: any };
}
@ccclass('ABTestingManager')
export class ABTestingManager extends Component {
@property
tests: ABTest[] = [];
private _activeTests: Map<string, ABTest> = new Map();
private _userAssignments: Map<string, string> = new Map();
private _analyticsManager: AnalyticsManager = null;
onLoad() {
this._analyticsManager = this.getComponent(AnalyticsManager);
this.loadActiveTests();
this.assignUserToTests();
}
private loadActiveTests() {
const now = Date.now();
this.tests.forEach(test => {
if (test.status === 'running' &&
now >= test.startDate.getTime() &&
now <= test.endDate.getTime()) {
this._activeTests.set(test.id, test);
}
});
}
private assignUserToTests() {
const userId = this.getUserId();
this._activeTests.forEach(test => {
if (Math.random() > test.trafficAllocation) {
return;
}
const variantIndex = this.hashUserId(userId, test.id) % test.variants.length;
const variant = test.variants[variantIndex];
this._userAssignments.set(test.id, variant.id);
this._analyticsManager.trackEvent('ab_test_assignment', {
test_id: test.id,
test_name: test.name,
variant_id: variant.id,
variant_name: variant.name
});
});
}
getVariant(testId: string): ABVariant | null {
const test = this._activeTests.get(testId);
const variantId = this._userAssignments.get(testId);
if (!test || !variantId) return null;
return test.variants.find(v => v.id === variantId) || null;
}
getParameter(testId: string, parameterName: string, defaultValue: any = null): any {
const variant = this.getVariant(testId);
if (!variant || !variant.parameters.hasOwnProperty(parameterName)) {
return defaultValue;
}
return variant.parameters[parameterName];
}
trackConversion(testId: string, metricName: string, value: number = 1) {
const variant = this.getVariant(testId);
if (!variant) return;
this._analyticsManager.trackEvent('ab_test_conversion', {
test_id: testId,
variant_id: variant.id,
metric_name: metricName,
metric_value: value
});
}
private hashUserId(userId: string, testId: string): number {
const str = userId + testId;
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return Math.abs(hash);
}
}
Real-time Dashboard
export interface AnalyticsDashboard {
kpis: KPIMetric[];
charts: ChartConfig[];
alerts: AlertConfig[];
refreshInterval: number;
}
export interface KPIMetric {
id: string;
name: string;
query: string;
format: 'number' | 'percentage' | 'currency' | 'duration';
target?: number;
critical?: number;
}
@ccclass('AnalyticsDashboard')
export class AnalyticsDashboard extends Component {
@property
dashboardConfig: AnalyticsDashboard = null;
@property(Label)
dauLabel: Label = null;
@property(Label)
retentionLabel: Label = null;
@property(Label)
arppuLabel: Label = null;
private _realTimeData: Map<string, number> = new Map();
private _alertThresholds: Map<string, number> = new Map();
onLoad() {
this.setupDashboard();
this.schedule(this.refreshDashboard, this.dashboardConfig.refreshInterval);
}
private setupDashboard() {
this.dashboardConfig.kpis.forEach(kpi => {
this.setupKPIDisplay(kpi);
});
this.dashboardConfig.alerts.forEach(alert => {
this._alertThresholds.set(alert.metric, alert.threshold);
});
}
private async refreshDashboard() {
try {
const metrics = await this.fetchLatestMetrics();
this.updateKPIDisplays(metrics);
this.checkAlerts(metrics);
} catch (error) {
console.error('Failed to refresh dashboard:', error);
}
}
private async fetchLatestMetrics(): Promise<Map<string, number>> {
const metrics = new Map<string, number>();
metrics.set('dau', await this.calculateDAU());
metrics.set('retention_day1', await this.calculateRetention(1));
metrics.set('retention_day7', await this.calculateRetention(7));
metrics.set('arppu', await this.calculateARPPU());
metrics.set('session_length', await this.calculateAverageSessionLength());
metrics.set('crash_rate', await this.calculateCrashRate());
return metrics;
}
private updateKPIDisplays(metrics: Map<string, number>) {
const dau = metrics.get('dau') || 0;
if (this.dauLabel) {
this.dauLabel.string = this.formatMetric(dau, 'number');
}
const retention = metrics.get('retention_day1') || 0;
if (this.retentionLabel) {
this.retentionLabel.string = this.formatMetric(retention, 'percentage');
}
const arppu = metrics.get('arppu') || 0;
if (this.arppuLabel) {
this.arppuLabel.string = this.formatMetric(arppu, 'currency');
}
}
private checkAlerts(metrics: Map<string, number>) {
metrics.forEach((value, metric) => {
const threshold = this._alertThresholds.get(metric);
if (threshold && value < threshold) {
this.triggerAlert(metric, value, threshold);
}
});
}
private triggerAlert(metric: string, value: number, threshold: number) {
console.warn(`Alert: ${metric} is ${value}, below threshold of ${threshold}`);
this.sendAlert({
metric,
current_value: value,
threshold,
severity: 'warning',
timestamp: Date.now()
});
}
private formatMetric(value: number, format: string): string {
switch (format) {
case 'percentage':
return `${(value * 100).toFixed(1)}%`;
case 'currency':
return `$${value.toFixed(2)}`;
case 'duration':
return `${Math.floor(value / 60)}:${(value % 60).toFixed(0).padStart(2, '0')}`;
default:
return value.toLocaleString();
}
}
}
Privacy-Compliant Analytics
GDPR/CCPA Compliance
@ccclass('PrivacyCompliantAnalytics')
export class PrivacyCompliantAnalytics extends Component {
@property
enableGDPRCompliance: boolean = true;
@property
enableCCPACompliance: boolean = true;
@property
dataRetentionDays: number = 365;
private _userConsent: ConsentStatus = null;
private _anonymizedTracking: boolean = false;
onLoad() {
this.checkPrivacyCompliance();
this.setupConsentManagement();
}
private checkPrivacyCompliance() {
const userLocation = this.getUserLocation();
if (this.enableGDPRCompliance && this.isEUUser(userLocation)) {
this.enableGDPRMode();
}
if (this.enableCCPACompliance && this.isCaliforniaUser(userLocation)) {
this.enableCCPAMode();
}
}
private enableGDPRMode() {
this.requestUserConsent([
'analytics',
'personalization',
'marketing'
]);
}
async requestUserConsent(purposes: string[]): Promise<ConsentStatus> {
const consent = await this.showConsentDialog(purposes);
this._userConsent = consent;
this.configureAnalyticsBasedOnConsent(consent);
return consent;
}
private configureAnalyticsBasedOnConsent(consent: ConsentStatus) {
if (!consent.analytics) {
this.disableAnalytics();
} else if (!consent.personalization) {
this._anonymizedTracking = true;
}
this.saveConsentRecord(consent);
}
trackEventWithPrivacy(eventName: string, parameters: any = {}) {
if (!this.hasAnalyticsConsent()) {
return;
}
if (this._anonymizedTracking) {
parameters = this.anonymizeParameters(parameters);
}
parameters._privacy_compliant = true;
parameters._anonymized = this._anonymizedTracking;
parameters._consent_version = this._userConsent?.version;
this.trackEvent(eventName, parameters);
}
private anonymizeParameters(parameters: any): any {
const anonymized = { ...parameters };
delete anonymized.user_id;
delete anonymized.email;
delete anonymized.device_id;
if (anonymized.ip_address) {
anonymized.ip_address = this.hashIP(anonymized.ip_address);
}
return anonymized;
}
async handleDataDeletionRequest(userId: string): Promise<boolean> {
try {
await this.deleteUserAnalyticsData(userId);
this.clearUserLocalData(userId);
this.logDataDeletion(userId);
return true;
} catch (error) {
console.error('Failed to delete user data:', error);
return false;
}
}
}
Handoff Guidance
To cocos-backend-integrator
Trigger: Backend analytics integration needed
Handoff: "Analytics events defined. Backend integration needed for: [data collection/API endpoints]"
To cocos-ux-designer
Trigger: Analytics-driven UX optimization needed
Handoff: "Analytics data collected. UX optimization needed based on: [user behavior patterns]"
To cocos-security-expert
Trigger: Analytics security needed
Handoff: "Analytics system implemented. Security measures needed for: [data protection/privacy]"
To cocos-performance-optimizer
Trigger: Analytics performance optimization needed
Handoff: "Analytics tracking active. Performance optimization needed for: [tracking overhead/data processing]"
Best Practices
- Privacy First: Always implement privacy-compliant tracking
- Actionable Metrics: Focus on metrics that drive decisions
- Real-time Monitoring: Set up alerts for critical issues
- A/B Testing: Use statistical rigor in test design
- Data Quality: Validate and clean analytics data
- Performance Impact: Minimize analytics overhead
- Documentation: Document all tracked events and metrics
- Compliance: Stay current with privacy regulations
Codex Operating Notes
- Prefer inspecting the actual Cocos Creator project before proposing changes.
- Keep edits scoped to the requested game system, platform, or workflow.
- After implementation, run the available TypeScript, lint, build, export, or size checks for the project.
- When the task naturally crosses domains, recommend the next relevant Cocos skill instead of expanding scope silently.