From 3350081e801485c6c28c549ca7d44c549135337b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=92=95=E8=B0=B7=E9=85=B1?= <74496778+GooGuJiang@users.noreply.github.com> Date: Fri, 22 Aug 2025 14:02:12 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=8F=90=E4=BA=A4=E6=88=90?= =?UTF-8?q?=E7=BB=A9=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MULTIPLAYER_IMPROVEMENTS.md | 167 --------------------------------- app/signalr/hub/multiplayer.py | 101 +++++++++++++++++++- 2 files changed, 96 insertions(+), 172 deletions(-) delete mode 100644 MULTIPLAYER_IMPROVEMENTS.md diff --git a/MULTIPLAYER_IMPROVEMENTS.md b/MULTIPLAYER_IMPROVEMENTS.md deleted file mode 100644 index aa88a6b..0000000 --- a/MULTIPLAYER_IMPROVEMENTS.md +++ /dev/null @@ -1,167 +0,0 @@ -# 多人游戏观战和实时排行榜改进说明 - -## 主要改进 - -### 1. 游戏状态缓冲区 (GameplayStateBuffer) -- **实时分数缓冲**: 为每个房间的每个玩家维护最多50帧的分数数据 -- **实时排行榜**: 自动计算和维护实时排行榜数据 -- **游戏状态快照**: 为新加入的观众创建完整的游戏状态快照 -- **观战者状态缓存**: 跟踪观战者状态以优化同步 - -### 2. 观战同步管理器 (SpectatorSyncManager) -- **跨Hub通信**: 通过Redis在MultiplayerHub和SpectatorHub之间同步状态 -- **事件通知**: 游戏开始/结束、用户状态变化等事件的实时通知 -- **异步消息处理**: 订阅和处理观战相关事件 - -### 3. 增强的MultiplayerHub功能 - -#### 新增方法: -- `UpdateScore(client, score_data)`: 接收实时分数更新 -- `GetLeaderboard(client)`: 获取当前排行榜 -- `RequestSpectatorSync(client)`: 观战者请求状态同步 - -#### 改进的方法: -- `JoinRoomWithPassword`: 增强新用户加入时的状态同步 -- `ChangeState`: 添加观战状态处理和分数缓冲区管理 -- `start_gameplay`: 启动实时排行榜广播和创建游戏快照 -- `change_room_state`: 处理游戏结束时的清理工作 - -### 4. 实时排行榜系统 -- **自动广播**: 每秒更新一次实时排行榜 -- **智能启停**: 根据游戏状态自动启动/停止广播任务 -- **最终排行榜**: 游戏结束时发送最终排行榜 - -## 客户端集成示例 - -### JavaScript客户端示例 -```javascript -// 连接到MultiplayerHub -const connection = new signalR.HubConnectionBuilder() - .withUrl("/multiplayer") - .build(); - -// 监听实时排行榜更新 -connection.on("LeaderboardUpdate", (leaderboard) => { - updateLeaderboardUI(leaderboard); -}); - -// 监听游戏状态同步(观战者) -connection.on("GameplayStateSync", (snapshot) => { - syncSpectatorUI(snapshot); -}); - -// 监听最终排行榜 -connection.on("FinalLeaderboard", (finalLeaderboard) => { - showFinalResults(finalLeaderboard); -}); - -// 发送分数更新(玩家) -async function updateScore(scoreData) { - try { - await connection.invoke("UpdateScore", scoreData); - } catch (err) { - console.error("Error updating score:", err); - } -} - -// 请求观战同步(观战者) -async function requestSpectatorSync() { - try { - await connection.invoke("RequestSpectatorSync"); - } catch (err) { - console.error("Error requesting sync:", err); - } -} - -// 获取当前排行榜 -async function getCurrentLeaderboard() { - try { - return await connection.invoke("GetLeaderboard"); - } catch (err) { - console.error("Error getting leaderboard:", err); - return []; - } -} -``` - -### Python客户端示例 -```python -import signalrcore - -# 创建连接 -connection = signalrcore.HubConnectionBuilder() \ - .with_url("ws://localhost:8000/multiplayer") \ - .build() - -# 监听排行榜更新 -def on_leaderboard_update(leaderboard): - print("Leaderboard update:", leaderboard) - # 更新UI显示排行榜 - -connection.on("LeaderboardUpdate", on_leaderboard_update) - -# 监听游戏状态同步 -def on_gameplay_state_sync(snapshot): - print("Gameplay state sync:", snapshot) - # 同步观战界面 - -connection.on("GameplayStateSync", on_gameplay_state_sync) - -# 发送分数更新 -async def send_score_update(score, combo, accuracy): - await connection.send("UpdateScore", { - "score": score, - "combo": combo, - "accuracy": accuracy, - "completed": False - }) - -# 启动连接 -connection.start() -``` - -## 配置要求 - -### Redis配置 -确保Redis服务器运行并配置正确的连接参数: -```python -# 在app/dependencies/database.py中 -REDIS_CONFIG = { - 'host': 'localhost', - 'port': 6379, - 'db': 0, - 'decode_responses': True -} -``` - -### 数据库表结构 -确保`multiplayer_event`表包含以下字段: -- `event_detail`: JSON字段,用于存储事件详细信息 - -## 性能优化建议 - -1. **缓冲区大小调整**: 根据实际需求调整分数帧缓冲区大小(默认50帧) -2. **广播频率调整**: 可以根据网络条件调整排行榜广播频率(默认1秒) -3. **内存清理**: 定期清理过期的游戏状态快照和观战者状态 -4. **连接池优化**: 配置Redis连接池以处理高并发请求 - -## 故障排除 - -### 常见问题 -1. **排行榜不更新**: 检查Redis连接和广播任务状态 -2. **观战者状态不同步**: 确认SpectatorSyncManager已正确初始化 -3. **分数数据丢失**: 检查缓冲区大小和清理逻辑 - -### 日志监控 -关键日志点: -- `[MultiplayerHub] Synced gameplay state for user X` -- `[MultiplayerHub] Broadcasted leaderboard update to room X` -- `Error updating score for user X` -- `Error in leaderboard broadcast loop` - -### 调试模式 -在开发环境中启用详细日志: -```python -import logging -logging.getLogger("app.signalr.hub.multiplayer").setLevel(logging.DEBUG) -``` diff --git a/app/signalr/hub/multiplayer.py b/app/signalr/hub/multiplayer.py index f8d8067..184f1eb 100644 --- a/app/signalr/hub/multiplayer.py +++ b/app/signalr/hub/multiplayer.py @@ -108,17 +108,72 @@ class GameplayStateBuffer: async def create_gameplay_snapshot(self, room_id: int, room_data: Dict): """创建游戏状态快照用于新加入的观众""" + # 序列化复杂对象 + serialized_room_data = self._serialize_room_data(room_data) + snapshot = { 'room_id': room_id, - 'state': room_data.get('state'), - 'current_item': room_data.get('current_item'), - 'users': room_data.get('users', []), + 'state': serialized_room_data.get('state'), + 'current_item': serialized_room_data.get('current_item'), + 'users': serialized_room_data.get('users', []), 'leaderboard': self.get_leaderboard(room_id), - 'created_at': datetime.now(UTC) + 'created_at': datetime.now(UTC).isoformat() } self.gameplay_snapshots[room_id] = snapshot return snapshot + def _serialize_room_data(self, room_data: Dict) -> Dict: + """序列化房间数据""" + result = {} + for key, value in room_data.items(): + if hasattr(value, 'value') and hasattr(value, 'name'): + # 枚举类型 + result[key] = {'name': value.name, 'value': value.value} + elif hasattr(value, '__dict__'): + # 复杂对象 + if hasattr(value, 'model_dump'): + result[key] = value.model_dump() + elif hasattr(value, 'dict'): + result[key] = value.dict() + else: + # 手动序列化 + obj_dict = {} + for attr_name, attr_value in value.__dict__.items(): + if not attr_name.startswith('_'): + obj_dict[attr_name] = self._serialize_value(attr_value) + result[key] = obj_dict + elif isinstance(value, (list, tuple)): + result[key] = [self._serialize_value(item) for item in value] + else: + result[key] = self._serialize_value(value) + return result + + def _serialize_value(self, value): + """序列化单个值""" + if hasattr(value, 'value') and hasattr(value, 'name'): + # 枚举类型 + return {'name': value.name, 'value': value.value} + elif hasattr(value, '__dict__'): + # 复杂对象 + if hasattr(value, 'model_dump'): + return value.model_dump() + elif hasattr(value, 'dict'): + return value.dict() + else: + obj_dict = {} + for attr_name, attr_value in value.__dict__.items(): + if not attr_name.startswith('_'): + obj_dict[attr_name] = self._serialize_value(attr_value) + return obj_dict + elif isinstance(value, (list, tuple)): + return [self._serialize_value(item) for item in value] + elif isinstance(value, dict): + return {k: self._serialize_value(v) for k, v in value.items()} + elif isinstance(value, (str, int, float, bool, type(None))): + return value + else: + return str(value) + def get_gameplay_snapshot(self, room_id: int) -> Optional[Dict]: """获取游戏状态快照""" return self.gameplay_snapshots.get(room_id) @@ -155,12 +210,48 @@ class SpectatorSyncManager: self.redis = redis_client self.channel_prefix = "multiplayer_spectator" + def _serialize_for_json(self, obj): + """递归序列化对象为JSON兼容格式""" + if hasattr(obj, '__dict__'): + # 如果对象有__dict__属性,将其转换为字典 + if hasattr(obj, 'model_dump'): + # 对于Pydantic模型 + return obj.model_dump() + elif hasattr(obj, 'dict'): + # 对于较旧的Pydantic模型 + return obj.dict() + else: + # 对于普通对象 + result = {} + for key, value in obj.__dict__.items(): + if not key.startswith('_'): # 跳过私有属性 + result[key] = self._serialize_for_json(value) + return result + elif isinstance(obj, dict): + return {key: self._serialize_for_json(value) for key, value in obj.items()} + elif isinstance(obj, (list, tuple)): + return [self._serialize_for_json(item) for item in obj] + elif isinstance(obj, datetime): + # 处理datetime对象 + return obj.isoformat() + elif hasattr(obj, 'value') and hasattr(obj, 'name'): + # 对于枚举类型 + return {'name': obj.name, 'value': obj.value} + elif isinstance(obj, (str, int, float, bool, type(None))): + return obj + else: + # 对于其他类型,尝试转换为字符串 + return str(obj) + async def notify_spectator_hubs(self, room_id: int, event_type: str, data: Dict): """通知观战Hub游戏状态变化""" + # 序列化复杂对象为JSON兼容格式 + serialized_data = self._serialize_for_json(data) + message = { 'room_id': room_id, 'event_type': event_type, - 'data': data, + 'data': serialized_data, 'timestamp': datetime.now(UTC).isoformat() }