Analytics
Custom Events

Custom Events

Track custom gameplay events to understand how players interact with your game. This guide covers the Analytics SDK API and best practices for event tracking.

Basic Usage

The Analytics SDK is available globally as Hyperstone in all your GDScript files (after exporting with analytics enabled).

Simple Events

Track an event without additional data:

Hyperstone.track("level_complete")

Events with Data

Track an event with custom data:

Hyperstone.track("player_death", {
    "level": 3,
    "cause": "enemy",
    "time_survived": 120.5
})

API Reference

track(event_name: String, event_data: Dictionary = {})

Tracks a custom event with optional data.

Parameters:

  • event_name (String): Name of the event (automatically normalized to lowercase)
  • event_data (Dictionary, optional): Custom data to attach to the event

Returns: None

Example:

Hyperstone.track("achievement_unlocked", {
    "achievement_id": "speedrunner",
    "time_to_unlock": 3600
})

Event Naming

Normalization

All event names are automatically normalized to lowercase:

Hyperstone.track("LevelComplete")  # Stored as "levelcomplete"
Hyperstone.track("PLAYER_DEATH")   # Stored as "player_death"
Hyperstone.track("Item_Purchased") # Stored as "item_purchased"

Best Practices

Use snake_case:

# Good
Hyperstone.track("level_complete")
Hyperstone.track("player_death")
Hyperstone.track("item_purchased")
 
# Avoid
Hyperstone.track("LevelComplete")
Hyperstone.track("playerDeath")
Hyperstone.track("Item-Purchased")

Be descriptive:

# Good
Hyperstone.track("boss_defeated")
Hyperstone.track("tutorial_completed")
 
# Too vague
Hyperstone.track("event1")
Hyperstone.track("action")

Use consistent naming:

# Good - consistent pattern
Hyperstone.track("level_started")
Hyperstone.track("level_completed")
Hyperstone.track("level_failed")
 
# Inconsistent
Hyperstone.track("level_start")
Hyperstone.track("complete_level")
Hyperstone.track("failed")

Event Data

Supported Types

Event data can include:

  • Numbers: int, float
  • Strings: String
  • Booleans: bool
  • Arrays: Array
  • Dictionaries: Dictionary (nested)

Example:

Hyperstone.track("level_complete", {
    "level": 5,                    # int
    "time_taken": 123.45,          # float
    "perfect_score": true,         # bool
    "collectibles": [1, 2, 3],     # Array
    "stats": {                     # Dictionary
        "deaths": 2,
        "secrets_found": 3
    }
})

Data Limits

  • Event name: Max 255 characters
  • Event data: Max 10 KB per event
  • Nested depth: Max 5 levels deep

Common Event Patterns

Level Progression

Track player progress through levels:

# Level started
func _on_level_start():
    Hyperstone.track("level_started", {
        "level": current_level,
        "difficulty": difficulty_setting
    })
 
# Level completed
func _on_level_complete():
    Hyperstone.track("level_completed", {
        "level": current_level,
        "time_taken": level_timer.time,
        "score": player_score,
        "deaths": death_count,
        "collectibles": collectibles_found
    })
 
# Level failed
func _on_level_failed():
    Hyperstone.track("level_failed", {
        "level": current_level,
        "reason": failure_reason,
        "progress_percent": calculate_progress()
    })

Player Actions

Track important player actions:

# Player death
func _on_player_death():
    Hyperstone.track("player_death", {
        "level": current_level,
        "cause": death_cause,
        "position_x": player.position.x,
        "position_y": player.position.y,
        "health_remaining": player.health
    })
 
# Ability used
func _on_ability_used(ability_name: String):
    Hyperstone.track("ability_used", {
        "ability": ability_name,
        "level": current_level,
        "cooldown_remaining": get_cooldown(ability_name)
    })
 
# Item collected
func _on_item_collected(item: Item):
    Hyperstone.track("item_collected", {
        "item_id": item.id,
        "item_type": item.type,
        "level": current_level,
        "total_collected": inventory.count(item.type)
    })

Achievements

Track achievement unlocks:

func unlock_achievement(achievement_id: String):
    Hyperstone.track("achievement_unlocked", {
        "achievement_id": achievement_id,
        "play_time": get_total_play_time(),
        "level_reached": max_level_reached
    })

In-Game Economy

Track purchases and currency:

# Item purchased
func purchase_item(item_id: String, price: int):
    Hyperstone.track("item_purchased", {
        "item_id": item_id,
        "price": price,
        "currency": "gold",
        "balance_before": player.gold,
        "balance_after": player.gold - price
    })
 
