前言:本文是 Godot(4.X): 外接Python处理Excel数据: 账号管理系统实现-CSDN博客 中Godot内 全局登录管理器AutoLoad部分 的补充完整实现,在阅读本文前,若有需要,请先阅读

Godot(4.X): 外接Python处理Excel数据: 账号管理系统实现-CSDN博客

Godot(2D)架构细分4:编辑器全局设置解释-CSDN博客

这些文字提供了阅读本文需要了解的内容。本文将会解释Godot内全局登录管理系统的主管理器子管理器登录界面 的实现方式以及演示

1. Godot内登录管理器概述

        本文篇章使用的登录管理器的主要功能是对接Python,向游戏内其他逻辑部分返回经过处理后的统一返回值。注意 本登录管理器本身并不实现任何逻辑 ,只负责接收Python后向其他需要登录信息的场景或者脚本提供信息。

这里需要注意的是,登录管理器还存储当前登录玩家ID,以及一些登录信息,这部分逻辑由登录系统简单判断,登录后更新。

这里以登录界面为解释流程(登录界面本身为单独实现的场景)

1. 登录界面将玩家输入的信息存入,并发送至登录系统

2. 登录系统接收信息,将信息转化为传参传递给Python判断,Python返回判断值

3. 登录系统将判断值处理成统一格式后返回给登录界面,同时更新当前登录状态

2. Godot内登录管理器具体实现

实现将从AutoLoad登录管理器开始解释,再解释登录界面的实现逻辑

先给出主管理器的节点样式

以及管理器场景文件结构

        1. AutoLoad登录管理器

Login子管理器和Register子管理器实现的原理一致,但是函数名和开放的API区分,可以使代码清晰易懂

先给出全局AutoLoad状态如下

1. 子登录管理器(Login)

负责接收Python的登录返回值

extends Node2D

"""
注意传输路径
Godot传出去的参数到Python接收,全部会被转化为字符串类型
这里给python传入的脚本格式为:
[python主脚本地址, str Excel请求路径, int 请求操作, 账户ID, 账户密码]
且传递内容全部统一为 str 类型
"""
## 登录信息验证(阻塞线程,运行的时候主程序会暂停)
## 这里的 input_info 传入的是[ID, Password]
func login_info_verify(input_info: Array[String]) -> String:
	var receive_return: 	Array[String]	= []		# 接收Python的返回值
	var transmit_argv: 		Array			= []		# Godot传给Python的参数
	
	# 补全玩家输入的信息
	while len(input_info) < 2:
		input_info.append("None")
		
	## 转化传输标准格式
	## ProjectSettings.globalize_path()是将Godot的相对文件路径转化为系统路径
	var python_path: String = ProjectSettings.globalize_path(LoginSystem.get_python_processing_path())
	
	## 这里是先将传入的参数放进一个数组 
	## [Python程序路径, Excel文件路径, Godot人为规定的给Python指令, 账户ID, 账户密码]
	transmit_argv = [python_path,
					LoginSystem.excel_path, 
					str(0), 
					input_info[0],
					input_info[1]]
	
	## 向python传输基本信息
	## 格式 OS.execute(程序运行编译环境(没有会从环境变量自动找),
	##				  传入参数(第一个是程序路径,给Godot找脚本同时也会被作为参数被传过去),
	##				  用于接收返回值的数组
	##				  决定是否接收没被Python收走的异常报错(try 模型收走报错所以不会被接收)
	##				  如果程序是命令终端类的,可以为True打开一个命令终端运行脚本,为False则后台静默运行
	OS.execute("python", 
				transmit_argv, 
				receive_return, 
				true, 
				true)
				
	##  由于接收为数组,需要使用 join 函数将数组拼起来转化为 string, 前面 "" 代表每一个元素之间连接为空字符串
	##  由于Python返回值中包含转义符,使用strip_escapes()删除所有转义符
	var result_output: String = "".join(receive_return).strip_escapes()
	# 返回Python处理结果
	return result_output

2. 子注册管理器

负责接收Python的注册返回值(实现原理同登录一致)

extends Node2D

"""
注意传输路径
这里给python传入的脚本格式为:
[python主脚本地址, str Excel请求路径, int 请求操作, 账户ID, 账户密码]
且传递内容全部统一为 str 类型
"""

