Skip to content

网页游戏的分析与遥测

分析能帮你搞清楚玩家在干什么,在哪里卡壳,又是什么让他们持续在玩。本教程讲清楚要跟踪什么、怎么跟踪。

1) 要跟踪什么

参与度指标:

  • 会话开始数和时长
  • 关卡开始数和完成数
  • 功能使用情况
  • 留存(回访)

性能指标:

  • 加载时间
  • 帧率
  • 内存占用
  • 错误和崩溃

转化指标:

  • 教程完成率
  • 首次购买
  • 社交分享

2) 简单的事件跟踪

js
class Analytics {
  constructor(endpoint) {
    this.endpoint = endpoint
    this.sessionId = crypto.randomUUID()
    this.queue = []
    this.flushInterval = 30000 // 30 秒
    
    setInterval(() => this.flush(), this.flushInterval)
    window.addEventListener('beforeunload', () => this.flush())
  }
  
  track(event, data = {}) {
    this.queue.push({
      event,
      data,
      sessionId: this.sessionId,
      timestamp: Date.now(),
      url: location.href,
    })
    
    // 重要事件立即上报
    if (event === 'error' || event === 'purchase') {
      this.flush()
    }
  }
  
  async flush() {
    if (this.queue.length === 0) return
    
    const events = [...this.queue]
    this.queue = []
    
    try {
      await fetch(this.endpoint, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ events }),
        keepalive: true, // 对 beforeunload 很重要
      })
    } catch {
      // 把事件放回队列
      this.queue.unshift(...events)
    }
  }
}

const analytics = new Analytics('/api/analytics')

3) 会话跟踪

js
// 跟踪会话开始
analytics.track('session_start', {
  referrer: document.referrer,
  screen: `${screen.width}x${screen.height}`,
  devicePixelRatio: window.devicePixelRatio,
  userAgent: navigator.userAgent,
})

// 跟踪会话结束
let sessionStart = Date.now()

window.addEventListener('beforeunload', () => {
  analytics.track('session_end', {
    duration: Date.now() - sessionStart,
  })
})

// 跟踪可见性变化
document.addEventListener('visibilitychange', () => {
  if (document.hidden) {
    analytics.track('tab_hidden')
  } else {
    analytics.track('tab_visible')
  }
})

4) 游戏特定事件

js
// 关卡跟踪
function onLevelStart(levelId) {
  analytics.track('level_start', { levelId })
}

function onLevelComplete(levelId, score, time) {
  analytics.track('level_complete', {
    levelId,
    score,
    timeSeconds: time,
  })
}

function onLevelFail(levelId, reason) {
  analytics.track('level_fail', {
    levelId,
    reason, // 'death', 'timeout', 'quit'
  })
}

// 成就跟踪
function onAchievementUnlocked(achievementId) {
  analytics.track('achievement', { achievementId })
}

// 教程跟踪
function onTutorialStep(step, skipped = false) {
  analytics.track('tutorial', { step, skipped })
}

5) 性能监控

js
class PerformanceMonitor {
  constructor(analytics) {
    this.analytics = analytics
    this.frameTimes = []
    this.lastFrame = performance.now()
  }
  
  recordFrame() {
    const now = performance.now()
    this.frameTimes.push(now - this.lastFrame)
    this.lastFrame = now
    
    // 保留最近 60 帧
    if (this.frameTimes.length > 60) {
      this.frameTimes.shift()
    }
  }
  
  getAverageFPS() {
    if (this.frameTimes.length === 0) return 0
    const avgFrameTime = this.frameTimes.reduce((a, b) => a + b) / this.frameTimes.length
    return 1000 / avgFrameTime
  }
  
  reportPerformance() {
    const fps = this.getAverageFPS()
    const memory = performance.memory?.usedJSHeapSize
    
    this.analytics.track('performance', {
      avgFPS: Math.round(fps),
      memoryMB: memory ? Math.round(memory / 1024 / 1024) : null,
    })
  }
}

// 每分钟上报一次
const perfMonitor = new PerformanceMonitor(analytics)
setInterval(() => perfMonitor.reportPerformance(), 60000)

6) 错误跟踪

js
window.addEventListener('error', (event) => {
  analytics.track('error', {
    message: event.message,
    filename: event.filename,
    lineno: event.lineno,
    colno: event.colno,
    stack: event.error?.stack,
  })
})

window.addEventListener('unhandledrejection', (event) => {
  analytics.track('error', {
    message: event.reason?.message || String(event.reason),
    type: 'unhandledrejection',
    stack: event.reason?.stack,
  })
})

// 自定义错误跟踪
function trackGameError(context, error) {
  analytics.track('game_error', {
    context,
    message: error.message,
    stack: error.stack,
  })
}

7) 加载时间跟踪

