Lhy19981018(讨论 | 贡献) (→UI组件) |
|||
(未显示3个用户的7个中间版本) | |||
第1行: | 第1行: | ||
{{Version|timeless | {{Version|timeless}} | ||
CK3的用户界面(UI)是高度可修改的,但由于这个原因,UI修改器会禁用成就,因为玩家可能会用它们作弊。 | CK3的用户界面(UI)是高度可修改的,但由于这个原因,UI修改器会禁用成就,因为玩家可能会用它们作弊。 | ||
第115行: | 第115行: | ||
这可以帮助你更好地看到代码的结构,注意到任何缺失的或额外的括号,对于一些编辑器来说,这也是正确折叠代码块所需要的。 | 这可以帮助你更好地看到代码的结构,注意到任何缺失的或额外的括号,对于一些编辑器来说,这也是正确折叠代码块所需要的。 | ||
=== 提升和功能 === | |||
每个窗口都有一个预定义的命令集合-提升和功能(promotes and functions) -可供使用。这些命令可以在使用 <code>DumpDataTypes</code> 控制台命令后,在 <code>Documents/Paradox Interactive/Crusader Kings III/logs/</code> 目录下的 <code>data_types.log</code> 文件中找到。 | |||
它们用于显示游戏中的所有数据,例如您的姓名、金币、子女,并且可以设置按钮操作。 | |||
一个“promote”返回一个作用域(scope),即一个游戏对象,如角色或省份,而一个 "function" 返回一个数字、一个字符串或一个布尔值(true/false)等等。 | |||
全局命令可以在任何地方使用,比如 <code>GetPlayer</code> (返回玩家角色)或 <code>GetCurrentDate</code> (获取当前日期)。 | |||
其他命令只能在它们所属的窗口/对象中使用,例如,GetParents 只能在角色窗口中使用,并且必须以 <code>CharacterWindow.GetParents</code> 开始。 | |||
命令可以像这样链接起来: | |||
<code>CharacterWindow.GetCharacter.GetPrimaryTitle.GetHeir.GetPrimarySpouse.GetFather</code> | |||
对象可以继承其父对象的范围,这意味着我们不需要重新键入上面的行来显示关于此角色的信息。相反,我们可以将小部件的数据上下文设置为此行,然后其中的每个文本框都将使用 <code>"[Character.GetNameNoTooltip]"</code> , <code>"[Character.GetGold]"</code> 等等。 | |||
同样的规定也适用于gridboxes的条目 | |||
=== UI组件 === | === UI组件 === | ||
第157行: | 第177行: | ||
* 在另一个hbox中的hbox将有0个大小,并且不会展开其子代。使用<code>layoutpolicy_horizontal = expanding</code>来调整它的大小(或者在vbox中使用<code>layoutpolicy_vertical = expanding</code>)。 | * 在另一个hbox中的hbox将有0个大小,并且不会展开其子代。使用<code>layoutpolicy_horizontal = expanding</code>来调整它的大小(或者在vbox中使用<code>layoutpolicy_vertical = expanding</code>)。 | ||
* 接受数据模型(从游戏数据中创建列表)。 | * 接受数据模型(从游戏数据中创建列表)。 | ||
<code>dynamicgridbox</code> | <code>dynamicgridbox</code> | ||
* | * 仅用于数据模型。 | ||
* | * 将所有条目垂直排列。使用<code>flipdirection = yes</code> 可以使其水平排列。 | ||
* | * 默认情况下不忽略不可见项目。使用 <code>ignoreinvisible = yes</code> 来进行更改。 | ||
* | * 根据内容的调整大小,并受到最小尺寸和最大尺寸的限制。 | ||
* | * 条目可以有不同的尺寸。 | ||
* | * 有时候在处理非常长的列表时可能会出现卡顿的情况。 | ||
<code>fixedgridbox</code> | <code>fixedgridbox</code> | ||
* | * 类似于动态框,但其所有项目都是固定尺寸的(本质上是一个表格)。 | ||
* | * 仅用于数据模型。 | ||
* | * 将所有项目垂直排列。使用 <code>flipdirection = yes</code> 可以使其水平排列。 | ||
* | * 无法忽视不可见的条目。 | ||
* | * 可以固定大小,根据内容进行调整,并受最小尺寸和最大尺寸的限制。 | ||
* | * 处理长列表性能更好。 | ||
<code>overlappingitembox</code> | <code>overlappingitembox</code> | ||
* | * 仅用于数据模型。 | ||
* | * 将所有项目水平排列,并在列表长度超过盒子大小时重叠显示。使用 <code>flipdirection = yes</code> 使其水平排列。 | ||
* | * 可以是固定尺寸或自动调整大小。 | ||
<code>scrollarea</code> | <code>scrollarea</code> | ||
* | * 一个带有滚动条的小部件,如果内容超过其大小,滚动条将出现。 | ||
* | * 滚动条可以通过 <code>scrollbarpolicy_horizontal = always_off</code> 和 <code>scrollbarpolicy_vertical = always_off</code> 来禁用。 | ||
** | ** 可以使用没有滚动条的滚动区域来裁剪列表或图片。 | ||
* | * 可以固定大小,根据内容进行调整,并受最小尺寸和最大尺寸的限制。 | ||
<code>button</code> | <code>button</code> | ||
* | * 一个可点击的对象。 接受 <code>onclick</code> 和 <code>onrightclick</code>. | ||
** | ** 在添加右键功能时,请包括 <code>button_ignore = none</code>. | ||
* | * 无默认材质 | ||
* | * 可以根据其子代进行固定大小或调整大小。 | ||
** | ** 一个0x0按钮可以用来添加不可见的快捷键。 | ||
<code>icon</code> | <code>icon</code> | ||
* | * 显示一个材质 | ||
* | * 可用作小部件来存储子代。 | ||
* | * 可以通过 <code>mirror = horizontal</code> 或 <code>mirror = vertical</code> 进行翻转. | ||
<code>textbox</code> | <code>textbox</code> | ||
* | * 显示文字内容。 | ||
* | * 可以是固定尺寸或自动调整大小。 | ||
* | * 使用 <code>elide = right</code> 或 <code>elide = left</code> 来截断过长的文本。 | ||
* | * 可以是单行或多行,当 <code>multiline = yes</code> 时为多行。 | ||
* | * 游戏文件通常使用在gui/shared/text.gui 中设置的模板,比如 <code>text_single</code> 。使用它们可以保持视觉一致性,并减少每次编写代码的工作量。 | ||
=== | ==== hbox/vbox ==== | ||
Hboxes和vboxes是可调整大小的容器,它们对其子代进行排序、调整大小或展开。hbox水平排序子代,vbox垂直排序子代,除此之外它们的工作方式相同,所以这里的所有示例都适用于两者。 | |||
以下是屏幕截图,hboxes 带有黑色背景。所有示例都可以在 [https://steamcommunity.com/sharedfiles/filedetails/?id=2579010074 UI Library mod] 中找到。 | |||
{| class="wikitable" | |||
|默认情况下,hbox 的工作方式类似于流容器(flowcontainer):它以水平方向排列子项,并调整大小以适应它们。 | |||
<pre> | |||
hbox = { | |||
button_round = {} | |||
button_round = {} | |||
} | |||
</pre> | |||
|[[File:simple_hbox_wide.jpg]] | |||
|- | |||
|使用 <code>layoutpolicy_horizontal = expanding</code> 时,它会扩展到其父级的宽度,并展开其子元素。 | |||
<pre> | |||
hbox = { | |||
layoutpolicy_horizontal = expanding | |||
button_round = {} | |||
button_round = {} | |||
} | |||
</pre> | |||
|[[File:Expanded_hbox.jpg]] | |||
|- | |||
|要将其子项分组,我们可以使用 <code>expand = {}</code> ,这是一个模板部件,其布局策略设置为"增长(growing)"(在gui/shared/windows.gui中定义)。 | |||
<pre> | |||
hbox = { | |||
layoutpolicy_horizontal = expanding | |||
button_round = {} | |||
button_round = {} | |||
expand = {} | |||
} | |||
</pre> | |||
|[[File:Ordered_hbox.jpg]] | |||
|- | |||
|"扩大(expanding)"策略涉及到子代的规模调整。 | |||
这对于创建标签页非常有用,无需手动设置它们的大小。 | |||
<pre> | |||
hbox = { | |||
layoutpolicy_horizontal = expanding | |||
button_standard = { layoutpolicy_horizontal = expanding } | |||
button_standard = { layoutpolicy_horizontal = expanding } | |||
} | |||
</pre> | |||
|[[File:tabs hbox 2.jpg]] | |||
|- | |||
|随着水平和垂直方向的“扩展(expanding) ”策略,hbox 及其子代将在两个方向上进行调整大小。 | |||
<pre> | |||
hbox = { | |||
layoutpolicy_horizontal = expanding layoutpolicy_vertical = expanding | |||
button_standard = { | |||
layoutpolicy_horizontal = expanding layoutpolicy_vertical = expanding | |||
} | |||
button_standard = { | |||
layoutpolicy_horizontal = expanding layoutpolicy_vertical = expanding | |||
} | |||
} | |||
</pre> | |||
|[[File:big hbox.png]] | |||
|} | |||
如果放置在固定大小的父级容器中,默认情况下,它会水平和垂直方向上扩展到整个父级容器的大小。但如果放置在其他垂直盒子或水平盒子中,它们将不会扩展到父级容器的大小。 | |||
==== 布局策略 ==== | |||
布局策略控制着hboxes和vboxes中子元素的调整大小方式。这同样适用于嵌套boxes中的boxes。 | |||
<code> | 有两种类型,<code>layoutpolicy_horizontal</code> 和 <code>layoutpolicy_vertical</code> ,分别控制水平和垂直行为。 | ||
默认情况下有五项固定的策略: | |||
#'''fixed'''(固定的) - 组件保持原始大小,无法放大或缩小。设置"fixed"属性的hboxes/vboxes将根据子代的大小进行调整,就像一个容器。 | |||
#'''expanding'''(扩展的) - 根据父代的宽度/高度自动调整大小,但不会缩小到原始尺寸以下。优先级高于其他策略的子代。如果多个子代设置为 "expanding" ,它们将平均分配可用空间。 | |||
#'''growing''' (增长的)- 与"expanding"相似,但优先级较低。如果存在带有"expanding"属性的子代,"growing"将不会发生。这还意味着 <code>expand = {}</code> 部件将调整大小为0,因此如果想要与"expanding"策略的组件一起使用,需要更改其策略。 | |||
#'''preferred''' (偏好的)- 随着可用空间的变化,它会随之放大或缩小。 | |||
#'''shrinking''' (缩水的)- 可以缩小至原始大小以下,但无法放大超过原始大小。 | |||
布局策略还会遵循最小尺寸 (minimumsize) 、最大尺寸 (maximumsize) 、最小宽度 (min_width)和最大宽度 (max_width) 。 | |||
{|class="wikitable" | |||
| "Expanding" 优先于 "growing" ,但不会使其缩小到小于原始(固定)尺寸。 | |||
<pre> | |||
hbox = { | |||
max_width = 400 | |||
layoutpolicy_horizontal = expanding | |||
=== | button_standard_small = { layoutpolicy_horizontal = expanding } | ||
button_standard_small = { layoutpolicy_horizontal = growing } | |||
} | |||
</pre> | |||
|[[File:growing hbox 2.png]] | |||
|- | |||
| "preferred" 和 "shrinking" 在空间不足时都可以收缩。为了看到这种效果,可能需要通过 <code>max_width</code> 或 "shrinking" 策略来限制 hbox 的宽度。 | |||
<pre> | |||
hbox = { | |||
layoutpolicy_horizontal = shrinking | |||
button_standard_small = { layoutpolicy_horizontal = growing } | |||
button_standard_small = { layoutpolicy_horizontal = preferred } | |||
button_standard_small = { layoutpolicy_horizontal = shrinking } | |||
} | |||
</pre> | |||
|[[File:shrinking hbox 2.png]] | |||
|} | |||
请注意,具有较大尺寸的物块 (objects) 可能会拉伸 hbox/vbox,即使看起来适合。设置 <code>max_width</code> ,并测试所有文本字段是否能够容纳很长的字符串,以确保窗口不会改变。 | |||
=== 模板 === | |||
Templates | '''模板''' (Templates) 和类型是代码的命名代码块,可以在代码中多次使用,例如按钮、窗口背景和文本样式。这有助于保持相同的样式,并减少我们编写的代码量。编辑模板将同时编辑所有的实例! | ||
模板可以存储整个窗口的内容或仅存储一行,就像这个例子: | |||
<pre> | <pre> | ||
template Window_Size_Sidebar | template Window_Size_Sidebar | ||
第245行: | 第353行: | ||
</pre> | </pre> | ||
这个模板可以与"using = Window_Size_Sidebar" 一起使用,基本上会用模板的内容替换 "using" 行。 | |||
模板是全局的,可以在任何.gui文件中定义。大部分游戏模板存储在 <code>gui/shared</code> 目录下。本地版本 <code>local_template</code> 必须在同一文件中定义。 | |||
常用的模板和类型可以在UI库中找到。要访问它,打开控制台,切换到发布模式(Release mode),然后会出现一个名为 "UI Library" 的新按钮。 | |||
==== | ==== 类型 ==== | ||
虽然模板可以只包含一个属性,但'''类型'''(Types)始终是完整的部件,比如按钮或小部件。 | |||
text_single | <code>text_single</code> 和 <code>text_multi</code> 是具有许多预定义属性的文本框类型,因此我们不需要每次重新输入它们,只需简单地写: | ||
<pre> | <pre> | ||
第259行: | 第371行: | ||
</pre> | </pre> | ||
类型的定义方式略有不同,首先要创建一个命名的组: | |||
<pre> | <pre> | ||
第270行: | 第382行: | ||
</pre> | </pre> | ||
==== | ==== 块覆写 ==== | ||
模板和类型可以具有命名的'''覆写块'''(blockoverride),这使我们能够在不更改整个模板的情况下编辑实例的一部分。例如,一个模板可以有一个默认的文本块: | |||
<pre> | <pre> | ||
第281行: | 第393行: | ||
</pre> | </pre> | ||
为了覆盖其中的内容,我们可以在我们的实例中添加一个具有相同名称的覆盖代码块: | |||
<pre> | <pre> | ||
第290行: | 第402行: | ||
</pre> | </pre> | ||
我们也可以像这样从我们的实例中移除它: | |||
<code>blockoverride "text" {}</code> | <code>blockoverride "text" {}</code> | ||
== | ==== 增加模组兼容性 ==== | ||
在制作一个涉及gui的mod时,所写的gui代码会与其他mod的代码覆盖同一个文件而无法同时运行。 | |||
模组开发者可以通过使用模板并为另一个模组创建一个“钩子(hook)”来实现兼容性。 | |||
为了做到这一点,每个模组开发者在已修改的窗口中包含其他模组的模板,而模板本身则在单独的文件中定义。 | |||
例如,全屏理发店模组包括了来自社区风味包(Community Flavor Pack)的“using = cfp_bg_buttons”模板,该模板添加了额外的背景。如果用户未订阅社区风味包,则无法找到该模板,也不会对游戏产生任何影响。如果用户同时订阅了这两个模组,则模板会被应用。 | |||
当然,这需要模组开发者合作,为彼此的模组设置模板。 | |||
== 脚本化的图形用户界面 == | |||
'''脚本化的GUI''' (Scripted guis) 实质上是从用户界面触发的隐藏事件。 | |||
它们以 <code>.txt</code> 文件的形式存储在游戏 <code>/common/scripted_guis</code> 目录下,与 <code>.gui</code> 文件不同,无法直接从游戏中重新加载。 | |||
脚本化GUI的基本结构如下: | |||
<pre> | <pre> | ||
gui_name = { | gui_name = { | ||
scope = character # | scope = character # 根作用域, 即:影响的目标 | ||
saved_scopes = {} # | saved_scopes = {} # 其他额外的目标 | ||
is_shown = {} # | is_shown = {} # 在用户界面可见吗? | ||
ai_is_valid = {} # | ai_is_valid = {} # AI 可用吗? 默认不可用. | ||
is_valid = {} # | is_valid = {} # 玩家可用吗? | ||
effect = { # | effect = { # 它做什么 | ||
custom_tooltip = "" # | custom_tooltip = "" # 添加提示 | ||
} | } | ||
} | } | ||
</pre> | </pre> | ||
不是所有代码块都是必须的。 有时一个脚本gui文件中可能只有一个作用域 (scope) 、一个是否显示 (is_shown) 、一个效果 (effect) 。 | |||
在 <code>.gui</code> 文件中我们用如下的代码: | |||
<pre> | <pre> | ||
第334行: | 第458行: | ||
</pre> | </pre> | ||
datacontext | 没有'''数据内容''' (datacontext) 是无法将脚本GUI与元素联系起来的。 | ||
在这个例子中,脚本GUI 是通过全局函数 <code>GetPlayer.MakeScope</code> 来为玩家设定范围的。它可以更改为角色窗口中的某个人,例如 <code>CharacterWindow.GetCharacter.MakeScope</code> ,或者如果范围是一个省份,则使用 <code>HoldingView.GetProvince.MakeScope</code> 。 | |||
ScriptedGui.Execute | '''ScriptedGui.Execute''' 用于按钮,它将执行脚本界面中 <code>effect</code> 块中列出的所有内容。 | ||
IsShown | 在 is_shown 和 is_valid 块中,'''IsShown''' 和 '''IsValid''' 用于检查条件。 | ||
BuildTooltip | '''BuildTooltip''' 可以与文本框一起使用,在用户界面中显示自定义文本。 | ||
为了将另一个作用域保存到我们的脚本化图形用户界面中,我们使用 '''AddScope''' : | |||
<pre> | |||
"[ScriptedGui.Execute( GuiScope.SetRoot( GetPlayer.MakeScope ).AddScope('target', CharacterWindow.GetCharacter.MakeScope ).End )]" | "[ScriptedGui.Execute( GuiScope.SetRoot( GetPlayer.MakeScope ).AddScope('target', CharacterWindow.GetCharacter.MakeScope ).End )]" | ||
</pre> | |||
然后我们在脚本化GUI中使用相同的名称: | |||
<pre> | |||
saved_scope = { | saved_scope = { | ||
target | target | ||
} | } | ||
</pre> | |||
通过这种方式可以保存多个作用域。 | |||
在点号或左括号之前不要加空格!<code>Execute(</code> 是正确的写法,<code>Execute (</code> 是错误的写法。其他空格可以省略,但它们有助于提高可读性。 | |||
=== | === 显示变量或脚本值 === | ||
变量或脚本值可以在用户界面显示,就像这样: | |||
变量: | |||
<code> | |||
text = "[GetPlayer.MakeScope.Var('test_var').GetValue|1]" | |||
</code> | |||
脚本值: | |||
<code> | |||
text = "[GuiScope.SetRoot( GetPlayer.MakeScope ).ScriptValue('test_value')|0]" | |||
</code> | |||
在这个例子中,变量存储在玩家角色中,名为 <code>test_var</code> 。可以触及任何其他作用域,只需记得添加 <code>MakeScope</code> 即可。 | |||
结尾的 "|1" 是可选的,它会截取所有小数位,只保留一个小数位,所以不论是 1.573 还是 1.5,您都将看到 1.5。请注意,这不会对数值进行四舍五入。您可以将其设置为任意数字,并添加 % 以将其转换为百分比,如果数值为正或负,还可以添加 = 或 + 以给数值上色。 | |||
在事件中使用本地化显示值时,我们使用以下方法: | |||
<pre> | |||
event_var: "[ROOT.GetCharacter.MakeScope.Var('test_var').GetValue|0]" | |||
event_value: "[SCOPE.ScriptValue('test_value')|0]" | |||
</pre> | |||
如果你有一个保存的作用域(这里命名为"target" ),它会变成这样: | |||
< | <pre> | ||
event_var: "[target.MakeScope.Var('test_var').GetValue|0]" | |||
event_value: "[GuiScope.SetRoot( target.MakeScope ).ScriptValue('test_value')|0]" | |||
</pre> | |||
=== 展示数据列表 === | |||
我们可以创建自定义字符列表,例如用于构建社交网络(societies)。 | |||
要实现这一点,首先需要将它们添加到一个变量列表(list)中。可以通过事件或脚本GUI来完成,就像这样: | |||
<pre> | <pre> | ||
第405行: | 第544行: | ||
</pre> | </pre> | ||
如果我们将此效果应用于玩家,他们将成为<code>root</code> ,因此列表将存储在他们身上。 | |||
然后,我们使用任何列表框(vbox、dynamicgridbox、fixedgridbox),将数据模型设置为我们的列表: | |||
<pre> | <pre> | ||
dynamicgridbox = { | dynamicgridbox = { | ||
第427行: | 第565行: | ||
</pre> | </pre> | ||
== | == 新窗口和开关 == | ||
没有简单的方法来创建一个新窗口并使您的模组与其他模组兼容。我们必须要么覆盖hud或其他窗口,要么使用事件来打开窗口。 | |||
通常的方法是将新窗口添加到<code>ingame_topbar</code>部件的hud.gui文件中,然后添加一个按钮来显示/隐藏它。 | |||
另一种方法是重写一个未使用的窗口,比如test_gui,并添加一个带有显示该窗口的控制台命令的按钮。 | |||
有几种方法可以添加切换功能,以下三种方法在游戏会话之间不保持持久性,但可以在.gui文件本身中轻松设置: | |||
*[[界面#P社GUI组件的开关|P社GUI组件]](PdxGuiWidget) - 直接简单,但在有多个切换功能时会变得复杂 | |||
*[[界面#动画开关|动画]](Animations) - 长一些,但更容易触发多个操作 | |||
*[[界面#系统变量|系统变量]](System variables) - 有些复杂,但非常灵活和方便 | |||
脚本GUI允许我们将切换功能保存到保存文件中作为变量,并触发多个操作,但它们更复杂,并且必须在单独的文件夹中进行设置。 | |||
=== P社GUI组件的开关 === | |||
'''PdxGuiWidget''' 是一个简单的功能,用于隐藏或显示指定的元素。 | |||
# | |||
# | 在这个示例中,我们有一个包含隐藏子菜单的容器,一个按钮用于显示子菜单,另一个按钮用于隐藏子菜单。 | ||
# 当点击第一个按钮时,它会返回到其父代,在父代中搜索子菜单和另一个按钮,然后显示它们并隐藏自身。 | |||
# 第二个按钮会搜索元素和按钮,并显示它们并隐藏自身。 | |||
<pre> | <pre> | ||
第469行: | 第613行: | ||
</pre> | </pre> | ||
如果元素之间有更多的父/ 子级别分隔,我们可以像这样重复使用 '''AccessParent''' : | |||
<pre> | |||
onclick = "[PdxGuiWidget.AccessParent.AccessParent.AccessParent.AccessParent.FindChild('submnenu').Show]" | |||
</pre> | |||
每个按钮都可以隐藏或显示多个任意类型的元素。您只需要提供名称。 | |||
优点: | |||
* | * 对于简单的切换设置很容易。 | ||
缺点: | |||
*切换将在每次重新启动游戏时重置。 | |||
*如果您想要隐藏多个事物或切换,代码会变得非常冗长和难以管理。 | |||
*尝试隐藏数据列表中的条目(如动态网格框)将只隐藏第一个实例。 | |||
=== 动画开关 === | |||
我们可以设置通过按钮(或条件)触发的动画来隐藏/显示元素,甚至移动它们。这样一来,我们就不需要计算按钮和窗口之间有多少个父级,而且可以通过一个 <code>onclick</code> 同时触发多个动作。 | |||
前面的示例将如下所示,并且以相同的方式工作。第一个按钮触发 "show_submenu",隐藏该按钮并显示其他内容,而第二个按钮触发 "hide_submenu",隐藏此按钮和小部件,并显示第一个按钮。 | |||
<pre> | <pre> | ||
container = { | container = { | ||
第534行: | 第680行: | ||
</pre> | </pre> | ||
虽然较长,但动画可以保存为模板并以一行代码的方式重复使用,就像 <code>using = hide_animation</code> 。全屏理发器使用很多动画,如果你需要更好的例子。 | |||
优点: | |||
* | * 更容易将许多事物链接在一起,甚至打开一个不同的窗口并触发其中的动画 | ||
缺点: | |||
* | * 动画块可能相当冗长 | ||
* | * 当游戏重新启动时,所有开关都会重置 | ||
=== | === 脚本GUI开关 === | ||
脚本化的GUI允许我们使用脚本来切换可见性。它们使得管理多个项目更加容易,但在大量使用时可能会影响性能。 | |||
对于一个脚本化的切换,我们需要在common/scripted_guis/ 目录下创建一个.txt文件。文件的名称可以任意取。 | |||
这个文件中的基本切换看起来像这样: | |||
<pre> | <pre> | ||
第574行: | 第719行: | ||
</pre> | </pre> | ||
单击时,它会向作用域角色添加一个变量,并在再次单击时将其删除。 | |||
然后,如果该变量存在,我们的窗口将可见。如果不存在,则隐藏。 | |||
这就是 GUI 文件的外观。我们可以只使用一个按钮,因为其功能会随着每次点击而改变。 | |||
<pre> | <pre> | ||
第594行: | 第739行: | ||
</pre> | </pre> | ||
"GetPlayer" | <code>"GetPlayer"</code> 是一个全局[[界面#提升和功能|Promote]]函数,返回玩家角色。 | ||
"Player.MakeScope" | <code>"Player.MakeScope"</code> 将我们的玩家设定为脚本化图形界面的作用域,这意味着我们将变量存储在这里。 | ||
如果你想要为两个按钮设置不同的工具提示,可以使用两个按钮。在这种情况下,将 <code>'visible'</code> 属性复制到两个按钮上,但是在其中一个上添加 <code>'Not'</code> ,这样它就会默认隐藏起来。 | |||
<pre> | |||
* | visible = "[Not(ScriptedGui.IsShown( GuiScope.SetRoot( GetPlayer.MakeScope ).End ))]" | ||
* | </pre> | ||
优点: | |||
*切换设置会保存在角色中,除非角色死亡或开始新游戏,否则不会重置。 | |||
* 更容易链接多个对象,甚至在不同的窗口中。 | |||
缺点: | |||
* 更难记住语法(从这里复制代码以减少错误的机会)。 | |||
=== 系统变量 === | |||
系统变量是游戏内部使用的,它们不会被保存,也不能通过脚本直接访问。 | |||
对于它们不需要进行设置,因为可以直接在.gui文件中创建。 | |||
使用系统变量的语法为: | |||
<code>onclick = "[GetVariableSystem.Toggle( 'var_name' )]"</code> | |||
或者: | |||
* | |||
* | <pre>datacontext = "[GetVariableSystem]" | ||
onclick = "[VariableSystem.Toggle( 'var_name' )]"</pre> | |||
可用的函数如下: | |||
*Clear - <code>Clear( 'var_name' )</code> 清除变量 | |||
*ClearIf - <code>ClearIf( 'var_name', Condition )</code> 如果条件为真,则清除变量 | |||
*Exists - <code>Exists( 'var_name' )</code> 布尔型,如果变量存在则返回true | |||
*Get - <code>Get( 'var_name' )</code> 字符串型,返回存储在变量中的值 | |||
*HasValue - <code>HasValue( 'var_name', 'string' )</code> 布尔型,如果存储的值与提供的值匹配则返回true | |||
*Set - <code>Set( 'var_name', 'string' )</code> 将存储的值设置为提供的值 | |||
*Toggle - <code>Toggle( 'var_name' )</code> 如果变量存在则清除它,如果不存在则创建它 | |||
==== 系统变量开关 ==== | |||
一个使用此文件中的系统变量的基本切换如下所示: | |||
<pre> | |||
container = { | |||
button = { | |||
onclick = "[GetVariableSystem.Toggle( 'gui_toggle' )]" | |||
} | |||
widget = { | |||
visible = "[GetVariableSystem.Exists( 'gui_toggle' )]" | |||
} | |||
} | |||
</pre> | |||
点击时,系统变量根据其存在与否切换,然后用于显示/隐藏部件。 | |||
==== 带有系统变量的选项 ==== | |||
一个包含三个选项的基本设置如下: | |||
<pre> | |||
container = { | |||
button = { | |||
onclick = "[GetVariableSystem.Set( 'gui_tabs', 'tab_1' )]" | |||
} | |||
button = { | |||
onclick = "[GetVariableSystem.Set( 'gui_tabs', 'tab_2' )]" | |||
} | |||
button = { | |||
onclick = "[GetVariableSystem.Set( 'gui_tabs', 'tab_3' )]" | |||
} | |||
widget = { | |||
visible = "[GetVariableSystem.HasValue( 'gui_toggle', 'tab_1' )]" | |||
} | |||
widget = { | |||
visible = "[GetVariableSystem.HasValue( 'gui_toggle', 'tab_2' )]" | |||
} | |||
widget = { | |||
visible = "[GetVariableSystem.HasValue( 'gui_toggle', 'tab_3' )]" | |||
} | |||
} | |||
</pre> | |||
注意,变量最初没有值,因此没有任何小部件会显示。 | |||
要设置默认选项卡,需要由打开窗口的按钮设置变量的值。 | |||
<pre> | |||
button = { | |||
onclick = "[GetVariableSystem.Toggle( 'gui_toggle' )]" # this opens the window | |||
onclick = "[GetVariableSystem.Set( 'gui_tabs', 'tab_1' )]" # this set the default tab | |||
} | |||
</pre> | |||
或在窗口显示时使用状态块: | |||
<pre> | |||
state = { | |||
name = _show | |||
on_start = "[GetVariableSystem.Set( 'gui_tabs', 'tab_1' )]" | |||
} | |||
</pre> | |||
或者,可以将其中一个小部件设置为在变量不存在时显示,避免初始值的需求。 | |||
<pre> | |||
container = { | |||
button = { | |||
onclick = "[GetVariableSystem.Clear( 'gui_tabs' )]" | |||
} | |||
button = { | |||
onclick = "[GetVariableSystem.Set( 'gui_tabs', 'tab_2' )]" | |||
} | |||
button = { | |||
onclick = "[GetVariableSystem.Set( 'gui_tabs', 'tab_3' )]" | |||
} | |||
widget = { | |||
visible = "[Not( GetVariableSystem.Exists( 'gui_toggle' ) )]" | |||
} | |||
widget = { | |||
visible = "[GetVariableSystem.HasValue( 'gui_toggle', 'tab_2' )]" | |||
} | |||
widget = { | |||
visible = "[GetVariableSystem.HasValue( 'gui_toggle', 'tab_3' )]" | |||
} | |||
} | |||
</pre> | |||
这相当于第一个示例,其中之一是设置默认值的方法。 | |||
优点: | |||
*简单且易于记忆的语法 | |||
*更容易连接许多事物,甚至在不同的窗口中 | |||
*可以通过附加命令进行扩展(见下文)以显示全新的窗口,避免在hud.gui中使用小部件的需要 | |||
缺点: | |||
*无法直接与脚本交互,必须使用GUI进行设置和清除 | |||
*可能更难跟踪 | |||
*游戏重新启动时,所有切换将重置 | |||
=== 添加新的UI元素 === | |||
在1.5.0版本之前只能通过控制台指令<code>ExecuteConsoleCommand( ... )</code> 来创建任何新窗口。 | |||
在1.5.0版本之后可以在Crusader Kings III\game\gui\scripted_widgets下添加新的UI元素,这对模组的兼容性和UI的动画支持有很大的改善。 | |||
首先需要在 .gui 文件创建一个可以被显示的window元素,例如 "gui/custom_windows/my_window.gui"。这个window必须要有一个名字。 | |||
<pre> | |||
window = { | |||
name = "my_custom_window" | |||
parentanchor = center | |||
layer = middle | |||
size = { 100 100 } | |||
using = Window_Background | |||
GetVariableSystem.Exists('my_menu_open') | |||
} | |||
</pre> | |||
上面的代码以屏幕中心未基准创建了一个100x100像素的window,最上面的name被用于在其他代码中创建这个window。 | |||
在1.5.0版本之后可以在 Crusader Kings III\game\gui\scripted_widgets 下添加新的UI元素,这可以取代之前使用控制台创建widget的方法。 | |||
首先需要在 gui\scripted_widgets 目录下创建一个.txt文件,例如 "gui/custom_windows/my_scripted_widgets.txt"。并且在文件中调出之前创建好的window: | |||
<pre> | |||
gui/test_custom_widget.gui = my_custom_window | |||
</pre> | |||
以上为:文件的路径/文件的名字.gui = 文件中window的名字。 | |||
所有被添加进这个文件的UI元素都将在游戏开始时被创建,每行代码都将创建一个新的元素,所有我们可以创建两个相同的UI元素,不过由于参数相同会重叠在一起。 | |||
所有在这个文件夹下的.txt文件都将被加载,所以这对不同mod的兼容性有所提升,除非文件重名或者UI位置重叠。 | |||
我们不仅仅可以创建window,好可以是widget和其他任何可以被创建的元素,这令模组作者们可以在不修改 hud.gui 的情况下新增自己的hud元素,例如顶部的资源显示和侧边的按钮。 | |||
我们需要做一个开关按钮来控制这个window的显示: | |||
<pre> | |||
button = { | |||
onclick = "[GetVariableSystem.Toggle('my_menu_open')]" | |||
} | |||
</pre> | |||
以上通过开关一个系统参数来实现UI元素的显现和消失。 | |||
{{Modding navbox}} | {{Modding navbox}} | ||
[[Category:模组制作]] | [[Category:模组制作]] | ||
[[en:Interface]] | [[en:Interface]] |
2023年6月21日 (三) 16:35的最新版本
CK3的用户界面(UI)是高度可修改的,但由于这个原因,UI修改器会禁用成就,因为玩家可能会用它们作弊。
游戏还包含一个GUI编辑器,可以在游戏中检查UI元素并编辑它们。
模组作者可以:
- 改变界面的视觉风格
- 使窗口可以移动和调整大小。
- 更改和删除要素
- 增加新的按钮
- 从代码中显示更多信息
- 增加新的窗口(有一个变通办法)
模组作者不能:
- 增加新的热键(只能重复使用现有的热键)。
- 在另一个窗口中显示一个窗口的信息,除非开发者包含这种可能性。
基础[编辑 | 编辑源代码]
CK3的界面是通过game/gui文件夹中的.gui文件创建的,它有点类似于html文件。
因此,你可以用任何文本编辑器来编辑它们,比如 VS Code, Sublime 或 Atom。语法高亮选择Python或Perl 6,它们很适合。
CK3使用.dds文件来制作纹理,它保存在game/gfx/文件夹内。
要编辑或保存.dds文件,可以使用带有Intel 插件的Photoshop或带有此插件的GIMP。
要在游戏中重新加载gui文件并使用GUI编辑器,添加-debug_mode和-develop启动选项。
- 在Steam上右击游戏,选择属性,设置启动选项,添加
-debug_mode -develop
。
你可以使用以下控制台指令:
reload gui
- 重载所有的gui文件,以显示游戏中的任何变化。reload texture
- 重新加载所有纹理文件。gui editor
- 打开GUI编辑器。tweak gui.debug
- 允许启用UI元素的高亮显示。提示工具条将显示其名称、大小和位置。DumpDataTypes
- 将在你的日志文件夹(Documents/Paradox Interactive/Crusader Kings III/logs)中创建一个data_types.log,列出每个窗口/游戏对象的所有可用的GUI函数。
其他提示。
- 始终打开错误日志,查看代码中是否有错误(在同一个日志文件夹中)。
- 点击控制台下方的“Toggle Release Mode”启用游戏中的错误跟踪器,查看你的更改是否导致任何新的错误。
- 当使用
reload gui
命令时,将游戏静音,因为每次都会触发前奏的声音。 - 使用Reload GUI mod,它增加了一个重新加载gui的按钮(用热键),并在你重新加载时关闭设置窗口。
- 将gui文件夹添加到你的文本编辑器中,这样它就会使用它来自动完成。
- 你可以在gui/debug中使用test_gui.gui进行测试。要显示这个窗口,打开控制台并点击测试窗口。
- 折叠代码,这样更容易看到它的结构。通常的热键是Ctrl+K、Ctrl+1(其中1是折叠代码的级别)。
创建GUI模组[编辑 | 编辑源代码]
1. 启动游戏启动器,进入Mods,Mod工具,填写所有字段,包括标签。
点击创建会在Documents/Paradox Interactive/Crusader Kings III/mod
中新建一个文件夹和一个.mod文件。
2. 接下来,在你的模组里创建一个“gui”文件夹,然后从game/gui里复制你要修改的文件到那里。
- 如果你不知道需要哪个文件,请使用GUI编辑器,并在游戏中检查它。
GUI编辑器[编辑 | 编辑源代码]
GUI编辑器(GUI Editor)是一款用于编辑游戏中UI的开发者工具。
要使用它,你需要在-debug_mode
和-developer
选项下启动游戏。
要打开编辑器,可以选择
- 按Ctrl+F8键
- 用`键(在Esc下面)打开控制台,点击GUI编辑器。
- 打开控制台,运行gui_editor命令。
特点[编辑 | 编辑源代码]
默认情况下,编辑器启动时启用了编辑模式。你可以在顶部窗口中禁用它,称为Outliner。热键“E”。
- 编辑模式类似于浏览器中的检查模式。当它被启用时,你不能与游戏互动,但它允许你选择UI的一部分,并在下面的属性窗口中更改它们。
- 滚动鼠标滚轮来改变编辑器应该关注的元素,因为hud.gui往往会被放在其他窗口之上。
- 黄色边框表示选中的元素。要隐藏其他边框,请取消勾选Outliner中的“Show Hierarchy”(热键“L”)。
- 按住鼠标右键可以移动所选元素。
- 要撤销任何操作,请按Ctrl+Z或Outliner中的撤销按钮。重做是旁边的按钮,Ctrl+Y。
- Outliner中的红星*表示未保存的更改。按Ctrl+S或顶部的保存按钮来保存它们。确保你正在编辑的gui文件在你的模组中,否则它会将更改写入游戏文件夹。(要重置它们,请从Steam的属性窗口验证完整性)
- 你可以通过拖动任何开发窗口来移动它们,并通过拖动边缘来调整它们的大小。
- 您可以在Outliner的层次结构中拖动UI元素来重新排序。右键单击以显示上下文菜单。
- 您可以在“属性(Properties)”窗口中更改或添加新属性(通过单击加号)。
通过单击Outliner中的“窗口(Window)”,您可以打开另外两个窗口。UI组件(UI components)和注册数据类型(Registered Data Types)。
- UI组件就像一个调色板,你可以拖动新的元素到UI中。gui/shared/standard.gui和gui/defaults.gui包含最常见的东西,如按钮、图标和文本。
- 注册数据类型可以用来查找能够显示游戏中数据的函数。
- 点击数据类型中的右上角按钮会将这些数据转储到你的日志文件夹(Documents/Paradox Interactive/CK3/logs)。你也可以使用“DumpDataTypes”控制台指令。
注意:通常很容易误选一个模板,改变模板会影响UI中的所有实例。留意你在Outliner中选择了哪些文件。如果你在属性(Properties)窗口中看到一个以“type:”开头的蓝色标题,那就是一个模板,所以要注意不要编辑这部分内容(除非你打算编辑)。
UI代码[编辑 | 编辑源代码]
CK3的UI是由容器和容器内的对象组成的。
例如,大多数窗口都是使用window
容器创建的,而地图图标则使用widget或hbox。
文件中的顺序决定了屏幕上的顺序:代码中较低的东西会出现在较高的一层。
大多数元素也可以包含其他元素,例如,在一个按钮的图标里面可以有一个文本框。嵌套的元素,也就是子元素,将和它们的父元素一起移动。
位置是相对于左上角设置的(屏幕或父元素)。这可以通过parentanchor
属性来改变。可用的选项有:左、右、上、下、hcenter(水平中心)和vcenter(垂直中心)。它们可以像这样与|组合。parentanchor = right|vcenter
。
每个元素都用大括号打开和关闭,像这样。container = { }
。
常见的代码风格是在同一层次上打开和关闭块,同时用一个标签缩进内容。
widget = { size = { 50 50 } alpha = 0.5 }
这可以帮助你更好地看到代码的结构,注意到任何缺失的或额外的括号,对于一些编辑器来说,这也是正确折叠代码块所需要的。
提升和功能[编辑 | 编辑源代码]
每个窗口都有一个预定义的命令集合-提升和功能(promotes and functions) -可供使用。这些命令可以在使用 DumpDataTypes
控制台命令后,在 Documents/Paradox Interactive/Crusader Kings III/logs/
目录下的 data_types.log
文件中找到。
它们用于显示游戏中的所有数据,例如您的姓名、金币、子女,并且可以设置按钮操作。
一个“promote”返回一个作用域(scope),即一个游戏对象,如角色或省份,而一个 "function" 返回一个数字、一个字符串或一个布尔值(true/false)等等。
全局命令可以在任何地方使用,比如 GetPlayer
(返回玩家角色)或 GetCurrentDate
(获取当前日期)。
其他命令只能在它们所属的窗口/对象中使用,例如,GetParents 只能在角色窗口中使用,并且必须以 CharacterWindow.GetParents
开始。
命令可以像这样链接起来:
CharacterWindow.GetCharacter.GetPrimaryTitle.GetHeir.GetPrimarySpouse.GetFather
对象可以继承其父对象的范围,这意味着我们不需要重新键入上面的行来显示关于此角色的信息。相反,我们可以将小部件的数据上下文设置为此行,然后其中的每个文本框都将使用 "[Character.GetNameNoTooltip]"
, "[Character.GetGold]"
等等。
同样的规定也适用于gridboxes的条目
UI组件[编辑 | 编辑源代码]
window
- 唯一可移动的容器。要启用移动,添加
movable = yes
属性。 - 可以固定大小,也可以由其子代调整大小。
- 在游戏中,背景是通过模板设置的,比如
using = Window_Background
和using = Window_Decoration
。 - 如果一个孩子在窗口之外,它将无法被点击,也不会显示工具提示。使用
allow_outside = yes
来改变这一点。没有z坐标,所以z的排序方式是第一个写的子代会被放在第二个写的子代后面。为了使组件可以点击,并且在其他组件的顶部,你需要把它放在其他组件的后面。
widget
- 一个静态容器。在其他方面类似于一个窗口。
margin_widget
- 类似于widget,但可以用margins调整大小。(这使我们可以通过将高度设置为100%,将边距设置为约50,使窗口可以调整到不同大小的屏幕上,以显示hud)
container
- 没有固定的尺寸(但你可以设置最大尺寸)。
- 自动调整大小以适应所有子代,包括不可见的子代。使用
ignoreinvisible = yes
来忽略它们。 - 常用于将多个元素组合在一起移动。
flowcontainer
- 将其所有子代排列成水平行。使用
direction = vertical
使其垂直。 - 默认情况下不会忽略不可见的子代。使用
ignoreinvisible = yes
来改变它。 - 没有固定的大小,可以使用
ignoreinvisible = yes
来改变。 - 它的子代不能有位置,因为它们是自动设置的。
- 如果你需要调整它的子代的位置,你可以把它放在一个容器或widget里面,然后改变相对于这个父代的位置。
hbox
vbox
- 将所有的子代排列成水平的一行,并沿其宽度分布。Vbox也是一样的,但是是垂直的。
- 不能有固定的大小,而是取其父代的宽度作为自己的宽度(忽略父代的边距)。Vbox取其高度。
- 如果它的父体不能有固定的大小(如flowcontainer),这可能会导致游戏崩溃。
- 可受最小尺寸/最大尺寸和边距的限制。
- 默认情况下,忽略不可见的子代,使用
ignoreinvisible = no
来更改。 - 在另一个hbox中的hbox将有0个大小,并且不会展开其子代。使用
layoutpolicy_horizontal = expanding
来调整它的大小(或者在vbox中使用layoutpolicy_vertical = expanding
)。 - 接受数据模型(从游戏数据中创建列表)。
dynamicgridbox
- 仅用于数据模型。
- 将所有条目垂直排列。使用
flipdirection = yes
可以使其水平排列。 - 默认情况下不忽略不可见项目。使用
ignoreinvisible = yes
来进行更改。 - 根据内容的调整大小,并受到最小尺寸和最大尺寸的限制。
- 条目可以有不同的尺寸。
- 有时候在处理非常长的列表时可能会出现卡顿的情况。
fixedgridbox
- 类似于动态框,但其所有项目都是固定尺寸的(本质上是一个表格)。
- 仅用于数据模型。
- 将所有项目垂直排列。使用
flipdirection = yes
可以使其水平排列。 - 无法忽视不可见的条目。
- 可以固定大小,根据内容进行调整,并受最小尺寸和最大尺寸的限制。
- 处理长列表性能更好。
overlappingitembox
- 仅用于数据模型。
- 将所有项目水平排列,并在列表长度超过盒子大小时重叠显示。使用
flipdirection = yes
使其水平排列。 - 可以是固定尺寸或自动调整大小。
scrollarea
- 一个带有滚动条的小部件,如果内容超过其大小,滚动条将出现。
- 滚动条可以通过
scrollbarpolicy_horizontal = always_off
和scrollbarpolicy_vertical = always_off
来禁用。- 可以使用没有滚动条的滚动区域来裁剪列表或图片。
- 可以固定大小,根据内容进行调整,并受最小尺寸和最大尺寸的限制。
button
- 一个可点击的对象。 接受
onclick
和onrightclick
.- 在添加右键功能时,请包括
button_ignore = none
.
- 在添加右键功能时,请包括
- 无默认材质
- 可以根据其子代进行固定大小或调整大小。
- 一个0x0按钮可以用来添加不可见的快捷键。
icon
- 显示一个材质
- 可用作小部件来存储子代。
- 可以通过
mirror = horizontal
或mirror = vertical
进行翻转.
textbox
- 显示文字内容。
- 可以是固定尺寸或自动调整大小。
- 使用
elide = right
或elide = left
来截断过长的文本。 - 可以是单行或多行,当
multiline = yes
时为多行。 - 游戏文件通常使用在gui/shared/text.gui中设置的模板,比如
text_single
。使用它们可以保持视觉一致性,并减少每次编写代码的工作量。
hbox/vbox[编辑 | 编辑源代码]
Hboxes和vboxes是可调整大小的容器,它们对其子代进行排序、调整大小或展开。hbox水平排序子代,vbox垂直排序子代,除此之外它们的工作方式相同,所以这里的所有示例都适用于两者。
以下是屏幕截图,hboxes 带有黑色背景。所有示例都可以在 UI Library mod 中找到。
如果放置在固定大小的父级容器中,默认情况下,它会水平和垂直方向上扩展到整个父级容器的大小。但如果放置在其他垂直盒子或水平盒子中,它们将不会扩展到父级容器的大小。
布局策略[编辑 | 编辑源代码]
布局策略控制着hboxes和vboxes中子元素的调整大小方式。这同样适用于嵌套boxes中的boxes。
有两种类型,layoutpolicy_horizontal
和 layoutpolicy_vertical
,分别控制水平和垂直行为。
默认情况下有五项固定的策略:
- fixed(固定的) - 组件保持原始大小,无法放大或缩小。设置"fixed"属性的hboxes/vboxes将根据子代的大小进行调整,就像一个容器。
- expanding(扩展的) - 根据父代的宽度/高度自动调整大小,但不会缩小到原始尺寸以下。优先级高于其他策略的子代。如果多个子代设置为 "expanding" ,它们将平均分配可用空间。
- growing (增长的)- 与"expanding"相似,但优先级较低。如果存在带有"expanding"属性的子代,"growing"将不会发生。这还意味着
expand = {}
部件将调整大小为0,因此如果想要与"expanding"策略的组件一起使用,需要更改其策略。 - preferred (偏好的)- 随着可用空间的变化,它会随之放大或缩小。
- shrinking (缩水的)- 可以缩小至原始大小以下,但无法放大超过原始大小。
布局策略还会遵循最小尺寸 (minimumsize) 、最大尺寸 (maximumsize) 、最小宽度 (min_width)和最大宽度 (max_width) 。
请注意,具有较大尺寸的物块 (objects) 可能会拉伸 hbox/vbox,即使看起来适合。设置 max_width
,并测试所有文本字段是否能够容纳很长的字符串,以确保窗口不会改变。
模板[编辑 | 编辑源代码]
模板 (Templates) 和类型是代码的命名代码块,可以在代码中多次使用,例如按钮、窗口背景和文本样式。这有助于保持相同的样式,并减少我们编写的代码量。编辑模板将同时编辑所有的实例!
模板可以存储整个窗口的内容或仅存储一行,就像这个例子:
template Window_Size_Sidebar { size = { 610 100% } }
这个模板可以与"using = Window_Size_Sidebar"一起使用,基本上会用模板的内容替换 "using" 行。
模板是全局的,可以在任何.gui文件中定义。大部分游戏模板存储在 gui/shared
目录下。本地版本 local_template
必须在同一文件中定义。
常用的模板和类型可以在UI库中找到。要访问它,打开控制台,切换到发布模式(Release mode),然后会出现一个名为 "UI Library" 的新按钮。
类型[编辑 | 编辑源代码]
虽然模板可以只包含一个属性,但类型(Types)始终是完整的部件,比如按钮或小部件。
text_single
和 text_multi
是具有许多预定义属性的文本框类型,因此我们不需要每次重新输入它们,只需简单地写:
text_single = { text = "my text" }
类型的定义方式略有不同,首先要创建一个命名的组:
types Standard_Types { type text_single = textbox { ... } }
块覆写[编辑 | 编辑源代码]
模板和类型可以具有命名的覆写块(blockoverride),这使我们能够在不更改整个模板的情况下编辑实例的一部分。例如,一个模板可以有一个默认的文本块:
block "text" { text = "default_text" }
为了覆盖其中的内容,我们可以在我们的实例中添加一个具有相同名称的覆盖代码块:
blockoverride "text" { text = "actual text" }
我们也可以像这样从我们的实例中移除它:
blockoverride "text" {}
增加模组兼容性[编辑 | 编辑源代码]
在制作一个涉及gui的mod时,所写的gui代码会与其他mod的代码覆盖同一个文件而无法同时运行。
模组开发者可以通过使用模板并为另一个模组创建一个“钩子(hook)”来实现兼容性。
为了做到这一点,每个模组开发者在已修改的窗口中包含其他模组的模板,而模板本身则在单独的文件中定义。
例如,全屏理发店模组包括了来自社区风味包(Community Flavor Pack)的“using = cfp_bg_buttons”模板,该模板添加了额外的背景。如果用户未订阅社区风味包,则无法找到该模板,也不会对游戏产生任何影响。如果用户同时订阅了这两个模组,则模板会被应用。
当然,这需要模组开发者合作,为彼此的模组设置模板。
脚本化的图形用户界面[编辑 | 编辑源代码]
脚本化的GUI (Scripted guis) 实质上是从用户界面触发的隐藏事件。
它们以 .txt
文件的形式存储在游戏 /common/scripted_guis
目录下,与 .gui
文件不同,无法直接从游戏中重新加载。
脚本化GUI的基本结构如下:
gui_name = { scope = character # 根作用域, 即:影响的目标 saved_scopes = {} # 其他额外的目标 is_shown = {} # 在用户界面可见吗? ai_is_valid = {} # AI可用吗? 默认不可用. is_valid = {} # 玩家可用吗? effect = { # 它做什么 custom_tooltip = "" # 添加提示 } }
不是所有代码块都是必须的。 有时一个脚本gui文件中可能只有一个作用域 (scope) 、一个是否显示 (is_shown) 、一个效果 (effect) 。
在 .gui
文件中我们用如下的代码:
datacontext = "[GetScriptedGui('gui_name')]" onclick = "[ScriptedGui.Execute( GuiScope.SetRoot( GetPlayer.MakeScope ).End)]" visible = "[ScriptedGui.IsShown( GuiScope.SetRoot( GetPlayer.MakeScope ).End)]" enabled = "[ScriptedGui.IsValid( GuiScope.SetRoot( GetPlayer.MakeScope ).End)]" tooltip = "[ScriptedGui.BuildTooltip( GuiScope.SetRoot( GetPlayer.MakeScope ).End)]"
没有数据内容 (datacontext) 是无法将脚本GUI与元素联系起来的。
在这个例子中,脚本GUI 是通过全局函数 GetPlayer.MakeScope
来为玩家设定范围的。它可以更改为角色窗口中的某个人,例如 CharacterWindow.GetCharacter.MakeScope
,或者如果范围是一个省份,则使用 HoldingView.GetProvince.MakeScope
。
ScriptedGui.Execute 用于按钮,它将执行脚本界面中 effect
块中列出的所有内容。
在 is_shown 和 is_valid 块中,IsShown 和 IsValid 用于检查条件。
BuildTooltip 可以与文本框一起使用,在用户界面中显示自定义文本。
为了将另一个作用域保存到我们的脚本化图形用户界面中,我们使用 AddScope :
"[ScriptedGui.Execute( GuiScope.SetRoot( GetPlayer.MakeScope ).AddScope('target', CharacterWindow.GetCharacter.MakeScope ).End )]"
然后我们在脚本化GUI中使用相同的名称:
saved_scope = { target }
通过这种方式可以保存多个作用域。
在点号或左括号之前不要加空格!Execute(
是正确的写法,Execute (
是错误的写法。其他空格可以省略,但它们有助于提高可读性。
显示变量或脚本值[编辑 | 编辑源代码]
变量或脚本值可以在用户界面显示,就像这样:
变量:
text = "[GetPlayer.MakeScope.Var('test_var').GetValue|1]"
脚本值:
text = "[GuiScope.SetRoot( GetPlayer.MakeScope ).ScriptValue('test_value')|0]"
在这个例子中,变量存储在玩家角色中,名为 test_var
。可以触及任何其他作用域,只需记得添加 MakeScope
即可。
结尾的 "|1" 是可选的,它会截取所有小数位,只保留一个小数位,所以不论是 1.573 还是 1.5,您都将看到 1.5。请注意,这不会对数值进行四舍五入。您可以将其设置为任意数字,并添加 % 以将其转换为百分比,如果数值为正或负,还可以添加 = 或 + 以给数值上色。
在事件中使用本地化显示值时,我们使用以下方法:
event_var: "[ROOT.GetCharacter.MakeScope.Var('test_var').GetValue|0]" event_value: "[SCOPE.ScriptValue('test_value')|0]"
如果你有一个保存的作用域(这里命名为"target"),它会变成这样:
event_var: "[target.MakeScope.Var('test_var').GetValue|0]" event_value: "[GuiScope.SetRoot( target.MakeScope ).ScriptValue('test_value')|0]"
展示数据列表[编辑 | 编辑源代码]
我们可以创建自定义字符列表,例如用于构建社交网络(societies)。
要实现这一点,首先需要将它们添加到一个变量列表(list)中。可以通过事件或脚本GUI来完成,就像这样:
effect = { every_living_character = { limit = { has_trait = paranoid } root = { add_to_variable_list = { name = secret_society target = prev } } } }
如果我们将此效果应用于玩家,他们将成为root
,因此列表将存储在他们身上。
然后,我们使用任何列表框(vbox、dynamicgridbox、fixedgridbox),将数据模型设置为我们的列表:
dynamicgridbox = { datamodel = "[GetPlayer.MakeScope.GetList('secret_society')]" item = { flowcontainer = { datacontext = "[Scope.GetCharacter]" portrait_head_small = {} text_single = { text = "[Character.GetNameNoTooltip]" } } } }
新窗口和开关[编辑 | 编辑源代码]
没有简单的方法来创建一个新窗口并使您的模组与其他模组兼容。我们必须要么覆盖hud或其他窗口,要么使用事件来打开窗口。
通常的方法是将新窗口添加到ingame_topbar
部件的hud.gui文件中,然后添加一个按钮来显示/隐藏它。
另一种方法是重写一个未使用的窗口,比如test_gui,并添加一个带有显示该窗口的控制台命令的按钮。
有几种方法可以添加切换功能,以下三种方法在游戏会话之间不保持持久性,但可以在.gui文件本身中轻松设置:
- P社GUI组件(PdxGuiWidget) - 直接简单,但在有多个切换功能时会变得复杂
- 动画(Animations) - 长一些,但更容易触发多个操作
- 系统变量(System variables) - 有些复杂,但非常灵活和方便
脚本GUI允许我们将切换功能保存到保存文件中作为变量,并触发多个操作,但它们更复杂,并且必须在单独的文件夹中进行设置。
P社GUI组件的开关[编辑 | 编辑源代码]
PdxGuiWidget 是一个简单的功能,用于隐藏或显示指定的元素。
在这个示例中,我们有一个包含隐藏子菜单的容器,一个按钮用于显示子菜单,另一个按钮用于隐藏子菜单。
- 当点击第一个按钮时,它会返回到其父代,在父代中搜索子菜单和另一个按钮,然后显示它们并隐藏自身。
- 第二个按钮会搜索元素和按钮,并显示它们并隐藏自身。
container = { button = { name = "show submenu" onclick = "[PdxGuiWidget.AccessParent.FindChild('submnenu').Show]" onclick = "[PdxGuiWidget.AccessParent.FindChild('hide submenu').Show]" onclick = "[PdxGuiWidget.Hide]" } button = { name = "hide submenu" visible = no onclick = "[PdxGuiWidget.AccessParent.FindChild('submnenu').Hide]" onclick = "[PdxGuiWidget.AccessParent.FindChild('show submenu').Show]" onclick = "[PdxGuiWidget.Hide]" } widget = { name = "submenu" visible = no } }
如果元素之间有更多的父/子级别分隔,我们可以像这样重复使用 AccessParent :
onclick = "[PdxGuiWidget.AccessParent.AccessParent.AccessParent.AccessParent.FindChild('submnenu').Show]"
每个按钮都可以隐藏或显示多个任意类型的元素。您只需要提供名称。
优点:
- 对于简单的切换设置很容易。
缺点:
- 切换将在每次重新启动游戏时重置。
- 如果您想要隐藏多个事物或切换,代码会变得非常冗长和难以管理。
- 尝试隐藏数据列表中的条目(如动态网格框)将只隐藏第一个实例。
动画开关[编辑 | 编辑源代码]
我们可以设置通过按钮(或条件)触发的动画来隐藏/显示元素,甚至移动它们。这样一来,我们就不需要计算按钮和窗口之间有多少个父级,而且可以通过一个 onclick
同时触发多个动作。
前面的示例将如下所示,并且以相同的方式工作。第一个按钮触发 "show_submenu",隐藏该按钮并显示其他内容,而第二个按钮触发 "hide_submenu",隐藏此按钮和小部件,并显示第一个按钮。
container = { button = { state = { # this is an animation name = show_submenu on_start = "[PdxGuiWidget.Hide]" } state = { name = hide_submenu on_start = "[PdxGuiWidget.Show]" } onclick = "[PdxGuiTriggerAllAnimations('show_submenu')]" } button = { visible = no state = { name = show_submenu on_start = "[PdxGuiWidget.Show]" } state = { name = hide_submenu on_start = "[PdxGuiWidget.Hide]" } onclick = "[PdxGuiTriggerAllAnimations('hide_submenu')]" } widget = { visible = no state = { name = show_submenu on_start = "[PdxGuiWidget.Show]" } state = { name = hide_submenu on_start = "[PdxGuiWidget.Hide]" } } }
虽然较长,但动画可以保存为模板并以一行代码的方式重复使用,就像 using = hide_animation
。全屏理发器使用很多动画,如果你需要更好的例子。
优点:
- 更容易将许多事物链接在一起,甚至打开一个不同的窗口并触发其中的动画
缺点:
- 动画块可能相当冗长
- 当游戏重新启动时,所有开关都会重置
脚本GUI开关[编辑 | 编辑源代码]
脚本化的GUI允许我们使用脚本来切换可见性。它们使得管理多个项目更加容易,但在大量使用时可能会影响性能。
对于一个脚本化的切换,我们需要在common/scripted_guis/目录下创建一个.txt文件。文件的名称可以任意取。
这个文件中的基本切换看起来像这样:
gui_toggle = { scope = character is_shown = { has_variable = gui_toggle } effect = { if = { limit = { has_variable = gui_toggle } remove_variable = gui_toggle } else = { set_variable = gui_toggle } } }
单击时,它会向作用域角色添加一个变量,并在再次单击时将其删除。
然后,如果该变量存在,我们的窗口将可见。如果不存在,则隐藏。
这就是 GUI 文件的外观。我们可以只使用一个按钮,因为其功能会随着每次点击而改变。
container = { button = { datacontext = "[GetScriptedGui('gui_toggle')]" onclick = "[ScriptedGui.Execute( GuiScope.SetRoot( GetPlayer.MakeScope ).End )]" } widget = { datacontext = "[GetScriptedGui('gui_toggle')]" visible = "[ScriptedGui.IsShown( GuiScope.SetRoot( GetPlayer.MakeScope ).End )]" } }
"GetPlayer"
是一个全局Promote函数,返回玩家角色。
"Player.MakeScope"
将我们的玩家设定为脚本化图形界面的作用域,这意味着我们将变量存储在这里。
如果你想要为两个按钮设置不同的工具提示,可以使用两个按钮。在这种情况下,将 'visible'
属性复制到两个按钮上,但是在其中一个上添加 'Not'
,这样它就会默认隐藏起来。
visible = "[Not(ScriptedGui.IsShown( GuiScope.SetRoot( GetPlayer.MakeScope ).End ))]"
优点:
- 切换设置会保存在角色中,除非角色死亡或开始新游戏,否则不会重置。
- 更容易链接多个对象,甚至在不同的窗口中。
缺点:
- 更难记住语法(从这里复制代码以减少错误的机会)。
系统变量[编辑 | 编辑源代码]
系统变量是游戏内部使用的,它们不会被保存,也不能通过脚本直接访问。 对于它们不需要进行设置,因为可以直接在.gui文件中创建。 使用系统变量的语法为:
onclick = "[GetVariableSystem.Toggle( 'var_name' )]"
或者:
datacontext = "[GetVariableSystem]" onclick = "[VariableSystem.Toggle( 'var_name' )]"
可用的函数如下:
- Clear -
Clear( 'var_name' )
清除变量 - ClearIf -
ClearIf( 'var_name', Condition )
如果条件为真,则清除变量 - Exists -
Exists( 'var_name' )
布尔型,如果变量存在则返回true - Get -
Get( 'var_name' )
字符串型,返回存储在变量中的值 - HasValue -
HasValue( 'var_name', 'string' )
布尔型,如果存储的值与提供的值匹配则返回true - Set -
Set( 'var_name', 'string' )
将存储的值设置为提供的值 - Toggle -
Toggle( 'var_name' )
如果变量存在则清除它,如果不存在则创建它
系统变量开关[编辑 | 编辑源代码]
一个使用此文件中的系统变量的基本切换如下所示:
container = { button = { onclick = "[GetVariableSystem.Toggle( 'gui_toggle' )]" } widget = { visible = "[GetVariableSystem.Exists( 'gui_toggle' )]" } }
点击时,系统变量根据其存在与否切换,然后用于显示/隐藏部件。
带有系统变量的选项[编辑 | 编辑源代码]
一个包含三个选项的基本设置如下:
container = { button = { onclick = "[GetVariableSystem.Set( 'gui_tabs', 'tab_1' )]" } button = { onclick = "[GetVariableSystem.Set( 'gui_tabs', 'tab_2' )]" } button = { onclick = "[GetVariableSystem.Set( 'gui_tabs', 'tab_3' )]" } widget = { visible = "[GetVariableSystem.HasValue( 'gui_toggle', 'tab_1' )]" } widget = { visible = "[GetVariableSystem.HasValue( 'gui_toggle', 'tab_2' )]" } widget = { visible = "[GetVariableSystem.HasValue( 'gui_toggle', 'tab_3' )]" } }
注意,变量最初没有值,因此没有任何小部件会显示。
要设置默认选项卡,需要由打开窗口的按钮设置变量的值。
button = { onclick = "[GetVariableSystem.Toggle( 'gui_toggle' )]" # this opens the window onclick = "[GetVariableSystem.Set( 'gui_tabs', 'tab_1' )]" # this set the default tab }
或在窗口显示时使用状态块:
state = { name = _show on_start = "[GetVariableSystem.Set( 'gui_tabs', 'tab_1' )]" }
或者,可以将其中一个小部件设置为在变量不存在时显示,避免初始值的需求。
container = { button = { onclick = "[GetVariableSystem.Clear( 'gui_tabs' )]" } button = { onclick = "[GetVariableSystem.Set( 'gui_tabs', 'tab_2' )]" } button = { onclick = "[GetVariableSystem.Set( 'gui_tabs', 'tab_3' )]" } widget = { visible = "[Not( GetVariableSystem.Exists( 'gui_toggle' ) )]" } widget = { visible = "[GetVariableSystem.HasValue( 'gui_toggle', 'tab_2' )]" } widget = { visible = "[GetVariableSystem.HasValue( 'gui_toggle', 'tab_3' )]" } }
这相当于第一个示例,其中之一是设置默认值的方法。
优点:
- 简单且易于记忆的语法
- 更容易连接许多事物,甚至在不同的窗口中
- 可以通过附加命令进行扩展(见下文)以显示全新的窗口,避免在hud.gui中使用小部件的需要
缺点:
- 无法直接与脚本交互,必须使用GUI进行设置和清除
- 可能更难跟踪
- 游戏重新启动时,所有切换将重置
添加新的UI元素[编辑 | 编辑源代码]
在1.5.0版本之前只能通过控制台指令ExecuteConsoleCommand( ... )
来创建任何新窗口。
在1.5.0版本之后可以在Crusader Kings III\game\gui\scripted_widgets下添加新的UI元素,这对模组的兼容性和UI的动画支持有很大的改善。
首先需要在 .gui 文件创建一个可以被显示的window元素,例如 "gui/custom_windows/my_window.gui"。这个window必须要有一个名字。
window = { name = "my_custom_window" parentanchor = center layer = middle size = { 100 100 } using = Window_Background GetVariableSystem.Exists('my_menu_open') }
上面的代码以屏幕中心未基准创建了一个100x100像素的window,最上面的name被用于在其他代码中创建这个window。
在1.5.0版本之后可以在 Crusader Kings III\game\gui\scripted_widgets 下添加新的UI元素,这可以取代之前使用控制台创建widget的方法。
首先需要在 gui\scripted_widgets 目录下创建一个.txt文件,例如 "gui/custom_windows/my_scripted_widgets.txt"。并且在文件中调出之前创建好的window:
gui/test_custom_widget.gui = my_custom_window
以上为:文件的路径/文件的名字.gui = 文件中window的名字。
所有被添加进这个文件的UI元素都将在游戏开始时被创建,每行代码都将创建一个新的元素,所有我们可以创建两个相同的UI元素,不过由于参数相同会重叠在一起。
所有在这个文件夹下的.txt文件都将被加载,所以这对不同mod的兼容性有所提升,除非文件重名或者UI位置重叠。
我们不仅仅可以创建window,好可以是widget和其他任何可以被创建的元素,这令模组作者们可以在不修改 hud.gui 的情况下新增自己的hud元素,例如顶部的资源显示和侧边的按钮。
我们需要做一个开关按钮来控制这个window的显示:
button = { onclick = "[GetVariableSystem.Toggle('my_menu_open')]" }
以上通过开关一个系统参数来实现UI元素的显现和消失。
文档 | Effects • 触发器 • 修正 • 作用域 • 变量 • 数据类型 • 本地化 • 可定制的本地化 |
脚本 | AI • 剧本 • 角色 • 效果指令 • 内阁 • 文化 • 决议 • 宗族 • 事件 • 政体 • 历史 • 地产 • 生活方式 • 军队 • 宗教 • Story cycles • 头衔 • 特质 |
地图 | 地图 • 地形 |
图形 | 3D模型 • Exporters • 界面 • Coat of arms • Graphical assets • Fonts • Particles • Shaders • Unit models |
音频 | Music • Sound |
其他 | 控制台指令 • 校验码 • 模组结构 • Troubleshooting |