# 注册信息验证(阻塞线程)
func register_info_verify(input_info: Array[String]) -> String:
	var receive_return: 	Array[String]	= []
	var transmit_argv: 		Array			= []
	# 防止数组越界
	while len(input_info) < 2:
		input_info.append("None")
		
	# 转化传输标准格式	
	var python_path: String = ProjectSettings.globalize_path(LoginSystem.get_python_processing_path())
	transmit_argv = [python_path,
					LoginSystem.excel_path, 
					str(1), 
					input_info[0],
					input_info[1]]
	
	# 向python传输基本信息
	OS.execute("python", 
				transmit_argv, 
				receive_return, 
				true, 
				true)
				
	# 返回结果
	var result_output: String = "".join(receive_return).strip_escapes()
	return result_output

3. 主管理器

主管理器负责使用子管理器的接收功能,后主动更新当前基本登录信息(玩家ID等),主要用于向外开放API接口

以下为示例代码

extends Node2D

"""
需要注意的是,下文的所有返回值
均可以换成全局枚举类型
本文使用的是字符串比对
使用全局枚举的在登录界面演示中会使用
"""

# 登录信息信号
signal login_success	# 登录成功信号
signal login_out		# 账户登出信号

# 登录和注册两个子管理器
@export var Login		: Node2D
@export var Register	: Node2D

# 存储当前账号状态
var current_account_id: String 	= ""		# 当前成功登录玩家ID
var is_Login: bool 				= false		# 当前是否登录

# 主管理器提供统一 Excel 文件地址,这里使用绝对地址,当然也可以使用相对Godot的相对地址
# 主管理器同样要提供 Python脚本 的地址
var excel_path = "D:/GodotProject/Godot Excel Spreadsheet/godot-excel-spreadsheet/06_GameData/ServerData/GameDataExcel.xlsx"
var python_script = "res://06_GameData/ServerData/FileProcessingMain.py"

# 获得默认python脚本路径
func get_python_processing_path() 	-> String:
	return python_script
	
# 获得默认excel路径
func get_excel_path() 				-> String:
	return excel_path

"""
以下登录返回值类型
返回 0 登录成功
返回 1 账户信息不存在
返回 2 账户信息验证错误(密码错误)
"""
# 进行登录验证
func login_verify(name: String, password: String)		-> String:
	
	# 将获得的输入改为标准格式: ["名字", "密码"]
	var input_info: Array[String] = [name, password]
	# 调用子登录管理器验证并获得返回值
	var verify_result = Login.login_info_verify(input_info)
	
	# 登录器自验证注册是否成功,成功则更改登录服务器存储信息
	# 登录自验证函数在下文
	verify_login_success(verify_result, name)
	return verify_result

"""
以下注册返回值类型
返回 0 注册成功
返回 1 账户注册出现问题(非法字符/账户密码问题)
返回 2 账户已被注册
"""		
# 进行注册验证
func register_verify(name: String, password: String)	-> String:
	
	# 将获得的输入改为标准格式: ["名字", "密码"]
	var input_info: Array[String] = [name, password]
	var verify_result = Register.register_info_verify(input_info)
	
	# 登录器自验证注册是否成功
	verify_login_success(verify_result, name)
	return verify_result

# 获得当前登录玩家ID
func get_current_id() -> String:
	return current_account_id
	
# 更改当前玩家登录ID
func change_current_id(new_id: String) -> void:
	current_account_id = new_id

# 获得登录状态
func get_login_state() -> bool:
	return is_Login

# 登录成功验证
func verify_login_success(verify_result: String, verift_account_name: String) -> void:
	
	# 验证结果为 "0"(通过) 则更新当前登录系统登录状态 
	if verify_result == "0":
		is_Login = true
		login_success.emit()	# 发出登录信号
	
	# 更新服务器信息
	if is_Login:
		current_account_id = verift_account_name

# 退出登录,同时更新服务器信息
func exit_login() -> void:
	is_Login = false			# 登录状态为 未登录
	current_account_id = ""		# 重置当前登录ID
	login_out.emit()			# 发送登出信号

这里就是本文章使用的全局登录系统的全部代码

        2. 登录界面

登录界面是一个 独立的场景,同时 与登录系统保持独立 ,使用登录系统所提供的API。

登录界面要做以下流程:

1. 用户提供登录或者注册信息

