AeThex-Engine-Core/docs/tutorials/ANALYTICS_TUTORIAL.md

18 KiB

Analytics Tutorial

Learn how to track player behavior, measure engagement, and make data-driven decisions with AeThex Analytics.


What You'll Build

A complete analytics system that tracks:

  • Player events (level complete, item collected, etc.)
  • User properties (level, skill, preferences)
  • Custom funnels (onboarding, purchases, retention)
  • Crash reports and errors
  • Performance metrics

Time: 25 minutes
Difficulty: Beginner
Prerequisites: Basic GDScript knowledge


Why Use Analytics?

Analytics helps you:

  • Understand players - See what they do in your game
  • Improve retention - Find where players drop off
  • Optimize monetization - Track purchase funnels
  • Fix bugs faster - Get crash reports automatically
  • Make data-driven decisions - Know what features to build

Step 1: Connect to Analytics

First, connect to AeThex Cloud and initialize analytics:

# main.gd
extends Node

func _ready():
    # Connect to cloud
    var result = await AeThexCloud.connect_to_cloud()
    
    if result.success:
        print("Connected to AeThex Cloud")
        initialize_analytics()
    else:
        print("Failed to connect: ", result.error)

func initialize_analytics():
    # Analytics initialization is automatic
    # Track app start
    AeThexAnalytics.track_event("app_started", {
        "platform": OS.get_name(),
        "version": ProjectSettings.get_setting("application/config/version")
    })

Step 2: Track Basic Events

Track important game events:

#player.gd
extends CharacterBody2D

var level = 1
var score = 0

func level_complete():
    # Track level completion
    AeThexAnalytics.track_event("level_complete", {
        "level": level,
        "score": score,
        "time_spent": get_level_time(),
        "deaths": death_count
    })
    
    level += 1

func collect_item(item_name: String):
    # Track item collection
    AeThexAnalytics.track_event("item_collected", {
        "item_name": item_name,
        "level": level,
        "timestamp": Time.get_unix_time_from_system()
    })

func player_died():
    # Track deaths
    AeThexAnalytics.track_event("player_died", {
        "level": level,
        "cause": death_cause,
        "position": global_position
    })

Step 3: Set User Properties

Store persistent information about users:

# analytics_manager.gd (autoload)
extends Node

func _ready():
    # Set up user properties when game starts
    setup_user_properties()

func setup_user_properties():
    # Basic user info
    AeThexAnalytics.set_user_property("platform", OS.get_name())
    AeThexAnalytics.set_user_property("game_version", ProjectSettings.get_setting("application/config/version"))
    
    # Load from save file
    var save_data = load_save()
    if save_data:
        AeThexAnalytics.set_user_property("player_level", save_data.level)
        AeThexAnalytics.set_user_property("total_playtime", save_data.playtime)
        AeThexAnalytics.set_user_property("achievements_unlocked", save_data.achievements.size())

func on_player_level_up(new_level: int):
    # Update user property when it changes
    AeThexAnalytics.set_user_property("player_level", new_level)

func on_achievement_unlocked(achievement_id: String):
    # Increment property
    AeThexAnalytics.increment_user_property("achievements_unlocked", 1)
    
    # Track event
    AeThexAnalytics.track_event("achievement_unlocked", {
        "achievement_id": achievement_id
    })

Step 4: Track Screen Views

Monitor which screens players visit:

# base_screen.gd (inherit from this for all screens)
extends Control

var screen_name: String = "Unknown"
var screen_enter_time: float = 0

func _ready():
    screen_enter_time = Time.get_ticks_msec() / 1000.0
    track_screen_view()

func _exit_tree():
    track_screen_duration()

func track_screen_view():
    AeThexAnalytics.track_event("screen_view", {
        "screen_name": screen_name,
        "timestamp": Time.get_unix_time_from_system()
    })

func track_screen_duration():
    var duration = (Time.get_ticks_msec() / 1000.0) - screen_enter_time
    AeThexAnalytics.track_event("screen_duration", {
        "screen_name": screen_name,
        "duration_seconds": duration
    })

Example usage:

# main_menu.gd
extends "res://scripts/base_screen.gd"

func _ready():
    screen_name = "main_menu"
    super._ready()

Step 5: Track Economy Events

Monitor in-game economy:

# economy_tracker.gd
extends Node

