class_name BaseLevel extends Node2D # 基础关卡脚本 # 负责处理通用的关卡逻辑,如玩家生成 @onready var spawn_points = $SpawnPoints if has_node("SpawnPoints") else null @onready var players_container = $Objects/Players if has_node("Objects/Players") else self func _ready(): # 延时一帧确保所有子节点就绪 call_deferred("_spawn_player") # 连接到多人会话 # 获取当前场景名字作为 Session ID _current_session_id = SceneManager.get_current_scene_name() if _current_session_id == "": _current_session_id = "square" # Fallback for direct run # 如果是私人场景,生成唯一的 Session ID (e.g., room_123) if _current_session_id in PRIVATE_SCENES: _current_session_id = _current_session_id + "_" + str(AuthManager.current_user_id) print("BaseLevel: 进入私密房间实例: ", _current_session_id) print("BaseLevel: Preparing to join session: ", _current_session_id) # 如果 WebSocket 已连接,直接加入 if WebSocketManager._socket.get_ready_state() == WebSocketPeer.STATE_OPEN: _join_session_with_player(_current_session_id) else: # 否则等待连接成功信号 WebSocketManager.connected_to_server.connect(func(): _join_session_with_player(_current_session_id)) # 连接远程玩家相关信号 WebSocketManager.session_joined.connect(_on_session_joined) WebSocketManager.user_joined.connect(_on_user_joined) WebSocketManager.user_left.connect(_on_user_left) WebSocketManager.position_updated.connect(_on_position_updated) # Debug: Simulate fake player to verify rendering # get_tree().create_timer(2.0).timeout.connect(_debug_fake_activity) func _exit_tree(): # 场景卸载时不需要再做操作,交给 DoorTeleport 或其他切换逻辑显示处理 pass func _debug_fake_activity(): print("BaseLevel: Starting fake player simulation") var fake_id = "fake_ghost" _on_user_joined({"user": {"userId": fake_id, "username": "Ghost"}, "position": {"x": 500, "y": 500}}) for i in range(20): await get_tree().create_timer(0.5).timeout var new_pos = Vector2(500 + i * 20, 500 + i * 10) _on_position_updated({"userId": fake_id, "position": {"x": new_pos.x, "y": new_pos.y}}) print("BaseLevel: Ghost moved to ", new_pos) var remote_players: Dictionary = {} # userId -> RemotePlayer instance var remote_player_scene = preload("res://scenes/characters/remote_player.tscn") var _player_spawned: bool = false # Track if local player has been spawned var _local_player: Node = null # Reference to the local player node var _position_update_timer: float = 0.0 # Throttle timer for position updates var _current_session_id: String = "" # Cache session ID to avoid race condition on exit const POSITION_UPDATE_INTERVAL: float = 0.125 # Send at most 8 times per second const PRIVATE_SCENES = ["room"] # List of scenes that should be private instances func _on_session_joined(data: Dictionary): # 对账远程玩家列表,使用 position.mapId 过滤 if not data.has("users"): return var current_map = _current_session_id if current_map == "": current_map = SceneManager.get_current_scene_name() if current_map == "": current_map = "square" print("BaseLevel: 同步会话 - 当前场景: ", current_map, ", 收到用户数: ", data.users.size()) # 1. 收集服务器返回的、且 mapId 匹配当前场景的用户ID var valid_user_ids: Array = [] var valid_users: Dictionary = {} # userId -> user data for user in data.users: if not user.has("userId") or str(user.userId) == str(AuthManager.current_user_id): continue # 检查 position.mapId 是否匹配当前场景 var user_map_id = "" if user.has("position") and user.position != null: if typeof(user.position) == TYPE_DICTIONARY and user.position.has("mapId"): user_map_id = str(user.position.mapId) print("BaseLevel: 用户 ", user.userId, " 的位置数据: ", user.position) else: print("BaseLevel: 用户 ", user.userId, " 没有位置数据") print("BaseLevel: 用户 ", user.userId, " mapId=", user_map_id, ", 当前场景=", current_map) if user_map_id == current_map: var uid = str(user.userId) valid_user_ids.append(uid) valid_users[uid] = user print("BaseLevel: 添加有效用户: ", uid) else: # mapId 不匹配,这是"幽灵玩家" print("BaseLevel: 跳过 mapId 不匹配的玩家: ", user.get("userId"), " (mapId=", user_map_id, ", expected=", current_map, ")") # 2. 清理幽灵:移除本地有但不在有效列表中的玩家 var local_user_ids = remote_players.keys() for user_id in local_user_ids: if str(user_id) not in valid_user_ids: print("BaseLevel: 清理幽灵玩家: ", user_id) _remove_remote_player(user_id) # 3. 添加或更新有效的玩家 for uid in valid_user_ids: var user = valid_users[uid] if remote_players.has(uid): # 已存在,更新位置 _update_remote_player_position(user) else: # 不存在,创建新玩家 print("BaseLevel: 创建远程玩家: ", uid) _add_remote_player(user) func _on_user_joined(data: Dictionary): var user = data.get("user", {}) if user.has("userId"): print("BaseLevel: 新玩家加入: ", user.userId) if user.userId == AuthManager.current_user_id: return # 将 position 数据合并到 user 字典中,以便 _add_remote_player 统一处理 if data.has("position"): user["position"] = data.position _add_remote_player(user) func _on_user_left(data: Dictionary): var user_id = data.get("userId") if user_id: print("BaseLevel: 玩家离开: ", user_id) _remove_remote_player(user_id) func _on_position_updated(data: Dictionary): var user_id = data.get("userId") if user_id and remote_players.has(user_id): var player = remote_players[user_id] # 数据可能直接是位置(扁平)或者包含在 position 字段中 # 根据后端协议: { userId:..., position: {x,y...}, ... } var pos_data = data.get("position", {}) if pos_data.is_empty(): pos_data = data # 兼容扁平格式 # print("BaseLevel: 收到位置更新: ", user_id, " Data: ", pos_data) # Debug log # 检查 mapId 是否匹配当前场景 if pos_data.has("mapId") and str(pos_data.mapId) != "": var current_map = _current_session_id if current_map == "": current_map = "square" if str(pos_data.mapId) != current_map: print("BaseLevel: 收到异地位置更新,移除幽灵玩家: ", user_id, " (在该玩家在 ", pos_data.mapId, ")") _remove_remote_player(user_id) return if player.has_method("update_position") and pos_data.has("x") and pos_data.has("y"): player.update_position(Vector2(pos_data.x, pos_data.y)) func _add_remote_player(user_data: Dictionary): var user_id = str(user_data.get("userId", "")) if user_id == "": return # 防止重复创建 if remote_players.has(user_id): return var remote_player = remote_player_scene.instantiate() # 使用统一的 setup 方法 if remote_player.has_method("setup"): remote_player.setup(user_data) else: # Fallback: 手动设置属性 (如果脚本没更新) remote_player.position = Vector2.ZERO if user_data.has("position"): var p = user_data.position if p.has("x") and p.has("y"): remote_player.position = Vector2(p.x, p.y) # 添加到场景玩家容器 if has_node("Objects/Players"): $Objects/Players.add_child(remote_player) else: add_child(remote_player) remote_players[user_id] = remote_player print("BaseLevel: 已创建远程玩家: ", user_id) func _remove_remote_player(user_id): var uid = str(user_id) if remote_players.has(uid): var player = remote_players[uid] if is_instance_valid(player): player.queue_free() remote_players.erase(uid) func _update_remote_player_position(user: Dictionary): var user_id = str(user.get("userId", "")) var player = remote_players.get(user_id) if not player or not is_instance_valid(player): return if user.has("position"): var pos = user.position if pos.has("x") and pos.has("y") and player.has_method("update_position"): player.update_position(Vector2(pos.x, pos.y)) func _join_session_with_player(session_id: String): # 检查是否有Token,如果没有则等待 if WebSocketManager._auth_token == "": print("BaseLevel: Token not ready, waiting...") # 轮询等待Token就绪 (简单重试机制) await get_tree().create_timer(0.5).timeout _join_session_with_player(session_id) return # 等待玩家生成完毕 if not _player_spawned or not _local_player: await get_tree().process_frame _join_session_with_player(session_id) return var pos = _local_player.global_position if is_instance_valid(_local_player) else Vector2.ZERO WebSocketManager.join_session(session_id, pos) # 强制广播一次位置更新,确保旧房间的玩家立即收到 "已切换地图" 的通知 # 这能解决"需要移动两步幽灵才消失"的问题 await get_tree().create_timer(0.1).timeout WebSocketManager.send_position_update(session_id, pos) func _process(delta): # 发送位置更新 (节流机制) if not _player_spawned or not _local_player: return # Wait for player to be spawned if WebSocketManager._socket.get_ready_state() != WebSocketPeer.STATE_OPEN: return # WebSocket not connected if not is_instance_valid(_local_player): return # Player was freed # 检查 velocity 属性 if not "velocity" in _local_player: return # 只有在移动时才更新计时器和发送 if _local_player.velocity.length() > 0: _position_update_timer += delta if _position_update_timer >= POSITION_UPDATE_INTERVAL: _position_update_timer = 0.0 # 使用 _current_session_id 确保有正确的 fallback var map_id = _current_session_id if _current_session_id != "" else "square" WebSocketManager.send_position_update(map_id, _local_player.global_position) func _spawn_player(): # 1. 确定出生位置 var spawn_pos = Vector2.ZERO var spawn_name = SceneManager.get_next_spawn_name() print("BaseLevel: Checking spawn point for name: '", spawn_name, "'") # 查找逻辑:优先查找名为 spawn_name 的节点,其次找 DefaultSpawn var target_node_name = spawn_name if spawn_name != "" else "DefaultSpawn" var marker_node = null # 策略 A: 在 SpawnPoints 容器中查找 if has_node("SpawnPoints"): marker_node = $SpawnPoints.get_node_or_null(target_node_name) # 策略 B: 如果没找到,在当前节点(根节点)下查找 if marker_node == null: marker_node = get_node_or_null(target_node_name) # 如果找到了标记点,使用其位置 if marker_node: spawn_pos = marker_node.global_position print("BaseLevel: Found spawn marker '", target_node_name, "' at ", spawn_pos) else: # 策略 C: 检查 SceneManager 是否有备用坐标 (兼容旧逻辑) var pos_param = SceneManager.get_next_scene_position() if pos_param != null: spawn_pos = pos_param print("BaseLevel: Using explicit position from SceneManager: ", spawn_pos) else: print("BaseLevel: Warning - Could not find marker '", target_node_name, "' and no explicit position set. Using (0,0)") # 2. 实例化玩家 var player_scene = preload("res://scenes/characters/player.tscn") var player = player_scene.instantiate() player.global_position = spawn_pos # 添加到场景 # 如果有Objects/Players容器则添加进去,否则直接添加到当前节点 if has_node("Objects/Players"): $Objects/Players.add_child(player) else: add_child(player) print("BaseLevel: Player spawned at ", player.global_position) _local_player = player # Save reference _player_spawned = true