2. 登录界面将信息转化为标准格式,将信息传输至全局登录管理器

3. 接收登录管理器返回的校验结果,弹出对应提示,完成对应逻辑

先给出登录界面的结构如下

1. 节点结构:

2. 显示界面:

        1. 操作

        2. 登录界面和注册界面复用一个窗口

文章将会解释登录注册窗口的代码。

全局类枚举管理器

extends Node

class_name GlobalEnum

# 注册或者登录状态枚举
enum User_State {LOGIN, REGISTER}

        1. 登录UI管理器(LoginGroup)

登录管理器负责管理其子节点的综合操作,比如子节点窗口是注册还是显示状态子节点初始是否可见等

以下为演示代码

extends Control

@export var Login_Windows: 		Window	# 子节点,登录注册复用窗口
@export var Confirm_Button: 	Button	# 子节点,确认登录按钮

# 管理器中存储的当前用户选择的注册或者登录状态,以及先前的注册或者登录状态
# 枚举状态从全局枚举类获得
var user_state: GlobalEnum.User_State 			= GlobalEnum.User_State.LOGIN
var user_per_state: GlobalEnum.User_State 		= GlobalEnum.User_State.REGISTER

# 初始化,函数在下文
func _ready() -> void:
	_initialize()
	
# 用户操作状态检测
func _user_operator_detected() -> void:
	# 仅在用户切换状态的时候触发
	if (user_state == user_per_state):
		return
	
	# 状态检测
	match user_state:
		#  用户处于登录状态,窗口为登录窗口
		GlobalEnum.User_State.LOGIN:
			Login_Windows.title = "登录"
			Confirm_Button.text = "登录"	
		#  用户处于注册状态,窗口为注册窗口
		GlobalEnum.User_State.REGISTER:
			Login_Windows.title = "注册"
			Confirm_Button.text = "注册"	
			
	# 更新完后更新用户先前状态与现在一致
	user_per_state = user_state
	
# 执行一次用户状态检测使第一次进入游戏窗口标题为登录
func _initialize() -> void:
	# 窗口初始化
	_user_operator_detected()

"""
改变用户状态
状态 0 登录
状态 1 注册
"""
# 这里是对两个选择(登录,注册)开放的API接口,用于改变当前用户选择状态
# 0 表示登录状态, 1 表示注册状态
# 这里 0 和 1 同样可以换成常量
func change_user_state(state: int) -> void:
	match state:
		0: user_state = GlobalEnum.User_State.LOGIN
		1: user_state = GlobalEnum.User_State.REGISTER
	# 更新一次用户选择状态
	_user_operator_detected() 

# 返回当前用户选择状态
func get_user_state() -> GlobalEnum.User_State:
	return user_state

          2. 用户选择管理器(ButtonGroup)

用来改变用户选择(登录或注册)

以下为代码演示

extends VBoxContainer

@export var Login_Window		: Window	# 登录界面窗口
@export var Login_Group			: Control	# 登录UI管理器(LoginGroup)

# 初始化
func _ready() -> void:
	_initialize()

# 按下登录按键,向登录UI管理器发送状态更改
func _on_login_pressed() -> void:
	Login_Window.visible = true			# 显示窗口
	Login_Group.change_user_state(0)	# 发送状态更改值为 0,上文已演示API对应代码

# 按下注册按键,向登录UI管理器发送状态更改
func _on_register_pressed() -> void:
	Login_Window.visible = true			# 显示窗口
	Login_Group.change_user_state(1)	# 发送状态更改值为 1,上文已演示API对应代码

# 退出登录系统(这里退出游戏)
func _on_exit_game_pressed() -> void:
	get_tree().quit()
	
# 初始化
func _initialize() -> void:
	# 窗口初始化登录窗口为不可见
	Login_Window.visible = false

        3. 用户输入管理器(InformationInput)

用户输入管理器用于接收并向登录管理器发送用户输入的账户信息,同时得到验证结果后执行弹窗等逻辑

以下为演示代码

extends VBoxContainer

@export var Login_Group:		Control		# 登录UI管理器(LoginGroup)
@export var Account_Input: 		LineEdit	# 账户单行输入框
@export var Password_Input: 	LineEdit	# 账户密码单行输入框

var login_success: 		String = "0"		# 账号成功状态
var login_info_issue: 	String = "1"		# 账号信息问题状态
var login_info_error:	String = "2"		# 账号无误,验证错误

