添加物体类型类教程


Evennia自带有一些非常基础的游戏实体的类:
对象
  |
  角色
  房间
  出口
更具体的对象类型都只是基本的物体类的子类(从技术上讲它们都是类型类实体,但在本教程中只是把它们当作普通的Python类)。

在你自己的游戏中,你很可能想扩展这些简单的基础类型。通常你会想给角色添加不同的属性,也许还想让房间带有额外的信息,甚至会想让游戏中的所有物体都带上Evennia没有默认具有的属性。

首先来概览一下Evennia是如何处理物体类的。默认的类定义在 src/objects/objects.py 中。你可以在那里查看它们,也可以打开 Python 提示符,以交互的方式查阅 ev.Object、ev.Room 等。

你可以在 game/gamesrc/objects 中创建自己的物体类。通常你应该从默认类继承(使用普通的Python类继承),并以此为基础进行扩充。一旦新类能正常工作,你就可以马上在游戏中创建继承自该类的物体。

如果你想更改默认的物体类,还要做一个步骤。 Evennia的默认命令及内部创建机制会在 settings.py 文件中查找一系列变量,以确定哪些是“默认”的类。如果你没有专门指定类型类,默认的创建命令就会用这些默认值来创建对象。如果Evennia在使用其它类型类创建对象时发生了错误,它们也会被用作备用类型类来创建对象,所以一定要确保你的新默认类是没有错误的。

下一节内容会对此做更详细的阐述。


创建新的类型类