js
// 跟踪初次加载
window.addEventListener('load', () => {
  const timing = performance.timing
  const loadTime = timing.loadEventEnd - timing.navigationStart
  const domReady = timing.domContentLoadedEventEnd - timing.navigationStart
  
  analytics.track('page_load', {
    totalMs: loadTime,
    domReadyMs: domReady,
  })
})

// 跟踪游戏特定的加载阶段
async function loadGame() {
  const start = performance.now()
  
  await loadCriticalAssets()
  const criticalTime = performance.now() - start
  
  analytics.track('load_critical', { ms: Math.round(criticalTime) })
  
  await loadGameAssets()
  const totalTime = performance.now() - start
  
  analytics.track('load_complete', { ms: Math.round(totalTime) })
}

8) 漏斗跟踪

跟踪玩家走完关键流程的过程:

js
class FunnelTracker {
  constructor(analytics, funnelName) {
    this.analytics = analytics
    this.funnelName = funnelName
    this.startTime = Date.now()
  }
  
  step(stepName) {
    this.analytics.track('funnel_step', {
      funnel: this.funnelName,
      step: stepName,
      elapsedMs: Date.now() - this.startTime,
    })
  }
  
  complete() {
    this.analytics.track('funnel_complete', {
      funnel: this.funnelName,
      totalMs: Date.now() - this.startTime,
    })
  }
  
  abandon(reason) {
    this.analytics.track('funnel_abandon', {
      funnel: this.funnelName,
      reason,
      elapsedMs: Date.now() - this.startTime,
    })
  }
}

// 用法
const onboarding = new FunnelTracker(analytics, 'onboarding')
onboarding.step('welcome_shown')
// ……玩家点击继续
onboarding.step('name_entered')
// ……玩家完成教程
onboarding.complete()

9) A/B 测试支持

js
class ABTest {
  constructor(testName, variants) {
    this.testName = testName
    this.variants = variants
    
    // 取出或分配实验组
    const stored = localStorage.getItem(`ab_${testName}`)
    if (stored && variants.includes(stored)) {
      this.variant = stored
    } else {
      this.variant = variants[Math.floor(Math.random() * variants.length)]
      localStorage.setItem(`ab_${testName}`, this.variant)
    }
    
    // 跟踪分配
    analytics.track('ab_assignment', {
      test: testName,
      variant: this.variant,
    })
  }
  
  getVariant() {
    return this.variant
  }
  
  trackConversion(metric) {
    analytics.track('ab_conversion', {
      test: this.testName,
      variant: this.variant,
      metric,
    })
  }
}

// 用法
const difficultyTest = new ABTest('difficulty', ['easy', 'normal', 'hard'])
game.difficulty = difficultyTest.getVariant()

// 玩家通关时
difficultyTest.trackConversion('level_complete')

10) 隐私考量

js
class PrivacyAwareAnalytics extends Analytics {
  constructor(endpoint) {
    super(endpoint)
    this.enabled = this.checkConsent()
  }
  
  checkConsent() {
    return localStorage.getItem('analytics_consent') === 'true'
  }
  
  setConsent(enabled) {
    localStorage.setItem('analytics_consent', enabled ? 'true' : 'false')
    this.enabled = enabled
    
    if (enabled) {
      this.track('consent_granted')
    }
  }
  
  track(event, data = {}) {
    if (!this.enabled) return
    
    // 去掉 PII
    const sanitized = { ...data }
    delete sanitized.email
    delete sanitized.name
    delete sanitized.ip
    
    super.track(event, sanitized)
  }
}

// 展示同意对话框
function showConsentDialog() {
  const dialog = document.createElement('div')
  dialog.innerHTML = `
    <p>我们用分析来改进游戏。可以吗?</p>
    <button id="accept">接受</button>
    <button id="decline">拒绝</button>
  `
  document.body.appendChild(dialog)
  
  dialog.querySelector('#accept').onclick = () => {
    analytics.setConsent(true)
    dialog.remove()
  }
  
  dialog.querySelector('#decline').onclick = () => {
    analytics.setConsent(false)
    dialog.remove()
  }
}

第三方备选方案

如果你不想自己搭:

  • Plausible —— 注重隐私,简单
  • Amplitude —— 产品分析、漏斗
  • Mixpanel —— 事件跟踪、用户旅程
  • Sentry —— 专门做错误跟踪
js
// 示例:Plausible
const script = document.createElement('script')
script.defer = true
script.dataset.domain = 'yourgame.com'
script.src = 'https://plausible.io/js/plausible.js'
document.head.appendChild(script)

// 跟踪自定义事件
window.plausible('level_complete', { props: { level: '1' } })

相关阅读

外部资源

  • Plausible Analytics —— 注重隐私的轻量分析
  • PostHog —— 开源产品分析,带事件跟踪
  • Sentry —— 网页应用的错误监控和崩溃报告