Files
whale-town-front/scenes/Maps/BaseLevel.gd
2026-02-07 14:11:00 +08:00

288 lines
9.3 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.
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