【Godot4.2】Tree控件自定义树形数据ETD及其解析

概述

在Godot中利用MenuBar+PopupMenu设计复杂菜单,以及利用Tree控件创建复杂的树形导航,都是一件繁复的工作。

通过某种数据形式,以及一个解析函数,自动加载生成菜单或Tree的树形列表,便成为了自然而然的选择。

我首先想到的是字典和JSON,两者其实类似,都是键值对形式,存在很多重复冗余的键定义。
所以我想自创一种树形数据表示形式。我对它的要求是:

  • 可以很好的表示树形结构
  • 基于纯文本,依靠字符串解析可以轻松获取数据
  • 语法简洁,便于手动书写和更改,更易于阅读

尝试了好几种形式后,两种思路被验证为可行,其中一种非常适用于动态解析生成菜单栏(本篇将不做介绍)。

而另一种适合用于Tree控件的树形结构定义,也就是本篇下面要讲的自定义数据形式。

简易树形数据(Easy Tree Data,ETD)

没错,我甚至给这种我自创的纯文本数据描述形式,起了个名字,叫“简易树形数据”,英文名叫Easy Tree Data,简称ETD。如果我选择保存文件的话,可能会采用.etd作为后缀名。
下面是最基本的ETD示例:

条目1
	条目1.1
	条目1.2
		条目1.2.1
		条目1.2.2
	条目1.3

没错,就是这么简洁,就是一个多行字符串,但是仍然有一些细节:

  • 只能有一个一级节点:或者叫根节点,与Tree控件特性对应
  • 用缩进代表层级关系:而缩进必须使用Tab键,也就是\t字符

解析原理

  • 将ETD字符串用\n划分为单行字符串组成的数组,每行代表一个树控件子节点(TreeItem)的定义数据,比如上文的ETD字符串用\n划分后,变为:
["条目1", "\t条目1.1", "\t条目1.2", "\t\t条目1.2.1", "\t\t条目1.2.2", "\t条目1.3"]
  • 然后我们遍历这个数组(相当于遍历每行数据),并比较当前项与前一项的缩进关系\t字符的数目),从而决定将当前项添加为前一项的子节点,还是前一项的父节点的子节点,还是前一项父节点的父节点的子节点等等…,其判断规则如下:
    • 如果是第1项直接添加为根节点
    • 否则判断和比较当前项与上一项的缩进深度
      • 如果缩进深度一样,将当前项添加到前一项的父节点上
      • 如果比前一项深,则添加为前一项的子节点
      • 如果比前一项浅,则添加为前一项爷爷或更爷爷节点的子节点

基础实现代码如下:

var tree = $Tree                      # 指定Tree控件

var items = data.split("\n",false)    # 将ETD字符串按行切分为字符串数组
var pre_itm:TreeItem                  # 记录前一项对应的TreeItem
var p_itm = null                      # 记录父节点

# 遍历每行数据
for i in range(items.size()):
	# 第1行直接添加为Tree控件的根节点(跳过下面if部分)
	# 从第2行开始比较当前行与前一行的缩进深度(也就是\t的数目)
	if i > 0: 
		var d_deep = deep(items[i-1]) - deep(items[i])  # 与前一行数据的缩进差值
		match d_deep:
			-1:                                # 缩进比前一项深:
				p_itm = pre_itm                # 将前一项作为父节点
			0:                                 # 缩进深度与前一项一样:
				p_itm = pre_itm.get_parent()   # 父节点与前一项父节点一样
			_:                                 
				if d_deep>0:                   # 缩进比前一项浅
					                           # 通过缩进差值计算获得合适的父节点
					p_itm = pre_itm            
					for j in range(d_deep+1):
						p_itm = p_itm.get_parent()
	
	# 实际创建和添加TreeItemTree控件
	var itm:TreeItem = tree.create_item(p_itm)
	itm.set_text(0,items[i])
	pre_itm = itm                              # 将当前项记录为前一项

实际解析和添加后的效果:

在这里插入图片描述

为项数据添加额外设定

上面的ETD只是最简单的形式,只解决了Tree控件的纯文本项的加载,实际使用中我们通常还需要设定每个TreeItem图标,或者鼠标提示文本文本颜色高亮颜色选中颜色,甚至元数据

因此需要每行数据都可以划分为几个子数据,设计思路也很简单,同样使用字符串分割为字符串数组的方式,只需要设定好特殊的分隔字符串就行。我的选择是使用|作为分隔符,也就是一个竖线两边各带一个空格。

这里我只拓展定义了图标索引的ETD形式:

条目1 | 0
	条目1.1 | 2
	条目1.2 | 2
		条目1.2.1 | 3
		条目1.2.2 | 3
	条目1.3 | 2

也就是直接在每行所代表的TreeItem数据中,添加一个代表图标索引的数字。

这里需要定义一个数组存储图标

@export var icons:Array[Texture2D]    # 图标集
@export var icon_width = 16           # 图标最大宽度

用导出变量形式我们可以更方便的设定图标列表:

在这里插入图片描述
这里我添加了4个图标。

修改ETD的解析代码,加入对图标索引数据的解析和处理:

var tree = $Tree                      # 指定Tree控件

var items = data.split("\n",false)    # 将ETD字符串按行切分为字符串数组
var pre_itm:TreeItem                  # 记录前一项对应的TreeItem
var p_itm = null                      # 记录父节点

# 遍历每行数据
for i in range(items.size()):
	# 第1行直接添加为Tree控件的根节点(跳过下面if部分)
	# 从第2行开始比较当前行与前一行的缩进深度(也就是\t的数目)
	if i > 0: 
		var d_deep = deep(items[i-1]) - deep(items[i])  # 与前一行数据的缩进差值
		match d_deep:
			-1:                                # 缩进比前一项深:
				p_itm = pre_itm                # 将前一项作为父节点
			0:                                 # 缩进深度与前一项一样:
				p_itm = pre_itm.get_parent()   # 父节点与前一项父节点一样
			_:                                 
				if d_deep>0:                   # 缩进比前一项浅
											   # 通过缩进差值计算获得合适的父节点
					p_itm = pre_itm            
					for j in range(d_deep+1):
						p_itm = p_itm.get_parent()
	
	# 实际创建和添加TreeItemTree控件
	var itm:TreeItem = tree.create_item(p_itm)
	# 解析单项的具体信息
	if items[i].find(" | ")>0:
		var itm_data = items[i].split(" | ")
		itm.set_text(0,itm_data[0])
		itm.set_icon(0,icons[int(itm_data[1])])
		itm.set_icon_max_width(0,icon_width)
	else:
		itm.set_text(0,items[i])
	pre_itm = itm                              # 将当前项记录为前一项

解析后的效果:

在这里插入图片描述
可以看到图标被正确的显示。

鼠标提示文本、文本颜色,高亮颜色、选中颜色以及元数据等,实现思路都是一样的。

ETD只是一个框架,基于它你可以具体的定义和存储不同的树形数据,并解析为Tree控件显示的内容。

自定义控件和data参数

我们可以创建继承自Tree的自定义控件,然后将ETD数据作为一个“检视器”面板参数(导出变量),我们可以直接在检视器面板修改ETD数据,并实时查看生成的树形结构。
在这里插入图片描述

创建静态函数

除了创建特化的Tree控件,解析特化的ETD数据外。也可以将不同的ETD数据定义和解析写成静态函数,统一的存入一个静态函数库中,方便任何地方调用。

这样,具体的ETD格式定义和解析就可以被保留,并且不断扩展。

  • 24
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

巽星石

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值