# Currency earned
func earn_currency(amount: int, source: String):
    Hyperstone.track("currency_earned", {
        "amount": amount,
        "source": source,
        "balance_after": player.gold
    })

Tutorial & Onboarding

Track tutorial completion:

# Tutorial step completed
func _on_tutorial_step_complete(step: int):
    Hyperstone.track("tutorial_step_completed", {
        "step": step,
        "time_taken": step_timer.time
    })
 
# Tutorial completed
func _on_tutorial_complete():
    Hyperstone.track("tutorial_completed", {
        "total_time": tutorial_timer.time,
        "steps_completed": tutorial_steps.size(),
        "skipped": false
    })
 
# Tutorial skipped
func _on_tutorial_skipped():
    Hyperstone.track("tutorial_skipped", {
        "step_reached": current_tutorial_step
    })

Settings & Preferences

Track settings changes:

func _on_setting_changed(setting_name: String, new_value):
    Hyperstone.track("setting_changed", {
        "setting": setting_name,
        "value": str(new_value)
    })

Boss Battles

Track boss encounters:

# Boss encountered
func _on_boss_encounter(boss_id: String):
    Hyperstone.track("boss_encountered", {
        "boss_id": boss_id,
        "player_level": player.level,
        "attempt_number": get_boss_attempts(boss_id)
    })
 
# Boss defeated
func _on_boss_defeated(boss_id: String):
    Hyperstone.track("boss_defeated", {
        "boss_id": boss_id,
        "time_taken": boss_timer.time,
        "health_remaining": player.health,
        "attempts": get_boss_attempts(boss_id)
    })

Best Practices

Track Meaningful Events

Do track:

  • Level progression
  • Player deaths and failures
  • Achievement unlocks
  • In-game purchases
  • Tutorial completion
  • Boss battles
  • Settings changes

Don't track:

  • Every frame update
  • Mouse movements
  • Continuous position updates
  • Rapid-fire events (button mashing)

Include Context

Always include relevant context with events:

# Good - includes context
Hyperstone.track("player_death", {
    "level": current_level,
    "cause": "enemy",
    "enemy_type": "goblin",
    "player_health": 0,
    "time_in_level": 45.2
})
 
# Bad - no context
Hyperstone.track("death")

Use Consistent Units

Be consistent with units across events:

# Good - consistent time units (seconds)
Hyperstone.track("level_complete", {
    "time_taken": 123.45  # seconds
})
 
# Bad - inconsistent units
Hyperstone.track("level_complete", {
    "time_taken": 2.05  # minutes? seconds?
})

Avoid PII

Never track personally identifiable information:

# Bad - contains PII
Hyperstone.track("player_registered", {
    "email": player_email,
    "name": player_name,
    "ip_address": player_ip
})
 
# Good - no PII
Hyperstone.track("player_registered", {
    "platform": OS.get_name(),
    "version": game_version
})

Batch Related Events

Group related data in a single event rather than multiple events:

# Good - single event with all data
Hyperstone.track("level_complete", {
    "level": 5,
    "time": 120.5,
    "score": 1000,
    "deaths": 2,
    "collectibles": 8
})
 
# Bad - multiple events
Hyperstone.track("level_complete")
Hyperstone.track("level_time", {"time": 120.5})
Hyperstone.track("level_score", {"score": 1000})
Hyperstone.track("level_deaths", {"deaths": 2})

Performance Considerations

Event Queuing

Events are queued in memory and sent in batches every 15 minutes. This means:

  • No immediate network overhead
  • Minimal performance impact
  • Events are cached if network is unavailable

Memory Usage

Each event uses approximately:

  • 100-500 bytes for event name and data
  • Events are cleared after successful transmission
  • Cached events are stored on disk if network fails

Recommended Limits

  • Max events per session: 1,000-5,000
  • Max events per minute: 10-50
  • Event data size: Keep under 1 KB per event

Testing Custom Events

Local Testing

  1. Export your game with analytics enabled
  2. Add custom event tracking to your code
  3. Run the exported game
  4. Trigger the events you want to test
  5. Wait 15-20 minutes for events to be sent
  6. Check the Analytics tab in Hyperstone

Debugging

Add debug prints to verify events are tracked:

func track_with_debug(event_name: String, event_data: Dictionary = {}):
    print("[Analytics] Tracking: ", event_name, " with data: ", event_data)
    Hyperstone.track(event_name, event_data)

Viewing Events

In Hyperstone's Analytics tab:

  1. Go to the Recent Events table
  2. Look for your custom event names
  3. Verify the event data is correct
  4. Check timestamps to ensure events are being sent

Related Documentation