第268行: | 第268行: | ||
|[[File:Ordered_hbox.jpg]] | |[[File:Ordered_hbox.jpg]] | ||
|- | |- | ||
|"扩大(expanding)" | |"扩大(expanding)"策 略 涉及到子代的规模调整。 | ||
这对于创建标签页非常有用,无需手动设置它们的大小。 | 这对于创建标签页非常有用,无需手动设置它们的大小。 | ||
第280行: | 第280行: | ||
|[[File:tabs hbox 2.jpg]] | |[[File:tabs hbox 2.jpg]] | ||
|- | |- | ||
|随着水平和垂直方向的“扩展(expanding)” | |随着水平和垂直方向的“扩展(expanding)”策 略 ,hbox 及其子代将在两个方向上进行调整大小。 | ||
<pre> | <pre> | ||
hbox = { | hbox = { | ||
第305行: | 第305行: | ||
默认情况下有五项固定的策略: | 默认情况下有五项固定的策略: | ||
#'''fixed'''(固定的) - 保持原始大小 | #'''fixed'''(固定的) - 组件 保持原始大小 , 无法放大或缩小。 设置"fixed"属性的hboxes/vboxes将根据子代的大小进行调整,就像一个容器。 | ||
#'''expanding'''(扩展的) - 根据父代的宽度/高度自动调整大小,但不会缩小到原始尺寸以下。优先级高于其他策略的子 | #'''expanding'''(扩展的) - 根据父代的宽度/高度自动调整大小,但不会缩小到原始尺寸以下。优先级高于其他策略的子 代 。如果多个子 代 设置为 "expanding" ,它们将平均分配可用空间。 | ||
#'''growing''' (增长的)- | #'''growing''' (增长的)- 与"expanding" 相似,但优先级较低。如果存在带有"expanding" 属性的子代,"growing" 将不会发生。这还意味着 <code>expand = {}</code> 部件将调整大小为0,因此如果想要与"expanding" 策略的组件一起使用,需要更改其策略。 | ||
#'''preferred''' (偏好的)- | #'''preferred''' (偏好的)- 随着可用空间的变化,它会随之放大或缩小。 | ||
#'''shrinking''' (缩水的)- | #'''shrinking''' (缩水的)- 可以缩小至原始大小以下,但无法放大超过原始大小。 | ||
布局策略还会遵循最小尺寸 (minimumsize) 、最大尺寸 (maximumsize) 、最小宽度 (min_width)和最大宽度 (max_width) 。 | |||
{|class="wikitable" | {|class="wikitable" | ||
|" | | "Expanding" 优先于 "growing" ,但不会使其缩小到小于原始(固定)尺寸。 | ||
<pre> | <pre> | ||
hbox = { | hbox = { | ||
第326行: | 第326行: | ||
|[[File:growing hbox 2.png]] | |[[File:growing hbox 2.png]] | ||
|- | |- | ||
|"preferred" | | "preferred" 和 "shrinking" 在空间不足时都可以收缩。为了看到这种效果,可能需要通过 <code>max_width</code> 或 "shrinking" 策略来限制 hbox 的宽度。 | ||
<pre> | <pre> | ||
hbox = { | hbox = { | ||
第339行: | 第339行: | ||
|} | |} | ||
请注意,具有较大尺寸的物块 (objects) 可能会拉伸 hbox/vbox ,即使看起来适合。设置 <code>max_width</code> ,并测试所有文本字段是否能够容纳很长的字符串,以确保窗口不会改变。 | |||
=== Templates模板 === | |||
模板和类型是代码的命名代码块,可以在代码中多次使用,例如按钮、窗口背景和文本样式。这有助于保持相同的样式,并减少我们编写的代码量。编辑模板将同时编辑所有的实例! | |||
模板可以存储整个窗口的内容或仅存储一行,就像这个例子: | |||
<pre> | <pre> | ||
template Window_Size_Sidebar | template Window_Size_Sidebar | ||
第361行: | 第353行: | ||
</pre> | </pre> | ||
这个模板可以与"using = Window_Size_Sidebar" 一起使用,基本上会用模板的内容替换 "using" 行。 | |||
模板是全局的,可以在任何.gui文件中定义。大部分游戏模板存储在 <code>gui/shared</code> 目录下。本地版本 <code>local_template</code> 必须在同一文件中定义。 | |||
常用的模板和类型可以在UI库中找到。要访问它,打开控制台,切换到发布模式(Release mode),然后会出现一个名为 "UI Library" 的新按钮。 | |||
==== | ==== 类型 ==== | ||
虽然模板可以只包含一个属性,但类型始终是完整的部件,比如按钮或小部件。 | |||
text_single | <code>text_single</code> 和 <code>text_multi</code> 是具有许多预定义属性的文本框类型,因此我们不需要每次重新输入它们,只需简单地写: | ||
<pre> | <pre> | ||
第375行: | 第371行: | ||
</pre> | </pre> | ||
类型的定义方式略有不同,首先要创建一个命名的类型组: | |||
<pre> | <pre> | ||
第386行: | 第382行: | ||
</pre> | </pre> | ||
==== | ==== 块覆写 ==== | ||
模板和类型可以具有命名的覆写块,这使我们能够在不更改整个模板的情况下编辑实例的一部分。例如,一个模板可以有一个默认的文本块: | |||
<pre> | <pre> | ||
第397行: | 第393行: | ||
</pre> | </pre> | ||
为了覆盖其中的内容,我们可以在我们的实例中添加一个具有相同名称的覆盖代码块: | |||
<pre> | <pre> | ||
第406行: | 第402行: | ||
</pre> | </pre> | ||
我们也可以像这样从我们的实例中移除它: | |||
<code>blockoverride "text" {}</code> | <code>blockoverride "text" {}</code> | ||
== | ==== 增加模组兼容性 ==== | ||
在制作一个涉及gui的mod时,所写的gui代码会与其他mod的代码覆盖同一个文件而无法同时运行。 | |||
模组开发者可以通过使用模板并为另一个模组创建一个“钩子(hook)”来实现兼容性。 | |||
为了做到这一点,每个模组开发者在已修改的窗口中包含其他模组的模板,而模板本身则在单独的文件中定义。 | |||
例如,全屏理发店模组包括了来自社区口味包(Community Flavor Pack)的“using = cfp_bg_buttons”模板,该模板添加了额外的背景。如果用户未订阅社区口味包,则无法找到该模板,也不会对游戏产生任何影响。如果用户同时订阅了这两个模组,则模板会被应用。 | |||
当然,这需要模组开发者合作,为彼此的模组设置模板。 | |||
== 脚本化的图形用户界面 == | |||
Scripted guis | 脚本化的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> | ||
第450行: | 第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> | <code> | ||
第484行: | 第496行: | ||
</code> | </code> | ||
脚本值: | |||
<code> | <code> | ||
第490行: | 第502行: | ||
</code> | </code> | ||
在这个例子中,变量存储在玩家角色中,名为 <code>test_var</code> 。可以触及任何其他作用域,只需记得添加 <code>MakeScope</code> 即可。 | |||
|1 | 结尾的 "|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> | ||
第529行: | 第544行: | ||
</pre> | </pre> | ||
如果我们将此效果应用于玩家,他们将成为<code>root</code> ,因此列表将存储在他们身上。 | |||
然后,我们使用任何列表框(vbox、dynamicgridbox、fixedgridbox),将数据模型设置为我们的列表: | |||
<pre> | <pre> | ||
dynamicgridbox = { | dynamicgridbox = { | ||
第551行: | 第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 | PdxGuiWidget 是一个简单的功能,用于隐藏或显示指定的元素。 | ||
在这个示例中,我们有一个包含隐藏子菜单的容器,一个按钮用于显示子菜单,另一个按钮用于隐藏子菜单。 | |||
# | # 当点击第一个按钮时,它会返回到其父代,在父代中搜索子菜单和另一个按钮,然后显示它们并隐藏自身。 | ||
# | # 第二个按钮会搜索元素和按钮,并显示它们并隐藏自身。 | ||
<pre> | <pre> | ||
第593行: | 第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 = { | ||
第658行: | 第680行: | ||
</pre> | </pre> | ||
虽然较长,但动画可以保存为模板并以一行代码的方式重复使用,就像 <code>using = hide_animation</code> 。全屏理发器使用很多动画,如果你需要更好的例子。 | |||
优点: | |||
* | * 更容易将许多事物链接在一起,甚至打开一个不同的窗口并触发其中的动画 | ||
缺点: | |||
* | * 动画块可能相当冗长 | ||
* | * 当游戏重新启动时,所有开关都会重置 | ||
=== | === 脚本GUI开关 === | ||
Scripted guis allows us to use script to toggle visibility. They make it easier to manage multiple things but can impact performance when used in big numbers. | Scripted guis allows us to use script to toggle visibility. They make it easier to manage multiple things but can impact performance when used in big numbers. | ||
第733行: | 第754行: | ||
* it's a little harder to remember the syntax (copy the code from here to reduce the chance of mistakes) | * it's a little harder to remember the syntax (copy the code from here to reduce the chance of mistakes) | ||
=== | === 系统变量 === | ||
System variables are used internally by the game, they are not save persistent and cannot be directly accessed through scripts. | System variables are used internally by the game, they are not save persistent and cannot be directly accessed through scripts. | ||
第757行: | 第778行: | ||
* Toggle - <code>Toggle( 'var_name' )</code> clears the variable if it exists, creates it if it does not | * Toggle - <code>Toggle( 'var_name' )</code> clears the variable if it exists, creates it if it does not | ||
==== | ==== 系统变量开关 ==== | ||
A basic toggle using system variables in this file would looks like this: | A basic toggle using system variables in this file would looks like this: |
2023年6月20日 (二) 22:59的版本
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" 的新按钮。
类型
虽然模板可以只包含一个属性,但类型始终是完整的部件,比如按钮或小部件。
text_single
和 text_multi
是具有许多预定义属性的文本框类型,因此我们不需要每次重新输入它们,只需简单地写:
text_single = { text = "my text" }
类型的定义方式略有不同,首先要创建一个命名的类型组:
types Standard_Types { type text_single = textbox { ... } }
块覆写
模板和类型可以具有命名的覆写块,这使我们能够在不更改整个模板的情况下编辑实例的一部分。例如,一个模板可以有一个默认的文本块:
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文件本身中轻松设置:
- Template:界面(PdxGuiWidget) - 直接简单,但在有多个切换功能时会变得复杂
- Template:界面(Animations) - 长一些,但更容易触发多个操作
- Template:界面(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开关
Scripted guis allows us to use script to toggle visibility. They make it easier to manage multiple things but can impact performance when used in big numbers.
For a scripted toggle we need to create a .txt file in common/scripted_guis/. The name of the file can be anything.
A basic toggle in this file would looks like this:
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 } } }
When clicked, it adds a variable to the scoped character and removes it when clicked again.
Then, if the variable is present, our window will be visible. If it's not, it's hidden.
This is how the gui file would look like. We can use just one button as its function will change with each click.
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" is a global promote and returns the player character.
"Player.MakeScope" makes our player the scope of the scripted gui, meaning this is where we store the variable in.
Two buttons can be used if you want different tooltips for them. In this case, copy the 'visible' property to both buttons, but add 'Not' to one of them, so it's hidden by default:
visible = "[Not(ScriptedGui.IsShown( GuiScope.SetRoot( GetPlayer.MakeScope ).End ))]"
Pros:
- the toggles are saved in the character and won't reset unless they die or you start a new game
- easier to link many things, even in different windows
The downsides:
- you need to restart the game to update scripted guis
- it's a little harder to remember the syntax (copy the code from here to reduce the chance of mistakes)
系统变量
System variables are used internally by the game, they are not save persistent and cannot be directly accessed through scripts.
No setup is needed for them as they can be directly created in the .gui file.
The syntax for using system variables is:
onclick = "[GetVariableSystem.Toggle( 'var_name' )]"
or:
datacontext = "[GetVariableSystem]" onclick = "[VariableSystem.Toggle( 'var_name' )]"
The available functions are:
- Clear -
Clear( 'var_name' )
clears the variable - ClearIf -
ClearIf( 'var_name', Condition )
clears the variable if Condition is true - Exists -
Exists( 'var_name' )
Boolean, returns true if the variable exists - Get -
Get( 'var_name' )
CString, returns the value stored in the variable - HasValue -
HasValue( 'var_name', 'string' )
Boolean, returns true if the stored value matches the provided value - Set -
Set( 'var_name', 'string' )
sets the stored value to the provided value - Toggle -
Toggle( 'var_name' )
clears the variable if it exists, creates it if it does not
系统变量开关
A basic toggle using system variables in this file would looks like this:
container = { button = { onclick = "[GetVariableSystem.Toggle( 'gui_toggle' )]" } widget = { visible = "[GetVariableSystem.Exists( 'gui_toggle' )]" } }
When clicked, the system variable is toggled depending on whether it exists, this is then used to show/hide the widget.
Tabs with System Variables
A basic setup for three tabs would look like this:
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' )]" } }
Note that the variable initially has no value and none of the widgets would show.
To set a default tab the variable needs to be set by the button opening the window:
button = { onclick = "[GetVariableSystem.Toggle( 'gui_toggle' )]" # this opens the window onclick = "[GetVariableSystem.Set( 'gui_tabs', 'tab_1' )]" # this set the default tab }
or using a state block when the window is shown:
state = { name = _show on_start = "[GetVariableSystem.Set( 'gui_tabs', 'tab_1' )]" }
Alternatively one of the widgets can be set to appear when the variable doesn't exist, avoiding the need for an initial value:
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' )]" } }
This is the equivalent of the first example with one of the methods to set a default value.
Pros:
- simple and easy to remember syntax
- easier to link many things, even in different windows
- can be extended with additional commands (see below) to show entirely new windows, avoiding the need to have the widget in hud.gui
The downsides:
- no direct interaction with scripts, they must be set & cleared using the gui
- can be harder to keep track of
- all toggles will reset when the game is restarted
添加新的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 |