上海网站备案审核时间,郑州做网站易云巢,网站建设公司未来发展方向,安阳企业网站优化外包概述
Godot中的菜单创建是一个复杂的灾难性工作#xff0c;往往无从下手#xff0c;我也是不止一次尝试简化菜单的创建。
从自己去年的发明“简易树形数据”用于简化Tree控件获得灵感#xff0c;于是尝试编写了用于表示菜单数据的EasyMenuData类#xff0c;以及对应的纯文…概述
Godot中的菜单创建是一个复杂的灾难性工作往往无从下手我也是不止一次尝试简化菜单的创建。
从自己去年的发明“简易树形数据”用于简化Tree控件获得灵感于是尝试编写了用于表示菜单数据的EasyMenuData类以及对应的纯文本数据格式和对应的MenuBar控件扩展ETDMenuBar。
于是乎你只需要在创建菜单栏时添加一个ETDMenuBar控件并为其data属性指定符合规定的简易数据以及图标集。便可以轻松设计和获得复杂层次的菜单以用于你创建的Godot桌面程序。 得到的效果 原理
MenuBar生成菜单的原理可以参看我之前写的《【Godot4.2】菜单相关控件和节点完全解析》简易树形数据解析的原理可以参看我之前写的《【Godot4.2】EasyTreeData通用解析》
EasyMenuData
我创建了一个名叫EasyMenuData的类可以解析形如下的数据并通过其to_menu()方法返回一个PopUpMenu实例
文件新建 | 0 | -1 | CtrlS打开 | 1 | 0最近 | 1 | 1文件1文件2文件3
关闭 | -1 | -1 | CtrlQ其格式采用
文本 | 图标索引 | 复选状态标记 | 快捷键其中
|是分隔符顺序和意义对应可以忽略后面的设定但顺序依然不能改变符合要求的形式举例如下
文本
文本 | 图标索引
文本 | 图标索引 | 复选状态标记
文本 | 图标索引 | 复选状态标记 | 快捷键图标索引为负数或超出icons属性提供的图标集范围时将不显示复选状态标记为负数则不显示复选框大于等于0显示复选框0不选中大于0选中快捷键只需要指定以号连接的字符串形式即可不区分大小写
ETDMenuBar
我创建了一个扩展的MenuBar类型ETDMenuBar其data属性接收如下形式的数据
文件新建 | 0 | -1 | CtrlS打开 | 1 | 0最近 | 1 | 1文件1文件2文件3---关闭 | -1 | -1 | CtrlQ编辑撤销重做---清空也就是在多个一级菜单之间用进行分隔。
运行场景后会自动根据data属性给定的数据生成菜单栏。
EasyMenuData源码
#
# 名称EasyMenuData
# 类型类
# 简介基于ETD构造PopupMenu的类
# 作者巽星石
# Godot版本v4.3.stable.steam [77dcf97d8]
# 创建时间2025年2月21日20:11:05
# 最后修改时间2025年3月1日22:24:40
#
class_name EasyMenuDatavar _root:EasyMenuItem# 获取生成的菜单
func to_menu() - PopupMenu:return _root.to_menu()# 获取根节点信息
func get_root_data() - String:return _root.data if _root else # 内部类
# 单项数据
class EasyMenuItem:# ------- 图标集var icons:Array[Texture2D]var icon_width:float # 图标最大宽度# ------- 菜单项信息var label:String # 菜单项文本var icon:Texture2D # 菜单项图标var shortcut:Shortcut # 菜单项快捷键var show_checkbox:bool # 是否显示复选var checked:bool # 是否选中# ------- 节点信息var data:String # 原始未解析的文本var deep:int # 节点深度var parent:EasyMenuItem # 父节点var children:Array[EasyMenuItem] # 子节点集合func _init(_data:String,_deep:int,_icons:Array[Texture2D],_icon_width:float) - void:data _datadeep _deepicons _iconsicon_width _icon_widthparse_data()children []# 解析传入的数据func parse_data():if data ! or data ! ---:var ds data.split( | ,false)match ds.size():1:label data2:label ds[0]var idx int(ds[1])icon get_idx_icon(idx)3:label ds[0]var idx int(ds[1])icon get_idx_icon(idx)show_checkbox is_show_box(int(ds[2]))checked int(ds[2])4:label ds[0]var idx int(ds[1])icon get_idx_icon(idx)show_checkbox is_show_box(int(ds[2]))checked int(ds[2])shortcut new_shortcut(ds[3])# 是否显示复选框func is_show_box(tag:int) - bool:return true if tag 0 else false # 仅在大于等于0显示# 获取对应下标的图标func get_idx_icon(idx:int) - Texture2D:var icn:Texture2Dif icons:if idx in range(icons.size()):icn icons[idx]return icn# 按给定字符串创建并返回一个快捷键func new_shortcut(key_str:String) - Shortcut:var sc:Shortcutif key_str ! :sc Shortcut.new()var event : InputEventKey.new()event.pressed truevar keys key_str.split(,false)for key in keys:match key.to_lower(): # 小写ctrl:event.ctrl_pressed truealt:event.alt_pressed trueshift:event.shift_pressed true_:event.keycode OS.find_keycode_from_string(key)sc.events.append(event)return scfunc get_path():var path path labelif parent:path parent.get_path() / pathreturn path# 转为菜单项或子菜单func to_menu(menu:PopupMenu null):var root_menu:PopupMenuif menu null: # 根节点menu PopupMenu.new()if data ! :menu.name label # 名称与菜单项一致root_menu menufor child in children:child.to_menu(root_menu)return root_menuelse:# 添加菜单项if data ---: # 分割线menu.add_separator()else:menu.add_item(label) # 文本var last menu.item_count-1# 将路径以元数据形式存储menu.set_item_metadata(last,get_path())# 图标if icon: menu.set_item_icon(last,icon)menu.set_item_icon_max_width(last,icon_width) # 设定图标宽度# 复选框menu.set_item_as_checkable(last,show_checkbox)if show_checkbox:menu.set_item_checked(last,checked)# 快捷键if shortcut:menu.set_item_shortcut(last,shortcut,true)# 如果有子节点if children.size()0:# 创建子PopupMenuvar sub_menu PopupMenu.new()sub_menu.name label # 名称与菜单项一致menu.add_child(sub_menu)# 指定为当前项的子菜单menu.set_item_submenu_node(menu.item_count-1,sub_menu)for child in children:child.to_menu(sub_menu)# 方法
# 创建并返回一个EasyTreeItem实例
func create_item(text:String,icons:Array[Texture2D],icon_width:float,p_node:EasyMenuItem null) - EasyMenuItem:var itm EasyMenuItem.new(text,0,icons,icon_width)if _root:if p_node:itm.deep p_node.deep 1itm.parent p_nodep_node.children.append(itm)else:itm.deep _root.deep 1itm.parent _root_root.children.append(itm)else:_root itmreturn itm# 由多行文本创建
static func new_with_etd_str(etd_str:String,icons:Array[Texture2D],icon_width:float) -EasyMenuData:var edt EasyMenuData.new()var items etd_str.split(\n,false) # 将ETD字符串按行切分为字符串数组var pre_itm:EasyMenuItem # 记录前一项var p_itm:EasyMenuItem null # 记录父节点# 遍历每行数据for i in range(items.size()):if items[i].strip_edges() ! :# 第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.parent # 父节点与前一项父节点一样_: if d_deep0: # 缩进比前一项浅# 通过缩进差值计算获得合适的父节点p_itm pre_itm for j in range(d_deep1):p_itm p_itm.parent# 实际创建和添加TreeItem到Tree控件var itm:EasyMenuItem edt.create_item(items[i].replace(\t,),icons,icon_width,p_itm)pre_itm itm # 将当前项记录为前一项return edt# 返回字符串的Tab缩进值
static func deep(sttr:String):return sttr.rstrip( ).count(\t)
ETDMenuBar源码
#
# 名称ETDMenuBar
# 类型自定义控件MenuBar扩展
# 简介基于EasyMenuData构造的MenuBar
# 作者巽星石
# Godot版本v4.3.stable.steam [77dcf97d8]
# 创建时间2025年2月21日20:49:44
# 最后修改时间2025年3月1日22:26
# class_name ETDMenuBar extends MenuBarsignal item_click(path:String) # 菜单项被点击# 参数
export_multiline var data:String ## 菜单栏简易数据
export var icons:Array[Texture2D] ## 图标集合
export var icon_width:float 16.0 ## 图标最大宽度func _ready() - void:reload()# 方法
# 重新加载
func reload():clear()for dt in data.split(.repeat(8),false):var emd EasyMenuData.new_with_etd_str(dt,icons,icon_width)var menu emd.to_menu()set_connects(menu)add_child(menu)# 递归形式为所有层级的菜单处理菜单项点击的信号处理
func set_connects(menu:PopupMenu):# 统一设置字号var font_size get(theme_override_font_sizes/font_size)menu.set(theme_override_font_sizes/font_size,font_size)menu.connect(index_pressed,func(index:int):emit_signal(item_click,menu.get_item_metadata(index)))for sub_menu in menu.get_children():if sub_menu is PopupMenu:set_connects(sub_menu)# 清空子节点
func clear():for child in get_children():child.queue_free()菜单项点击的处理
只需要链接item_click信号就可以处理菜单项的点击了。 信号参数path会返回菜单项的路径类似于下面这样
文件/最近/文件2
文件/关闭这样通过写一个math分支语句来匹配菜单项的路径就可以实现具体菜单项的功能。
美化
通过外面套一个PanelContainer并且设定flat可以快速的美化菜单栏。
而且我设定所有的子菜单都保持于MenuBar一致的字号只要在MenuBar上设置一次就可以了。 总结
EasyMenuData其实对应的是单个PopupMenu的描述和生成可以用于普通菜单生成也可用于弹出菜单的设计而ETDMenuBar则是对应菜单栏生成。