forked from datawhale/whale-town-front
Merge pull request 'feature/auth-system-refactor' (#8) from feature/auth-system-refactor into main
Reviewed-on: datawhale/whale-town-front#8
This commit is contained in:
@@ -16,6 +16,8 @@ class_name ProjectPaths
|
|||||||
const CORE_ROOT = "res://_Core/"
|
const CORE_ROOT = "res://_Core/"
|
||||||
const CORE_MANAGERS = CORE_ROOT + "managers/"
|
const CORE_MANAGERS = CORE_ROOT + "managers/"
|
||||||
const CORE_SYSTEMS = CORE_ROOT + "systems/"
|
const CORE_SYSTEMS = CORE_ROOT + "systems/"
|
||||||
|
const CORE_COMPONENTS = CORE_ROOT + "components/"
|
||||||
|
const CORE_UTILS = CORE_ROOT + "utils/"
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# 场景路径
|
# 场景路径
|
||||||
|
|||||||
589
_Core/managers/AuthManager.gd
Normal file
589
_Core/managers/AuthManager.gd
Normal file
@@ -0,0 +1,589 @@
|
|||||||
|
class_name AuthManager
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# AuthManager.gd - 认证管理器
|
||||||
|
# ============================================================================
|
||||||
|
# 认证系统的业务逻辑管理器,负责处理所有认证相关的业务逻辑
|
||||||
|
#
|
||||||
|
# 核心职责:
|
||||||
|
# - 用户登录业务逻辑(密码登录 + 验证码登录)
|
||||||
|
# - 用户注册业务逻辑
|
||||||
|
# - 表单验证逻辑
|
||||||
|
# - 验证码管理逻辑
|
||||||
|
# - 网络请求管理
|
||||||
|
# - 响应处理和状态管理
|
||||||
|
#
|
||||||
|
# 使用方式:
|
||||||
|
# var auth_manager = AuthManager.new()
|
||||||
|
# auth_manager.login_success.connect(_on_login_success)
|
||||||
|
# auth_manager.execute_password_login(username, password)
|
||||||
|
#
|
||||||
|
# 注意事项:
|
||||||
|
# - 这是业务逻辑层,不包含任何UI相关代码
|
||||||
|
# - 通过信号与UI层通信
|
||||||
|
# - 所有验证逻辑都在这里实现
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
# ============ 信号定义 ============
|
||||||
|
|
||||||
|
# 登录成功信号
|
||||||
|
signal login_success(username: String)
|
||||||
|
|
||||||
|
# 登录失败信号
|
||||||
|
signal login_failed(message: String)
|
||||||
|
|
||||||
|
# 注册成功信号
|
||||||
|
signal register_success(message: String)
|
||||||
|
|
||||||
|
# 注册失败信号
|
||||||
|
signal register_failed(message: String)
|
||||||
|
|
||||||
|
# 验证码发送成功信号
|
||||||
|
signal verification_code_sent(message: String)
|
||||||
|
|
||||||
|
# 验证码发送失败信号
|
||||||
|
signal verification_code_failed(message: String)
|
||||||
|
|
||||||
|
# 表单验证失败信号
|
||||||
|
signal form_validation_failed(field: String, message: String)
|
||||||
|
|
||||||
|
# 网络状态变化信号
|
||||||
|
signal network_status_changed(is_connected: bool, message: String)
|
||||||
|
|
||||||
|
# 按钮状态变化信号
|
||||||
|
signal button_state_changed(button_name: String, is_loading: bool, text: String)
|
||||||
|
|
||||||
|
# Toast消息信号
|
||||||
|
signal show_toast_message(message: String, is_success: bool)
|
||||||
|
|
||||||
|
# ============ 枚举定义 ============
|
||||||
|
|
||||||
|
# 登录模式枚举
|
||||||
|
enum LoginMode {
|
||||||
|
PASSWORD, # 密码登录模式
|
||||||
|
VERIFICATION # 验证码登录模式
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============ 成员变量 ============
|
||||||
|
|
||||||
|
# 登录状态
|
||||||
|
var current_login_mode: LoginMode = LoginMode.PASSWORD
|
||||||
|
var is_processing: bool = false
|
||||||
|
|
||||||
|
# 验证码管理
|
||||||
|
var verification_codes_sent: Dictionary = {}
|
||||||
|
var code_cooldown: float = 60.0
|
||||||
|
var current_email: String = ""
|
||||||
|
|
||||||
|
# 网络请求管理
|
||||||
|
var active_request_ids: Array = []
|
||||||
|
|
||||||
|
# ============ 生命周期方法 ============
|
||||||
|
|
||||||
|
# 初始化管理器
|
||||||
|
func _init():
|
||||||
|
print("AuthManager 初始化完成")
|
||||||
|
|
||||||
|
# 清理资源
|
||||||
|
func cleanup():
|
||||||
|
# 取消所有活动的网络请求
|
||||||
|
for request_id in active_request_ids:
|
||||||
|
NetworkManager.cancel_request(request_id)
|
||||||
|
active_request_ids.clear()
|
||||||
|
|
||||||
|
# ============ 登录相关方法 ============
|
||||||
|
|
||||||
|
# 执行密码登录
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# username: String - 用户名/邮箱
|
||||||
|
# password: String - 密码
|
||||||
|
#
|
||||||
|
# 功能:
|
||||||
|
# - 验证输入参数
|
||||||
|
# - 发送登录请求
|
||||||
|
# - 处理响应结果
|
||||||
|
func execute_password_login(username: String, password: String):
|
||||||
|
if is_processing:
|
||||||
|
show_toast_message.emit("请等待当前操作完成", false)
|
||||||
|
return
|
||||||
|
|
||||||
|
# 验证输入
|
||||||
|
var validation_result = validate_login_inputs(username, password)
|
||||||
|
if not validation_result.valid:
|
||||||
|
form_validation_failed.emit(validation_result.field, validation_result.message)
|
||||||
|
return
|
||||||
|
|
||||||
|
# 设置处理状态
|
||||||
|
is_processing = true
|
||||||
|
button_state_changed.emit("main_btn", true, "登录中...")
|
||||||
|
show_toast_message.emit("正在验证登录信息...", true)
|
||||||
|
|
||||||
|
# 发送网络请求
|
||||||
|
var request_id = NetworkManager.login(username, password, _on_login_response)
|
||||||
|
if request_id != "":
|
||||||
|
active_request_ids.append(request_id)
|
||||||
|
else:
|
||||||
|
_reset_login_state()
|
||||||
|
show_toast_message.emit("网络请求失败", false)
|
||||||
|
|
||||||
|
# 执行验证码登录
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# identifier: String - 用户标识符
|
||||||
|
# verification_code: String - 验证码
|
||||||
|
func execute_verification_login(identifier: String, verification_code: String):
|
||||||
|
if is_processing:
|
||||||
|
show_toast_message.emit("请等待当前操作完成", false)
|
||||||
|
return
|
||||||
|
|
||||||
|
# 验证输入
|
||||||
|
if identifier.is_empty():
|
||||||
|
form_validation_failed.emit("username", "请输入用户名/手机/邮箱")
|
||||||
|
return
|
||||||
|
|
||||||
|
if verification_code.is_empty():
|
||||||
|
form_validation_failed.emit("verification", "请输入验证码")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 设置处理状态
|
||||||
|
is_processing = true
|
||||||
|
button_state_changed.emit("main_btn", true, "登录中...")
|
||||||
|
show_toast_message.emit("正在验证验证码...", true)
|
||||||
|
|
||||||
|
# 发送网络请求
|
||||||
|
var request_id = NetworkManager.verification_code_login(identifier, verification_code, _on_verification_login_response)
|
||||||
|
if request_id != "":
|
||||||
|
active_request_ids.append(request_id)
|
||||||
|
else:
|
||||||
|
_reset_login_state()
|
||||||
|
show_toast_message.emit("网络请求失败", false)
|
||||||
|
|
||||||
|
# 切换登录模式
|
||||||
|
func toggle_login_mode():
|
||||||
|
if current_login_mode == LoginMode.PASSWORD:
|
||||||
|
current_login_mode = LoginMode.VERIFICATION
|
||||||
|
else:
|
||||||
|
current_login_mode = LoginMode.PASSWORD
|
||||||
|
|
||||||
|
# 获取当前登录模式
|
||||||
|
func get_current_login_mode() -> LoginMode:
|
||||||
|
return current_login_mode
|
||||||
|
|
||||||
|
# ============ 注册相关方法 ============
|
||||||
|
|
||||||
|
# 执行用户注册
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# username: String - 用户名
|
||||||
|
# email: String - 邮箱
|
||||||
|
# password: String - 密码
|
||||||
|
# confirm_password: String - 确认密码
|
||||||
|
# verification_code: String - 邮箱验证码
|
||||||
|
func execute_register(username: String, email: String, password: String, confirm_password: String, verification_code: String):
|
||||||
|
if is_processing:
|
||||||
|
show_toast_message.emit("请等待当前操作完成", false)
|
||||||
|
return
|
||||||
|
|
||||||
|
# 验证注册表单
|
||||||
|
var validation_result = validate_register_form(username, email, password, confirm_password, verification_code)
|
||||||
|
if not validation_result.valid:
|
||||||
|
form_validation_failed.emit(validation_result.field, validation_result.message)
|
||||||
|
return
|
||||||
|
|
||||||
|
# 设置处理状态
|
||||||
|
is_processing = true
|
||||||
|
button_state_changed.emit("register_btn", true, "注册中...")
|
||||||
|
show_toast_message.emit("正在创建账户...", true)
|
||||||
|
|
||||||
|
# 发送注册请求
|
||||||
|
var request_id = NetworkManager.register(username, password, username, email, verification_code, _on_register_response)
|
||||||
|
if request_id != "":
|
||||||
|
active_request_ids.append(request_id)
|
||||||
|
else:
|
||||||
|
_reset_register_state()
|
||||||
|
show_toast_message.emit("网络请求失败", false)
|
||||||
|
|
||||||
|
# ============ 验证码相关方法 ============
|
||||||
|
|
||||||
|
# 发送邮箱验证码
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# email: String - 邮箱地址
|
||||||
|
func send_email_verification_code(email: String):
|
||||||
|
# 验证邮箱格式
|
||||||
|
var email_validation = validate_email(email)
|
||||||
|
if not email_validation.valid:
|
||||||
|
form_validation_failed.emit("email", email_validation.message)
|
||||||
|
return
|
||||||
|
|
||||||
|
# 检查冷却时间
|
||||||
|
if not _can_send_verification_code(email):
|
||||||
|
var remaining = get_remaining_cooldown_time(email)
|
||||||
|
show_toast_message.emit("该邮箱请等待 %d 秒后再次发送" % remaining, false)
|
||||||
|
return
|
||||||
|
|
||||||
|
# 记录发送状态
|
||||||
|
_record_verification_code_sent(email)
|
||||||
|
|
||||||
|
# 发送请求
|
||||||
|
var request_id = NetworkManager.send_email_verification(email, _on_send_code_response)
|
||||||
|
if request_id != "":
|
||||||
|
active_request_ids.append(request_id)
|
||||||
|
else:
|
||||||
|
_reset_verification_code_state(email)
|
||||||
|
show_toast_message.emit("网络请求失败", false)
|
||||||
|
|
||||||
|
# 发送登录验证码
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# identifier: String - 用户标识符
|
||||||
|
func send_login_verification_code(identifier: String):
|
||||||
|
if identifier.is_empty():
|
||||||
|
form_validation_failed.emit("username", "请先输入用户名/手机/邮箱")
|
||||||
|
return
|
||||||
|
|
||||||
|
button_state_changed.emit("get_code_btn", true, "发送中...")
|
||||||
|
show_toast_message.emit("正在发送登录验证码...", true)
|
||||||
|
|
||||||
|
var request_id = NetworkManager.send_login_verification_code(identifier, _on_send_login_code_response)
|
||||||
|
if request_id != "":
|
||||||
|
active_request_ids.append(request_id)
|
||||||
|
else:
|
||||||
|
button_state_changed.emit("get_code_btn", false, "获取验证码")
|
||||||
|
show_toast_message.emit("网络请求失败", false)
|
||||||
|
|
||||||
|
# 发送密码重置验证码
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# identifier: String - 用户标识符
|
||||||
|
func send_password_reset_code(identifier: String):
|
||||||
|
if identifier.is_empty():
|
||||||
|
show_toast_message.emit("请先输入邮箱或手机号", false)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not _is_valid_identifier(identifier):
|
||||||
|
show_toast_message.emit("请输入有效的邮箱或手机号", false)
|
||||||
|
return
|
||||||
|
|
||||||
|
button_state_changed.emit("forgot_password_btn", true, "发送中...")
|
||||||
|
show_toast_message.emit("正在发送密码重置验证码...", true)
|
||||||
|
|
||||||
|
var request_id = NetworkManager.forgot_password(identifier, _on_forgot_password_response)
|
||||||
|
if request_id != "":
|
||||||
|
active_request_ids.append(request_id)
|
||||||
|
else:
|
||||||
|
button_state_changed.emit("forgot_password_btn", false, "忘记密码")
|
||||||
|
show_toast_message.emit("网络请求失败", false)
|
||||||
|
|
||||||
|
# ============ 验证方法 ============
|
||||||
|
|
||||||
|
# 验证登录输入
|
||||||
|
func validate_login_inputs(username: String, password: String) -> Dictionary:
|
||||||
|
var result = {"valid": false, "field": "", "message": ""}
|
||||||
|
|
||||||
|
if username.is_empty():
|
||||||
|
result.field = "username"
|
||||||
|
result.message = "用户名不能为空"
|
||||||
|
return result
|
||||||
|
|
||||||
|
if password.is_empty():
|
||||||
|
result.field = "password"
|
||||||
|
result.message = "密码不能为空"
|
||||||
|
return result
|
||||||
|
|
||||||
|
result.valid = true
|
||||||
|
return result
|
||||||
|
|
||||||
|
# 验证注册表单
|
||||||
|
func validate_register_form(username: String, email: String, password: String, confirm_password: String, verification_code: String) -> Dictionary:
|
||||||
|
var result = {"valid": false, "field": "", "message": ""}
|
||||||
|
|
||||||
|
# 验证用户名
|
||||||
|
var username_validation = validate_username(username)
|
||||||
|
if not username_validation.valid:
|
||||||
|
result.field = "username"
|
||||||
|
result.message = username_validation.message
|
||||||
|
return result
|
||||||
|
|
||||||
|
# 验证邮箱
|
||||||
|
var email_validation = validate_email(email)
|
||||||
|
if not email_validation.valid:
|
||||||
|
result.field = "email"
|
||||||
|
result.message = email_validation.message
|
||||||
|
return result
|
||||||
|
|
||||||
|
# 验证密码
|
||||||
|
var password_validation = validate_password(password)
|
||||||
|
if not password_validation.valid:
|
||||||
|
result.field = "password"
|
||||||
|
result.message = password_validation.message
|
||||||
|
return result
|
||||||
|
|
||||||
|
# 验证确认密码
|
||||||
|
var confirm_validation = validate_confirm_password(password, confirm_password)
|
||||||
|
if not confirm_validation.valid:
|
||||||
|
result.field = "confirm"
|
||||||
|
result.message = confirm_validation.message
|
||||||
|
return result
|
||||||
|
|
||||||
|
# 验证验证码
|
||||||
|
var code_validation = validate_verification_code(verification_code)
|
||||||
|
if not code_validation.valid:
|
||||||
|
result.field = "verification"
|
||||||
|
result.message = code_validation.message
|
||||||
|
return result
|
||||||
|
|
||||||
|
# 检查是否已发送验证码
|
||||||
|
if not _has_sent_verification_code(email):
|
||||||
|
result.field = "verification"
|
||||||
|
result.message = "请先获取邮箱验证码"
|
||||||
|
return result
|
||||||
|
|
||||||
|
result.valid = true
|
||||||
|
return result
|
||||||
|
|
||||||
|
# 验证用户名
|
||||||
|
func validate_username(username: String) -> Dictionary:
|
||||||
|
var result = {"valid": false, "message": ""}
|
||||||
|
|
||||||
|
if username.is_empty():
|
||||||
|
result.message = "用户名不能为空"
|
||||||
|
return result
|
||||||
|
|
||||||
|
if not StringUtils.is_valid_username(username):
|
||||||
|
if username.length() > 50:
|
||||||
|
result.message = "用户名长度不能超过50字符"
|
||||||
|
else:
|
||||||
|
result.message = "用户名只能包含字母、数字和下划线"
|
||||||
|
return result
|
||||||
|
|
||||||
|
result.valid = true
|
||||||
|
return result
|
||||||
|
|
||||||
|
# 验证邮箱
|
||||||
|
func validate_email(email: String) -> Dictionary:
|
||||||
|
var result = {"valid": false, "message": ""}
|
||||||
|
|
||||||
|
if email.is_empty():
|
||||||
|
result.message = "邮箱不能为空"
|
||||||
|
return result
|
||||||
|
|
||||||
|
if not StringUtils.is_valid_email(email):
|
||||||
|
result.message = "请输入有效的邮箱地址"
|
||||||
|
return result
|
||||||
|
|
||||||
|
result.valid = true
|
||||||
|
return result
|
||||||
|
|
||||||
|
# 验证密码
|
||||||
|
func validate_password(password: String) -> Dictionary:
|
||||||
|
return StringUtils.validate_password_strength(password)
|
||||||
|
|
||||||
|
# 验证确认密码
|
||||||
|
func validate_confirm_password(password: String, confirm: String) -> Dictionary:
|
||||||
|
var result = {"valid": false, "message": ""}
|
||||||
|
|
||||||
|
if confirm.is_empty():
|
||||||
|
result.message = "确认密码不能为空"
|
||||||
|
return result
|
||||||
|
|
||||||
|
if password != confirm:
|
||||||
|
result.message = "两次输入的密码不一致"
|
||||||
|
return result
|
||||||
|
|
||||||
|
result.valid = true
|
||||||
|
return result
|
||||||
|
|
||||||
|
# 验证验证码
|
||||||
|
func validate_verification_code(code: String) -> Dictionary:
|
||||||
|
var result = {"valid": false, "message": ""}
|
||||||
|
|
||||||
|
if code.is_empty():
|
||||||
|
result.message = "验证码不能为空"
|
||||||
|
return result
|
||||||
|
|
||||||
|
if code.length() != 6:
|
||||||
|
result.message = "验证码必须是6位数字"
|
||||||
|
return result
|
||||||
|
|
||||||
|
for i in range(code.length()):
|
||||||
|
var character = code[i]
|
||||||
|
if not (character >= '0' and character <= '9'):
|
||||||
|
result.message = "验证码必须是6位数字"
|
||||||
|
return result
|
||||||
|
|
||||||
|
result.valid = true
|
||||||
|
return result
|
||||||
|
|
||||||
|
# ============ 网络响应处理 ============
|
||||||
|
|
||||||
|
# 处理登录响应
|
||||||
|
func _on_login_response(success: bool, data: Dictionary, error_info: Dictionary):
|
||||||
|
_reset_login_state()
|
||||||
|
|
||||||
|
var result = ResponseHandler.handle_login_response(success, data, error_info)
|
||||||
|
|
||||||
|
if result.should_show_toast:
|
||||||
|
show_toast_message.emit(result.message, result.success)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
# 延迟发送登录成功信号
|
||||||
|
await Engine.get_main_loop().create_timer(1.0).timeout
|
||||||
|
login_success.emit(username)
|
||||||
|
else:
|
||||||
|
login_failed.emit(result.message)
|
||||||
|
|
||||||
|
# 处理验证码登录响应
|
||||||
|
func _on_verification_login_response(success: bool, data: Dictionary, error_info: Dictionary):
|
||||||
|
_reset_login_state()
|
||||||
|
|
||||||
|
var result = ResponseHandler.handle_verification_code_login_response(success, data, error_info)
|
||||||
|
|
||||||
|
if result.should_show_toast:
|
||||||
|
show_toast_message.emit(result.message, result.success)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
await Engine.get_main_loop().create_timer(1.0).timeout
|
||||||
|
login_success.emit(username)
|
||||||
|
else:
|
||||||
|
login_failed.emit(result.message)
|
||||||
|
|
||||||
|
# 处理注册响应
|
||||||
|
func _on_register_response(success: bool, data: Dictionary, error_info: Dictionary):
|
||||||
|
_reset_register_state()
|
||||||
|
|
||||||
|
var result = ResponseHandler.handle_register_response(success, data, error_info)
|
||||||
|
|
||||||
|
if result.should_show_toast:
|
||||||
|
show_toast_message.emit(result.message, result.success)
|
||||||
|
|
||||||
|
if result.success:
|
||||||
|
register_success.emit(result.message)
|
||||||
|
else:
|
||||||
|
register_failed.emit(result.message)
|
||||||
|
|
||||||
|
# 处理发送验证码响应
|
||||||
|
func _on_send_code_response(success: bool, data: Dictionary, error_info: Dictionary):
|
||||||
|
var result = ResponseHandler.handle_send_verification_code_response(success, data, error_info)
|
||||||
|
|
||||||
|
if result.should_show_toast:
|
||||||
|
show_toast_message.emit(result.message, result.success)
|
||||||
|
|
||||||
|
if result.success:
|
||||||
|
verification_code_sent.emit(result.message)
|
||||||
|
else:
|
||||||
|
verification_code_failed.emit(result.message)
|
||||||
|
_reset_verification_code_state(current_email)
|
||||||
|
|
||||||
|
# 处理发送登录验证码响应
|
||||||
|
func _on_send_login_code_response(success: bool, data: Dictionary, error_info: Dictionary):
|
||||||
|
button_state_changed.emit("get_code_btn", false, "获取验证码")
|
||||||
|
|
||||||
|
var result = ResponseHandler.handle_send_login_code_response(success, data, error_info)
|
||||||
|
|
||||||
|
if result.should_show_toast:
|
||||||
|
show_toast_message.emit(result.message, result.success)
|
||||||
|
|
||||||
|
# 处理忘记密码响应
|
||||||
|
func _on_forgot_password_response(success: bool, data: Dictionary, error_info: Dictionary):
|
||||||
|
button_state_changed.emit("forgot_password_btn", false, "忘记密码")
|
||||||
|
|
||||||
|
var result = ResponseHandler.handle_send_login_code_response(success, data, error_info)
|
||||||
|
|
||||||
|
if result.should_show_toast:
|
||||||
|
show_toast_message.emit(result.message, result.success)
|
||||||
|
|
||||||
|
# ============ 网络测试 ============
|
||||||
|
|
||||||
|
# 测试网络连接
|
||||||
|
func test_network_connection():
|
||||||
|
var request_id = NetworkManager.get_app_status(_on_network_test_response)
|
||||||
|
if request_id != "":
|
||||||
|
active_request_ids.append(request_id)
|
||||||
|
|
||||||
|
# 处理网络测试响应
|
||||||
|
func _on_network_test_response(success: bool, data: Dictionary, error_info: Dictionary):
|
||||||
|
var result = ResponseHandler.handle_network_test_response(success, data, error_info)
|
||||||
|
network_status_changed.emit(result.success, result.message)
|
||||||
|
|
||||||
|
# ============ 私有辅助方法 ============
|
||||||
|
|
||||||
|
# 重置登录状态
|
||||||
|
func _reset_login_state():
|
||||||
|
is_processing = false
|
||||||
|
button_state_changed.emit("main_btn", false, "进入小镇")
|
||||||
|
|
||||||
|
# 重置注册状态
|
||||||
|
func _reset_register_state():
|
||||||
|
is_processing = false
|
||||||
|
button_state_changed.emit("register_btn", false, "注册")
|
||||||
|
|
||||||
|
# 检查是否可以发送验证码
|
||||||
|
func _can_send_verification_code(email: String) -> bool:
|
||||||
|
if not verification_codes_sent.has(email):
|
||||||
|
return true
|
||||||
|
|
||||||
|
var email_data = verification_codes_sent[email]
|
||||||
|
if not email_data.sent:
|
||||||
|
return true
|
||||||
|
|
||||||
|
var current_time = Time.get_time_dict_from_system()
|
||||||
|
var current_timestamp = current_time.hour * 3600 + current_time.minute * 60 + current_time.second
|
||||||
|
|
||||||
|
return (current_timestamp - email_data.time) >= code_cooldown
|
||||||
|
|
||||||
|
# 获取剩余冷却时间
|
||||||
|
func get_remaining_cooldown_time(email: String) -> int:
|
||||||
|
if not verification_codes_sent.has(email):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
var email_data = verification_codes_sent[email]
|
||||||
|
var current_time = Time.get_time_dict_from_system()
|
||||||
|
var current_timestamp = current_time.hour * 3600 + current_time.minute * 60 + current_time.second
|
||||||
|
|
||||||
|
return int(code_cooldown - (current_timestamp - email_data.time))
|
||||||
|
|
||||||
|
# 记录验证码发送状态
|
||||||
|
func _record_verification_code_sent(email: String):
|
||||||
|
var current_time = Time.get_time_dict_from_system()
|
||||||
|
var current_timestamp = current_time.hour * 3600 + current_time.minute * 60 + current_time.second
|
||||||
|
|
||||||
|
if not verification_codes_sent.has(email):
|
||||||
|
verification_codes_sent[email] = {}
|
||||||
|
|
||||||
|
verification_codes_sent[email].sent = true
|
||||||
|
verification_codes_sent[email].time = current_timestamp
|
||||||
|
current_email = email
|
||||||
|
|
||||||
|
# 重置验证码状态
|
||||||
|
func _reset_verification_code_state(email: String):
|
||||||
|
if verification_codes_sent.has(email):
|
||||||
|
verification_codes_sent[email].sent = false
|
||||||
|
|
||||||
|
# 检查是否已发送验证码
|
||||||
|
func _has_sent_verification_code(email: String) -> bool:
|
||||||
|
if not verification_codes_sent.has(email):
|
||||||
|
return false
|
||||||
|
|
||||||
|
return verification_codes_sent[email].get("sent", false)
|
||||||
|
|
||||||
|
# 验证标识符格式
|
||||||
|
func _is_valid_identifier(identifier: String) -> bool:
|
||||||
|
return StringUtils.is_valid_email(identifier) or _is_valid_phone(identifier)
|
||||||
|
|
||||||
|
# 验证手机号格式
|
||||||
|
func _is_valid_phone(phone: String) -> bool:
|
||||||
|
var regex = RegEx.new()
|
||||||
|
regex.compile("^\\+?[1-9]\\d{1,14}$")
|
||||||
|
return regex.search(phone) != null
|
||||||
1
_Core/managers/AuthManager.gd.uid
Normal file
1
_Core/managers/AuthManager.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://bpdyraefv0yta
|
||||||
@@ -1,50 +1,142 @@
|
|||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
# 游戏管理器 - 全局游戏状态管理
|
# ============================================================================
|
||||||
# 单例模式,管理游戏的整体状态和生命周期
|
# GameManager.gd - 游戏管理器
|
||||||
|
# ============================================================================
|
||||||
|
# 全局单例管理器,负责游戏状态管理和生命周期控制
|
||||||
|
#
|
||||||
|
# 核心职责:
|
||||||
|
# - 游戏状态切换 (加载、认证、游戏中、暂停等)
|
||||||
|
# - 用户信息管理
|
||||||
|
# - 全局配置访问
|
||||||
|
# - 系统初始化和清理
|
||||||
|
#
|
||||||
|
# 使用方式:
|
||||||
|
# GameManager.change_state(GameManager.GameState.IN_GAME)
|
||||||
|
# GameManager.set_current_user("player123")
|
||||||
|
#
|
||||||
|
# 注意事项:
|
||||||
|
# - 作为自动加载单例,全局可访问
|
||||||
|
# - 状态变更会触发 game_state_changed 信号
|
||||||
|
# - 状态切换应该通过 change_state() 方法进行
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# ============ 信号定义 ============
|
||||||
|
|
||||||
|
# 游戏状态变更信号
|
||||||
|
# 参数: new_state - 新的游戏状态
|
||||||
signal game_state_changed(new_state: GameState)
|
signal game_state_changed(new_state: GameState)
|
||||||
|
|
||||||
|
# ============ 枚举定义 ============
|
||||||
|
|
||||||
|
# 游戏状态枚举
|
||||||
|
# 定义了游戏的各种运行状态
|
||||||
enum GameState {
|
enum GameState {
|
||||||
LOADING, # 加载中
|
LOADING, # 加载中 - 游戏启动时的初始化状态
|
||||||
AUTH, # 认证状态
|
AUTH, # 认证状态 - 用户登录/注册界面
|
||||||
MAIN_MENU, # 主菜单
|
MAIN_MENU, # 主菜单 - 游戏主界面
|
||||||
IN_GAME, # 游戏中
|
IN_GAME, # 游戏中 - 正在进行游戏
|
||||||
PAUSED, # 暂停
|
PAUSED, # 暂停 - 游戏暂停状态
|
||||||
SETTINGS # 设置
|
SETTINGS # 设置 - 设置界面
|
||||||
}
|
}
|
||||||
|
|
||||||
var current_state: GameState = GameState.LOADING
|
# ============ 成员变量 ============
|
||||||
var previous_state: GameState = GameState.LOADING
|
|
||||||
var current_user: String = ""
|
|
||||||
var game_version: String = "1.0.0"
|
|
||||||
|
|
||||||
|
# 状态管理
|
||||||
|
var current_state: GameState = GameState.LOADING # 当前游戏状态
|
||||||
|
var previous_state: GameState = GameState.LOADING # 上一个游戏状态
|
||||||
|
|
||||||
|
# 用户信息
|
||||||
|
var current_user: String = "" # 当前登录用户名
|
||||||
|
|
||||||
|
# 游戏配置
|
||||||
|
var game_version: String = "1.0.0" # 游戏版本号
|
||||||
|
|
||||||
|
# ============ 生命周期方法 ============
|
||||||
|
|
||||||
|
# 初始化游戏管理器
|
||||||
|
# 在节点准备就绪时调用,设置初始状态
|
||||||
func _ready():
|
func _ready():
|
||||||
print("GameManager 初始化完成")
|
print("GameManager 初始化完成")
|
||||||
change_state(GameState.AUTH)
|
change_state(GameState.AUTH) # 启动时进入认证状态
|
||||||
|
|
||||||
|
# ============ 状态管理方法 ============
|
||||||
|
|
||||||
|
# 切换游戏状态
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# new_state: GameState - 要切换到的新状态
|
||||||
|
#
|
||||||
|
# 功能:
|
||||||
|
# - 检查状态是否需要切换
|
||||||
|
# - 记录状态变更历史
|
||||||
|
# - 发送状态变更信号
|
||||||
|
# - 输出状态变更日志
|
||||||
func change_state(new_state: GameState):
|
func change_state(new_state: GameState):
|
||||||
|
# 避免重复切换到相同状态
|
||||||
if current_state == new_state:
|
if current_state == new_state:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# 记录状态变更
|
||||||
previous_state = current_state
|
previous_state = current_state
|
||||||
current_state = new_state
|
current_state = new_state
|
||||||
|
|
||||||
|
# 输出状态变更日志
|
||||||
print("游戏状态变更: ", GameState.keys()[previous_state], " -> ", GameState.keys()[current_state])
|
print("游戏状态变更: ", GameState.keys()[previous_state], " -> ", GameState.keys()[current_state])
|
||||||
|
|
||||||
|
# 发送状态变更信号
|
||||||
game_state_changed.emit(new_state)
|
game_state_changed.emit(new_state)
|
||||||
|
|
||||||
|
# 获取当前游戏状态
|
||||||
|
#
|
||||||
|
# 返回值:
|
||||||
|
# GameState - 当前的游戏状态
|
||||||
func get_current_state() -> GameState:
|
func get_current_state() -> GameState:
|
||||||
return current_state
|
return current_state
|
||||||
|
|
||||||
|
# 获取上一个游戏状态
|
||||||
|
#
|
||||||
|
# 返回值:
|
||||||
|
# GameState - 上一个游戏状态
|
||||||
|
#
|
||||||
|
# 使用场景:
|
||||||
|
# - 从暂停状态恢复时,返回到之前的状态
|
||||||
|
# - 错误处理时回退到安全状态
|
||||||
func get_previous_state() -> GameState:
|
func get_previous_state() -> GameState:
|
||||||
return previous_state
|
return previous_state
|
||||||
|
|
||||||
|
# ============ 用户管理方法 ============
|
||||||
|
|
||||||
|
# 设置当前登录用户
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# username: String - 用户名
|
||||||
|
#
|
||||||
|
# 功能:
|
||||||
|
# - 存储当前登录用户信息
|
||||||
|
# - 输出用户设置日志
|
||||||
|
#
|
||||||
|
# 注意事项:
|
||||||
|
# - 用户登录成功后调用此方法
|
||||||
|
# - 用户登出时应传入空字符串
|
||||||
func set_current_user(username: String):
|
func set_current_user(username: String):
|
||||||
current_user = username
|
current_user = username
|
||||||
print("当前用户设置为: ", username)
|
print("当前用户设置为: ", username)
|
||||||
|
|
||||||
|
# 获取当前登录用户
|
||||||
|
#
|
||||||
|
# 返回值:
|
||||||
|
# String - 当前登录的用户名,未登录时为空字符串
|
||||||
func get_current_user() -> String:
|
func get_current_user() -> String:
|
||||||
return current_user
|
return current_user
|
||||||
|
|
||||||
|
# 检查用户是否已登录
|
||||||
|
#
|
||||||
|
# 返回值:
|
||||||
|
# bool - true表示已登录,false表示未登录
|
||||||
|
#
|
||||||
|
# 使用场景:
|
||||||
|
# - 进入需要登录的功能前检查
|
||||||
|
# - UI显示逻辑判断
|
||||||
func is_user_logged_in() -> bool:
|
func is_user_logged_in() -> bool:
|
||||||
return not current_user.is_empty()
|
return not current_user.is_empty()
|
||||||
|
|||||||
@@ -1,45 +1,96 @@
|
|||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
# 网络请求管理器 - 统一处理所有HTTP请求
|
# ============================================================================
|
||||||
|
# NetworkManager.gd - 网络请求管理器
|
||||||
|
# ============================================================================
|
||||||
|
# 全局单例管理器,统一处理所有HTTP请求
|
||||||
|
#
|
||||||
|
# 核心职责:
|
||||||
|
# - 统一的HTTP请求接口 (GET, POST, PUT, DELETE, PATCH)
|
||||||
|
# - 认证相关API封装 (登录、注册、验证码等)
|
||||||
|
# - 请求状态管理和错误处理
|
||||||
|
# - 支持API v1.1.1规范的响应处理
|
||||||
|
#
|
||||||
|
# 使用方式:
|
||||||
|
# NetworkManager.login("user@example.com", "password", callback)
|
||||||
|
# var request_id = NetworkManager.get_request("/api/data", callback)
|
||||||
|
#
|
||||||
|
# 注意事项:
|
||||||
|
# - 作为自动加载单例,全局可访问
|
||||||
|
# - 所有请求都是异步的,通过回调函数或信号处理结果
|
||||||
|
# - 支持请求超时和取消功能
|
||||||
|
# - 自动处理JSON序列化和反序列化
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
# 信号定义
|
# ============ 信号定义 ============
|
||||||
|
|
||||||
|
# 请求完成信号
|
||||||
|
# 参数:
|
||||||
|
# request_id: String - 请求唯一标识符
|
||||||
|
# success: bool - 请求是否成功
|
||||||
|
# data: Dictionary - 响应数据
|
||||||
signal request_completed(request_id: String, success: bool, data: Dictionary)
|
signal request_completed(request_id: String, success: bool, data: Dictionary)
|
||||||
|
|
||||||
|
# 请求失败信号
|
||||||
|
# 参数:
|
||||||
|
# request_id: String - 请求唯一标识符
|
||||||
|
# error_type: String - 错误类型名称
|
||||||
|
# message: String - 错误消息
|
||||||
signal request_failed(request_id: String, error_type: String, message: String)
|
signal request_failed(request_id: String, error_type: String, message: String)
|
||||||
|
|
||||||
# API配置
|
# ============ 常量定义 ============
|
||||||
|
|
||||||
|
# API基础URL - 所有请求的根地址
|
||||||
const API_BASE_URL = "https://whaletownend.xinghangee.icu"
|
const API_BASE_URL = "https://whaletownend.xinghangee.icu"
|
||||||
|
|
||||||
|
# 默认请求超时时间(秒)
|
||||||
const DEFAULT_TIMEOUT = 30.0
|
const DEFAULT_TIMEOUT = 30.0
|
||||||
|
|
||||||
# 请求类型枚举
|
# ============ 枚举定义 ============
|
||||||
|
|
||||||
|
# HTTP请求方法枚举
|
||||||
enum RequestType {
|
enum RequestType {
|
||||||
GET,
|
GET, # 获取数据
|
||||||
POST,
|
POST, # 创建数据
|
||||||
PUT,
|
PUT, # 更新数据
|
||||||
DELETE,
|
DELETE, # 删除数据
|
||||||
PATCH
|
PATCH # 部分更新数据
|
||||||
}
|
}
|
||||||
|
|
||||||
# 错误类型枚举
|
# 错误类型枚举
|
||||||
|
# 用于分类不同类型的网络错误
|
||||||
enum ErrorType {
|
enum ErrorType {
|
||||||
NETWORK_ERROR, # 网络连接错误
|
NETWORK_ERROR, # 网络连接错误 - 无法连接到服务器
|
||||||
TIMEOUT_ERROR, # 请求超时
|
TIMEOUT_ERROR, # 请求超时 - 服务器响应时间过长
|
||||||
PARSE_ERROR, # JSON解析错误
|
PARSE_ERROR, # JSON解析错误 - 服务器返回格式错误
|
||||||
HTTP_ERROR, # HTTP状态码错误
|
HTTP_ERROR, # HTTP状态码错误 - 4xx, 5xx状态码
|
||||||
BUSINESS_ERROR # 业务逻辑错误
|
BUSINESS_ERROR # 业务逻辑错误 - API返回的业务错误
|
||||||
}
|
}
|
||||||
|
|
||||||
# 请求状态
|
# ============ 请求信息类 ============
|
||||||
class RequestInfo:
|
|
||||||
var id: String
|
|
||||||
var url: String
|
|
||||||
var method: RequestType
|
|
||||||
var headers: PackedStringArray
|
|
||||||
var body: String
|
|
||||||
var timeout: float
|
|
||||||
var start_time: float
|
|
||||||
var http_request: HTTPRequest
|
|
||||||
var callback: Callable
|
|
||||||
|
|
||||||
|
# 请求信息封装类
|
||||||
|
# 存储单个HTTP请求的所有相关信息
|
||||||
|
class RequestInfo:
|
||||||
|
var id: String # 请求唯一标识符
|
||||||
|
var url: String # 完整的请求URL
|
||||||
|
var method: RequestType # HTTP请求方法
|
||||||
|
var headers: PackedStringArray # 请求头数组
|
||||||
|
var body: String # 请求体内容
|
||||||
|
var timeout: float # 超时时间(秒)
|
||||||
|
var start_time: float # 请求开始时间戳
|
||||||
|
var http_request: HTTPRequest # Godot HTTPRequest节点引用
|
||||||
|
var callback: Callable # 完成时的回调函数
|
||||||
|
|
||||||
|
# 构造函数
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# request_id: String - 请求唯一标识符
|
||||||
|
# request_url: String - 请求URL
|
||||||
|
# request_method: RequestType - HTTP方法
|
||||||
|
# request_headers: PackedStringArray - 请求头(可选)
|
||||||
|
# request_body: String - 请求体(可选)
|
||||||
|
# request_timeout: float - 超时时间(可选,默认使用DEFAULT_TIMEOUT)
|
||||||
func _init(request_id: String, request_url: String, request_method: RequestType,
|
func _init(request_id: String, request_url: String, request_method: RequestType,
|
||||||
request_headers: PackedStringArray = [], request_body: String = "",
|
request_headers: PackedStringArray = [], request_body: String = "",
|
||||||
request_timeout: float = DEFAULT_TIMEOUT):
|
request_timeout: float = DEFAULT_TIMEOUT):
|
||||||
@@ -49,40 +100,107 @@ class RequestInfo:
|
|||||||
headers = request_headers
|
headers = request_headers
|
||||||
body = request_body
|
body = request_body
|
||||||
timeout = request_timeout
|
timeout = request_timeout
|
||||||
|
# 记录请求开始时间(简化版时间戳)
|
||||||
start_time = Time.get_time_dict_from_system().hour * 3600 + Time.get_time_dict_from_system().minute * 60 + Time.get_time_dict_from_system().second
|
start_time = Time.get_time_dict_from_system().hour * 3600 + Time.get_time_dict_from_system().minute * 60 + Time.get_time_dict_from_system().second
|
||||||
|
|
||||||
# 活动请求管理
|
# ============ 成员变量 ============
|
||||||
var active_requests: Dictionary = {}
|
|
||||||
var request_counter: int = 0
|
|
||||||
|
|
||||||
|
# 活动请求管理
|
||||||
|
var active_requests: Dictionary = {} # 存储所有活动请求 {request_id: RequestInfo}
|
||||||
|
var request_counter: int = 0 # 请求计数器,用于生成唯一ID
|
||||||
|
|
||||||
|
# ============ 生命周期方法 ============
|
||||||
|
|
||||||
|
# 初始化网络管理器
|
||||||
|
# 在节点准备就绪时调用
|
||||||
func _ready():
|
func _ready():
|
||||||
print("NetworkManager 已初始化")
|
print("NetworkManager 已初始化")
|
||||||
|
|
||||||
# ============ 公共API接口 ============
|
# ============ 公共API接口 ============
|
||||||
|
|
||||||
# 发送GET请求
|
# 发送GET请求
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# endpoint: String - API端点路径(如: "/api/users")
|
||||||
|
# callback: Callable - 完成时的回调函数(可选)
|
||||||
|
# timeout: float - 超时时间(可选,默认30秒)
|
||||||
|
#
|
||||||
|
# 返回值:
|
||||||
|
# String - 请求ID,可用于取消请求或跟踪状态
|
||||||
|
#
|
||||||
|
# 使用示例:
|
||||||
|
# var request_id = NetworkManager.get_request("/api/users", my_callback)
|
||||||
func get_request(endpoint: String, callback: Callable = Callable(), timeout: float = DEFAULT_TIMEOUT) -> String:
|
func get_request(endpoint: String, callback: Callable = Callable(), timeout: float = DEFAULT_TIMEOUT) -> String:
|
||||||
return send_request(endpoint, RequestType.GET, [], "", callback, timeout)
|
return send_request(endpoint, RequestType.GET, [], "", callback, timeout)
|
||||||
|
|
||||||
# 发送POST请求
|
# 发送POST请求
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# endpoint: String - API端点路径
|
||||||
|
# data: Dictionary - 要发送的数据(将自动转换为JSON)
|
||||||
|
# callback: Callable - 完成时的回调函数(可选)
|
||||||
|
# timeout: float - 超时时间(可选)
|
||||||
|
#
|
||||||
|
# 返回值:
|
||||||
|
# String - 请求ID
|
||||||
|
#
|
||||||
|
# 使用示例:
|
||||||
|
# var data = {"name": "张三", "age": 25}
|
||||||
|
# var request_id = NetworkManager.post_request("/api/users", data, my_callback)
|
||||||
func post_request(endpoint: String, data: Dictionary, callback: Callable = Callable(), timeout: float = DEFAULT_TIMEOUT) -> String:
|
func post_request(endpoint: String, data: Dictionary, callback: Callable = Callable(), timeout: float = DEFAULT_TIMEOUT) -> String:
|
||||||
var body = JSON.stringify(data)
|
var body = JSON.stringify(data)
|
||||||
var headers = ["Content-Type: application/json"]
|
var headers = ["Content-Type: application/json"]
|
||||||
return send_request(endpoint, RequestType.POST, headers, body, callback, timeout)
|
return send_request(endpoint, RequestType.POST, headers, body, callback, timeout)
|
||||||
|
|
||||||
# 发送PUT请求
|
# 发送PUT请求
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# endpoint: String - API端点路径
|
||||||
|
# data: Dictionary - 要更新的数据
|
||||||
|
# callback: Callable - 完成时的回调函数(可选)
|
||||||
|
# timeout: float - 超时时间(可选)
|
||||||
|
#
|
||||||
|
# 返回值:
|
||||||
|
# String - 请求ID
|
||||||
func put_request(endpoint: String, data: Dictionary, callback: Callable = Callable(), timeout: float = DEFAULT_TIMEOUT) -> String:
|
func put_request(endpoint: String, data: Dictionary, callback: Callable = Callable(), timeout: float = DEFAULT_TIMEOUT) -> String:
|
||||||
var body = JSON.stringify(data)
|
var body = JSON.stringify(data)
|
||||||
var headers = ["Content-Type: application/json"]
|
var headers = ["Content-Type: application/json"]
|
||||||
return send_request(endpoint, RequestType.PUT, headers, body, callback, timeout)
|
return send_request(endpoint, RequestType.PUT, headers, body, callback, timeout)
|
||||||
|
|
||||||
# 发送DELETE请求
|
# 发送DELETE请求
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# endpoint: String - API端点路径
|
||||||
|
# callback: Callable - 完成时的回调函数(可选)
|
||||||
|
# timeout: float - 超时时间(可选)
|
||||||
|
#
|
||||||
|
# 返回值:
|
||||||
|
# String - 请求ID
|
||||||
func delete_request(endpoint: String, callback: Callable = Callable(), timeout: float = DEFAULT_TIMEOUT) -> String:
|
func delete_request(endpoint: String, callback: Callable = Callable(), timeout: float = DEFAULT_TIMEOUT) -> String:
|
||||||
return send_request(endpoint, RequestType.DELETE, [], "", callback, timeout)
|
return send_request(endpoint, RequestType.DELETE, [], "", callback, timeout)
|
||||||
|
|
||||||
# ============ 认证相关API ============
|
# ============ 认证相关API ============
|
||||||
|
|
||||||
# 用户登录
|
# 用户登录
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# identifier: String - 用户标识符(邮箱或手机号)
|
||||||
|
# password: String - 用户密码
|
||||||
|
# callback: Callable - 完成时的回调函数(可选)
|
||||||
|
#
|
||||||
|
# 返回值:
|
||||||
|
# String - 请求ID
|
||||||
|
#
|
||||||
|
# 回调函数签名:
|
||||||
|
# func callback(success: bool, data: Dictionary, error_info: Dictionary)
|
||||||
|
#
|
||||||
|
# 使用示例:
|
||||||
|
# NetworkManager.login("user@example.com", "password123", func(success, data, error):
|
||||||
|
# if success:
|
||||||
|
# print("登录成功: ", data)
|
||||||
|
# else:
|
||||||
|
# print("登录失败: ", error.message)
|
||||||
|
# )
|
||||||
func login(identifier: String, password: String, callback: Callable = Callable()) -> String:
|
func login(identifier: String, password: String, callback: Callable = Callable()) -> String:
|
||||||
var data = {
|
var data = {
|
||||||
"identifier": identifier,
|
"identifier": identifier,
|
||||||
@@ -91,6 +209,18 @@ func login(identifier: String, password: String, callback: Callable = Callable()
|
|||||||
return post_request("/auth/login", data, callback)
|
return post_request("/auth/login", data, callback)
|
||||||
|
|
||||||
# 验证码登录
|
# 验证码登录
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# identifier: String - 用户标识符(邮箱或手机号)
|
||||||
|
# verification_code: String - 验证码
|
||||||
|
# callback: Callable - 完成时的回调函数(可选)
|
||||||
|
#
|
||||||
|
# 返回值:
|
||||||
|
# String - 请求ID
|
||||||
|
#
|
||||||
|
# 使用场景:
|
||||||
|
# - 用户忘记密码时的替代登录方式
|
||||||
|
# - 提供更安全的登录选项
|
||||||
func verification_code_login(identifier: String, verification_code: String, callback: Callable = Callable()) -> String:
|
func verification_code_login(identifier: String, verification_code: String, callback: Callable = Callable()) -> String:
|
||||||
var data = {
|
var data = {
|
||||||
"identifier": identifier,
|
"identifier": identifier,
|
||||||
@@ -99,11 +229,39 @@ func verification_code_login(identifier: String, verification_code: String, call
|
|||||||
return post_request("/auth/verification-code-login", data, callback)
|
return post_request("/auth/verification-code-login", data, callback)
|
||||||
|
|
||||||
# 发送登录验证码
|
# 发送登录验证码
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# identifier: String - 用户标识符(邮箱或手机号)
|
||||||
|
# callback: Callable - 完成时的回调函数(可选)
|
||||||
|
#
|
||||||
|
# 返回值:
|
||||||
|
# String - 请求ID
|
||||||
|
#
|
||||||
|
# 功能:
|
||||||
|
# - 向已注册用户发送登录验证码
|
||||||
|
# - 支持邮箱和手机号
|
||||||
|
# - 有频率限制保护
|
||||||
func send_login_verification_code(identifier: String, callback: Callable = Callable()) -> String:
|
func send_login_verification_code(identifier: String, callback: Callable = Callable()) -> String:
|
||||||
var data = {"identifier": identifier}
|
var data = {"identifier": identifier}
|
||||||
return post_request("/auth/send-login-verification-code", data, callback)
|
return post_request("/auth/send-login-verification-code", data, callback)
|
||||||
|
|
||||||
# 用户注册
|
# 用户注册
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# username: String - 用户名
|
||||||
|
# password: String - 密码
|
||||||
|
# nickname: String - 昵称
|
||||||
|
# email: String - 邮箱地址(可选)
|
||||||
|
# email_verification_code: String - 邮箱验证码(可选)
|
||||||
|
# callback: Callable - 完成时的回调函数(可选)
|
||||||
|
#
|
||||||
|
# 返回值:
|
||||||
|
# String - 请求ID
|
||||||
|
#
|
||||||
|
# 注意事项:
|
||||||
|
# - 如果提供邮箱,建议同时提供验证码
|
||||||
|
# - 用户名和邮箱必须唯一
|
||||||
|
# - 密码需要符合安全要求
|
||||||
func register(username: String, password: String, nickname: String, email: String = "",
|
func register(username: String, password: String, nickname: String, email: String = "",
|
||||||
email_verification_code: String = "", callback: Callable = Callable()) -> String:
|
email_verification_code: String = "", callback: Callable = Callable()) -> String:
|
||||||
var data = {
|
var data = {
|
||||||
@@ -112,6 +270,7 @@ func register(username: String, password: String, nickname: String, email: Strin
|
|||||||
"nickname": nickname
|
"nickname": nickname
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 可选参数处理
|
||||||
if email != "":
|
if email != "":
|
||||||
data["email"] = email
|
data["email"] = email
|
||||||
if email_verification_code != "":
|
if email_verification_code != "":
|
||||||
@@ -120,11 +279,35 @@ func register(username: String, password: String, nickname: String, email: Strin
|
|||||||
return post_request("/auth/register", data, callback)
|
return post_request("/auth/register", data, callback)
|
||||||
|
|
||||||
# 发送邮箱验证码
|
# 发送邮箱验证码
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# email: String - 邮箱地址
|
||||||
|
# callback: Callable - 完成时的回调函数(可选)
|
||||||
|
#
|
||||||
|
# 返回值:
|
||||||
|
# String - 请求ID
|
||||||
|
#
|
||||||
|
# 功能:
|
||||||
|
# - 向指定邮箱发送验证码
|
||||||
|
# - 用于注册时的邮箱验证
|
||||||
|
# - 支持测试模式(开发环境)
|
||||||
func send_email_verification(email: String, callback: Callable = Callable()) -> String:
|
func send_email_verification(email: String, callback: Callable = Callable()) -> String:
|
||||||
var data = {"email": email}
|
var data = {"email": email}
|
||||||
return post_request("/auth/send-email-verification", data, callback)
|
return post_request("/auth/send-email-verification", data, callback)
|
||||||
|
|
||||||
# 验证邮箱
|
# 验证邮箱
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# email: String - 邮箱地址
|
||||||
|
# verification_code: String - 验证码
|
||||||
|
# callback: Callable - 完成时的回调函数(可选)
|
||||||
|
#
|
||||||
|
# 返回值:
|
||||||
|
# String - 请求ID
|
||||||
|
#
|
||||||
|
# 功能:
|
||||||
|
# - 验证邮箱验证码的有效性
|
||||||
|
# - 通常在注册流程中使用
|
||||||
func verify_email(email: String, verification_code: String, callback: Callable = Callable()) -> String:
|
func verify_email(email: String, verification_code: String, callback: Callable = Callable()) -> String:
|
||||||
var data = {
|
var data = {
|
||||||
"email": email,
|
"email": email,
|
||||||
@@ -133,20 +316,66 @@ func verify_email(email: String, verification_code: String, callback: Callable =
|
|||||||
return post_request("/auth/verify-email", data, callback)
|
return post_request("/auth/verify-email", data, callback)
|
||||||
|
|
||||||
# 获取应用状态
|
# 获取应用状态
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# callback: Callable - 完成时的回调函数(可选)
|
||||||
|
#
|
||||||
|
# 返回值:
|
||||||
|
# String - 请求ID
|
||||||
|
#
|
||||||
|
# 功能:
|
||||||
|
# - 检查API服务器状态
|
||||||
|
# - 获取应用基本信息
|
||||||
|
# - 用于网络连接测试
|
||||||
func get_app_status(callback: Callable = Callable()) -> String:
|
func get_app_status(callback: Callable = Callable()) -> String:
|
||||||
return get_request("/", callback)
|
return get_request("/", callback)
|
||||||
|
|
||||||
# 重新发送邮箱验证码
|
# 重新发送邮箱验证码
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# email: String - 邮箱地址
|
||||||
|
# callback: Callable - 完成时的回调函数(可选)
|
||||||
|
#
|
||||||
|
# 返回值:
|
||||||
|
# String - 请求ID
|
||||||
|
#
|
||||||
|
# 使用场景:
|
||||||
|
# - 用户未收到验证码时重新发送
|
||||||
|
# - 验证码过期后重新获取
|
||||||
func resend_email_verification(email: String, callback: Callable = Callable()) -> String:
|
func resend_email_verification(email: String, callback: Callable = Callable()) -> String:
|
||||||
var data = {"email": email}
|
var data = {"email": email}
|
||||||
return post_request("/auth/resend-email-verification", data, callback)
|
return post_request("/auth/resend-email-verification", data, callback)
|
||||||
|
|
||||||
# 忘记密码 - 发送重置验证码
|
# 忘记密码 - 发送重置验证码
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# identifier: String - 用户标识符(邮箱或手机号)
|
||||||
|
# callback: Callable - 完成时的回调函数(可选)
|
||||||
|
#
|
||||||
|
# 返回值:
|
||||||
|
# String - 请求ID
|
||||||
|
#
|
||||||
|
# 功能:
|
||||||
|
# - 向用户发送密码重置验证码
|
||||||
|
# - 用于密码找回流程的第一步
|
||||||
func forgot_password(identifier: String, callback: Callable = Callable()) -> String:
|
func forgot_password(identifier: String, callback: Callable = Callable()) -> String:
|
||||||
var data = {"identifier": identifier}
|
var data = {"identifier": identifier}
|
||||||
return post_request("/auth/forgot-password", data, callback)
|
return post_request("/auth/forgot-password", data, callback)
|
||||||
|
|
||||||
# 重置密码
|
# 重置密码
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# identifier: String - 用户标识符
|
||||||
|
# verification_code: String - 重置验证码
|
||||||
|
# new_password: String - 新密码
|
||||||
|
# callback: Callable - 完成时的回调函数(可选)
|
||||||
|
#
|
||||||
|
# 返回值:
|
||||||
|
# String - 请求ID
|
||||||
|
#
|
||||||
|
# 功能:
|
||||||
|
# - 使用验证码重置用户密码
|
||||||
|
# - 密码找回流程的第二步
|
||||||
func reset_password(identifier: String, verification_code: String, new_password: String, callback: Callable = Callable()) -> String:
|
func reset_password(identifier: String, verification_code: String, new_password: String, callback: Callable = Callable()) -> String:
|
||||||
var data = {
|
var data = {
|
||||||
"identifier": identifier,
|
"identifier": identifier,
|
||||||
@@ -156,6 +385,19 @@ func reset_password(identifier: String, verification_code: String, new_password:
|
|||||||
return post_request("/auth/reset-password", data, callback)
|
return post_request("/auth/reset-password", data, callback)
|
||||||
|
|
||||||
# 修改密码
|
# 修改密码
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# user_id: String - 用户ID
|
||||||
|
# old_password: String - 旧密码
|
||||||
|
# new_password: String - 新密码
|
||||||
|
# callback: Callable - 完成时的回调函数(可选)
|
||||||
|
#
|
||||||
|
# 返回值:
|
||||||
|
# String - 请求ID
|
||||||
|
#
|
||||||
|
# 功能:
|
||||||
|
# - 已登录用户修改密码
|
||||||
|
# - 需要验证旧密码
|
||||||
func change_password(user_id: String, old_password: String, new_password: String, callback: Callable = Callable()) -> String:
|
func change_password(user_id: String, old_password: String, new_password: String, callback: Callable = Callable()) -> String:
|
||||||
var data = {
|
var data = {
|
||||||
"user_id": user_id,
|
"user_id": user_id,
|
||||||
@@ -165,6 +407,21 @@ func change_password(user_id: String, old_password: String, new_password: String
|
|||||||
return put_request("/auth/change-password", data, callback)
|
return put_request("/auth/change-password", data, callback)
|
||||||
|
|
||||||
# GitHub OAuth登录
|
# GitHub OAuth登录
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# github_id: String - GitHub用户ID
|
||||||
|
# username: String - GitHub用户名
|
||||||
|
# nickname: String - 显示昵称
|
||||||
|
# email: String - GitHub邮箱
|
||||||
|
# avatar_url: String - 头像URL(可选)
|
||||||
|
# callback: Callable - 完成时的回调函数(可选)
|
||||||
|
#
|
||||||
|
# 返回值:
|
||||||
|
# String - 请求ID
|
||||||
|
#
|
||||||
|
# 功能:
|
||||||
|
# - 通过GitHub账号登录或注册
|
||||||
|
# - 支持第三方OAuth认证
|
||||||
func github_login(github_id: String, username: String, nickname: String, email: String, avatar_url: String = "", callback: Callable = Callable()) -> String:
|
func github_login(github_id: String, username: String, nickname: String, email: String, avatar_url: String = "", callback: Callable = Callable()) -> String:
|
||||||
var data = {
|
var data = {
|
||||||
"github_id": github_id,
|
"github_id": github_id,
|
||||||
@@ -173,6 +430,7 @@ func github_login(github_id: String, username: String, nickname: String, email:
|
|||||||
"email": email
|
"email": email
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 可选头像URL
|
||||||
if avatar_url != "":
|
if avatar_url != "":
|
||||||
data["avatar_url"] = avatar_url
|
data["avatar_url"] = avatar_url
|
||||||
|
|
||||||
|
|||||||
@@ -1,33 +1,96 @@
|
|||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
# 场景管理器 - 负责场景切换和管理
|
# ============================================================================
|
||||||
# 提供场景切换的统一接口
|
# SceneManager.gd - 场景管理器
|
||||||
|
# ============================================================================
|
||||||
|
# 全局单例管理器,负责场景切换和管理
|
||||||
|
#
|
||||||
|
# 核心职责:
|
||||||
|
# - 场景切换的统一接口
|
||||||
|
# - 场景路径映射管理
|
||||||
|
# - 场景切换过渡效果
|
||||||
|
# - 场景状态跟踪
|
||||||
|
#
|
||||||
|
# 使用方式:
|
||||||
|
# SceneManager.change_scene("main")
|
||||||
|
# SceneManager.register_scene("custom", "res://scenes/custom.tscn")
|
||||||
|
#
|
||||||
|
# 注意事项:
|
||||||
|
# - 作为自动加载单例,全局可访问
|
||||||
|
# - 场景切换是异步操作,支持过渡效果
|
||||||
|
# - 场景名称必须在 scene_paths 中注册
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# ============ 信号定义 ============
|
||||||
|
|
||||||
|
# 场景切换完成信号
|
||||||
|
# 参数: scene_name - 切换到的场景名称
|
||||||
signal scene_changed(scene_name: String)
|
signal scene_changed(scene_name: String)
|
||||||
|
|
||||||
|
# 场景切换开始信号
|
||||||
|
# 参数: scene_name - 即将切换到的场景名称
|
||||||
signal scene_change_started(scene_name: String)
|
signal scene_change_started(scene_name: String)
|
||||||
|
|
||||||
var current_scene_name: String = ""
|
# ============ 成员变量 ============
|
||||||
var is_changing_scene: bool = false
|
|
||||||
|
|
||||||
# 场景路径映射
|
# 场景状态
|
||||||
|
var current_scene_name: String = "" # 当前场景名称
|
||||||
|
var is_changing_scene: bool = false # 是否正在切换场景
|
||||||
|
|
||||||
|
# 场景路径映射表
|
||||||
|
# 将场景名称映射到实际的文件路径
|
||||||
|
# 便于统一管理和修改场景路径
|
||||||
var scene_paths: Dictionary = {
|
var scene_paths: Dictionary = {
|
||||||
"main": "res://scenes/maps/main_scene.tscn",
|
"main": "res://scenes/MainScene.tscn", # 主场景 - 游戏入口
|
||||||
"auth": "res://scenes/ui/LoginWindow.tscn",
|
"auth": "res://scenes/ui/LoginWindow.tscn", # 认证场景 - 登录窗口
|
||||||
"game": "res://scenes/maps/game_scene.tscn",
|
"game": "res://scenes/maps/game_scene.tscn", # 游戏场景 - 主要游戏内容
|
||||||
"battle": "res://scenes/maps/battle_scene.tscn",
|
"battle": "res://scenes/maps/battle_scene.tscn", # 战斗场景 - 战斗系统
|
||||||
"inventory": "res://scenes/ui/InventoryWindow.tscn",
|
"inventory": "res://scenes/ui/InventoryWindow.tscn", # 背包界面
|
||||||
"shop": "res://scenes/ui/ShopWindow.tscn",
|
"shop": "res://scenes/ui/ShopWindow.tscn", # 商店界面
|
||||||
"settings": "res://scenes/ui/SettingsWindow.tscn"
|
"settings": "res://scenes/ui/SettingsWindow.tscn" # 设置界面
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ============ 生命周期方法 ============
|
||||||
|
|
||||||
|
# 初始化场景管理器
|
||||||
|
# 在节点准备就绪时调用
|
||||||
func _ready():
|
func _ready():
|
||||||
print("SceneManager 初始化完成")
|
print("SceneManager 初始化完成")
|
||||||
|
|
||||||
|
# ============ 场景切换方法 ============
|
||||||
|
|
||||||
|
# 切换到指定场景
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# scene_name: String - 要切换到的场景名称(必须在scene_paths中注册)
|
||||||
|
# use_transition: bool - 是否使用过渡效果,默认为true
|
||||||
|
#
|
||||||
|
# 返回值:
|
||||||
|
# bool - 切换是否成功
|
||||||
|
#
|
||||||
|
# 功能:
|
||||||
|
# - 检查场景切换状态和场景是否存在
|
||||||
|
# - 显示过渡效果(可选)
|
||||||
|
# - 执行场景切换
|
||||||
|
# - 更新当前场景状态
|
||||||
|
# - 发送相关信号
|
||||||
|
#
|
||||||
|
# 使用示例:
|
||||||
|
# var success = SceneManager.change_scene("main", true)
|
||||||
|
# if success:
|
||||||
|
# print("场景切换成功")
|
||||||
|
#
|
||||||
|
# 注意事项:
|
||||||
|
# - 场景切换是异步操作
|
||||||
|
# - 切换过程中会阻止新的切换请求
|
||||||
|
# - 场景名称必须预先注册
|
||||||
func change_scene(scene_name: String, use_transition: bool = true):
|
func change_scene(scene_name: String, use_transition: bool = true):
|
||||||
|
# 防止重复切换
|
||||||
if is_changing_scene:
|
if is_changing_scene:
|
||||||
print("场景切换中,忽略新的切换请求")
|
print("场景切换中,忽略新的切换请求")
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
# 检查场景是否存在
|
||||||
if not scene_paths.has(scene_name):
|
if not scene_paths.has(scene_name):
|
||||||
print("错误: 未找到场景 ", scene_name)
|
print("错误: 未找到场景 ", scene_name)
|
||||||
return false
|
return false
|
||||||
@@ -35,40 +98,89 @@ func change_scene(scene_name: String, use_transition: bool = true):
|
|||||||
var scene_path = scene_paths[scene_name]
|
var scene_path = scene_paths[scene_name]
|
||||||
print("开始切换场景: ", current_scene_name, " -> ", scene_name)
|
print("开始切换场景: ", current_scene_name, " -> ", scene_name)
|
||||||
|
|
||||||
|
# 设置切换状态
|
||||||
is_changing_scene = true
|
is_changing_scene = true
|
||||||
scene_change_started.emit(scene_name)
|
scene_change_started.emit(scene_name)
|
||||||
|
|
||||||
|
# 显示过渡效果
|
||||||
if use_transition:
|
if use_transition:
|
||||||
await show_transition()
|
await show_transition()
|
||||||
|
|
||||||
|
# 执行场景切换
|
||||||
var error = get_tree().change_scene_to_file(scene_path)
|
var error = get_tree().change_scene_to_file(scene_path)
|
||||||
if error != OK:
|
if error != OK:
|
||||||
print("场景切换失败: ", error)
|
print("场景切换失败: ", error)
|
||||||
is_changing_scene = false
|
is_changing_scene = false
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
# 更新状态
|
||||||
current_scene_name = scene_name
|
current_scene_name = scene_name
|
||||||
is_changing_scene = false
|
is_changing_scene = false
|
||||||
scene_changed.emit(scene_name)
|
scene_changed.emit(scene_name)
|
||||||
|
|
||||||
|
# 隐藏过渡效果
|
||||||
if use_transition:
|
if use_transition:
|
||||||
await hide_transition()
|
await hide_transition()
|
||||||
|
|
||||||
print("场景切换完成: ", scene_name)
|
print("场景切换完成: ", scene_name)
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
# ============ 查询方法 ============
|
||||||
|
|
||||||
|
# 获取当前场景名称
|
||||||
|
#
|
||||||
|
# 返回值:
|
||||||
|
# String - 当前场景的名称
|
||||||
func get_current_scene_name() -> String:
|
func get_current_scene_name() -> String:
|
||||||
return current_scene_name
|
return current_scene_name
|
||||||
|
|
||||||
|
# ============ 场景注册方法 ============
|
||||||
|
|
||||||
|
# 注册新场景
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# scene_name: String - 场景名称(用于切换时引用)
|
||||||
|
# scene_path: String - 场景文件路径
|
||||||
|
#
|
||||||
|
# 功能:
|
||||||
|
# - 将场景名称和路径添加到映射表
|
||||||
|
# - 支持运行时动态注册场景
|
||||||
|
#
|
||||||
|
# 使用示例:
|
||||||
|
# SceneManager.register_scene("boss_battle", "res://scenes/boss/boss_battle.tscn")
|
||||||
func register_scene(scene_name: String, scene_path: String):
|
func register_scene(scene_name: String, scene_path: String):
|
||||||
scene_paths[scene_name] = scene_path
|
scene_paths[scene_name] = scene_path
|
||||||
print("注册场景: ", scene_name, " -> ", scene_path)
|
print("注册场景: ", scene_name, " -> ", scene_path)
|
||||||
|
|
||||||
|
# ============ 过渡效果方法 ============
|
||||||
|
|
||||||
|
# 显示场景切换过渡效果
|
||||||
|
#
|
||||||
|
# 功能:
|
||||||
|
# - 显示场景切换时的过渡动画
|
||||||
|
# - 为用户提供视觉反馈
|
||||||
|
#
|
||||||
|
# 注意事项:
|
||||||
|
# - 这是异步方法,需要await等待完成
|
||||||
|
# - 当前实现为简单的延时,可扩展为复杂动画
|
||||||
|
#
|
||||||
|
# TODO: 实现淡入淡出、滑动等过渡效果
|
||||||
func show_transition():
|
func show_transition():
|
||||||
# TODO: 实现场景切换过渡效果
|
# TODO: 实现场景切换过渡效果
|
||||||
print("显示场景切换过渡效果")
|
print("显示场景切换过渡效果")
|
||||||
await get_tree().create_timer(0.2).timeout
|
await get_tree().create_timer(0.2).timeout
|
||||||
|
|
||||||
|
# 隐藏场景切换过渡效果
|
||||||
|
#
|
||||||
|
# 功能:
|
||||||
|
# - 隐藏场景切换完成后的过渡动画
|
||||||
|
# - 恢复正常的游戏显示
|
||||||
|
#
|
||||||
|
# 注意事项:
|
||||||
|
# - 这是异步方法,需要await等待完成
|
||||||
|
# - 与show_transition()配对使用
|
||||||
|
#
|
||||||
|
# TODO: 实现与show_transition()对应的隐藏效果
|
||||||
func hide_transition():
|
func hide_transition():
|
||||||
# TODO: 隐藏场景切换过渡效果
|
# TODO: 隐藏场景切换过渡效果
|
||||||
print("隐藏场景切换过渡效果")
|
print("隐藏场景切换过渡效果")
|
||||||
|
|||||||
234
_Core/managers/ToastManager.gd
Normal file
234
_Core/managers/ToastManager.gd
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
class_name ToastManager
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# ToastManager.gd - Toast消息管理器
|
||||||
|
# ============================================================================
|
||||||
|
# 负责创建和管理Toast消息的显示
|
||||||
|
#
|
||||||
|
# 核心功能:
|
||||||
|
# - 创建Toast消息实例
|
||||||
|
# - 管理Toast动画和生命周期
|
||||||
|
# - 支持多个Toast同时显示
|
||||||
|
# - 自动排列和清理Toast
|
||||||
|
# - 支持中文字体显示
|
||||||
|
#
|
||||||
|
# 使用方式:
|
||||||
|
# var toast_manager = ToastManager.new()
|
||||||
|
# toast_manager.setup(toast_container)
|
||||||
|
# toast_manager.show_toast("消息内容", true)
|
||||||
|
#
|
||||||
|
# 注意事项:
|
||||||
|
# - 需要提供一个容器节点来承载Toast
|
||||||
|
# - 自动处理Toast的位置计算和动画
|
||||||
|
# - 支持Web平台的字体处理
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
# ============ 成员变量 ============
|
||||||
|
|
||||||
|
# Toast容器和管理
|
||||||
|
var toast_container: Control # Toast消息容器
|
||||||
|
var active_toasts: Array = [] # 当前显示的Toast消息列表
|
||||||
|
var toast_counter: int = 0 # Toast计数器,用于生成唯一ID
|
||||||
|
|
||||||
|
# ============ 初始化方法 ============
|
||||||
|
|
||||||
|
# 设置Toast管理器
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# container: Control - Toast消息的容器节点
|
||||||
|
func setup(container: Control):
|
||||||
|
toast_container = container
|
||||||
|
print("ToastManager 初始化完成")
|
||||||
|
|
||||||
|
# ============ 公共方法 ============
|
||||||
|
|
||||||
|
# 显示Toast消息
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# message: String - 消息内容
|
||||||
|
# is_success: bool - 是否为成功消息(影响颜色)
|
||||||
|
func show_toast(message: String, is_success: bool = true):
|
||||||
|
if toast_container == null:
|
||||||
|
print("错误: toast_container 节点不存在")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("显示Toast消息: ", message, " 成功: ", is_success)
|
||||||
|
_create_toast_instance(message, is_success)
|
||||||
|
|
||||||
|
# 清理所有Toast
|
||||||
|
func clear_all_toasts():
|
||||||
|
for toast in active_toasts:
|
||||||
|
if is_instance_valid(toast):
|
||||||
|
toast.queue_free()
|
||||||
|
active_toasts.clear()
|
||||||
|
|
||||||
|
# ============ 私有方法 ============
|
||||||
|
|
||||||
|
# 创建Toast实例
|
||||||
|
func _create_toast_instance(message: String, is_success: bool):
|
||||||
|
toast_counter += 1
|
||||||
|
|
||||||
|
# Web平台字体处理
|
||||||
|
var is_web = OS.get_name() == "Web"
|
||||||
|
|
||||||
|
# 1. 创建Toast Panel(方框UI)
|
||||||
|
var toast_panel = Panel.new()
|
||||||
|
toast_panel.name = "Toast_" + str(toast_counter)
|
||||||
|
|
||||||
|
# 设置Toast样式
|
||||||
|
var style = StyleBoxFlat.new()
|
||||||
|
if is_success:
|
||||||
|
style.bg_color = Color(0.15, 0.7, 0.15, 0.95)
|
||||||
|
style.border_color = Color(0.2, 0.9, 0.2, 0.9)
|
||||||
|
else:
|
||||||
|
style.bg_color = Color(0.7, 0.15, 0.15, 0.95)
|
||||||
|
style.border_color = Color(0.9, 0.2, 0.2, 0.9)
|
||||||
|
|
||||||
|
style.border_width_left = 3
|
||||||
|
style.border_width_top = 3
|
||||||
|
style.border_width_right = 3
|
||||||
|
style.border_width_bottom = 3
|
||||||
|
style.corner_radius_top_left = 12
|
||||||
|
style.corner_radius_top_right = 12
|
||||||
|
style.corner_radius_bottom_left = 12
|
||||||
|
style.corner_radius_bottom_right = 12
|
||||||
|
style.shadow_color = Color(0, 0, 0, 0.3)
|
||||||
|
style.shadow_size = 4
|
||||||
|
style.shadow_offset = Vector2(2, 2)
|
||||||
|
|
||||||
|
toast_panel.add_theme_stylebox_override("panel", style)
|
||||||
|
|
||||||
|
# 设置Toast基本尺寸
|
||||||
|
var toast_width = 320
|
||||||
|
toast_panel.size = Vector2(toast_width, 60)
|
||||||
|
|
||||||
|
# 2. 创建VBoxContainer
|
||||||
|
var vbox = VBoxContainer.new()
|
||||||
|
vbox.add_theme_constant_override("separation", 0)
|
||||||
|
vbox.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||||
|
vbox.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||||
|
|
||||||
|
# 3. 创建CenterContainer
|
||||||
|
var center_container = CenterContainer.new()
|
||||||
|
center_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
center_container.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
||||||
|
|
||||||
|
# 4. 创建Label(文字控件)
|
||||||
|
var text_label = Label.new()
|
||||||
|
text_label.text = message
|
||||||
|
text_label.add_theme_color_override("font_color", Color(1, 1, 1, 1))
|
||||||
|
text_label.add_theme_font_size_override("font_size", 14)
|
||||||
|
|
||||||
|
# 平台特定的字体处理
|
||||||
|
if is_web:
|
||||||
|
print("Web平台Toast字体处理")
|
||||||
|
# Web平台使用主题文件
|
||||||
|
var chinese_theme = load("res://assets/ui/chinese_theme.tres")
|
||||||
|
if chinese_theme:
|
||||||
|
text_label.theme = chinese_theme
|
||||||
|
print("Web平台应用中文主题")
|
||||||
|
else:
|
||||||
|
print("Web平台中文主题加载失败")
|
||||||
|
else:
|
||||||
|
print("桌面平台Toast字体处理")
|
||||||
|
# 桌面平台直接加载中文字体
|
||||||
|
var desktop_chinese_font = load("res://assets/fonts/msyh.ttc")
|
||||||
|
if desktop_chinese_font:
|
||||||
|
text_label.add_theme_font_override("font", desktop_chinese_font)
|
||||||
|
print("桌面平台使用中文字体")
|
||||||
|
|
||||||
|
text_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||||
|
text_label.custom_minimum_size = Vector2(280, 0)
|
||||||
|
text_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
|
text_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||||
|
|
||||||
|
# 组装控件层级
|
||||||
|
center_container.add_child(text_label)
|
||||||
|
vbox.add_child(center_container)
|
||||||
|
toast_panel.add_child(vbox)
|
||||||
|
|
||||||
|
# 计算位置
|
||||||
|
var margin = 20
|
||||||
|
var start_x = toast_container.get_viewport().get_visible_rect().size.x
|
||||||
|
var final_x = toast_container.get_viewport().get_visible_rect().size.x - toast_width - margin
|
||||||
|
|
||||||
|
# 计算Y位置
|
||||||
|
var y_position = margin
|
||||||
|
for existing_toast in active_toasts:
|
||||||
|
if is_instance_valid(existing_toast):
|
||||||
|
y_position += existing_toast.size.y + 15
|
||||||
|
|
||||||
|
# 设置初始位置
|
||||||
|
toast_panel.position = Vector2(start_x, y_position)
|
||||||
|
|
||||||
|
# 添加到容器
|
||||||
|
toast_container.add_child(toast_panel)
|
||||||
|
active_toasts.append(toast_panel)
|
||||||
|
|
||||||
|
# 等待一帧让布局系统计算尺寸
|
||||||
|
await toast_container.get_tree().process_frame
|
||||||
|
|
||||||
|
# 让Toast高度自适应内容
|
||||||
|
var content_size = vbox.get_combined_minimum_size()
|
||||||
|
var final_height = max(60, content_size.y + 20) # 最小60,加20像素边距
|
||||||
|
toast_panel.size.y = final_height
|
||||||
|
|
||||||
|
# 重新排列所有Toast
|
||||||
|
_rearrange_toasts()
|
||||||
|
|
||||||
|
# 开始动画
|
||||||
|
_animate_toast_in(toast_panel, final_x)
|
||||||
|
|
||||||
|
# Toast入场动画
|
||||||
|
func _animate_toast_in(toast_panel: Panel, final_x: float):
|
||||||
|
var tween = toast_container.create_tween()
|
||||||
|
tween.set_ease(Tween.EASE_OUT)
|
||||||
|
tween.set_trans(Tween.TRANS_BACK)
|
||||||
|
|
||||||
|
tween.parallel().tween_property(toast_panel, "position:x", final_x, 0.6)
|
||||||
|
tween.parallel().tween_property(toast_panel, "modulate:a", 1.0, 0.4)
|
||||||
|
|
||||||
|
toast_panel.modulate.a = 0.0
|
||||||
|
|
||||||
|
# 等待3秒后开始退场动画
|
||||||
|
await toast_container.get_tree().create_timer(3.0).timeout
|
||||||
|
_animate_toast_out(toast_panel)
|
||||||
|
|
||||||
|
# Toast退场动画
|
||||||
|
func _animate_toast_out(toast_panel: Panel):
|
||||||
|
if not is_instance_valid(toast_panel):
|
||||||
|
return
|
||||||
|
|
||||||
|
var tween = toast_container.create_tween()
|
||||||
|
tween.set_ease(Tween.EASE_IN)
|
||||||
|
tween.set_trans(Tween.TRANS_QUART)
|
||||||
|
|
||||||
|
var end_x = toast_container.get_viewport().get_visible_rect().size.x + 50
|
||||||
|
tween.parallel().tween_property(toast_panel, "position:x", end_x, 0.4)
|
||||||
|
tween.parallel().tween_property(toast_panel, "modulate:a", 0.0, 0.3)
|
||||||
|
|
||||||
|
await tween.finished
|
||||||
|
_cleanup_toast(toast_panel)
|
||||||
|
|
||||||
|
# 清理Toast
|
||||||
|
func _cleanup_toast(toast_panel: Panel):
|
||||||
|
if not is_instance_valid(toast_panel):
|
||||||
|
return
|
||||||
|
|
||||||
|
active_toasts.erase(toast_panel)
|
||||||
|
_rearrange_toasts()
|
||||||
|
toast_panel.queue_free()
|
||||||
|
|
||||||
|
# 重新排列Toast位置
|
||||||
|
func _rearrange_toasts():
|
||||||
|
var margin = 20
|
||||||
|
var current_y = margin
|
||||||
|
|
||||||
|
for i in range(active_toasts.size()):
|
||||||
|
var toast = active_toasts[i]
|
||||||
|
if is_instance_valid(toast):
|
||||||
|
var tween = toast_container.create_tween()
|
||||||
|
tween.tween_property(toast, "position:y", current_y, 0.2)
|
||||||
|
current_y += toast.size.y + 15
|
||||||
1
_Core/managers/ToastManager.gd.uid
Normal file
1
_Core/managers/ToastManager.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://buk7d21cag262
|
||||||
@@ -1,44 +1,125 @@
|
|||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
# 全局事件系统 - 提供解耦的事件通信机制
|
# ============================================================================
|
||||||
# 允许不同模块之间通过事件进行通信,避免直接依赖
|
# EventSystem.gd - 全局事件系统
|
||||||
|
# ============================================================================
|
||||||
|
# 全局单例管理器,提供解耦的事件通信机制
|
||||||
|
#
|
||||||
|
# 核心职责:
|
||||||
|
# - 事件监听器注册和管理
|
||||||
|
# - 事件发送和分发
|
||||||
|
# - 自动清理无效监听器
|
||||||
|
# - 支持带参数的事件通信
|
||||||
|
#
|
||||||
|
# 使用方式:
|
||||||
|
# EventSystem.connect_event("player_moved", _on_player_moved)
|
||||||
|
# EventSystem.emit_event("player_moved", {"position": Vector2(100, 200)})
|
||||||
|
#
|
||||||
|
# 注意事项:
|
||||||
|
# - 作为自动加载单例,全局可访问
|
||||||
|
# - 监听器会自动检查目标节点的有效性
|
||||||
|
# - 建议使用EventNames类中定义的事件名称常量
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# ============ 成员变量 ============
|
||||||
|
|
||||||
# 事件监听器存储
|
# 事件监听器存储
|
||||||
|
# 结构: {event_name: [{"callback": Callable, "target": Node}, ...]}
|
||||||
var event_listeners: Dictionary = {}
|
var event_listeners: Dictionary = {}
|
||||||
|
|
||||||
|
# ============ 生命周期方法 ============
|
||||||
|
|
||||||
|
# 初始化事件系统
|
||||||
|
# 在节点准备就绪时调用
|
||||||
func _ready():
|
func _ready():
|
||||||
print("EventSystem 初始化完成")
|
print("EventSystem 初始化完成")
|
||||||
|
|
||||||
|
# ============ 事件监听器管理 ============
|
||||||
|
|
||||||
# 注册事件监听器
|
# 注册事件监听器
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# event_name: String - 事件名称(建议使用EventNames中的常量)
|
||||||
|
# callback: Callable - 回调函数
|
||||||
|
# target: Node - 目标节点(可选,用于自动清理)
|
||||||
|
#
|
||||||
|
# 功能:
|
||||||
|
# - 将回调函数注册到指定事件
|
||||||
|
# - 支持同一事件多个监听器
|
||||||
|
# - 自动管理监听器生命周期
|
||||||
|
#
|
||||||
|
# 使用示例:
|
||||||
|
# EventSystem.connect_event(EventNames.PLAYER_MOVED, _on_player_moved, self)
|
||||||
|
#
|
||||||
|
# 注意事项:
|
||||||
|
# - 如果提供target参数,当target节点被销毁时会自动清理监听器
|
||||||
|
# - 同一个callback可以监听多个事件
|
||||||
func connect_event(event_name: String, callback: Callable, target: Node = null):
|
func connect_event(event_name: String, callback: Callable, target: Node = null):
|
||||||
|
# 初始化事件监听器数组
|
||||||
if not event_listeners.has(event_name):
|
if not event_listeners.has(event_name):
|
||||||
event_listeners[event_name] = []
|
event_listeners[event_name] = []
|
||||||
|
|
||||||
|
# 创建监听器信息
|
||||||
var listener_info = {
|
var listener_info = {
|
||||||
"callback": callback,
|
"callback": callback,
|
||||||
"target": target
|
"target": target
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 添加到监听器列表
|
||||||
event_listeners[event_name].append(listener_info)
|
event_listeners[event_name].append(listener_info)
|
||||||
print("注册事件监听器: ", event_name, " -> ", callback)
|
print("注册事件监听器: ", event_name, " -> ", callback)
|
||||||
|
|
||||||
# 移除事件监听器
|
# 移除事件监听器
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# event_name: String - 事件名称
|
||||||
|
# callback: Callable - 要移除的回调函数
|
||||||
|
# target: Node - 目标节点(可选,用于精确匹配)
|
||||||
|
#
|
||||||
|
# 功能:
|
||||||
|
# - 从指定事件中移除特定的监听器
|
||||||
|
# - 支持精确匹配(callback + target)
|
||||||
|
#
|
||||||
|
# 使用示例:
|
||||||
|
# EventSystem.disconnect_event(EventNames.PLAYER_MOVED, _on_player_moved, self)
|
||||||
func disconnect_event(event_name: String, callback: Callable, target: Node = null):
|
func disconnect_event(event_name: String, callback: Callable, target: Node = null):
|
||||||
if not event_listeners.has(event_name):
|
if not event_listeners.has(event_name):
|
||||||
return
|
return
|
||||||
|
|
||||||
var listeners = event_listeners[event_name]
|
var listeners = event_listeners[event_name]
|
||||||
|
# 从后往前遍历,避免删除元素时索引问题
|
||||||
for i in range(listeners.size() - 1, -1, -1):
|
for i in range(listeners.size() - 1, -1, -1):
|
||||||
var listener = listeners[i]
|
var listener = listeners[i]
|
||||||
|
# 匹配callback和target
|
||||||
if listener.callback == callback and listener.target == target:
|
if listener.callback == callback and listener.target == target:
|
||||||
listeners.remove_at(i)
|
listeners.remove_at(i)
|
||||||
print("移除事件监听器: ", event_name, " -> ", callback)
|
print("移除事件监听器: ", event_name, " -> ", callback)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
# ============ 事件发送 ============
|
||||||
|
|
||||||
# 发送事件
|
# 发送事件
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# event_name: String - 事件名称
|
||||||
|
# data: Variant - 事件数据(可选)
|
||||||
|
#
|
||||||
|
# 功能:
|
||||||
|
# - 向所有注册的监听器发送事件
|
||||||
|
# - 自动跳过无效的监听器
|
||||||
|
# - 支持任意类型的事件数据
|
||||||
|
#
|
||||||
|
# 使用示例:
|
||||||
|
# EventSystem.emit_event(EventNames.PLAYER_MOVED, {"position": Vector2(100, 200)})
|
||||||
|
# EventSystem.emit_event(EventNames.GAME_PAUSED) # 无数据事件
|
||||||
|
#
|
||||||
|
# 注意事项:
|
||||||
|
# - 事件发送是同步的,所有监听器会立即执行
|
||||||
|
# - 如果监听器执行出错,不会影响其他监听器
|
||||||
func emit_event(event_name: String, data: Variant = null):
|
func emit_event(event_name: String, data: Variant = null):
|
||||||
print("发送事件: ", event_name, " 数据: ", data)
|
print("发送事件: ", event_name, " 数据: ", data)
|
||||||
|
|
||||||
|
# 检查是否有监听器
|
||||||
if not event_listeners.has(event_name):
|
if not event_listeners.has(event_name):
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -57,24 +138,58 @@ func emit_event(event_name: String, data: Variant = null):
|
|||||||
else:
|
else:
|
||||||
callback.call()
|
callback.call()
|
||||||
|
|
||||||
|
# ============ 维护方法 ============
|
||||||
|
|
||||||
# 清理无效的监听器
|
# 清理无效的监听器
|
||||||
|
#
|
||||||
|
# 功能:
|
||||||
|
# - 遍历所有监听器,移除已销毁节点的监听器
|
||||||
|
# - 防止内存泄漏
|
||||||
|
# - 建议定期调用或在场景切换时调用
|
||||||
|
#
|
||||||
|
# 使用场景:
|
||||||
|
# - 场景切换时清理
|
||||||
|
# - 定期维护(如每分钟一次)
|
||||||
|
# - 内存优化时调用
|
||||||
func cleanup_invalid_listeners():
|
func cleanup_invalid_listeners():
|
||||||
for event_name in event_listeners.keys():
|
for event_name in event_listeners.keys():
|
||||||
var listeners = event_listeners[event_name]
|
var listeners = event_listeners[event_name]
|
||||||
|
# 从后往前遍历,避免删除元素时索引问题
|
||||||
for i in range(listeners.size() - 1, -1, -1):
|
for i in range(listeners.size() - 1, -1, -1):
|
||||||
var listener = listeners[i]
|
var listener = listeners[i]
|
||||||
var target = listener.target
|
var target = listener.target
|
||||||
|
# 如果目标节点无效,移除监听器
|
||||||
if target != null and not is_instance_valid(target):
|
if target != null and not is_instance_valid(target):
|
||||||
listeners.remove_at(i)
|
listeners.remove_at(i)
|
||||||
print("清理无效监听器: ", event_name)
|
print("清理无效监听器: ", event_name)
|
||||||
|
|
||||||
|
# ============ 查询方法 ============
|
||||||
|
|
||||||
# 获取事件监听器数量
|
# 获取事件监听器数量
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# event_name: String - 事件名称
|
||||||
|
#
|
||||||
|
# 返回值:
|
||||||
|
# int - 监听器数量
|
||||||
|
#
|
||||||
|
# 使用场景:
|
||||||
|
# - 调试时检查监听器数量
|
||||||
|
# - 性能分析
|
||||||
func get_listener_count(event_name: String) -> int:
|
func get_listener_count(event_name: String) -> int:
|
||||||
if not event_listeners.has(event_name):
|
if not event_listeners.has(event_name):
|
||||||
return 0
|
return 0
|
||||||
return event_listeners[event_name].size()
|
return event_listeners[event_name].size()
|
||||||
|
|
||||||
# 清空所有事件监听器
|
# 清空所有事件监听器
|
||||||
|
#
|
||||||
|
# 功能:
|
||||||
|
# - 移除所有已注册的事件监听器
|
||||||
|
# - 通常在游戏重置或退出时使用
|
||||||
|
#
|
||||||
|
# 警告:
|
||||||
|
# - 这是一个危险操作,会影响所有模块
|
||||||
|
# - 使用前请确保所有模块都能正确处理监听器丢失
|
||||||
func clear_all_listeners():
|
func clear_all_listeners():
|
||||||
event_listeners.clear()
|
event_listeners.clear()
|
||||||
print("清空所有事件监听器")
|
print("清空所有事件监听器")
|
||||||
|
|||||||
@@ -1,26 +1,102 @@
|
|||||||
class_name StringUtils
|
class_name StringUtils
|
||||||
|
|
||||||
# 字符串工具类 - 提供常用的字符串处理功能
|
# ============================================================================
|
||||||
|
# StringUtils.gd - 字符串工具类
|
||||||
|
# ============================================================================
|
||||||
|
# 静态工具类,提供常用的字符串处理功能
|
||||||
|
#
|
||||||
|
# 核心功能:
|
||||||
|
# - 输入验证(邮箱、用户名、密码)
|
||||||
|
# - 字符串格式化和转换
|
||||||
|
# - 时间格式化和相对时间计算
|
||||||
|
# - 文件大小格式化
|
||||||
|
#
|
||||||
|
# 使用方式:
|
||||||
|
# var is_valid = StringUtils.is_valid_email("user@example.com")
|
||||||
|
# var formatted_time = StringUtils.format_utc_to_local_time(utc_string)
|
||||||
|
#
|
||||||
|
# 注意事项:
|
||||||
|
# - 所有方法都是静态的,无需实例化
|
||||||
|
# - 验证方法返回布尔值或包含详细信息的字典
|
||||||
|
# - 时间处理方法支持UTC到本地时间的转换
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# ============ 输入验证方法 ============
|
||||||
|
|
||||||
# 验证邮箱格式
|
# 验证邮箱格式
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# email: String - 待验证的邮箱地址
|
||||||
|
#
|
||||||
|
# 返回值:
|
||||||
|
# bool - true表示格式正确,false表示格式错误
|
||||||
|
#
|
||||||
|
# 验证规则:
|
||||||
|
# - 必须包含@符号
|
||||||
|
# - @前后都必须有内容
|
||||||
|
# - 域名部分必须包含至少一个点
|
||||||
|
# - 顶级域名至少2个字符
|
||||||
|
#
|
||||||
|
# 使用示例:
|
||||||
|
# if StringUtils.is_valid_email("user@example.com"):
|
||||||
|
# print("邮箱格式正确")
|
||||||
static func is_valid_email(email: String) -> bool:
|
static func is_valid_email(email: String) -> bool:
|
||||||
var regex = RegEx.new()
|
var regex = RegEx.new()
|
||||||
regex.compile("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$")
|
regex.compile("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$")
|
||||||
return regex.search(email) != null
|
return regex.search(email) != null
|
||||||
|
|
||||||
# 验证用户名格式(字母、数字、下划线)
|
# 验证用户名格式
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# username: String - 待验证的用户名
|
||||||
|
#
|
||||||
|
# 返回值:
|
||||||
|
# bool - true表示格式正确,false表示格式错误
|
||||||
|
#
|
||||||
|
# 验证规则:
|
||||||
|
# - 只能包含字母、数字、下划线
|
||||||
|
# - 长度不能为空且不超过50个字符
|
||||||
|
# - 不能包含空格或特殊字符
|
||||||
|
#
|
||||||
|
# 使用示例:
|
||||||
|
# if StringUtils.is_valid_username("user_123"):
|
||||||
|
# print("用户名格式正确")
|
||||||
static func is_valid_username(username: String) -> bool:
|
static func is_valid_username(username: String) -> bool:
|
||||||
|
# 检查长度
|
||||||
if username.is_empty() or username.length() > 50:
|
if username.is_empty() or username.length() > 50:
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
# 检查字符组成
|
||||||
var regex = RegEx.new()
|
var regex = RegEx.new()
|
||||||
regex.compile("^[a-zA-Z0-9_]+$")
|
regex.compile("^[a-zA-Z0-9_]+$")
|
||||||
return regex.search(username) != null
|
return regex.search(username) != null
|
||||||
|
|
||||||
# 验证密码强度
|
# 验证密码强度
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# password: String - 待验证的密码
|
||||||
|
#
|
||||||
|
# 返回值:
|
||||||
|
# Dictionary - 包含验证结果的详细信息
|
||||||
|
# {
|
||||||
|
# "valid": bool, # 是否符合最低要求
|
||||||
|
# "message": String, # 验证结果消息
|
||||||
|
# "strength": int # 强度等级 (1-4)
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# 验证规则:
|
||||||
|
# - 最少8位,最多128位
|
||||||
|
# - 必须包含字母和数字
|
||||||
|
# - 强度评级:包含字母(+1)、数字(+1)、特殊字符(+1)、长度>=12(+1)
|
||||||
|
#
|
||||||
|
# 使用示例:
|
||||||
|
# var result = StringUtils.validate_password_strength("MyPass123!")
|
||||||
|
# if result.valid:
|
||||||
|
# print("密码强度: ", result.message)
|
||||||
static func validate_password_strength(password: String) -> Dictionary:
|
static func validate_password_strength(password: String) -> Dictionary:
|
||||||
var result = {"valid": false, "message": "", "strength": 0}
|
var result = {"valid": false, "message": "", "strength": 0}
|
||||||
|
|
||||||
|
# 检查长度限制
|
||||||
if password.length() < 8:
|
if password.length() < 8:
|
||||||
result.message = "密码长度至少8位"
|
result.message = "密码长度至少8位"
|
||||||
return result
|
return result
|
||||||
@@ -29,9 +105,10 @@ static func validate_password_strength(password: String) -> Dictionary:
|
|||||||
result.message = "密码长度不能超过128位"
|
result.message = "密码长度不能超过128位"
|
||||||
return result
|
return result
|
||||||
|
|
||||||
var has_letter = false
|
# 检查字符类型
|
||||||
var has_digit = false
|
var has_letter = false # 是否包含字母
|
||||||
var has_special = false
|
var has_digit = false # 是否包含数字
|
||||||
|
var has_special = false # 是否包含特殊字符
|
||||||
|
|
||||||
for i in range(password.length()):
|
for i in range(password.length()):
|
||||||
var character = password[i]
|
var character = password[i]
|
||||||
@@ -42,6 +119,7 @@ static func validate_password_strength(password: String) -> Dictionary:
|
|||||||
elif character in "!@#$%^&*()_+-=[]{}|;:,.<>?":
|
elif character in "!@#$%^&*()_+-=[]{}|;:,.<>?":
|
||||||
has_special = true
|
has_special = true
|
||||||
|
|
||||||
|
# 计算强度等级
|
||||||
var strength = 0
|
var strength = 0
|
||||||
if has_letter:
|
if has_letter:
|
||||||
strength += 1
|
strength += 1
|
||||||
@@ -54,27 +132,72 @@ static func validate_password_strength(password: String) -> Dictionary:
|
|||||||
|
|
||||||
result.strength = strength
|
result.strength = strength
|
||||||
|
|
||||||
|
# 检查最低要求
|
||||||
if not (has_letter and has_digit):
|
if not (has_letter and has_digit):
|
||||||
result.message = "密码必须包含字母和数字"
|
result.message = "密码必须包含字母和数字"
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
# 密码符合要求
|
||||||
result.valid = true
|
result.valid = true
|
||||||
result.message = "密码强度: " + ["弱", "中", "强", "很强"][min(strength - 1, 3)]
|
result.message = "密码强度: " + ["弱", "中", "强", "很强"][min(strength - 1, 3)]
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
# ============ 字符串格式化方法 ============
|
||||||
|
|
||||||
# 截断字符串
|
# 截断字符串
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# text: String - 原始字符串
|
||||||
|
# max_length: int - 最大长度
|
||||||
|
# suffix: String - 截断后缀(默认为"...")
|
||||||
|
#
|
||||||
|
# 返回值:
|
||||||
|
# String - 截断后的字符串
|
||||||
|
#
|
||||||
|
# 功能:
|
||||||
|
# - 如果字符串长度超过限制,截断并添加后缀
|
||||||
|
# - 如果字符串长度未超过限制,返回原字符串
|
||||||
|
#
|
||||||
|
# 使用示例:
|
||||||
|
# var short_text = StringUtils.truncate("这是一个很长的文本", 10, "...")
|
||||||
|
# # 结果: "这是一个很长..."
|
||||||
static func truncate(text: String, max_length: int, suffix: String = "...") -> String:
|
static func truncate(text: String, max_length: int, suffix: String = "...") -> String:
|
||||||
if text.length() <= max_length:
|
if text.length() <= max_length:
|
||||||
return text
|
return text
|
||||||
return text.substr(0, max_length - suffix.length()) + suffix
|
return text.substr(0, max_length - suffix.length()) + suffix
|
||||||
|
|
||||||
# 首字母大写
|
# 首字母大写
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# text: String - 原始字符串
|
||||||
|
#
|
||||||
|
# 返回值:
|
||||||
|
# String - 首字母大写的字符串
|
||||||
|
#
|
||||||
|
# 使用示例:
|
||||||
|
# var capitalized = StringUtils.capitalize_first("hello world")
|
||||||
|
# # 结果: "Hello world"
|
||||||
static func capitalize_first(text: String) -> String:
|
static func capitalize_first(text: String) -> String:
|
||||||
if text.is_empty():
|
if text.is_empty():
|
||||||
return text
|
return text
|
||||||
return text[0].to_upper() + text.substr(1)
|
return text[0].to_upper() + text.substr(1)
|
||||||
|
|
||||||
# 转换为标题格式(每个单词首字母大写)
|
# 转换为标题格式
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# text: String - 原始字符串
|
||||||
|
#
|
||||||
|
# 返回值:
|
||||||
|
# String - 每个单词首字母大写的字符串
|
||||||
|
#
|
||||||
|
# 功能:
|
||||||
|
# - 将每个单词的首字母转换为大写
|
||||||
|
# - 其余字母转换为小写
|
||||||
|
# - 以空格分隔单词
|
||||||
|
#
|
||||||
|
# 使用示例:
|
||||||
|
# var title = StringUtils.to_title_case("hello world game")
|
||||||
|
# # 结果: "Hello World Game"
|
||||||
static func to_title_case(text: String) -> String:
|
static func to_title_case(text: String) -> String:
|
||||||
var words = text.split(" ")
|
var words = text.split(" ")
|
||||||
var result = []
|
var result = []
|
||||||
@@ -84,27 +207,75 @@ static func to_title_case(text: String) -> String:
|
|||||||
return " ".join(result)
|
return " ".join(result)
|
||||||
|
|
||||||
# 移除HTML标签
|
# 移除HTML标签
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# html: String - 包含HTML标签的字符串
|
||||||
|
#
|
||||||
|
# 返回值:
|
||||||
|
# String - 移除HTML标签后的纯文本
|
||||||
|
#
|
||||||
|
# 功能:
|
||||||
|
# - 使用正则表达式移除所有HTML标签
|
||||||
|
# - 保留标签之间的文本内容
|
||||||
|
#
|
||||||
|
# 使用示例:
|
||||||
|
# var plain_text = StringUtils.strip_html_tags("<p>Hello <b>World</b></p>")
|
||||||
|
# # 结果: "Hello World"
|
||||||
static func strip_html_tags(html: String) -> String:
|
static func strip_html_tags(html: String) -> String:
|
||||||
var regex = RegEx.new()
|
var regex = RegEx.new()
|
||||||
regex.compile("<[^>]*>")
|
regex.compile("<[^>]*>")
|
||||||
return regex.sub(html, "", true)
|
return regex.sub(html, "", true)
|
||||||
|
|
||||||
# 格式化文件大小
|
# 格式化文件大小
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# bytes: int - 文件大小(字节)
|
||||||
|
#
|
||||||
|
# 返回值:
|
||||||
|
# String - 格式化后的文件大小字符串
|
||||||
|
#
|
||||||
|
# 功能:
|
||||||
|
# - 自动选择合适的单位(B, KB, MB, GB, TB)
|
||||||
|
# - 保留一位小数(除了字节)
|
||||||
|
# - 使用1024作为换算基数
|
||||||
|
#
|
||||||
|
# 使用示例:
|
||||||
|
# var size_text = StringUtils.format_file_size(1536)
|
||||||
|
# # 结果: "1.5 KB"
|
||||||
static func format_file_size(bytes: int) -> String:
|
static func format_file_size(bytes: int) -> String:
|
||||||
var units = ["B", "KB", "MB", "GB", "TB"]
|
var units = ["B", "KB", "MB", "GB", "TB"]
|
||||||
var size = float(bytes)
|
var size = float(bytes)
|
||||||
var unit_index = 0
|
var unit_index = 0
|
||||||
|
|
||||||
|
# 自动选择合适的单位
|
||||||
while size >= 1024.0 and unit_index < units.size() - 1:
|
while size >= 1024.0 and unit_index < units.size() - 1:
|
||||||
size /= 1024.0
|
size /= 1024.0
|
||||||
unit_index += 1
|
unit_index += 1
|
||||||
|
|
||||||
|
# 格式化输出
|
||||||
if unit_index == 0:
|
if unit_index == 0:
|
||||||
return str(int(size)) + " " + units[unit_index]
|
return str(int(size)) + " " + units[unit_index]
|
||||||
else:
|
else:
|
||||||
return "%.1f %s" % [size, units[unit_index]]
|
return "%.1f %s" % [size, units[unit_index]]
|
||||||
|
|
||||||
|
# ============ 时间处理方法 ============
|
||||||
|
|
||||||
# 将UTC时间字符串转换为本地时间显示
|
# 将UTC时间字符串转换为本地时间显示
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# utc_time_str: String - UTC时间字符串(格式: 2025-12-25T11:23:52.175Z)
|
||||||
|
#
|
||||||
|
# 返回值:
|
||||||
|
# String - 格式化的本地时间字符串
|
||||||
|
#
|
||||||
|
# 功能:
|
||||||
|
# - 解析ISO 8601格式的UTC时间
|
||||||
|
# - 转换为本地时区时间
|
||||||
|
# - 格式化为易读的中文时间格式
|
||||||
|
#
|
||||||
|
# 使用示例:
|
||||||
|
# var local_time = StringUtils.format_utc_to_local_time("2025-12-25T11:23:52.175Z")
|
||||||
|
# # 结果: "2025年12月25日 19:23:52" (假设本地时区为UTC+8)
|
||||||
static func format_utc_to_local_time(utc_time_str: String) -> String:
|
static func format_utc_to_local_time(utc_time_str: String) -> String:
|
||||||
# 解析UTC时间字符串 (格式: 2025-12-25T11:23:52.175Z)
|
# 解析UTC时间字符串 (格式: 2025-12-25T11:23:52.175Z)
|
||||||
var regex = RegEx.new()
|
var regex = RegEx.new()
|
||||||
@@ -148,7 +319,22 @@ static func format_utc_to_local_time(utc_time_str: String) -> String:
|
|||||||
local_dict.second
|
local_dict.second
|
||||||
]
|
]
|
||||||
|
|
||||||
# 获取相对时间描述(多少分钟后)
|
# 获取相对时间描述
|
||||||
|
#
|
||||||
|
# 参数:
|
||||||
|
# utc_time_str: String - UTC时间字符串
|
||||||
|
#
|
||||||
|
# 返回值:
|
||||||
|
# String - 相对时间描述(如"5分钟后"、"2小时30分钟后")
|
||||||
|
#
|
||||||
|
# 功能:
|
||||||
|
# - 计算指定时间与当前时间的差值
|
||||||
|
# - 返回人性化的相对时间描述
|
||||||
|
# - 支持秒、分钟、小时的组合显示
|
||||||
|
#
|
||||||
|
# 使用示例:
|
||||||
|
# var relative_time = StringUtils.get_relative_time_until("2025-12-25T12:00:00Z")
|
||||||
|
# # 结果: "30分钟后" 或 "现在可以重试"
|
||||||
static func get_relative_time_until(utc_time_str: String) -> String:
|
static func get_relative_time_until(utc_time_str: String) -> String:
|
||||||
# 解析UTC时间字符串
|
# 解析UTC时间字符串
|
||||||
var regex = RegEx.new()
|
var regex = RegEx.new()
|
||||||
@@ -183,6 +369,7 @@ static func get_relative_time_until(utc_time_str: String) -> String:
|
|||||||
# 计算时间差(秒)
|
# 计算时间差(秒)
|
||||||
var diff_seconds = target_timestamp - current_timestamp
|
var diff_seconds = target_timestamp - current_timestamp
|
||||||
|
|
||||||
|
# 格式化相对时间
|
||||||
if diff_seconds <= 0:
|
if diff_seconds <= 0:
|
||||||
return "现在可以重试"
|
return "现在可以重试"
|
||||||
elif diff_seconds < 60:
|
elif diff_seconds < 60:
|
||||||
|
|||||||
@@ -82,6 +82,8 @@ EventSystem.emit_event("player_moved", {"position": Vector2(100, 200)})
|
|||||||
|
|
||||||
```
|
```
|
||||||
scenes/
|
scenes/
|
||||||
|
├── MainScene.tscn # 🎯 主入口场景 - 所有图像显示的入口文件
|
||||||
|
├── MainScene.gd # 主场景控制器脚本
|
||||||
├── maps/ # 地图场景
|
├── maps/ # 地图场景
|
||||||
│ ├── main_world.tscn # 主世界地图
|
│ ├── main_world.tscn # 主世界地图
|
||||||
│ ├── dungeon_01.tscn # 地牢场景
|
│ ├── dungeon_01.tscn # 地牢场景
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ config_version=5
|
|||||||
[application]
|
[application]
|
||||||
|
|
||||||
config/name="whaleTown"
|
config/name="whaleTown"
|
||||||
run/main_scene="res://scenes/maps/main_scene.tscn"
|
run/main_scene="res://scenes/MainScene.tscn"
|
||||||
config/features=PackedStringArray("4.5", "Forward Plus")
|
config/features=PackedStringArray("4.5", "Forward Plus")
|
||||||
config/icon="res://icon.svg"
|
config/icon="res://icon.svg"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,16 @@
|
|||||||
extends Control
|
extends Control
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# MainScene.gd - 主场景控制器
|
||||||
|
# ============================================================================
|
||||||
|
# 这是游戏的主入口场景,负责管理所有图像显示和界面切换
|
||||||
|
# 功能包括:
|
||||||
|
# - 登录/注册界面管理
|
||||||
|
# - 主游戏界面显示
|
||||||
|
# - 用户状态管理
|
||||||
|
# - 游戏功能模块入口
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
# 场景节点引用
|
# 场景节点引用
|
||||||
@onready var auth_scene: Control = $AuthScene
|
@onready var auth_scene: Control = $AuthScene
|
||||||
@onready var main_game_ui: Control = $MainGameUI
|
@onready var main_game_ui: Control = $MainGameUI
|
||||||
1
scenes/MainScene.gd.uid
Normal file
1
scenes/MainScene.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://ghehm4srs0ho
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
[gd_scene load_steps=3 format=3 uid="uid://21a49e14a0c58d7941d04142a5bf9ddc"]
|
[gd_scene load_steps=3 format=3 uid="uid://cjabtnqbdd2ey"]
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://scenes/maps/MainScene.gd" id="1_script"]
|
[ext_resource type="Script" path="res://scenes/MainScene.gd" id="1_script"]
|
||||||
[ext_resource type="PackedScene" uid="uid://by7m8snb4xllf" path="res://scenes/ui/LoginWindow.tscn" id="2_main"]
|
[ext_resource type="PackedScene" uid="uid://by7m8snb4xllf" path="res://scenes/ui/AuthScene.tscn" id="2_main"]
|
||||||
|
|
||||||
[node name="Main" type="Control"]
|
[node name="Main" type="Control"]
|
||||||
layout_mode = 3
|
layout_mode = 3
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://cn2xjgj3h847p
|
|
||||||
File diff suppressed because it is too large
Load Diff
1
tests/unit/test_toast_manager.gd.uid
Normal file
1
tests/unit/test_toast_manager.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cfcsbf2237mm2
|
||||||
Reference in New Issue
Block a user