Godot自定义控件样式语法解析

前言

本篇原始文章写于2023年8月7日,存储在我的语雀文档中。但是语雀分享有诸多不便,为了让更多Godoter更轻松的搜到和看到,就转过来了。

这个项目我上传了Github,后续会贴上链接。

概述

Godot控件体系存在的问题之一就是样式无法用纯文本形式简洁而清晰的定义,一切都要靠主题编辑器或检视器面板那一套手动的东西。4.x提供了一些样式属性和方法,但仍然算不上简洁。

在样式定义方面,前端的CSS样式表可谓是最佳实践之一。

如果能够以类似CSS一样的纯文本形式解析和控制Godot的控件样式,那么样式定义就能更轻松。

基于这样的想法,笔者尝试建立了一个类CSS样式的样式书写和解析机制。让使用者可以基于一个简单的导出变量,用纯文本的方式定义控件的样式。

样式解析函数库

ConfigFile提供了一个名叫parse() 的方法,可以将符合ConfigFile风格和书写规则的字符串直接解析到ConfigFile实例。进而可以使用其提供的方法便捷的遍历节、键和值。

基于此我创建了一个名为Sty的静态函数库。

下面是初期的一个效果,已经可以解析按钮多个状态下的一些简单样式。

swift 复制代码
# ========================================================
# 名称:Sty
# 类型:静态函数库
# 简介:用于解析和应用控件样式
# 作者:巽星石
# Godot版本:4.1.1-stable (official)
# 创建时间:2023-08-07 23:11:57
# 最后修改时间:2023-08-07 23:11:57
# ========================================================
class_name Sty

# 样式解析
static func parse_style(ctl:Control,style_str:String):
	var cfg = ConfigFile.new()
	var err = cfg.parse(style_str.replace(":","=\"").replace(";","\""))
	if err == OK: # 解析成功
		for section in cfg.get_sections():
			for key in cfg.get_section_keys(section):
				var val = cfg.get_value(section,key)
				match key:
					"font_size":
						pass
						
					"color":
						match section:
							"normal":
								ctl.add_theme_color_override("font_color",Color(val))
							"hover","pressed","disabled","focus":
								ctl.add_theme_color_override("font_%s_color" % section,Color(val))
					"bg_color":
						match section:
							"normal","hover","pressed","disabled","focus":
								var stylebox:StyleBoxFlat = get_stylebox(ctl,section)
								stylebox.bg_color = Color(val)
					"radius":
						match section:
							"normal","hover","pressed","disabled","focus":
								var stylebox:StyleBoxFlat = get_stylebox(ctl,section)
								var vals = val.split(",")
								stylebox.corner_radius_top_left = int(vals[0])
								stylebox.corner_radius_top_right = int(vals[1])
								stylebox.corner_radius_bottom_left = int(vals[2])
								stylebox.corner_radius_bottom_right = int(vals[3])
					"border_width":
						match section:
							"normal","hover","pressed","disabled","focus":
								var stylebox:StyleBoxFlat = get_stylebox(ctl,section)
								var vals = val.split(",")
								stylebox.border_width_left = int(vals[0])
								stylebox.border_width_top = int(vals[1])
								stylebox.border_width_right = int(vals[2])
								stylebox.border_width_bottom = int(vals[3])
					"border_color":
						match section:
							"normal","hover","pressed","disabled","focus":
								var stylebox:StyleBoxFlat = get_stylebox(ctl,section)
								stylebox.border_color = Color(val)
					"padding":
						match section:
							"normal","hover","pressed","disabled","focus":
								var stylebox:StyleBoxFlat = get_stylebox(ctl,section)
								var vals = val.split(",")
								stylebox.content_margin_left = int(vals[0])
								stylebox.content_margin_top = int(vals[1])
								stylebox.content_margin_right = int(vals[2])
								stylebox.content_margin_bottom = int(vals[3])
					"margin":
						match section:
							"normal","hover","pressed","disabled","focus":
								var stylebox:StyleBoxFlat = get_stylebox(ctl,section)
								var vals = val.split(",")
								stylebox.expand_margin_left = int(vals[0])
								stylebox.expand_margin_top = int(vals[1])
								stylebox.expand_margin_right = int(vals[2])
								stylebox.expand_margin_bottom = int(vals[3])
					"shadow_color":
						match section:
							"normal","hover","pressed","disabled","focus":
								var stylebox:StyleBoxFlat = get_stylebox(ctl,section)
								stylebox.shadow_color = Color(val)
					"shadow_size":
						match section:
							"normal","hover","pressed","disabled","focus":
								var stylebox:StyleBoxFlat = get_stylebox(ctl,section)
								stylebox.shadow_size = int(val)
					"shadow_offset":
						match section:
							"normal","hover","pressed","disabled","focus":
								var stylebox:StyleBoxFlat = get_stylebox(ctl,section)
								var vals = val.split(",")
								stylebox.shadow_offset = Vector2(float(vals[0]),float(vals[1]))
					"skew":
						match section:
							"normal","hover","pressed","disabled","focus":
								var stylebox:StyleBoxFlat = get_stylebox(ctl,section)
								var vals = val.split(",")
								stylebox.skew = Vector2(float(vals[0]),float(vals[1]))

