构建菜单¶
由 vincent-lg 贡献,2018年
构建菜单是在游戏内的菜单,类似于 EvMenu
,但采用了不同的方法。构建菜单特别设计用于作为构建者编辑信息。在命令中创建构建菜单可以让构建者快速编辑给定对象,比如一个房间。如果你按照步骤添加这个贡献,你将可以使用 edit
命令来编辑任何默认对象,提供更改其键和描述的功能。
安装¶
在
mygame/commands/default_cmdset.py
文件中导入这个贡献的GenericBuildingCmd
类:from evennia.contrib.base_systems.building_menu import GenericBuildingCmd
在
CharacterCmdSet
中添加命令:# ... 这些行应该存在于文件中 class CharacterCmdSet(default_cmds.CharacterCmdSet): key = "DefaultCharacter" def at_cmdset_creation(self): super().at_cmdset_creation() # ... 添加下面的行 self.add(GenericBuildingCmd())
基本用法¶
edit
命令将允许你编辑任何对象。你需要指定对象的名称或 ID 作为参数。例如:edit here
将编辑当前房间。但是,构建菜单可以执行比这个非常简单的示例更多的功能,继续阅读以获取更多详情。
构建菜单可以被设置为编辑任何东西。以下是你在编辑房间时获得的输出示例:
正在编辑房间: Limbo(#2)
[T]itle: the limbo room
[D]escription
This is the limbo room. You can easily change this default description,
either by using the |y@desc/edit|n command, or simply by entering this
menu (enter |yd|n).
[E]xits:
north to A parking(#4)
[Q]uit this menu
从那里,你可以通过按 t 来打开标题选项。你可以一路输入文本来改变房间标题,然后输入 @ 以返回主菜单(这些都是可自定义的)。按 q 退出此菜单。
首先,创建一个新模块并在其中放置一个继承自 BuildingMenu
的类。
from evennia.contrib.base_systems.building_menu import BuildingMenu
class RoomBuildingMenu(BuildingMenu):
# ...
接下来,重写 init
方法(而不是 __init__
!)。你可以使用 add_choice
方法添加选项(如上面看到的标题、描述和出口选项)。
class RoomBuildingMenu(BuildingMenu):
def init(self, room):
self.add_choice("title", "t", attr="key")
这将创建第一个选项,即标题选项。如果有人打开你的菜单并输入 t,她将进入标题选项。她可以更改标题(它将写入房间的 key
属性),然后通过输入 @
返回主菜单。
add_choice
有很多参数,并提供了很大的灵活性。最有用的可能是回调的使用,因为几乎可以将 add_choice
中的任何参数设置为回调,这是你在模块上方定义的函数。当菜单元素被触发时,这个函数将被调用。
请注意,要编辑描述,最好的方法不是调用 add_choice
,而是调用 add_choice_edit
。这是一个方便的快捷方式,可以快速打开 EvEditor
,然后在编辑器关闭时返回菜单。
class RoomBuildingMenu(BuildingMenu):
def init(self, room):
self.add_choice("title", "t", attr="key")
self.add_choice_edit("description", key="d", attr="db.desc")
当你想创建一个构建菜单时,只需导入你的类,创建它并指定你的意图呼叫者和要编辑的对象,然后调用 open
:
from <wherever> import RoomBuildingMenu
class CmdEdit(Command):
key = "redit"
def func(self):
menu = RoomBuildingMenu(self.caller, self.caller.location)
menu.open()
简单菜单示例¶
在深入之前,有一些事情需要指出:
构建菜单作用于一个对象。此对象将在菜单操作中被编辑。因此,你可以创建一个菜单来添加/编辑房间、出口、角色等等。
构建菜单以选项的层次结构排列。一个选项可以访问一个子菜单。选项与命令链接(通常非常简短)。例如,在下面的示例中,要编辑房间键,在打开构建菜单后,可以输入
k
。这将带你进入键选项,你可以输入新的键以房间的名称。然后你可以通过输入@
离开这个选项并返回到整个菜单。(所有这些都可以更改)。要打开菜单,你将需要类似于命令的东西。此贡献提供了一个基本命令以供演示,但我们将在此示例中使用相同的代码重写它,以获得更多灵活性。
那么让我们添加一个非常基本的示例开始。
通用编辑命令¶
首先添加一个新命令。你可以添加或编辑以下文件(这里没有技巧,随意以不同的方式组织代码):
# 文件: commands/building.py
from evennia.contrib.building_menu import BuildingMenu
from commands.command import Command
class EditCmd(Command):
"""
编辑命令。
使用:
@edit [object]
打开一个构建菜单以编辑指定对象。此菜单允许你指定此对象的相关信息。
示例:
@edit here
@edit self
@edit #142
"""
key = "@edit"
locks = "cmd:id(1) or perm(Builders)"
help_category = "Building"
def func(self):
if not self.args.strip():
self.msg("|r你应该提供一个参数来执行此功能:要编辑的对象.|n")
return
obj = self.caller.search(self.args.strip(), global_search=True)
if not obj:
return
if obj.typename == "Room":
Menu = RoomBuildingMenu
else:
obj_name = obj.get_display_name(self.caller)
self.msg(f"|r对象 {obj_name} 不能被编辑.|n")
return
menu = Menu(self.caller, obj)
menu.open()
这个命令本身相当简单:
它有一个键
@edit
,并且锁定以仅允许构建者使用它。在其
func
方法中,它首先检查参数,如果没有指定参数则返回错误。然后,它搜索给定的参数。我们全局搜索。以这种方式使用的
search
方法将返回找到的对象或None
。如果需要,search
还会向调用者发送错误消息。假设我们找到了一个对象,我们检查对象的
typename
。以后我们将用到这一点,当我们想显示多个构建菜单时。当前我们只处理Room
。如果调用者指定了其他内容,我们将显示错误。假设这个对象是一个
Room
,我们定义了一个Menu
对象,包含我们的构建菜单类。我们构建这个类(创建一个实例),将调用者和要编辑的对象传递给它。然后,我们打开构建菜单,使用
open
方法。
最后一点可能乍一看让人感到惊讶。但过程仍然非常简单:我们创建一个构建菜单实例并调用它的 open
方法。没有更多了。
我们的构建菜单在哪里?
如果你继续添加这个命令并进行测试,你将遇到错误。我们还没有定义 RoomBuildingMenu
。
要添加此命令,请编辑 commands/default_cmdsets.py
。导入我们的命令,在文件顶部添加一行导入:
"""
...
"""
from evennia import default_cmds
# 添加以下行
from commands.building import EditCmd
并在下面的类中(CharacterCmdSet
),添加这行代码:
class CharacterCmdSet(default_cmds.CharacterCmdSet):
"""
`CharacterCmdSet` 包含像 `look`、`get` 等的常规游戏内命令,这些命令在游戏内角色对象上可用。
它与 `AccountCmdSet` 合并,当一个帐户木偶化角色时。
"""
key = "DefaultCharacter"
def at_cmdset_creation(self):
"""
填充 cmdset
"""
super().at_cmdset_creation()
#
# 你在下面添加的所有命令都会覆盖默认命令。
#
self.add(EditCmd())
我们的第一个菜单¶
到目前为止,我们无法使用我们的构建菜单。我们的 @edit
命令将抛出错误。我们必须定义 RoomBuildingMenu
类。打开 commands/building.py
文件,在文件末尾添加:
# ... 在 commands/building.py 的末尾
# 我们的构建菜单
class RoomBuildingMenu(BuildingMenu):
"""
用于编辑房间的构建菜单。
目前我们只有一个选项:键,用来编辑房间的键。
"""
def init(self, room):
self.add_choice("key", "k", attr="key")
保存这些更改,重新加载你的游戏。现在你可以使用 @edit
命令。请看我们得到的内容(请注意,输入游戏的命令前将会有 >
前缀,虽然此前缀可能不会出现在你的 MUD 客户端中):
> look
Limbo(#2)
欢迎来到你的新 Evennia 基于的游戏!如需帮助、想要贡献、报告问题或只是想加入社区,请访问 https://www.evennia.com。
作为账户 #1,你可以通过 @batchcommand tutorial_world.build 创建一个演示/教程区域。
> @edit here
构建菜单: Limbo
[K]ey: Limbo
[Q]uit the menu
> q
关闭构建菜单。
> @edit here
构建菜单: Limbo
[K]ey: Limbo
[Q]uit the menu
> k
-------------------------------------------------------------------------------
Limbo(#2) 的键
你可以通过输入它来简单地更改这个值。
使用 @ 返回主菜单。
当前值: Limbo
> A beautiful meadow
-------------------------------------------------------------------------------
A beautiful meadow(#2) 的键
你可以通过输入它来简单地更改这个值。
使用 @ 返回主菜单。
当前值: A beautiful meadow
> @
构建菜单: A beautiful meadow
[K]ey: A beautiful meadow
[Q]uit the menu
> q
关闭构建菜单。
> look
A beautiful meadow(#2)
欢迎来到你的新 Evennia 基于的游戏!如需帮助、想要贡献、报告问题或只是想加入社区,请访问 https://www.evennia.com。
作为账户 #1,你可以通过 @batchcommand tutorial_world.build 创建一个演示/教程区域。
在我们开始代码之前,让我们检查一下我们有什么:
当我们使用
@edit here
命令时,房间的构建菜单出现。此菜单有两个选项:
输入
k
来编辑房间键。你将进入一个选项,你可以简单地输入房间键(正如我们在这里做的那样)。你可以使用@
返回菜单。你可以使用
q
来退出菜单。
接下来我们检查,使用 look
命令确认菜单已修改了这个房间的键。因此,通过添加一个类、一个方法和一行代码,我们已经添加了一个包含两个选项的菜单。
代码说明¶
让我们再次检查我们的代码:
class RoomBuildingMenu(BuildingMenu):
"""
用于编辑房间的构建菜单。
目前我们只有一个选项:键,用来编辑房间的键。
"""
def init(self, room):
self.add_choice("key", "k", attr="key")
我们首先创建一个继承自
BuildingMenu
的类。当我们想要使用这个贡献创建构建菜单时,通常会这样做。在这个类中,我们重写
init
方法,当菜单打开时会被调用。在这个
init
方法中,我们调用add_choice
。这接受多个参数,但在这里我们只定义了三个:选项名称。这是强制性的,构建菜单会使用它来知道如何显示这个选项。
访问此选项的命令键。我们给予了简单的
"k"
。菜单命令通常是相当简短的(这正是构建菜单受到构建者喜爱的部分原因)。你还可以指定额外的别名,稍后我们将看到。我们添加了一个关键字参数
attr
。这告诉构建菜单,当我们在选项中时,输入的文本进入这个属性名称。它叫做attr
,但它可以是房间的属性或类型类的持久性或非持久性属性(我们还会看到其他示例)。
我们在这里添加了
key
的菜单选项,但为什么会为quit
定义另一个菜单选项?
我们的构建菜单会在选项列表的末尾创建一个选项,如果它是顶级菜单(子菜单没有这个特性)。但是你可以覆盖它,提供一个不同的“退出”消息或执行某些操作。
我鼓励你与这段代码互动。虽然它相当简单,但已经提供了一些功能。
自定义构建菜单¶
这一段较长的部分解释了如何自定义构建菜单。具体的方法取决于你想要实现的目标。我们将从具体到更高级的方法进行介绍。
通用选项¶
在之前的示例中,我们使用了 add_choice
。这是三种可以用来添加选项的方法之一。其他两种用于处理更通用的操作:
add_choice_edit
:这被称为添加一个指向EvEditor
的选项。它通常用于编辑描述,尽管你可以编辑其他内容。我们将很快看到示例。add_choice_edit
使用我们将看到的大多数add_choice
关键字参数,但通常我们只指定两个(有时三个):选项标题如往常一样。
选项键(命令键)如往常一样。
可选地,使用
attr
关键字参数指定要编辑的对象的属性。默认attr
的值为db.desc
,这意味着该持久数据属性将由EvEditor
编辑。尽管你可以将其更改为任何你想要的。
add_choice_quit
:这允许添加一个选择以退出编辑器。大多数情况下,建议使用此选项!如果你不这样做,构建菜单将自动执行该操作,除非你真的告诉它不这样。再次,你可以指定选项的标题和键。你还可以在此菜单关闭时调用一个函数。
所以这是一个更完整的示例(你可以用以下代码替换 commands/building.py
中的 RoomBuildingMenu
类以查看效果):
class RoomBuildingMenu(BuildingMenu):
"""
用于编辑房间的构建菜单。
"""
def init(self, room):
self.add_choice("key", "k", attr="key")
self.add_choice_edit("description", "d")
self.add_choice_quit("退出此编辑器", "q")
到目前为止,我们的构建菜单类仍然很薄…但我们已经有一些有趣的特性。看看下面的 MUD 客户端输出(同样,输入游戏的命令前将会有 >
前缀,以区分):
> @reload
> @edit here
构建菜单: A beautiful meadow
[K]ey: A beautiful meadow
[D]escription:
欢迎来到你的新 Evennia 基于的游戏!如需帮助、想要贡献、报告问题或只是想加入社区,请访问 https://www.evennia.com。
作为账户 #1,你可以通过 @batchcommand tutorial_world.build 创建一个演示/教程区域。
[Q]uit this editor
> d
----------行编辑器 [editor]----------------------------------------------------
01| 欢迎来到你的新 |wEvennia|n 基于的游戏!如需帮助、想要贡献、报告问题或只是想加入社区,请访问 https://www.evennia.com。
02| 作为账户 #1,你可以通过 |w@batchcommand tutorial_world.build|n 创建一个演示/教程区域。
> :DD
----------[l:03 w:034 c:0247]------------(:h for help)----------------------------
清除了缓冲区中的 3 行。
> 这是一个美丽的草地,但太美了以至于我无法描述它。
01| 这是一个美丽的草地,但太美了以至于我无法描述它。
> :wq
构建菜单: A beautiful meadow
[K]ey: A beautiful meadow
[D]escription:
这是一个美丽的草地,但太美了以至于我无法描述它。
[Q]uit this editor
> q
关闭构建菜单。
> look
A beautiful meadow(#2)
这是一个美丽的草地,但太美了。
通过使用构建菜单中的 d
快捷键,打开了 EvEditor
。你可以使用 EvEditor
命令(就像我们在这里做的那样,使用 :DD
来移除所有,使用 :wq
来保存并退出)。当你退出编辑器时,描述被保存(在这里,即 room.db.desc
)并返回构建菜单。
请注意,退出选项的选择也发生了变化,这是由于我们添加了 add_choice_quit
。在大多数情况下,你可能不需要使用此方法,因为退出菜单会被自动添加。
add_choice
选项¶
add_choice
和两个方法 add_choice_edit
和 add_choice_quit
采取许多可选参数以便于自定义。某些这些选项可能并不适用于 add_choice_edit
或 add_choice_quit
。
以下是 add_choice
的选项,作为参数指定它们:
第一个位置参数,强制性参数是选择的标题,如我们所见。这将影响该选择在菜单中的显示方式。
第二个位置参数,强制性参数是访问此菜单的命令键。最好使用关键字参数用于其他参数。
aliases
关键字参数可以包含一个别名列表,用于访问此菜单。例如:add_choice(..., aliases=['t'])
attr
关键字参数包含在选择被选中时要编辑的属性。它是一个字符串,必须是对象(在菜单构造函数中指定的对象)到达此属性的名称。例如,attr
为"key"
将尝试找到obj.key
以读取和写入属性。你可以指定更复杂的属性名称,例如,attr="db.desc"
以设置持久属性desc
,或者使用attr="ndb.something"
以使用对象上的非持久性数据属性。text
关键字参数用于更改在选择被选择时所显示的文本。菜单选项提供默认文本,你可以更改它。因为这是一个较长的文本,所以有多行字符串非常有用(见下文的示例)。glance
关键字参数用于指定当前信息的显示方式,当菜单选择未被打开时。在前面的示例中,你会看到当前的(key
或db.desc
)在菜单中显示,位于命令键旁边。这个功能对于查看当前值(因此命名为 glance)非常有用。再次,菜单选择将提供默认的 glance,如果不指定则将显示。on_enter
关键字参数允许在菜单选择被打开时添加一个回调。这更高级,但在某些情况下非常有用。on_nomatch
关键字参数在进入菜单时,调用者输入的文本与任何命令不匹配时被调用(包括@
命令)。默认情况下,这将编辑指定的attr
。on_leave
关键字参数允许指定当调用者离开菜单选择时使用的回调。这对清理非常有用。
这些是许多可能性,但大多数情况下,你不会需要它们。以下是一个简短的示例,使用这些参数的一些(同样,用以下代码替换 commands/building.py
中的 RoomBuildingMenu
类以查看效果):
class RoomBuildingMenu(BuildingMenu):
"""
用于编辑房间的构建菜单。
目前我们只有一个选项:键,用来编辑房间的键。
"""
def init(self, room):
self.add_choice("title", key="t", attr="key", glance="{obj.key}", text="""
-------------------------------------------------------------------------------
编辑 {{obj.key}}(#{{obj.id}}) 的标题
你可以简单通过输入来更改标题。
使用 |y{back}|n 返回主菜单。
当前标题: |c{{obj.key}}|n
""".format(back="|n 或 |y".join(self.keys_go_back)))
self.add_choice_edit("description", "d")
重新加载你的游戏,看它如何运作:
> @edit here
构建菜单: A beautiful meadow
[T]itle: A beautiful meadow
[D]escription:
欢迎来到你的新 Evennia 基于的游戏!如需帮助、想要贡献、报告问题或只是想加入社区,请访问 https://www.evennia.com。
作为账户 #1,你可以通过 @batchcommand tutorial_world.build 创建一个演示/教程区域。
[Q]uit the menu
> t
-------------------------------------------------------------------------------
编辑 {{obj.key}}(#{{obj.id}}) 的标题
你可以简单通过输入来更改标题。
使用 |y{back}|n 返回主菜单。
当前标题: A beautiful meadow
> @
构建菜单: A beautiful meadow
[T]itle: A beautiful meadow
[D]escription:
欢迎来到你的新 Evennia 基于的游戏!如需帮助、想要贡献、报告问题或只是想加入社区,请访问 https://www.evennia.com。
作为账户 #1,你可以通过 @batchcommand tutorial_world.build 创建一个演示/教程区域。
[Q]uit the menu
> q
关闭构建菜单。
最令人惊讶的部分无疑是文本。我们使用多行语法(使用 """
)。过多的空格将自动从每一行的左侧移除。我们在大括号之间指定了一些信息…有时使用双大括号。这可能有点奇怪:
{back}
是我们将使用的直接格式参数(请查看.format
指定符)。{{obj...}}
指的是正在编辑的对象。我们用两个大括号,因为.format
会删除它们。
在 glance
中,我们也使用 {obj.key}
来指示我们想显示房间的键。
一切都可以是函数¶
add_choice
的关键字参数通常是字符串(类型 str
)。但是,这些参数中的每一个也可以是一个函数。这允许了很多自定义,因为我们定义将执行的回调来实现某种操作。
为了演示这一点,我们将尝试添加一个新特性。我们的房间构建菜单仍然不错,但能够编辑出口就更好了。所以我们可以在描述下方添加一个新选项…但怎么实际编辑出口呢?出口不仅是一个要设置的属性:出口是介于两个房间之间的对象(默认是 Exit
类型)。那么,我们该如何显示这个呢?
首先,让我们在 limbo 中添加几个出口,因此我们有东西可以使用:
@tunnel n
@tunnel s
这应该在 limbo 中创建两个新房间,出口分别通向它们,并且可以从 limbo 返回。
> look
A beautiful meadow(#2)
这是一个美丽的草地,但太美了。
出口: north(#4) 和 south(#7)
我们可以使用 exits
属性访问房间出口:
> @py here.exits
[<Exit: north>, <Exit: south>]
所以我们需要在构建菜单中显示这个列表…并且能够编辑它会很棒。也许甚至添加新出口?
首先,让我们写一个函数来显示当前出口的 glance
。以下是代码,下面进行解释:
class RoomBuildingMenu(BuildingMenu):
"""
用于编辑房间的构建菜单。
"""
def init(self, room):
self.add_choice("title", key="t", attr="key", glance="{obj.key}", text="""
-------------------------------------------------------------------------------
编辑 {{obj.key}}(#{{obj.id}}) 的标题
你可以简单通过输入来更改标题。
使用 |y{back}|n 返回主菜单。
当前标题: |c{{obj.key}}|n
""".format(back="|n 或 |y".join(self.keys_go_back)))
self.add_choice_edit("description", "d")
self.add_choice("exits", "e", glance=glance_exits, attr="exits")
# 菜单函数
def glance_exits(room):
"""显示房间出口。"""
if room.exits:
glance = ""
for exit in room.exits:
glance += f"\n |y{exit.key}|n"
return glance
return "\n |g尚无出口|n"
当构建菜单打开时,它向调用者显示每个选项。一个选项以其标题显示(稍微渲染得好看一点,以显示键和 glance)。在 exits
选项的情况下,glance 是一个函数,因此构建菜单会调用这个函数,给它提供正在编辑的对象(在这里是房间)。这个函数应返回文本。
> @edit here
构建菜单: A beautiful meadow
[T]itle: A beautiful meadow
[D]escription:
这是一个美丽的草地,但太美了。
[E]xits:
north
south
[Q]uit the menu
> q
关闭编辑器。
我怎么知道函数的参数?
你提供的函数可以接受许多不同的参数。这允许更灵活的方法,但可能看起来在开始时有点复杂。基本上,你的函数可以接受任何参数,构建菜单将根据参数名称发送参数。如果你的函数定义了名为 caller
的参数(例如 def func(caller):
),那么构建菜单知道第一个参数应该包含构建菜单的调用者。以下是参数列表,你不必明确指定(如果你这样做,则参数名称必须相同):
menu
:如果你的函数定义了名为menu
的参数,它将含有构建菜单本身。choice
:如果你的函数定义了名为choice
的参数,它将含有表示此菜单选择的Choice
对象。string
:如果你的函数定义了名为string
的参数,它将包含用户输入以到达此菜单选择。这在nomatch
回调(我们稍后将看到)中不是很有用。obj
:如果你的函数定义了名为obj
的参数,它将包含通过构建菜单所编辑的对象。caller
:如果你的函数定义了名为caller
的参数,它将包含构建菜单的调用者。其他任何内容:任何其他参数将包含通过构建菜单编辑的对象。
所以在我们的案例中:
def glance_exits(room):
唯一需要的参数是 room
。它不在可能参数列表中,因此将构建菜单(在这里是房间)给出的编辑对象。
获取菜单或选择对象有什么好处?
大多数情况下,你不需要这些参数。很少情况下,你会使用它们来获得特定数据(例如,设置的默认属性)。本教程不会详细阐述这些可能性。只需了解它们的存在。
我们还应该定义一个文本回调,以便能够进入菜单以查看房间的出口。我们将看到如何编辑它们在下一部分,但这是一个展示更完整回调的好机会。为了让它在操作时生效,通常用以下代码替换 commands/building.py
中的类和函数:
# 我们的构建菜单
class RoomBuildingMenu(BuildingMenu):
"""
用于编辑房间的构建菜单。
"""
def init(self, room):
self.add_choice("title", key="t", attr="key", glance="{obj.key}", text="""
-------------------------------------------------------------------------------
编辑 {{obj.key}}(#{{obj.id}}) 的标题
你可以简单通过输入来更改标题。
使用 |y{back}|n 返回主菜单。
当前标题: |c{{obj.key}}|n
""".format(back="|n 或 |y".join(self.keys_go_back)))
self.add_choice_edit("description", "d")
self.add_choice("exits", "e", glance=glance_exits, attr="exits", text=text_exits)
# 菜单函数
def glance_exits(room):
"""显示房间出口。"""
if room.exits:
glance = ""
for exit in room.exits:
glance += f"\n |y{exit.key}|n"
return glance
return "\n |g尚无出口|n"
def text_exits(caller, room):
"""在选择本身显示房间出口。"""
text = "-" * 79
text += "\n\n房间出口:"
text += "\n 使用 |y@c|n 创建新出口。"
text += "\n\n现有出口:"
if room.exits:
for exit in room.exits:
text += f"\n |y@e {exit.key}|n"
if exit.aliases.all():
text += " (|y{aliases}|n)".format(aliases="|n, |y".join(
alias for alias in exit.aliases.all()
))
if exit.destination:
text += f" 向 {exit.get_display_name(caller)}"
else:
text += "\n\n |g尚未定义任何出口.|n"
return text
特别注意第二个回调。它接受一个额外的参数,调用者(记住,参数名称很重要,参数顺序并不相关)。这在准确显示出口目标时非常有用。以下是这个菜单的演示:
> @edit here
构建菜单: A beautiful meadow
[T]itle: A beautiful meadow
[D]escription:
这是一个美丽的草地,但太美了。
[E]xits:
north
south
[Q]uit the menu
> e
-------------------------------------------------------------------------------
房间出口:
使用 |y@c|n 创建新出口。
现有出口:
@e north (n) 向 north(#4)
@e south (s) 向 south(#7)
> @
构建菜单: A beautiful meadow
[T]itle: A beautiful meadow
[D]escription:
这是一个美丽的草地,但太美了。
[E]xits:
north
south
[Q]uit the menu
> q
关闭构建菜单。
使用回调允许了巨大的灵活性。我们现在将看到如何处理子菜单。
复杂菜单的子菜单¶
一个菜单相对简单:它有一个根(你能够看到所有的菜单选择)和一个用户可以通过菜单选择键到达的单个选项。一旦在选项中,你可以输入一些内容或通过输入返回命令(通常是 @
)返回根菜单。
然而,个别出口为什么不可以拥有自己的菜单呢?假设你编辑一个出口,可以更改其键、描述或别名…也许甚至目标?为什么不呢?这将使建筑变得更简单!
构建菜单系统提供了两种方法来实现这一点。第一种是嵌套键:嵌套键允许你添加带有子菜单的选项。使用它们很快,但起初可能会感觉有点反直觉。另一种选择是创建一个不同的菜单类并从第一个重定向到第二个。这种方式可能需要更多行,但更加明确,并且可以重复使用以便于多个菜单。根据你的兴趣选择其中一种。
嵌套菜单键¶
到目前为止,我们只使用了长度为一个字母的菜单键。当然,我们可以添加更多,但菜单键在其简单形式下只是命令键。按“e”将转到“exits”选项。
但是菜单键可以嵌套。嵌套键允许添加具有子菜单的选项。例如,输入“e”转到“exits”选项,再输入“c”打开创建新出口的菜单,或输入“d”打开删除出口的菜单。第一个菜单将具有 “e.c” 键,第二个菜单具有 “e.d” 的键。
这需要一点代码和说明。以下是代码,接下来会进行解释:
# ... 从 commands/building.py
# 我们的构建菜单
class RoomBuildingMenu(BuildingMenu):
"""
用于编辑房间的构建菜单。
目前我们只有一个选项:键,用来编辑房间的键。
"""
def init(self, room):
self.add_choice("title", key="t", attr="key", glance="{obj.key}", text="""
-------------------------------------------------------------------------------
编辑 {{obj.key}}(#{{obj.id}}) 的标题
你可以简单通过输入来更改标题。
使用 |y{back}|n 返回主菜单。
当前标题: |c{{obj.key}}|n
""".format(back="|n 或 |y".join(self.keys_go_back)))
self.add_choice_edit("description", "d")
self.add_choice("exits", "e", glance=glance_exits, text=text_exits, on_nomatch=nomatch_exits)
# 出口子菜单
self.add_choice("exit", "e.*", text=text_single_exit, on_nomatch=nomatch_single_exit)
# 菜单函数
def glance_exits(room):
"""显示房间出口。"""
if room.exits:
glance = ""
for exit in room.exits:
glance += f"\n |y{exit.key}|n"
return glance
return "\n |g尚无出口|n"
def text_exits(caller, room):
"""在选择本身显示房间出口。"""
text = "-" * 79
text += "\n\n房间出口:"
text += "\n 使用 |y@c|n 创建新出口。"
text += "\n\n现有出口:"
if room.exits:
for exit in room.exits:
text += f"\n |y@e {exit.key}|n"
if exit.aliases.all():
text += " (|y{aliases}|n)".format(aliases="|n, |y".join(
alias for alias in exit.aliases.all()
))
if exit.destination:
text += f" 向 {exit.get_display_name(caller)}"
else:
text += "\n\n |g尚未定义任何出口.|n"
return text
def nomatch_exits(menu, caller, room, string):
"""
用户在出口列表中输入了某些内容。也许是出口名称?
"""
string = string[3:]
exit = caller.search(string, candidates=room.exits)
if exit is None:
return
# 打开子菜单,使用嵌套键
caller.msg(f"正在编辑: {exit.key}")
menu.move(exit)
return False
# 出口子菜单
def text_single_exit(menu, caller):
"""显示以编辑单个出口的文本。"""
exit = menu.keys[1]
if exit is None:
return ""
return f"""
出口 {exit.key}:
输入出口键以更改,或 |y@|n 返回。
新出口键:
"""
def nomatch_single_exit(menu, caller, room, string):
"""用户在出口子菜单中输入了某些内容。替换出口键。"""
# exit 是第二个键元素:键应该包含 ['e', <Exit object>]
exit = menu.keys[1]
if exit is None:
caller.msg("|r无法找到出口.|n")
menu.move(back=True)
return False
exit.key = string
return True
这很多代码!而且我们只处理出口键的编辑!
这就是你某个时候可能想要写一个真实子菜单的原因,而不是使用简单的嵌套键。但你可能同时需要两者,以构建漂亮的菜单!
新的内容在我们的菜单类中。在为出口菜单创建了
on_nomatch
回调后(这没什么惊讶的),我们需要添加一个嵌套键。我们给这个菜单一个e.*
键。这有点奇怪!“e” 是我们的出口选择的键,“.” 是分隔符,表示一个嵌套菜单,而 “*” 表示任何内容。所以基本上,我们创建一个嵌套菜单,包含在出口菜单内以及任何内容。我们将在实际操作中看到这个“任何内容”。glance_exits
和text_exits
基本上是相同的。nomatch_exits
很短但很有趣。当我们在“出口”菜单中输入一些文本时调用(即,在出口列表中)。我们说过,用户应该输入@e
后跟出口名称以进行编辑。所以在nomatch_exits
回调中,我们检查该输入。如果输入的文本以@e
开头,我们尝试在房间中找到出口。如果找到…我们调用
menu.move
方法。这是事情变得有点复杂的地方,使用嵌套菜单:我们需要使用menu.move
从一个层次切换到另一个层次。在这里,我们在出口的选择中(钥匙是“e”)。我们需要向下一个层次去编辑一个出口。因此,我们调用menu.move
,并给它一个出口对象。菜单系统记住用户基于她输入的键所处的位置:当用户打开菜单时,没有关键字。如果她选择出口选项,则当前菜单键为 ‘e’,(一个包含菜单键的列表)。如果我们调用menu.move
,我们给任何内容传递到这个方法,将附加到键的列表中,因此用户位置变为["e", <Exit object>]
。在菜单类中,我们定义了菜单
"e.*"
,表示“包含在出口选择中的菜单加任何内容”。“任何内容”在这里是一个出口:我们调用menu.move(exit)
,因此选择了e.*
菜单。在该菜单中,文本被设置为回调。当用户输入某些文本时,还定义了一个
on_nomatch
回调。
像这样使用 menu.move
一开始可能有点混乱。在某些情况下,它非常有用。在这种情况下,如果我们希望出口有更复杂的菜单,就更明智地使用真实的子菜单,而不是像这样嵌套键。但有时,你会发现自己处于一个不需要完整菜单来处理选择的情况。
完全子菜单作为单独类¶
处理单个出口的最佳方式是创建两个单独的类:
一个用于房间菜单。
一个用于单个出口菜单。
第一个将需要重定向到第二个。这可能更直观和灵活,具体取决于你想要实现的目标。因此,让我们构建两个菜单:
# 仍在 commands/building.py,替换菜单类和函数为...
# 我们的构建菜单
class RoomBuildingMenu(BuildingMenu):
"""
用于编辑房间的构建菜单。
"""
def init(self, room):
self.add_choice("title", key="t", attr="key", glance="{obj.key}", text="""
-------------------------------------------------------------------------------
编辑 {{obj.key}}(#{{obj.id}}) 的标题
你可以简单通过输入来更改标题。
使用 |y{back}|n 返回主菜单。
当前标题: |c{{obj.key}}|n
""".format(back="|n 或 |y".join(self.keys_go_back)))
self.add_choice_edit("description", "d")
self.add_choice("exits", "e", glance=glance_exits, text=text_exits,
on_nomatch=nomatch_exits)
# 菜单函数
def glance_exits(room):
"""显示房间出口。"""
if room.exits:
glance = ""
for exit in room.exits:
glance += f"\n |y{exit.key}|n"
return glance
return "\n |g尚无出口|n"
def text_exits(caller, room):
"""显示在选择本身的房间出口。"""
text = "-" * 79
text += "\n\n房间出口:"
text += "\n 使用 |y@c|n 创建新出口。"
text += "\n\n现有出口:"
if room.exits:
for exit in room.exits:
text += f"\n |y@e {exit.key}|n"
if exit.aliases.all():
text += " (|y{aliases}|n)".format(aliases="|n, |y".join(
alias for alias in exit.aliases.all()
))
if exit.destination:
text += f" 向 {exit.get_display_name(caller)}"
else:
text += "\n\n |g尚未定义任何出口.|n"
return text
def nomatch_exits(menu, caller, room, string):
"""
用户在出口列表中输入了某些内容。也许是出口名称?
"""
string = string[3:]
exit = caller.search(string, candidates=room.exits)
if exit is None:
return
# 打开子菜单,使用嵌套键
caller.msg(f"正在编辑: {exit.key}")
menu.open_submenu("commands.building.ExitBuildingMenu", exit, parent_keys=["e"])
return False
class ExitBuildingMenu(BuildingMenu):
"""
用于编辑出口的构建菜单。
"""
def init(self, exit):
self.add_choice("key", key="k", attr="key", glance="{obj.key}")
self.add_choice_edit("description", "d")
代码可能更容易阅读。但在详细说明之前,先看看它的行为:
> @edit here
构建菜单: A beautiful meadow
[T]itle: A beautiful meadow
[D]escription:
这是一个美丽的草地,但太美了。
[E]xits:
door
south
[Q]uit the menu
> e
-------------------------------------------------------------------------------
房间出口:
使用 |y@c|n 创建新出口。
现有出口:
@e door (n) 向 door(#4)
@e south (s) 向 south(#7)
正在编辑: door
> @e door
构建菜单: door
[K]ey: door
[D]escription:
没有
> k
-------------------------------------------------------------------------------
door(#4) 的键
你可以通过输入它来简单地更改这个值。
使用 @ 返回主菜单。
当前值: door
> north
-------------------------------------------------------------------------------
key for north(#4)
你可以简单通过输入来更改这个值。
使用 @ 返回主菜单。
当前值: north
> @
构建菜单: north
[K]ey: north
[D]escription:
没有
> d
----------行编辑器 [editor]----------------------------------------------------
01| 没有
----------[l:01 w:001 c:0004]------------(:h for help)----------------------------
> :DD
清除了缓冲区中的 1 行。
> 这是北边的出口。太酷了吧?
01| 这是北边的出口。太酷了吧?
> :wq
构建菜单: north
[K]ey: north
[D]escription:
这是北边的出口。太酷了吧?
> @
-------------------------------------------------------------------------------
房间出口:
使用 |y@c|n 创建新出口。
现有出口:
@e north (n) 向 north(#4)
@e south (s) 向 south(#7)
> @
构建菜单: A beautiful meadow
[T]itle: A beautiful meadow
[D]escription:
这是一个美丽的草地,但太美了。
[E]xits:
north
south
[Q]uit the menu
> q
关闭构建菜单。
> look
A beautiful meadow(#2)
这是一个美丽的草地,但太美了。
出口: north(#4) 和 south(#7)
> @py here.exits[0]
>>> here.exits[0]
north
> @py here.exits[0].db.desc
>>> here.exits[0].db.desc
这是北边的出口。太酷了吧?
我们非常简单地创建了两个菜单并将它们连接起来。这需要的回调更少。在 nomatch_exits
中我们只需要添加一行:
menu.open_submenu("commands.building.ExitBuildingMenu", exit, parent_keys=["e"])
我们必须在菜单对象上调用 open_submenu
(如其名,打开一个子菜单),并给出三个参数:
创建菜单类的路径。它是通向菜单的 Python 类(请注意其中的点)。
将由菜单编辑的对象。在这里它是我们的
exit
,因此我们将其传递给子菜单。子菜单关闭时要打开的父级的键。基本上,当我们在子菜单的根目录中并按
@
时,我们将打开父菜单,带上父键。因此,我们指定["e"]
,因为父菜单是“exits”选择。
就这样。新类将自动创建。正如你所看到的,我们还必须创建 on_nomatch
回调以打开子菜单,但一旦打开,它在需要时会自动关闭。
通用菜单选项¶
所有菜单类都有一些可以设置的选项。这些选项允许更大的自定义。它们是类属性(见下面的示例),只需在类体中设置即可:
keys_go_back
(默认为["@"]
):用于在菜单层次结构中向后返回的键,从选择到根菜单,从子菜单到父菜单。默认情况下,仅使用@
。你可以为一个菜单或所有菜单更改这个键。如果您希望有多个返回命令,也可以定义多个。sep_keys
(默认为"."
):这是嵌套键的分隔符。除非你真的需要将点用作键并且需要菜单中有嵌套键,否则无需重新定义它。joker_key
(默认为"*"
):用于嵌套键以指示“任何键”。同样,除非你想使用@*@
作为命令键,并且还需要菜单中有嵌套键,否则你不应该更改它。min_shortcut
(默认为1
):尽管我们在这里没有看到,但可以创建没有为其提供键的菜单选择。如果是这样,菜单系统将尝试“猜测”密钥。此选项允许更改安全原因下任何键的最小长度。
要设置其中一个,只需在你的菜单类中这样做:
class RoomBuildingMenu(BuildingMenu):
keys_go_back = ["/"]
min_shortcut = 2
结论¶
构建菜单意味着节省时间并创建丰富而又简单的界面。但是它们可能很复杂,学习需要时间,需要阅读源代码才能发现如何做某事。尽管如此,这篇文档(尽管冗长)试图描述这一系统,但在阅读后,你可能仍然会对它有疑问,特别是如果你尝试推动该系统到一个较大程度。请随时阅读此贡献的文档,它旨在详尽且用户友好。
此文档页面并非由 evennia/contrib/base_systems/building_menu/README.md
自动生成。如想阅读最新文档,请参阅原始README.md文件。