func currency_earned(currency_name: String, amount: int, source: String):
    AeThexAnalytics.track_event("currency_earned", {
        "currency": currency_name,
        "amount": amount,
        "source": source,  # "quest", "shop", "reward", etc.
        "total_balance": get_currency_balance(currency_name)
    })

func currency_spent(currency_name: String, amount: int, item: String):
    AeThexAnalytics.track_event("currency_spent", {
        "currency": currency_name,
        "amount": amount,
        "item": item,
        "remaining_balance": get_currency_balance(currency_name)
    })

func item_purchased(item_id: String, price: int, currency: String):
    AeThexAnalytics.track_event("item_purchased", {
        "item_id": item_id,
        "price": price,
        "currency": currency,
        "source": "shop"  # or "chest", "reward", etc.
    })

func real_money_purchase(product_id: String, price: float, currency: String):
    AeThexAnalytics.track_event("iap_purchase", {
        "product_id": product_id,
        "price": price,
        "currency": currency,
        "revenue": price  # Important for revenue tracking
    })

Step 6: Create Funnels

Track conversion funnels to understand player flow:

# funnel_tracker.gd
extends Node

# Onboarding funnel
func track_onboarding_funnel(step: String):
    var funnel_steps = [
        "tutorial_start",
        "tutorial_complete",
        "first_level_start",
        "first_level_complete",
        "account_created"
    ]
    
    AeThexAnalytics.track_event("onboarding_funnel", {
        "step": step,
        "step_number": funnel_steps.find(step) + 1,
        "total_steps": funnel_steps.size()
    })

# Purchase funnel
func track_purchase_funnel(step: String, product_id: String = ""):
    AeThexAnalytics.track_event("purchase_funnel", {
        "step": step,  # "view_shop", "select_item", "confirm", "complete"
        "product_id": product_id
    })

# Level progression funnel
func track_level_funnel(step: String, level: int):
    AeThexAnalytics.track_event("level_funnel", {
        "step": step,  # "start", "complete", "fail", "abandon"
        "level": level
    })

Example usage:

# tutorial.gd
func _ready():
    FunnelTracker.track_onboarding_funnel("tutorial_start")

func on_tutorial_complete():
    FunnelTracker.track_onboarding_funnel("tutorial_complete")

# shop.gd
func _on_item_clicked(product_id):
    FunnelTracker.track_purchase_funnel("select_item", product_id)

func _on_purchase_confirmed(product_id):
    FunnelTracker.track_purchase_funnel("confirm", product_id)

Step 7: Track Engagement Metrics

Measure player engagement:

# engagement_tracker.gd (autoload)
extends Node

var session_start_time: float = 0
var total_sessions: int = 0
var events_this_session: int = 0

func _ready():
    start_session()

func start_session():
    session_start_time = Time.get_ticks_msec() / 1000.0
    total_sessions = load_total_sessions() + 1
    save_total_sessions(total_sessions)
    
    AeThexAnalytics.track_event("session_start", {
        "session_number": total_sessions,
        "days_since_install": get_days_since_install()
    })
    
    # Set user property
    AeThexAnalytics.set_user_property("total_sessions", total_sessions)

func end_session():
    var session_duration = (Time.get_ticks_msec() / 1000.0) - session_start_time
    
    AeThexAnalytics.track_event("session_end", {
        "duration_seconds": session_duration,
        "events_tracked": events_this_session
    })

func _notification(what):
    if what == NOTIFICATION_WM_CLOSE_REQUEST:
        end_session()

func track_engagement_event():
    events_this_session += 1

func get_days_since_install() -> int:
    var install_date = load_install_date()
    if install_date == 0:
        install_date = Time.get_unix_time_from_system()
        save_install_date(install_date)
        return 0
    
    var current_time = Time.get_unix_time_from_system()
    var days = (current_time - install_date) / 86400.0  # seconds in a day
    return int(days)

Step 8: Track Errors and Crashes

Automatically report errors:

# error_tracker.gd (autoload)
extends Node

func _ready():
    # Catch unhandled errors
    Engine.get_singleton("ScriptServer").add_global_constant("ErrorTracker", self)

func track_error(error_message: String, stack_trace: String = ""):
    AeThexAnalytics.track_event("error_occurred", {
        "error_message": error_message,
        "stack_trace": stack_trace,
        "platform": OS.get_name(),
        "version": ProjectSettings.get_setting("application/config/version")
    })