# 登录提示框大小
var tip_size: Vector2 = Vector2(400, 200)

# 弹出登录提示
# 输入参数 (Python验证结果, 是否自定义弹窗内容(默认False), 自定义弹窗内容(默认空字符串))
func _general_tip(
	python_result: String, 
	is_custom: bool = false, 
	custom_tip: String = "") -> void:
	
		
	# 获取当前玩家选择状态
	var current_user_state: GlobalEnum.User_State
	current_user_state = Login_Group.get_user_state()

	# 创建弹窗,并修改弹窗标题大小
	var login_tip = AcceptDialog.new()
	login_tip.title = "登录提示"
	login_tip.size = tip_size
	
	# 自定义提示
	if is_custom:
		# 加入自定义信息
		login_tip.dialog_text = custom_tip	
					
		# 将登录提示加入场景并居中
		get_tree().current_scene.add_child(login_tip)
		login_tip.popup_centered()
		# 自定义提示加入完成后直接完成函数
		return
	
	# 匹配弹出类型(登录或注册)
	match current_user_state:
		# 登录状态
		GlobalEnum.User_State.LOGIN:
			# 匹配登录状态
			match python_result:
				login_success:
					login_tip.dialog_text = "登录成功"
				login_info_issue:
					login_tip.dialog_text = "账号信息不存在"
				login_info_error:
					login_tip.dialog_text = "密码错误"
		# 注册状态
		GlobalEnum.User_State.REGISTER:
			# 匹配注册状态
			match python_result:
				login_success:
					login_tip.dialog_text = "注册成功"
				login_info_issue:
					login_tip.dialog_text = "注册密码非法或长度小于4(账号长度不能小于3)"
				login_info_error:
					login_tip.dialog_text = "注册信息已存在"
	
	# 将登录提示加入场景并居中
	# 注意 login_tip.popup_centered() 一定要先在弹窗加入节点树后再居中
	# 不然就弹窗由于没加入场景树,执行一次居中后不是在当前场景内居中,再加入场景树后就会居中失败
	get_tree().current_scene.add_child(login_tip)
	login_tip.popup_centered()

# 登录按钮被点击,传输登录数据
func _on_login_pressed() -> void:
	
	# 获取当前状态
	var current_user_state: GlobalEnum.User_State
	current_user_state = Login_Group.get_user_state()
	
	# 获得ID输入信息
	var input_account_name: 	String 		= Account_Input.text.strip_edges()	# 账户ID
	var input_account_password: String 		= Password_Input.text.strip_edges()	# 账户密码
	
	# 先检查账户的输入是否完全
	if input_account_name.is_empty() or input_account_password.is_empty():
		_general_tip("0", true, "账号或密码不能为空")
		Password_Input.clear()
		return
		
	var verify_result
	# 匹配用户状态
	match current_user_state:
		# 登录状态使用登录验证
		GlobalEnum.User_State.LOGIN:
			verify_result = LoginSystem.login_verify(input_account_name, input_account_password)
		# 注册状态使用注册验证
		GlobalEnum.User_State.REGISTER:
			verify_result = LoginSystem.register_verify(input_account_name, input_account_password)
	
	# 输出提示
	_general_tip(verify_result)
	# 清除密码输入框
	Password_Input.clear()

3. 登录界面演示

先给出演示场景结构

可以看到并没有登录场景,这里放在了 UiControl 代码里

extends CanvasLayer

# 这里是登录界面场景
@export var Login_System: PackedScene

func _ready() -> void:
	_initialize()
	
# 初始化主菜单
func _initialize() -> void:
	# 初始化登录系统
	if not LoginSystem.get_login_state():
		# 添加登录界面场景
		var login_system = Login_System.instantiate()
		add_child(login_system)

启动游戏,看到登录界面显示

登录和注册分别根据选择类型弹出不同窗口

登录已经有账号

显示

其余状态同理,登录失败,用户名或密码不能为空

我们演示注册,同样先给出Excel状态

然后注册

显示

检查Excel可以看到信息被存入

4. 结尾返回

        这里展示了Godot内部对接Python后登录管理器的构建与使用,这里返回主要文章

Godot(4.X): 外接Python处理Excel数据: 账号管理系统实现-CSDN博客

更多推荐