这是最简单例子,假设你要创建一个角色无法拿起的“沉重”物体。
  1. 前往 game/gamesrc/objects/ ,它应该已经包含有目录 examples/ 。
  2. 在那里创建一个新模块,命名为 heavy.py。或者你可以将 examples/object.py 复制到上一层目录,并将文件改名为 heavy.py,这样你就有可以作为基础的模板了。
  3. 在 heavy.py 模块中编码,实现“重”的功能。详细信息可以参见物体文档以及以下的示例类。让我们把这个类型类简单命名为“Heavy”。
  4. 完成之后,用具有建造权限的账号登录进游戏,执行 @create/drop rock:heavy.Heavy 在你的当前位置放置一个新的沉重的“石头”对象。接着试着把它拿起来看看。请注意:超级用户(用户 #1)会忽略所有的锁,在测试此类功能时一定要用非超级用户角色。

这样就行了。以下是你可以用来试验的 Heavy 类型类。请注意,类型类中的锁和属性同样可以在游戏中通过命令设置,这只是一个非常简单的例子。我们还给它添加了自定义命令。
    # file game/gamesrc/objects/heavy.py
    from ev import Object
    # 假定之前我们已经定义过自己的类型类了
    from game.gamesrc.commands.mycmdsets import HeavySet 

    class Heavy(Object):
       "Heavy object"
       def at_object_creation(self):
           "Called whenever a new object is created"
           # 默认锁住物体
           self.locks.add("get:false()")
           # 默认的“get”命令会查找这个属性以返回用户自定义的错误信息
           # (我们只是刚好知道这些,你最好深入 get 命令代码把它找出来)
           self.db.get_err_msg = "This is too heavy to pick up."
           # 用你自定义的命令集(已在别处定义)扩展默认的命令集
           self.cmdset.add(HeavySet)

更改房间、出口、角色的默认类型类
这只比创建其他类型类稍微复杂一点。实际上它只多包含了一步:要告诉Evennia使用新的默认值。

比如我们想让房间改用新的类型类 MyRoom。
  1. 在 game/gamesrc/objects/myroom.py 中创建新的模块,并且按上一节所述的方法编写 MyRoom 类型类。通过新建几个房间来检验这个类(比如 @dig Hall:myroom.MyRoom)。
  2. 一旦你确定新的类可以正常工作,编辑 game/settings.py 并添加 BASE_ROOM_TYPECLASS="game.gamesrc.objects.myroom.MyRoom" 。
  3. 重启Evennia(没有人会因此断开连接)。

今后,只要你没有专门指定类型类,@dig、@tunnel 之类的命令就都会使用这个新的默认类型类了。对于其他的子类型,可以相应地修改 BASE_CHARACTER_TYPECLASS(用于创建角色的命令)和 BASE_EXIT_TYPECLASS(用于 @dig、@tunnel 等命令)。

更改默认的物体类型类
更改物体基类的工作和更改角色、房间、出口相同。在创建新的类型类之后,设置 settings.BASE_OBJECT_TYPECLASS 指向你的新类。让我们假设新的默认物体类名为 MyObject 。

还有很重要一点需要记住:角色、房间和出口仍然会从旧物体类继承(而不是 MyObject)。我们就是这样设计的:在你的游戏中,你可能并不想让部分或全部子类都继承 MyObject 里新加的东西。

如果你想要这么做,你就需要重载这些子类。对于你想要自定义的角色、房间或出口,请执行以下操作:
  1. 在 game/gamesrc/ 中创建一个新模块,如 mycharacter.py 等。如果你只想重载部分默认配置,一个比较灵活的方案是让子类多重继承(见下文)。为了占位子,你可以只创建空的类(里面只写 pass)。
  2. 在你的 settings.py 文件中添加并设定 BASE_CHARACTER_TYPECLASS、BASE_ROOM_TYPECLASS 和 BASE_EXIT_TYPECLASS 指向你的新类型类。
  3. 重启Evennia(没有人会因此断开连接)。

这样做会在你选择是否创建、扩展自己的房间、角色、出口类型时,给你最大的灵活性。下面是新 myroom.py 的例子:
    # file gamesrc/objects/myroom.py
    from ev import Object
    from gamesrc.objects.myobject import MyObject
    # 我们使用了多重继承,它会优先使用MyObject,如果所需要的东西
    # 在MyObject中没有重载,则会去使用默认的Object中的定义。
    class MyRoom(MyObject, Object):
        "My own expandable room class"
        pass


更新已有的物体

假如你已经创建了一些物体(不管是角色、房间,还是其它类型的),现在你又改变了这些物体类型的默认类型类(如上文所述)。不幸的是这些旧的物体并不知道这事,如果你想更新它们就必须手动操作了,幸好你只需这样做一次。所以说,最好在开始动手创建东西之前,先规划好你的游戏及基本的类型类。

如果你只有不多的几个对象需要更新(比如更改角色类型类时你是唯一的玩家),可以在游戏中执行命令 @typeclass 来强行改变对象的类型类:
 @typeclass/force/reset object:path.to.your.new.typeclass
/force/reset 参数会清除旧类型类中的所有东西,包括属性,所以一定要明白你在做什么。而且,在对自己执行这些改变之前,要先确保你的新类型类是可以正常工作的!可以先在其它对象上做测试。

对于更大范围的改动,你最好用代码来修改。类型类对象有一个很有用的方法 swap_typeclass。你所要做的就是遍历所有的现有对象并调用这个方法。下面的例子会教你如何利用Django的魔力来做到这些:
    from django.conf import settings
    import ev

    old_default = "src.objects.objects.Object"
    new_default = "game.gamesrc.objects.myobj.MyObject"

    # 利用Django查询数据库中所有具有旧类型类路径的物体(类型类的
    # 路径保存在数据库的“db_typeclass_path”字段中)

    for obj in ev.managers.objects.filter(db_typeclass_path=old_default):
        obj.swap_typeclass(new_default)

上面,我们使用了Django数据库管理器来查询数据库。搜索了保存在数据库中的主要类型类信息,即类型类的完整Python路径。我们找出所有仍在使用旧类型类的对象,并将它们切换到新的类型类。关于Django数据库访问的更多信息,可参考Django手册或仔细阅读ev.managers。


注意事项

在以上所有例子中,每个类都是存放在自己的模块中的。这样做可以让你很容易地找到它们,但也可以完全按你的方式来组织这些东西,比如你可以把所有基类都放在一个模块中。

同时要记住,Python可以在导入模块时动态重命名模块中的类。所以,如果你觉得不必在任何时候都将新的默认类型类称作MyObject,你也可以在导入它们时给它们起另外的名字,如下面这个例子:
    from ev import Object as BaseObject
    from gamesrc.objects.myobject import MyObject as Object
    class MyRoom(Object, BaseObject):
         [...]
这实际上并没有改变代码的内容,但可能会让模块之间的关系更加清晰。