func track_crash(crash_reason: String):
    AeThexAnalytics.track_event("game_crash", {
        "reason": crash_reason,
        "platform": OS.get_name(),
        "memory_used": Performance.get_monitor(Performance.MEMORY_STATIC),
        "fps": Engine.get_frames_per_second()
    })

# Catch GDScript errors
func _on_error(error_message: String):
    track_error(error_message, get_stack())

# Helper to get stack trace
func get_stack() -> String:
    var stack = get_stack_trace()
    var result = ""
    for frame in stack:
        result += "%s:%d in %s()\n" % [frame.source, frame.line, frame.function]
    return result

func get_stack_trace() -> Array:
    return []  # Implemented in debug builds

Step 9: Performance Tracking

Track game performance metrics:

# performance_tracker.gd (autoload)
extends Node

const SAMPLE_INTERVAL = 5.0  # seconds
var sample_timer: Timer

func _ready():
    sample_timer = Timer.new()
    sample_timer.timeout.connect(_sample_performance)
    sample_timer.wait_time = SAMPLE_INTERVAL
    add_child(sample_timer)
    sample_timer.start()

func _sample_performance():
    var fps = Engine.get_frames_per_second()
    var memory_mb = Performance.get_monitor(Performance.MEMORY_STATIC) / 1024.0 / 1024.0
    var draw_calls = Performance.get_monitor(Performance.RENDER_TOTAL_DRAW_CALLS_IN_FRAME)
    
    # Track performance event
    AeThexAnalytics.track_event("performance_sample", {
        "fps": fps,
        "memory_mb": memory_mb,
        "draw_calls": draw_calls,
        "scene": get_tree().current_scene.name if get_tree().current_scene else "unknown"
    })
    
    # Alert on low performance
    if fps < 30:
        track_low_performance(fps, memory_mb)

func track_low_performance(fps: int, memory_mb: float):
    AeThexAnalytics.track_event("low_performance", {
        "fps": fps,
        "memory_mb": memory_mb,
        "platform": OS.get_name(),
        "scene": get_tree().current_scene.name if get_tree().current_scene else "unknown"
    })

Step 10: A/B Testing

Implement A/B tests to compare features:

# ab_testing.gd (autoload)
extends Node

var user_variant: String = ""

func _ready():
    assign_variant()

func assign_variant():
    # Check if user already has a variant assigned
    user_variant = load_user_variant()
    
    if user_variant.is_empty():
        # Randomly assign variant (50/50 split)
        user_variant = "A" if randf() < 0.5 else "B"
        save_user_variant(user_variant)
    
    # Set as user property
    AeThexAnalytics.set_user_property("ab_test_variant", user_variant)
    
    # Track assignment
    AeThexAnalytics.track_event("ab_test_assigned", {
        "variant": user_variant
    })

func get_variant() -> String:
    return user_variant

func is_variant_a() -> bool:
    return user_variant == "A"

func is_variant_b() -> bool:
    return user_variant == "B"

func track_conversion(goal: String):
    AeThexAnalytics.track_event("ab_test_conversion", {
        "variant": user_variant,
        "goal": goal
    })

Example usage:

# Configure feature based on variant
func _ready():
    if ABTesting.is_variant_a():
        # Show red button
        $Button.modulate = Color.RED
    else:
        # Show blue button
        $Button.modulate = Color.BLUE

func _on_button_pressed():
    # Track which variant converted
    ABTesting.track_conversion("button_clicked")

Analytics Dashboard

View your analytics data:

  1. Go to: https://studio.aethex.io/analytics
  2. Select your project
  3. View dashboards:
    • Overview: DAU, MAU, retention
    • Events: All tracked events
    • Funnels: Conversion funnels
    • User Properties: Audience segments
    • Errors: Crash reports
    • Performance: FPS, memory, load times

Best Practices

1. Name Events Consistently

# ✓ DO - Use snake_case
AeThexAnalytics.track_event("level_complete", {})
AeThexAnalytics.track_event("item_collected", {})

# ❌ DON'T - Inconsistent naming
AeThexAnalytics.track_event("LevelComplete", {})
AeThexAnalytics.track_event("item-collected", {})

2. Include Context in Events

# ✓ DO - Rich context
AeThexAnalytics.track_event("button_clicked", {
    "button_name": "play",
    "screen": "main_menu",
    "session_time": get_session_time()
})

# ❌ DON'T - No context
AeThexAnalytics.track_event("button_clicked", {})

3. Track the User Journey

