3. Python与Evennia入门指南¶
是时候开始接触一些编码了!Evennia是使用Python编写和扩展的。Python是一种成熟且专业的编程语言,开发效率非常高。
虽然Python被广泛认为易于学习,但本教程只能涵盖基础知识。我们会尽量讲解最重要的部分,但您可能还需要自行补充学习。幸运的是,网上有大量免费的Python学习资源。可以参考我们的链接章节获取一些示例。
如果您是经验丰富的开发者,可能会觉得这些内容很基础,但建议至少阅读前几节,了解如何在Evennia中运行Python。
如果您正在体验教程世界,请确保恢复超级用户权限:
unquell
3.1. Evennia的Hello World¶
py
命令(或别名!
)允许超级用户在游戏中执行原始Python代码,非常适合快速测试。在游戏输入行中输入:
> py print("Hello World!")
您将看到:
> print("Hello world!")
Hello World!
print(...)
是Python中基本的文本输出函数。我们将”Hello World”作为单个参数传递给这个函数。如果传递多个参数,需要用逗号分隔。
引号"..."
表示您输入的是字符串(即文本)。也可以使用单引号'...'
- Python都接受。
第三种输入Python字符串的方式是使用三引号(
"""..."""
或'''...'''
),用于跨多行的长字符串。但在py
命令中直接插入代码时,我们只能使用单行。
3.2. 制作一些文本”图形”¶
在制作文本游戏时,您会大量处理文本。即使偶尔有按钮或图形元素,通常流程也是用户输入文本命令并获取文本反馈。如上所示,在Python中一段文本称为字符串,用单引号或双引号括起来。
字符串可以相加:
> py print("这是一个" + "重大改变。")
这是一个重大改变。
字符串乘以数字会重复该字符串:
> py print("|" + "-" * 40 + "|")
|----------------------------------------|
或
> py print("A" + "a" * 5 + "rgh!")
Aaaaaargh!
3.2.1. .format()¶
虽然组合字符串很有用,但更强大的是能够就地修改字符串内容。Python有几种方式可以实现,这里展示其中两种。第一种是使用字符串的.format
方法:
> py print("这是一个{}主意!".format("好"))
这是一个好主意!
方法可以看作对象上的资源。方法知道它所属的对象,因此可以以各种方式影响它。您通过句点.
访问它。这里,字符串有一个format(...)
资源来修改它。具体来说,它用传递给format的值替换字符串中的{}
标记。可以多次这样做:
> py print("这是{}和{} {}主意!".format("第一个", "第二个", "绝妙"))
这是第一个和第二个绝妙主意!
注意结尾的双括号 - 第一个关闭
format(...
方法,最外层的关闭print(...
。不关闭会导致可怕的SyntaxError
。我们将在下一节讨论错误。
这里我们传递了三个逗号分隔的字符串作为参数给字符串的format
方法。它们按给定顺序替换了{}
标记。
输入不一定非是字符串:
> py print("力量: {}, 敏捷: {}, 智力: {}".format(12, 14, 8))
力量: 12, 敏捷: 14, 智力: 8
要在同一行分隔两个Python指令,使用分号;
。试试:
> py a = "超赞酱料" ; print("这是{}!".format(a))
这是超赞酱料!
警告
MUD客户端与分号
有些MUD客户端使用分号;
将客户端输入分成多个发送。如果是这样,上面会出错。大多数客户端允许使用”逐字”模式或重新映射为其他分隔符。如果仍有问题,请使用Evennia网页客户端。
这里我们赋值字符串"超赞酱料"
给一个名为a
的变量。在下一个语句中,Python记住了a
是什么,我们将其传递给format()
获取输出。如果在中间更改a
的值,将打印新值。
再次展示属性示例,将属性移到变量中(这里只是设置,但在真实游戏中可能会随时间变化或受环境影响):
> py 力量, 敏捷, 智力 = 13, 14, 8 ; print("力量: {}, 敏捷: {}, 智力: {}".format(力量, 敏捷, 智力))
力量: 13, 敏捷: 14, 智力: 8
关键是即使属性值变化,print()语句也不会改变 - 它只是漂亮地打印给定的任何内容。
也可以使用命名标记:
> py print("力量: {stren}, 智力: {intel}, 力量再次: {stren}".format(dext=10, intel=18, stren=9))
力量: 9, 智力: 18, 力量再次: 9
添加的key=value
对称为format()
方法的关键字参数。每个命名参数将匹配字符串中的{key}
。使用关键字时,添加顺序无关紧要。字符串中没有{dext}
但有两次{stren}
,这完全没问题。
3.2.2. f-字符串¶
使用.format()
很强大(还有更多功能)。但f-字符串更方便。f-字符串看起来像普通字符串…只是前面有个f
:
f"这是一个f-字符串。"
单独的f-字符串就像任何其他字符串。但让我们用f-字符串重做之前的例子:
> py a = "超赞酱料" ; print(f"这是{a}!")
这是超赞酱料!
我们使用{a}
直接将变量a
插入f-字符串。需要记住的括号更少,可以说也更易读!
> py 力量, 敏捷, 智力 = 13, 14, 8 ; print(f"力量: {力量}, 敏捷: {敏捷}, 智力: {智力}")
力量: 13, 敏捷: 14, 智力: 8
在现代Python代码中,f-字符串比.format()
更常用,但阅读代码时需要了解两者。
当我们创建命令并需要解析和理解玩家输入时,将探索更复杂的字符串概念。
3.2.3. 彩色文本¶
Python本身不支持彩色文本,这是Evennia的功能。Evennia支持传统MUD的标准配色方案。
> py print("|r这是红色文本!|n 这是正常颜色。")
开头的|r
将使输出变为亮红色。|R
是深红色。|n
恢复正常文本颜色。也可以使用0-5的RGB值(Xterm256颜色):
> py print("|043这是蓝绿色。|[530|003 现在是橙色背景上的深蓝色文本。")
如果看不到预期颜色,您的客户端或终端可能不支持Xterm256(或根本不支持颜色)。请使用Evennia网页客户端。
使用命令color ansi
或color xterm
查看可用颜色。尽情尝试!更多信息请参阅颜色文档。
3.3. 从其他模块导入代码¶
如前所述,我们使用.format
格式化字符串,使用me.msg
访问me
上的msg
方法。这种使用句点字符的方式可用于访问各种资源,包括其他Python模块中的资源。
保持游戏运行,然后打开您选择的文本编辑器。如果游戏文件夹名为mygame
,在子文件夹mygame/world
中创建新文本文件test.py
。文件结构应如下:
mygame/
world/
test.py
暂时只在test.py
中添加一行:
print("Hello World!")
别忘了保存文件。我们刚创建了第一个Python模块! 要在游戏中使用,必须导入它。试试:
> py import world.test
Hello World
如果出错(下面将介绍如何处理错误),确保文本与上面完全一致,然后在游戏中运行reload
命令使更改生效。
…如您所见,导入world.test
实际上意味着导入world/test.py
。将句点.
视为替换路径中的/
(Windows中是\
)。
test.py
的.py
扩展名不包含在这个”Python路径”中,但只有带有该扩展名的文件才能这样导入。mygame
在哪里?答案是Evennia已经告诉Python您的mygame
文件夹是一个很好的导入查找位置。所以我们不应在路径中包含mygame
- Evennia已为我们处理。
导入模块时,其顶层代码会立即执行。这里会立即打印”Hello World”。
现在尝试再次运行:
> py import world.test
这次或以后都不会看到输出!这不是bug。而是因为Python导入的工作方式 - 它会存储所有导入的模块并避免重复导入。所以print
只会在模块首次导入时运行一次。
试试:
> reload
然后
> py import world.test
Hello World!
现在又看到了。reload
清除了服务器内存中的导入内容,所以必须重新导入。每次想要显示hello-world时都必须这样做,这不太实用。
我们将在后续课程中回到更高级的导入方式 - 这是一个重要主题。但现在,让我们继续解决这个特定问题。
3.3.1. 第一个自定义函数¶
我们希望随时打印hello-world消息,而不仅是在服务器重载后一次。将mygame/world/test.py
改为:
def hello_world():
print("Hello World!")
随着转向多行Python代码,有一些重要事项需记住:
Python中大小写敏感。必须是
def
而非DEF
,hello_world()
与Hello_World()
不同。Python中缩进很重要。第二行必须缩进,否则不是有效代码。还应使用一致的缩进长度。为了您的理智,强烈建议设置编辑器在按TAB键时总是缩进4个空格(不是单个制表符)。
关于这个函数。第1行:
def
是”define”的缩写,定义函数(或对象上的方法)。这是Python保留关键字;尽量不要在其他地方使用这些词。函数名不能有空格,但其他方面几乎可以任意命名。我们称它为
hello_world
。Evennia遵循Python标准命名风格,使用小写字母和下划线。建议您也这样做。行尾的冒号(
:
)表示函数头已完成。
第2行:
缩进标记函数实际操作代码的开始(函数体)。如果希望更多行属于此函数,这些行都必须至少以此缩进级别开始。
现在试试。首先reload
游戏以获取更新的Python模块,然后导入它。
> reload
> py import world.test
没发生任何事!这是因为模块中的函数仅通过导入不会执行任何操作(这正是我们想要的)。只有在调用时才会执行。所以需要先导入模块,然后访问其中的函数:
> py import world.test ; world.test.hello_world()
Hello world!
出现了”Hello World”!如前所述,使用分号将多个Python语句放在一行。也请注意之前关于MUD客户端使用;
的警告。
发生了什么?首先照常导入world.test
。但这次模块的”顶层”只定义了函数,并未实际执行函数体。
通过在hello_world
函数后添加()
,我们调用了它。即执行函数体并打印文本。现在可以多次重复,无需在中间reload
:
> py import world.test ; world.test.hello_world()
Hello world!
> py import world.test ; world.test.hello_world()
Hello world!
3.4. 向他人发送文本¶
print
是标准Python结构。我们可以在py
命令中使用它,因为可以看到输出。非常适合调试和快速测试。但如果需要向实际玩家发送文本,print
不行,因为它不知道发送给谁。试试:
> py me.msg("Hello world!")
Hello world!
看起来与print
结果相同,但现在实际上是向特定对象me
发送消息。me
是’我们’的快捷方式,即运行py
命令的人。它不是特殊的Python东西,而是Evennia为了方便在py
命令中提供的(self
是其别名)。
me
是对象实例的例子。对象在Python和Evennia中是基础。me
对象还包含许多有用的资源来操作该对象。我们通过’.
’访问这些资源。
其中一个资源是msg
,类似于print
,但将文本发送给它所属的对象。例如,如果有对象you
,执行you.msg(...)
会向对象you
发送消息。
目前,print
和me.msg
行为相同,但请记住print
主要用于调试,而.msg()
将来对您更有用。
3.5. 解析Python错误¶
让我们在刚创建的函数中尝试这个新文本发送功能。回到test.py
文件,将函数替换为:
def hello_world():
me.msg("Hello World!")
保存文件并reload
服务器告诉Evennia重新导入新代码,然后像之前一样运行:
> py import world.test ; world.test.hello_world()
不行 - 这次出现错误!
File "./world/test.py", line 2, in hello_world
me.msg("Hello World!")
NameError: name 'me' is not defined
这称为回溯。Python的错误非常友好,大多数时候会准确告诉您问题及位置。学会解析回溯很重要,这样才能修复代码。
回溯应从下往上阅读:
(第3行)
NameError
类型错误是问题…(第3行) …更具体地说是因为变量
me
未定义。(第2行) 这发生在
me.msg("Hello world!")
行…(第1行) …即文件
./world/test.py
的第2
行。
本例中回溯很短。上面可能有更多行,追踪不同模块如何互相调用直到问题行。有时这些信息很有用,但从底部开始总是好的起点。
这里的NameError
是因为模块是独立的。它对导入环境一无所知。它知道print
是什么,因为那是特殊的Python保留关键字。但me
不是这样的保留字(如前所述,只是Evennia为了方便在py
命令中添加的)。对模块来说,me
是一个陌生的名字,不知从哪冒出来的。因此出现NameError
。
3.6. 向函数传递参数¶
我们知道在运行py
命令时me
存在,因为可以无问题地执行py me.msg("Hello World!")
。所以让我们将me
传递给函数,让它知道应该是什么。回到test.py
,改为:
def hello_world(who):
who.msg("Hello World!")
我们为函数添加了一个参数。可以任意命名。无论who
是什么,我们都将调用其.msg()
方法。
照常reload
服务器确保新代码可用。
> py import world.test ; world.test.hello_world(me)
Hello World!
现在工作了。我们将me
传递给函数。它在函数内重命名为who
,现在函数正常工作并按预期打印。注意hello_world
函数不关心您传递什么,只要它有.msg()
方法。因此可以重复使用此函数处理其他合适目标。
3.7. 寻找其他发送对象¶
让我们通过寻找其他发送对象来结束第一个Pythonpy
速成课程。
在Evennia的contrib/
文件夹(evennia/contrib/tutorial_examples/mirror.py
)中有一个方便的小对象叫TutorialMirror
。镜子会将它收到的任何内容回显到所在房间。
在游戏命令行中,创建一个镜子:
> create/drop mirror:contrib.tutorials.mirror.TutorialMirror
镜子应出现在您的位置。
> look mirror
mirror shows your reflection:
This is User #1
您看到的实际上是游戏中的自己的化身,与py
命令中的me
相同。
现在目标是实现mirror.msg("Mirror Mirror on the wall")
的等效操作。但首先想到的不会工作:
> py mirror.msg("Mirror, Mirror on the wall ...")
NameError: name 'mirror' is not defined.
这不奇怪:Python对”mirrors”或位置等一无所知。我们使用的me
如前所述,只是Evennia开发者为方便py
命令提供的。他们不可能预测您想与镜子对话。
相反,我们需要先搜索mirror
对象才能发送。确保与镜子在同一位置并尝试:
> py me.search("mirror")
mirror
me.search("name")
默认会搜索并返回与me
对象在同一位置中给定名称的对象。如果找不到,会看到错误。
> py me.search("dummy")
Could not find 'dummy'.
通常希望在同一位置找到东西,但随着继续,我们会发现Evennia提供了丰富的工具来标记、搜索和查找游戏中的所有内容。
现在知道如何找到’mirror’对象,只需用它替代me
!
> py mirror = self.search("mirror") ; mirror.msg("Mirror, Mirror on the wall ...")
mirror echoes back to you:
"Mirror, Mirror on the wall ..."
镜子对测试很有用,因为它的.msg
方法只是将发送给它的任何内容回显到房间。更常见的是与玩家角色对话,在这种情况下,您发送的文本会出现在他们的游戏客户端中。
3.8. 多行py¶
到目前为止,我们以单行模式使用py
,用;
分隔多个输入。这在快速测试时非常方便。但也可以在Evennia中启动完整的多行Python交互解释器。
> py
Evennia Interactive Python mode
Python 3.11.0 (default, Nov 22 2022, 11:21:55)
[GCC 8.2.0] on Linux
[py mode - quit() to exit]
(输出的详细信息因Python版本和操作系统而异)。您现在处于python解释器模式。意味着从现在插入的所有内容都将成为Python代码行(您不能再查看或执行其他命令)。
> print("Hello World")
>>> print("Hello World")
Hello World
[py mode - quit() to exit]
注意现在不需要在前面加py
。系统还会回显您的输入(这是>>>
之后的部分)。为简洁起见,本教程将关闭回显。先退出py
,然后使用/noecho
标志重新启动。
> quit()
Closing the Python console.
> py/noecho
Evennia Interactive Python mode (no echoing of prompts)
Python 3.11.0 (default, Nov 22 2022, 11:21:56)
[GCC 8.2.0] on Linux
[py mode - quit() to exit]
现在可以输入多行Python代码:
> a = "Test"
> print(f"This is a {a}.")
This is a Test.
让我们尝试定义一个函数:
> def hello_world(who, txt):
...
> who.msg(txt)
...
>
[py mode - quit() to exit]
上面一些重要事项:
用
def
定义函数意味着我们开始一个新的代码块。Python通过缩进标记块内容。所以下一行必须手动缩进(4个空格是个好标准),以便Python知道它是函数体的一部分。我们扩展
hello_world
函数,添加另一个参数txt
。这允许我们发送任何文本,而不只是重复”Hello World”。告诉
py
不再向函数体添加行,我们以空输入结束。当正常提示返回时,我们知道已完成。
现在定义了一个新函数。试试:
> hello_world(me, "Hello world to me!")
Hello world to me!
me
仍然可用,所以我们将其作为who
参数传递,以及稍长的字符串。让我们结合搜索镜子。
> mirror = me.search("mirror")
> hello_world(mirror, "Mirror, Mirror on the wall ...")
mirror echoes back to you:
"Mirror, Mirror on the wall ..."
用以下方式退出py
模式:
> quit()
Closing the Python console.
3.9. 其他测试Python代码的方式¶
py
命令在游戏中实验Python非常强大。非常适合快速测试。但仍受限于通过telnet或webclient工作,这些接口本身不了解Python。
在游戏外,转到运行Evennia的终端(或任何evennia
命令可用的终端)。
cd
到游戏目录。evennia shell
打开Python shell。这与游戏中的py
类似,只是默认没有me
可用。如果需要me
,必须先找到自己:
> import evennia
> me = evennia.search_object("YourChar")[0]
这里我们直接导入evennia
,使用其搜索功能之一。后面将介绍更高级的搜索,现在只需将”YourChar”替换为您自己的角色名。
结尾的
[0]
是因为.search_object
返回对象列表,我们想要第一个(计数从0开始)。
使用Ctrl-D
(Mac上是Cmd-D
)或quit()
退出Python控制台。
3.10. ipython¶
默认Python shell相当有限且丑陋。强烈建议安装ipython
。这是一个更美观的第三方Python解释器,具有颜色和许多可用性改进。
pip install ipython
如果安装了ipython
,evennia shell
会自动使用它。
evennia shell
...
IPython 7.4.0 -- An enhanced Interactive Python. Type '?' for help
In [1]: 现在有Tab补全:
> import evennia
> evennia.<TAB>
即输入evennia.
然后按TAB键 - 将获得evennia
对象上所有可用资源的列表。这对探索Evennia提供的功能非常有用。例如,用箭头键滚动到search_object()
填充它。
> evennia.search_object?
添加?
并按回车将获得.search_object
的完整文档。使用??
如果想查看整个源代码。
与普通python解释器一样,使用Ctrl-D
/Cmd-D
或quit()
退出ipython。
重要
持久代码
py
和python
/ipython
的共同点是编写的代码不持久 - 关闭解释器后会消失(但ipython会记住输入历史)。要创建持久的Python代码,需要将其保存在Python模块中,就像我们对world/test.py
所做的那样。
3.11. 结论¶
这涵盖了相当多的基本Python用法。我们打印和格式化字符串,定义了第一个函数,修复了错误,甚至搜索并与镜子对话!能够在游戏内外访问python是测试和调试的重要技能,但在实践中,您将在Python模块中编写大部分代码。
为此,我们还在mygame/
游戏目录中创建了第一个新的Python模块,然后导入并使用它。现在让我们看看mygame/
文件夹中的其他内容…