feat:增加多角色在线功能
- 增加远程登录角色精灵 - 基于后端接口完成位置同步 - 实现多人在线以及跳转 - 增加个人房间功能
This commit is contained in:
@@ -29,7 +29,7 @@ extends RefCounted
|
||||
# ============ 信号定义 ============
|
||||
|
||||
# 登录成功信号
|
||||
signal login_success(username: String)
|
||||
signal login_success(username: String, token: String)
|
||||
|
||||
# 登录失败信号
|
||||
signal login_failed(message: String)
|
||||
@@ -80,6 +80,9 @@ var current_email: String = ""
|
||||
# 网络请求管理
|
||||
var active_request_ids: Array = []
|
||||
|
||||
# 当前登录用户ID (静态变量,全局访问)
|
||||
static var current_user_id: String = ""
|
||||
|
||||
# ============ 生命周期方法 ============
|
||||
|
||||
# 初始化管理器
|
||||
@@ -431,12 +434,20 @@ func _on_login_response(success: bool, data: Dictionary, error_info: Dictionary)
|
||||
|
||||
if result.success:
|
||||
var username = ""
|
||||
if data.has("data") and data.data.has("user") and data.data.user.has("username"):
|
||||
username = data.data.user.username
|
||||
if data.has("data") and data.data.has("user"):
|
||||
var user_data = data.data.user
|
||||
if user_data.has("username"):
|
||||
username = user_data.username
|
||||
if user_data.has("id"):
|
||||
current_user_id = user_data.id
|
||||
print("AuthManager: Current User ID set to ", current_user_id)
|
||||
|
||||
# 延迟发送登录成功信号
|
||||
await Engine.get_main_loop().create_timer(1.0).timeout
|
||||
login_success.emit(username)
|
||||
var token = ""
|
||||
if data.has("data") and data.data.has("access_token"):
|
||||
token = data.data.access_token
|
||||
login_success.emit(username, token)
|
||||
else:
|
||||
login_failed.emit(result.message)
|
||||
|
||||
@@ -451,11 +462,16 @@ func _on_verification_login_response(success: bool, data: Dictionary, error_info
|
||||
|
||||
if result.success:
|
||||
var username = ""
|
||||
if data.has("data") and data.data.has("user") and data.data.user.has("username"):
|
||||
username = data.data.user.username
|
||||
if data.has("data") and data.data.has("user"):
|
||||
var user_data = data.data.user
|
||||
if user_data.has("username"):
|
||||
username = user_data.username
|
||||
if user_data.has("id"):
|
||||
current_user_id = user_data.id
|
||||
print("AuthManager: Current User ID set to ", current_user_id)
|
||||
|
||||
await Engine.get_main_loop().create_timer(1.0).timeout
|
||||
login_success.emit(username)
|
||||
login_success.emit(username, data.get("access_token", ""))
|
||||
else:
|
||||
login_failed.emit(result.message)
|
||||
|
||||
|
||||
@@ -41,8 +41,13 @@ signal request_failed(request_id: String, error_type: String, message: String)
|
||||
# ============ 常量定义 ============
|
||||
|
||||
# API基础URL - 所有请求的根地址
|
||||
# [Remote] 正式环境地址 (实际正式项目用此地址)
|
||||
# [Remote] 正式环境地址 (实际正式项目用此地址)
|
||||
const API_BASE_URL = "https://whaletownend.xinghangee.icu"
|
||||
|
||||
# [Local] 本地调试地址 (本地调试用此地址)
|
||||
# const API_BASE_URL = "http://localhost:3000"
|
||||
|
||||
# 默认请求超时时间(秒)
|
||||
const DEFAULT_TIMEOUT = 30.0
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ signal scene_change_started(scene_name: String)
|
||||
var current_scene_name: String = "" # 当前场景名称
|
||||
var is_changing_scene: bool = false # 是否正在切换场景
|
||||
var _next_scene_position: Variant = null # 下一个场景的初始位置 (Vector2 or null)
|
||||
var _next_spawn_name: String = "" # 下一个场景的出生点名称 (String)
|
||||
|
||||
# 场景路径映射表
|
||||
# 将场景名称映射到实际的文件路径
|
||||
@@ -109,15 +110,18 @@ func change_scene(scene_name: String, use_transition: bool = true):
|
||||
if use_transition:
|
||||
await show_transition()
|
||||
|
||||
# 更新场景名称(在切换之前设置,确保新场景的 _ready 能获取正确的名称)
|
||||
current_scene_name = scene_name
|
||||
|
||||
# 执行场景切换
|
||||
var error = get_tree().change_scene_to_file(scene_path)
|
||||
if error != OK:
|
||||
print("场景切换失败: ", error)
|
||||
current_scene_name = "" # 恢复为空
|
||||
is_changing_scene = false
|
||||
return false
|
||||
|
||||
# 更新状态
|
||||
current_scene_name = scene_name
|
||||
is_changing_scene = false
|
||||
scene_changed.emit(scene_name)
|
||||
|
||||
@@ -147,6 +151,16 @@ func get_next_scene_position() -> Variant:
|
||||
_next_scene_position = null
|
||||
return pos
|
||||
|
||||
# 设置下一个场景的出生点名称
|
||||
func set_next_spawn_name(spawn_name: String) -> void:
|
||||
_next_spawn_name = spawn_name
|
||||
|
||||
# 获取并清除下一个场景的出生点名称
|
||||
func get_next_spawn_name() -> String:
|
||||
var name = _next_spawn_name
|
||||
_next_spawn_name = ""
|
||||
return name
|
||||
|
||||
|
||||
# ============ 场景注册方法 ============
|
||||
|
||||
|
||||
172
_Core/managers/WebSocketManager.gd
Normal file
172
_Core/managers/WebSocketManager.gd
Normal file
@@ -0,0 +1,172 @@
|
||||
extends Node
|
||||
|
||||
# ============================================================================
|
||||
# WebSocketManager.gd - WebSocket连接管理器
|
||||
# ============================================================================
|
||||
# 负责与后端 Native WebSocket 服务进行实时通信
|
||||
#
|
||||
# 协议文档: new_docs/game_architecture_design.md
|
||||
# 后端地址: ws://localhost:3000/location-broadcast
|
||||
# ============================================================================
|
||||
|
||||
signal connected_to_server()
|
||||
signal connection_closed()
|
||||
signal connection_error()
|
||||
signal session_joined(data: Dictionary)
|
||||
signal user_joined(data: Dictionary)
|
||||
signal user_left(data: Dictionary)
|
||||
signal position_updated(data: Dictionary)
|
||||
|
||||
const WS_URL = "wss://whaletownend.xinghangee.icu/location-broadcast"
|
||||
const PING_INTERVAL = 25.0 # 秒
|
||||
|
||||
var _socket: WebSocketPeer
|
||||
var _connected: bool = false
|
||||
var _ping_timer: float = 0.0
|
||||
var _auth_token: String = ""
|
||||
|
||||
func _ready():
|
||||
_socket = WebSocketPeer.new()
|
||||
process_mode = Node.PROCESS_MODE_ALWAYS # 保证暂停时也能处理网络
|
||||
print("WebSocketManager Initialized")
|
||||
|
||||
func _process(delta):
|
||||
_socket.poll()
|
||||
var state = _socket.get_ready_state()
|
||||
|
||||
if state == WebSocketPeer.STATE_OPEN:
|
||||
if not _connected:
|
||||
_on_connected()
|
||||
|
||||
# 处理接收到的数据包
|
||||
while _socket.get_available_packet_count() > 0:
|
||||
var packet = _socket.get_packet()
|
||||
_handle_packet(packet)
|
||||
|
||||
# 心跳处理
|
||||
_ping_timer += delta
|
||||
if _ping_timer >= PING_INTERVAL:
|
||||
_send_heartbeat()
|
||||
_ping_timer = 0.0
|
||||
|
||||
elif state == WebSocketPeer.STATE_CLOSED:
|
||||
if _connected:
|
||||
_on_disconnected()
|
||||
|
||||
func connect_to_server():
|
||||
if _socket.get_ready_state() == WebSocketPeer.STATE_OPEN:
|
||||
print("WebSocket 已经是连接状态")
|
||||
return
|
||||
|
||||
print("正在连接 WebSocket: ", WS_URL)
|
||||
var err = _socket.connect_to_url(WS_URL)
|
||||
if err != OK:
|
||||
print("WebSocket 连接请求失败: ", err)
|
||||
connection_error.emit()
|
||||
else:
|
||||
# Godot WebSocket connect is non-blocking, wait for state change in _process
|
||||
pass
|
||||
|
||||
func close_connection():
|
||||
_socket.close()
|
||||
|
||||
func set_auth_token(token: String):
|
||||
_auth_token = token
|
||||
|
||||
# ============ 协议发送 ============
|
||||
|
||||
func send_packet(event: String, data: Dictionary):
|
||||
if _socket.get_ready_state() != WebSocketPeer.STATE_OPEN:
|
||||
return
|
||||
|
||||
var message = {
|
||||
"event": event,
|
||||
"data": data
|
||||
}
|
||||
var json_str = JSON.stringify(message)
|
||||
_socket.put_packet(json_str.to_utf8_buffer())
|
||||
|
||||
func join_session(map_id: String, initial_pos: Vector2):
|
||||
var data = {
|
||||
"sessionId": map_id,
|
||||
"initialPosition": {
|
||||
"x": initial_pos.x,
|
||||
"y": initial_pos.y,
|
||||
"mapId": map_id
|
||||
},
|
||||
"token": _auth_token
|
||||
}
|
||||
print("发送加入会话请求: ", map_id, " mapId Payload: ", data.initialPosition.mapId)
|
||||
send_packet("join_session", data)
|
||||
|
||||
func leave_session(map_id: String):
|
||||
print("发送离开会话请求: ", map_id)
|
||||
send_packet("leave_session", {"sessionId": map_id})
|
||||
|
||||
func send_position_update(map_id: String, pos: Vector2, anim_data: Dictionary = {}):
|
||||
var data = {
|
||||
"x": pos.x,
|
||||
"y": pos.y,
|
||||
"mapId": map_id,
|
||||
"metadata": anim_data
|
||||
}
|
||||
# print("发送位置更新: ", map_id)
|
||||
if map_id == "":
|
||||
print("WARNING: Sending position update with EMPTY mapId! Pos: ", pos)
|
||||
send_packet("position_update", data)
|
||||
|
||||
func _send_heartbeat():
|
||||
send_packet("heartbeat", {"timestamp": Time.get_unix_time_from_system()})
|
||||
|
||||
# ============ 事件处理 ============
|
||||
|
||||
func _on_connected():
|
||||
_connected = true
|
||||
print("WebSocket 连接成功!")
|
||||
connected_to_server.emit()
|
||||
|
||||
func _on_disconnected():
|
||||
_connected = false
|
||||
var code = _socket.get_close_code()
|
||||
var reason = _socket.get_close_reason()
|
||||
print("WebSocket 连接断开. Code: %d, Reason: %s" % [code, reason])
|
||||
connection_closed.emit()
|
||||
|
||||
func _handle_packet(packet: PackedByteArray):
|
||||
var json_str = packet.get_string_from_utf8()
|
||||
var json = JSON.new()
|
||||
var err = json.parse(json_str)
|
||||
|
||||
if err != OK:
|
||||
print("JSON 解析失败: ", json.get_error_message())
|
||||
return
|
||||
|
||||
var message = json.data
|
||||
if not message is Dictionary or not message.has("event"):
|
||||
return
|
||||
|
||||
var event = message["event"]
|
||||
var data = message.get("data", {})
|
||||
|
||||
if event != "heartbeat_response":
|
||||
print("WebSocket Rx: ", event) # Debug logs for all events
|
||||
|
||||
match event:
|
||||
"session_joined":
|
||||
session_joined.emit(data)
|
||||
print("加入会话成功,当前房间人数: ", data.get("users", []).size())
|
||||
"user_joined":
|
||||
user_joined.emit(data)
|
||||
print("用户加入: ", data.get("userId"))
|
||||
"user_left":
|
||||
user_left.emit(data)
|
||||
print("用户离开: ", data.get("userId"))
|
||||
"position_update":
|
||||
print("WebSocket Rx position_update: ", data.get("userId", "unknown"))
|
||||
position_updated.emit(data)
|
||||
"heartbeat_response":
|
||||
pass # 静默处理
|
||||
"error":
|
||||
print("WebSocket 错误事件: ", JSON.stringify(data))
|
||||
_:
|
||||
print("未处理的 WebSocket 事件: ", event)
|
||||
1
_Core/managers/WebSocketManager.gd.uid
Normal file
1
_Core/managers/WebSocketManager.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://stpl2jdeqo0d
|
||||
Reference in New Issue
Block a user