# Track entire player flow
AeThexAnalytics.track_event("game_started", {})
AeThexAnalytics.track_event("tutorial_started", {})
AeThexAnalytics.track_event("tutorial_completed", {})
AeThexAnalytics.track_event("first_level_started", {})
AeThexAnalytics.track_event("first_level_completed", {})

4. Avoid PII (Personally Identifiable Information)

# ❌ DON'T - Include PII
AeThexAnalytics.track_event("user_info", {
    "email": "user@example.com",  # Never track emails
    "name": "John Doe",            # Never track real names
    "ip_address": "192.168.1.1"   # Never track IPs
})

# ✓ DO - Use anonymous identifiers
AeThexAnalytics.track_event("user_info", {
    "user_id": "anon_123456",
    "player_level": 5
})

5. Batch Events if Needed

# For high-frequency events, batch them
var event_batch = []

func track_player_movement():
    event_batch.append({
        "event": "player_moved",
        "position": player.position
    })
    
    if event_batch.size() >= 10:
        flush_events()

func flush_events():
    for event in event_batch:
        AeThexAnalytics.track_event(event.event, event)
    event_batch.clear()

Common Metrics to Track

Engagement Metrics:

  • Daily Active Users (DAU)
  • Monthly Active Users (MAU)
  • Session length
  • Sessions per user
  • Day 1/7/30 retention

Monetization Metrics:

  • Average Revenue Per User (ARPU)
  • Paying User Rate
  • Lifetime Value (LTV)
  • Conversion rate

Game Metrics:

  • Level completion rate
  • Tutorial completion rate
  • Time to first action
  • Churn points
  • Player progression

Example: Complete Analytics Setup

# analytics_complete.gd (autoload)
extends Node

signal analytics_ready

var is_initialized = false

func _ready():
    initialize()

func initialize():
    # Wait for cloud connection
    await get_tree().create_timer(1.0).timeout
    
    if not AeThexCloud.is_connected():
        await AeThexCloud.connect_to_cloud()
    
    # Set up user properties
    setup_user_properties()
    
    # Start session tracking
    start_session()
    
    # Connect to game signals
    connect_game_signals()
    
    is_initialized = true
    analytics_ready.emit()

func setup_user_properties():
    AeThexAnalytics.set_user_property("platform", OS.get_name())
    AeThexAnalytics.set_user_property("game_version", get_game_version())
    AeThexAnalytics.set_user_property("install_date", get_install_date())

func start_session():
    AeThexAnalytics.track_event("session_start", {
        "platform": OS.get_name(),
        "version": get_game_version()
    })

func connect_game_signals():
    # Connect to various game events
    var game = get_tree().root.get_node("Game")
    if game:
        game.level_completed.connect(on_level_completed)
        game.player_died.connect(on_player_died)
        game.item_collected.connect(on_item_collected)

func on_level_completed(level: int, score: int):
    AeThexAnalytics.track_event("level_complete", {
        "level": level,
        "score": score
    })

func on_player_died(level: int, cause: String):
    AeThexAnalytics.track_event("player_died", {
        "level": level,
        "cause": cause
    })

func on_item_collected(item: String):
    AeThexAnalytics.track_event("item_collected", {
        "item": item
    })

func get_game_version() -> String:
    return ProjectSettings.get_setting("application/config/version", "1.0.0")

func get_install_date() -> int:
    # Load from save or set to current time
    var save_file = FileAccess.open("user://install_date.save", FileAccess.READ)
    if save_file:
        var date = save_file.get_64()
        save_file.close()
        return date
    else:
        var date = Time.get_unix_time_from_system()
        save_file = FileAccess.open("user://install_date.save", FileAccess.WRITE)
        save_file.store_64(date)
        save_file.close()
        return date

Testing Analytics

Test your analytics implementation:

# test_analytics.gd
extends Node

func _ready():
    test_analytics()

func test_analytics():
    print("Testing analytics...")
    
    # Test basic event
    AeThexAnalytics.track_event("test_event", {
        "test": true
    })
    
    # Test user property
    AeThexAnalytics.set_user_property("test_property", "test_value")
    
    # Check console for confirmation
    print("Analytics test complete. Check dashboard for events.")

Next Steps


Summary

You've learned how to: Track custom events
Set user properties
Create conversion funnels
Monitor performance
Track errors and crashes
Implement A/B testing
Measure engagement metrics

Analytics gives you the insights to make your game better and understand your players!

Ready to publish? Check out the Publishing Guide! 🚀