class_name BaseLevel extends Node2D # 基础关卡脚本 # 负责处理通用的关卡逻辑,如玩家生成 const CHAT_UI_SCENE: PackedScene = preload("res://scenes/ui/ChatUI.tscn") const CHAT_UI_NODE_NAME: String = "ChatUI" const UI_LAYER_NODE_NAME: String = "UILayer" const UI_LAYER_ORDER: int = 10 func _ready(): _ensure_chat_ui() # 延时一帧确保所有子节点就绪 call_deferred("_spawn_player") # 连接到多人会话 # 获取当前场景名字作为 Session ID _current_session_id = SceneManager.get_current_scene_name() if _current_session_id == "": _current_session_id = "square" # 如果是私人场景,生成唯一的 Session ID (e.g., room_123) if _current_session_id in PRIVATE_SCENES: _current_session_id = _current_session_id + "_" + str(AuthManager.current_user_id) # 如果 WebSocket 已连接,直接加入 if LocationManager._socket.get_ready_state() == WebSocketPeer.STATE_OPEN: _join_session_with_player(_current_session_id) else: # 否则等待连接成功信号 LocationManager.connected_to_server.connect(func(): _join_session_with_player(_current_session_id)) # 连接远程玩家相关信号 LocationManager.session_joined.connect(_on_session_joined) LocationManager.user_joined.connect(_on_user_joined) LocationManager.user_left.connect(_on_user_left) LocationManager.position_updated.connect(_on_position_updated) func _ensure_chat_ui() -> void: var ui_layer := get_node_or_null(UI_LAYER_NODE_NAME) as CanvasLayer if ui_layer == null: ui_layer = CanvasLayer.new() ui_layer.name = UI_LAYER_NODE_NAME ui_layer.layer = UI_LAYER_ORDER add_child(ui_layer) var chat_ui := ui_layer.get_node_or_null(CHAT_UI_NODE_NAME) if chat_ui == null: chat_ui = CHAT_UI_SCENE.instantiate() chat_ui.name = CHAT_UI_NODE_NAME ui_layer.add_child(chat_ui) if chat_ui.has_method("hide_chat"): chat_ui.call_deferred("hide_chat") 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" # 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) if user_map_id == current_map: var uid = str(user.userId) valid_user_ids.append(uid) valid_users[uid] = user # 2. 清理幽灵:移除本地有但不在有效列表中的玩家 var local_user_ids = remote_players.keys() for user_id in local_user_ids: if str(user_id) not in valid_user_ids: _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: _add_remote_player(user) func _on_user_joined(data: Dictionary): var user = data.get("user", {}) if user.has("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: _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 # 检查 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: _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: # 回退到手动设置位置 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 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 LocationManager._auth_token == "": # 轮询等待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 LocationManager.join_session(session_id, pos) # 进入会话后立即同步一次位置 await get_tree().create_timer(0.1).timeout LocationManager.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 LocationManager._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 var map_id = _current_session_id if _current_session_id != "" else "square" LocationManager.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() # 查找逻辑:优先查找名为 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 else: # 策略 C: 检查 SceneManager 是否有备用坐标 var pos_param = SceneManager.get_next_scene_position() if pos_param != null: spawn_pos = pos_param else: push_warning("BaseLevel: 出生点 '%s' 不存在,使用默认坐标 (0, 0)" % target_node_name) # 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) _local_player = player _player_spawned = true