Files
whale-town/scripts/GameStatistics.gd
2025-12-05 19:00:14 +08:00

550 lines
16 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
extends Node
class_name GameStatistics
## 游戏统计和分析功能
## 收集和分析游戏整体运行数据和玩家统计信息
# 统计数据类型
enum StatType {
PLAYER_COUNT, # 玩家数量
SESSION_DURATION, # 会话时长
FEATURE_USAGE, # 功能使用率
PERFORMANCE, # 性能指标
ERROR_RATE, # 错误率
ENGAGEMENT, # 参与度
RETENTION, # 留存率
SOCIAL_ACTIVITY # 社交活动
}
# 统计数据结构
class GameStat:
var stat_type: StatType
var value: float
var timestamp: float
var metadata: Dictionary = {}
func _init(type: StatType, val: float, meta: Dictionary = {}):
stat_type = type
value = val
timestamp = Time.get_unix_time_from_system()
metadata = meta.duplicate()
# 数据存储
var statistics_history: Array[GameStat] = []
var daily_statistics: Dictionary = {} # date_string -> Dictionary
var feature_usage_stats: Dictionary = {}
var performance_metrics: Dictionary = {}
var player_statistics: Dictionary = {}
# 配置
var max_history_entries: int = 10000
var statistics_enabled: bool = true
var collection_interval: float = 300.0 # 5分钟收集一次
# 引用其他系统
var user_behavior_analytics: UserBehaviorAnalytics
var social_manager: SocialManager
# 数据持久化
var stats_file_path: String = "user://game_statistics.json"
# 信号
signal statistic_recorded(stat_type: StatType, value: float)
signal daily_report_generated(date: String, report: Dictionary)
signal performance_alert(metric: String, value: float, threshold: float)
func _ready():
"""初始化游戏统计系统"""
_load_statistics_data()
# 设置定时统计收集
var collection_timer = Timer.new()
collection_timer.wait_time = collection_interval
collection_timer.timeout.connect(_collect_periodic_statistics)
collection_timer.autostart = true
add_child(collection_timer)
# 设置每日报告生成
var daily_timer = Timer.new()
daily_timer.wait_time = 86400.0 # 24小时
daily_timer.timeout.connect(_generate_daily_report)
daily_timer.autostart = true
add_child(daily_timer)
print("GameStatistics initialized")
## 设置系统引用
func set_system_references(uba: UserBehaviorAnalytics, sm: SocialManager) -> void:
"""
设置其他系统的引用
@param uba: 用户行为分析系统
@param sm: 社交管理器
"""
user_behavior_analytics = uba
social_manager = sm
# 连接信号
if user_behavior_analytics:
user_behavior_analytics.behavior_recorded.connect(_on_behavior_recorded)
if social_manager:
social_manager.friend_activity.connect(_on_social_activity)
## 记录统计数据
func record_statistic(stat_type: StatType, value: float, metadata: Dictionary = {}) -> void:
"""
记录统计数据
@param stat_type: 统计类型
@param value: 统计值
@param metadata: 元数据
"""
if not statistics_enabled:
return
var stat = GameStat.new(stat_type, value, metadata)
statistics_history.append(stat)
# 限制历史记录数量
if statistics_history.size() > max_history_entries:
statistics_history.pop_front()
# 更新当日统计
_update_daily_statistics(stat)
# 检查性能警报
_check_performance_alerts(stat_type, value)
# 发射信号
statistic_recorded.emit(stat_type, value)
print("Statistic recorded: ", StatType.keys()[stat_type], " = ", value)
## 记录玩家数量
func record_player_count(online_count: int, total_count: int) -> void:
"""
记录玩家数量统计
@param online_count: 在线玩家数
@param total_count: 总玩家数
"""
record_statistic(StatType.PLAYER_COUNT, online_count, {
"total_players": total_count,
"online_ratio": float(online_count) / max(total_count, 1)
})
## 记录功能使用情况
func record_feature_usage(feature_name: String, usage_count: int, user_count: int) -> void:
"""
记录功能使用统计
@param feature_name: 功能名称
@param usage_count: 使用次数
@param user_count: 使用用户数
"""
# 更新功能使用统计
if not feature_usage_stats.has(feature_name):
feature_usage_stats[feature_name] = {
"total_usage": 0,
"unique_users": {},
"daily_usage": {}
}
var feature_stat = feature_usage_stats[feature_name]
feature_stat.total_usage += usage_count
# 记录到统计历史
record_statistic(StatType.FEATURE_USAGE, usage_count, {
"feature": feature_name,
"user_count": user_count,
"usage_rate": float(usage_count) / max(user_count, 1)
})
## 记录性能指标
func record_performance_metric(metric_name: String, value: float, threshold: float = 0.0) -> void:
"""
记录性能指标
@param metric_name: 指标名称
@param value: 指标值
@param threshold: 警报阈值
"""
# 更新性能指标历史
if not performance_metrics.has(metric_name):
performance_metrics[metric_name] = []
var metric_history = performance_metrics[metric_name]
metric_history.append({
"value": value,
"timestamp": Time.get_unix_time_from_system()
})
# 限制历史记录长度
if metric_history.size() > 100:
metric_history.pop_front()
# 记录统计
record_statistic(StatType.PERFORMANCE, value, {
"metric": metric_name,
"threshold": threshold
})
## 记录参与度指标
func record_engagement_metric(session_duration: float, actions_count: int, features_used: int) -> void:
"""
记录用户参与度指标
@param session_duration: 会话时长
@param actions_count: 操作次数
@param features_used: 使用功能数
"""
var engagement_score = _calculate_engagement_score(session_duration, actions_count, features_used)
record_statistic(StatType.ENGAGEMENT, engagement_score, {
"session_duration": session_duration,
"actions_count": actions_count,
"features_used": features_used
})
## 计算参与度分数
func _calculate_engagement_score(duration: float, actions: int, features: int) -> float:
"""
计算参与度分数
@param duration: 会话时长(秒)
@param actions: 操作次数
@param features: 使用功能数
@return: 参与度分数0-100
"""
# 时长分数最多30分钟满分
var duration_score = min(duration / 1800.0, 1.0) * 40.0
# 操作频率分数
var action_rate = actions / max(duration / 60.0, 1.0) # 每分钟操作数
var action_score = min(action_rate / 10.0, 1.0) * 30.0
# 功能多样性分数
var feature_score = min(features / 5.0, 1.0) * 30.0
return duration_score + action_score + feature_score
## 定期收集统计数据
func _collect_periodic_statistics() -> void:
"""定期收集系统统计数据"""
if not statistics_enabled:
return
# 收集性能数据
var fps = Engine.get_frames_per_second()
var memory = OS.get_static_memory_usage_by_type()
record_performance_metric("fps", fps, 30.0)
record_performance_metric("memory_mb", memory / 1024.0 / 1024.0, 512.0)
# 收集用户行为数据
if user_behavior_analytics:
var behavior_stats = user_behavior_analytics.get_realtime_statistics()
record_statistic(StatType.SESSION_DURATION, behavior_stats.get("current_session_duration", 0.0))
# 收集社交活动数据
if social_manager:
var social_stats = social_manager.get_statistics()
_record_social_statistics(social_stats)
## 记录社交统计
func _record_social_statistics(social_stats: Dictionary) -> void:
"""记录社交活动统计"""
if social_stats.has("friend_system"):
var friend_stats = social_stats.friend_system
record_statistic(StatType.SOCIAL_ACTIVITY, friend_stats.get("total_friends", 0), {
"type": "friends",
"pending_requests": friend_stats.get("pending_requests", 0)
})
if social_stats.has("community_events"):
var event_stats = social_stats.community_events
record_statistic(StatType.SOCIAL_ACTIVITY, event_stats.get("active_events", 0), {
"type": "events",
"total_events": event_stats.get("total_events", 0)
})
## 更新每日统计
func _update_daily_statistics(stat: GameStat) -> void:
"""更新每日统计数据"""
var date_string = Time.get_date_string_from_unix_time(stat.timestamp)
if not daily_statistics.has(date_string):
daily_statistics[date_string] = {
"date": date_string,
"stats_by_type": {},
"total_records": 0,
"performance_summary": {},
"feature_usage_summary": {}
}
var daily_data = daily_statistics[date_string]
daily_data.total_records += 1
# 按类型统计
var type_name = StatType.keys()[stat.stat_type]
if not daily_data.stats_by_type.has(type_name):
daily_data.stats_by_type[type_name] = {
"count": 0,
"total_value": 0.0,
"min_value": stat.value,
"max_value": stat.value
}
var type_stats = daily_data.stats_by_type[type_name]
type_stats.count += 1
type_stats.total_value += stat.value
type_stats.min_value = min(type_stats.min_value, stat.value)
type_stats.max_value = max(type_stats.max_value, stat.value)
## 生成每日报告
func _generate_daily_report() -> void:
"""生成每日统计报告"""
var yesterday = Time.get_date_string_from_unix_time(Time.get_unix_time_from_system() - 86400)
if not daily_statistics.has(yesterday):
return
var daily_data = daily_statistics[yesterday]
var report = _create_daily_report(daily_data)
daily_report_generated.emit(yesterday, report)
print("Daily report generated for: ", yesterday)
## 创建每日报告
func _create_daily_report(daily_data: Dictionary) -> Dictionary:
"""创建每日报告数据"""
var report = daily_data.duplicate()
# 计算平均值
for type_name in daily_data.stats_by_type:
var type_stats = daily_data.stats_by_type[type_name]
type_stats["average_value"] = type_stats.total_value / max(type_stats.count, 1)
# 添加趋势分析
report["trends"] = _analyze_daily_trends(daily_data.date)
# 添加关键指标摘要
report["key_metrics"] = _extract_key_metrics(daily_data)
return report
## 分析每日趋势
func _analyze_daily_trends(date: String) -> Dictionary:
"""分析每日数据趋势"""
var trends = {}
# 获取前一天数据进行对比
var prev_date_time = Time.get_unix_time_from_datetime_string(date + "T00:00:00") - 86400
var prev_date = Time.get_date_string_from_unix_time(prev_date_time)
if daily_statistics.has(prev_date):
var current_data = daily_statistics[date]
var prev_data = daily_statistics[prev_date]
# 比较总记录数
var current_total = current_data.get("total_records", 0)
var prev_total = prev_data.get("total_records", 0)
if prev_total > 0:
trends["activity_change"] = (float(current_total - prev_total) / prev_total) * 100.0
# 比较各类型统计
trends["type_changes"] = {}
for type_name in current_data.get("stats_by_type", {}):
var current_count = current_data.stats_by_type[type_name].get("count", 0)
var prev_count = prev_data.get("stats_by_type", {}).get(type_name, {}).get("count", 0)
if prev_count > 0:
trends.type_changes[type_name] = (float(current_count - prev_count) / prev_count) * 100.0
return trends
## 提取关键指标
func _extract_key_metrics(daily_data: Dictionary) -> Dictionary:
"""提取每日关键指标"""
var metrics = {}
var stats_by_type = daily_data.get("stats_by_type", {})
# 性能指标
if stats_by_type.has("PERFORMANCE"):
metrics["performance"] = {
"average": stats_by_type.PERFORMANCE.get("average_value", 0.0),
"min": stats_by_type.PERFORMANCE.get("min_value", 0.0),
"max": stats_by_type.PERFORMANCE.get("max_value", 0.0)
}
# 参与度指标
if stats_by_type.has("ENGAGEMENT"):
metrics["engagement"] = {
"average_score": stats_by_type.ENGAGEMENT.get("average_value", 0.0),
"peak_score": stats_by_type.ENGAGEMENT.get("max_value", 0.0)
}
# 社交活动指标
if stats_by_type.has("SOCIAL_ACTIVITY"):
metrics["social"] = {
"activity_count": stats_by_type.SOCIAL_ACTIVITY.get("count", 0),
"average_activity": stats_by_type.SOCIAL_ACTIVITY.get("average_value", 0.0)
}
return metrics
## 检查性能警报
func _check_performance_alerts(stat_type: StatType, value: float) -> void:
"""检查性能警报条件"""
if stat_type == StatType.PERFORMANCE:
# FPS过低警报
if value < 20.0:
performance_alert.emit("low_fps", value, 20.0)
# 内存使用过高警报假设单位是MB
if value > 1000.0:
performance_alert.emit("high_memory", value, 1000.0)
## 获取统计摘要
func get_statistics_summary(days: int = 7) -> Dictionary:
"""
获取统计摘要
@param days: 统计天数
@return: 统计摘要
"""
var summary = {}
var current_time = Time.get_unix_time_from_system()
var start_time = current_time - (days * 86400)
# 获取时间范围内的统计
var recent_stats = statistics_history.filter(func(stat): return stat.timestamp >= start_time)
summary["period_days"] = days
summary["total_records"] = recent_stats.size()
summary["start_date"] = Time.get_date_string_from_unix_time(start_time)
summary["end_date"] = Time.get_date_string_from_unix_time(current_time)
# 按类型汇总
var type_summary = {}
for stat in recent_stats:
var type_name = StatType.keys()[stat.stat_type]
if not type_summary.has(type_name):
type_summary[type_name] = {
"count": 0,
"values": []
}
type_summary[type_name].count += 1
type_summary[type_name].values.append(stat.value)
# 计算统计指标
for type_name in type_summary:
var values = type_summary[type_name].values
if values.size() > 0:
type_summary[type_name]["average"] = _calculate_average(values)
type_summary[type_name]["min"] = values.min()
type_summary[type_name]["max"] = values.max()
summary["by_type"] = type_summary
return summary
## 计算平均值
func _calculate_average(values: Array) -> float:
"""计算数组平均值"""
if values.is_empty():
return 0.0
var sum = 0.0
for value in values:
sum += float(value)
return sum / values.size()
## 信号处理函数
func _on_behavior_recorded(event_type: UserBehaviorAnalytics.EventType, data: Dictionary):
"""处理用户行为记录事件"""
# 将用户行为转换为游戏统计
match event_type:
UserBehaviorAnalytics.EventType.LOGIN:
record_statistic(StatType.PLAYER_COUNT, 1.0, {"action": "login"})
UserBehaviorAnalytics.EventType.UI_ACTION:
var element = data.get("element", "unknown")
record_feature_usage(element, 1, 1)
func _on_social_activity(activity_type: String, data: Dictionary):
"""处理社交活动事件"""
record_statistic(StatType.SOCIAL_ACTIVITY, 1.0, {
"activity_type": activity_type,
"data": data
})
## 保存统计数据
func _save_statistics_data() -> void:
"""保存统计数据到本地文件"""
var data = {
"statistics_history": [],
"daily_statistics": daily_statistics,
"feature_usage_stats": feature_usage_stats,
"performance_metrics": performance_metrics,
"saved_at": Time.get_unix_time_from_system()
}
# 序列化统计历史(只保存最近的数据)
var stats_to_save = statistics_history.slice(max(0, statistics_history.size() - 1000), statistics_history.size())
for stat in stats_to_save:
data.statistics_history.append({
"stat_type": stat.stat_type,
"value": stat.value,
"timestamp": stat.timestamp,
"metadata": stat.metadata
})
var file = FileAccess.open(stats_file_path, FileAccess.WRITE)
if file:
var json_string = JSON.stringify(data)
file.store_string(json_string)
file.close()
print("Game statistics saved: ", stats_to_save.size(), " records")
else:
print("Failed to save game statistics")
## 加载统计数据
func _load_statistics_data() -> void:
"""从本地文件加载统计数据"""
if not FileAccess.file_exists(stats_file_path):
print("No game statistics file found, starting fresh")
return
var file = FileAccess.open(stats_file_path, FileAccess.READ)
if file:
var json_string = file.get_as_text()
file.close()
var json = JSON.new()
var parse_result = json.parse(json_string)
if parse_result == OK:
var data = json.data
# 加载统计历史
if data.has("statistics_history"):
for stat_data in data.statistics_history:
var stat = GameStat.new(
stat_data.get("stat_type", StatType.PERFORMANCE),
stat_data.get("value", 0.0),
stat_data.get("metadata", {})
)
stat.timestamp = stat_data.get("timestamp", 0.0)
statistics_history.append(stat)
# 加载其他数据
daily_statistics = data.get("daily_statistics", {})
feature_usage_stats = data.get("feature_usage_stats", {})
performance_metrics = data.get("performance_metrics", {})
print("Game statistics loaded: ", statistics_history.size(), " records")
else:
print("Failed to parse game statistics JSON")
else:
print("Failed to open game statistics file")
func _exit_tree():
"""节点退出时保存数据"""
_save_statistics_data()