# 获取控件对应名称的样式盒
static func get_stylebox(ctl:Control,name:String) -> StyleBoxFlat:
	var stylebox:StyleBoxFlat
	if ctl.has_theme_stylebox_override(name):
		stylebox = ctl.get_theme_stylebox(name)
	else:
		stylebox= StyleBoxFlat.new()
		ctl.add_theme_stylebox_override(name, stylebox)
	return stylebox

实际使用

为普通节点添加style属性

我们创建一个UI场景,添加一个Button

Button添加如下代码:

swift 复制代码
@tool
extends Button

@export_multiline var style:String = "":
	set(val):
		style = val
		Sty.parse_style(self,val)

接着我们在检视器面板的style变量中,定义如下的样式:

[normal]
font_size :64;
color:#FF4400;
bg_color:yellow;
radius:55,5,56,45;
skew:0.1,0;
border_width:5,2,5,2;
border_color:#444;
[disable]
color:#00FF00;
[hover]
color:#00FF00;
bg_color:#ccc;
radius:55,5,5,45;

关于语法

因为我是以Button控件为模板进行初期的样式语法测试,所以以Button为例的话,我们可以看到一个按钮的样式其实是可以分为几个状态的:正常(normal),禁用(disable),鼠标经过(hover),按下(pressed),获得焦点(focus)。

所以我采用了状态优先,属性名称简化和重用的设计,并且采用了Godot的ConfigFile格式。

将按钮的不同状态作为配置文件的section,但是为了简化书写,让其更像是CSS风格,所以采用了冒号和封号,而不是等号来设定键值对。在解析时冒号和封号会被替换。

然后对应的按钮样式被定义为如下图:

因为加了@tool关键字,所以在normal状态中定义的样式都会被实时的显示在编辑器中,而其他的诸如hover等需要在运行后查看。

相关推荐
老大白菜2 天前
Godot RPG 游戏开发指南
游戏引擎·godot
ChoSeitaku1 个月前
No.1 杀戮尖塔Godot复刻|项目概述|场景设置
游戏引擎·godot
Cici_ovo1 个月前
godot游戏引擎_瓦片集和瓦片地图介绍
游戏引擎·godot
峰度偏偏1 个月前
【适配】屏幕拖拽-滑动手感在不同分辨率下的机型适配
算法·游戏·unity·ue5·ue4·godot
代码盗圣2 个月前
GODOT 4 不用scons编译cpp扩展的方法
游戏引擎·godot
知兀2 个月前
单例模式(自动加载)
笔记·游戏引擎·godot
知兀2 个月前
Godot中的信号
笔记·游戏引擎·godot
知兀2 个月前
【godot游戏引擎学习笔记】初识界面
笔记·游戏引擎·godot
知兀2 个月前
Godot中类和静态类型
笔记·游戏引擎·godot
知兀2 个月前
编辑器、节点树、基础设置
笔记·游戏引擎·godot