# 怪物生成

温馨提示:开始阅读这篇指南之前,我们希望你对《我的世界》基岩版附加包有一定了解,有能力撰写 JSON 数据格式,对 Python 进行模组开发有了解,并能够独立阅读《我的世界》开发者官网-开发指南或其他技术引用文档。

本文将带你了解生物生成的规则。

在本教程中,您将学习以下内容。

  • ✅实体生成规则;
  • ✅如何使用代码生成自定义实体;

# 实体生成规则

在原版行为包下的 spawn_rules 目录下,定义了许多实体的生成规则,也就是如何生成实体在世界中。当你希望自定义实体自然生成时,你就需要使用生成规则。不同的组件允许可以定义实体不同的生成时间、位置和方式。

一般来说,我们都会选择与原版实体非常相似的方式来生成。比如,像牛一样生成在牛群中、像僵尸一样出生在晚上,或者像鱼一样只出生在水里。

# 基础结构

当你要使用原版的生成规则时,首先,需要在行为包的 spawn_rules 目录下新建一个 identifier.json (这里的 identifier 代指生物标识符)的新文件。该文件的内容应该如下所示:

{
    "format_version": "1.8.0",
    "minecraft:spawn_rules": {
        "description": {
            "identifier": "tutiroal:identifier",
            "population_control": "animal"
        },
        "conditions": []
    }
}

minecraft:spawn_rules 内部,我们需要考虑两件事:人口控制条件

description description 定义文件的基本属性。 identifier identifier 应该匹配我们实体的标识符。 population_control population_control 定义游戏如何知道要生成多少生物,并且稍微复杂一些。

# 人口控制

我的世界有不同的实体池。当此处定义的池被认为已满时,游戏将不再生成该池中的生物。 共有以下几种不同的选择(一般我们使用前面三种即可):

  • "animal":被动生物,如牛和猪
  • "water_animal":热带鱼、海豚等水生生物
  • "monster":敌对生物,如骷髅和僵尸
  • "villager":村民专属。
  • "ambient":蝙蝠专属。
  • "cat":猫专属。
  • "pillager":掠夺者专属。

# 条件

conditions 是一系列允许生物在世界中生成的可能条件。 每个条件分别尝试在世界中生成生物。 每个条件都由一组组件组成,这些组件定义何时生产或不生成生物。

比如,我们可以查看原版僵尸的生成条件:

{
    "format_version": "1.8.0",
    "minecraft:spawn_rules": {
        "description": {
            "identifier": "minecraft:zombie",
            "population_control": "monster"
        },
        "conditions": [
            {
                "minecraft:spawns_on_surface": {},
                "minecraft:spawns_underground": {},
                "minecraft:brightness_filter": {
                    "min": 0,
                    "max": 7,
                    "adjust_for_weather": true
                },
                "minecraft:difficulty_filter": {
                    "min": "easy",
                    "max": "hard"
                },
                "minecraft:weight": {
                    "default": 100
                },
                "minecraft:herd": {
                    "min_size": 2,
                    "max_size": 4
                },
                "minecraft:permute_type": [
                    {
                        "weight": 95
                    },
                    {
                        "weight": 5,
                        "entity_type": "minecraft:zombie_villager"
                    }
                ],
                "minecraft:biome_filter": {
                    "test": "has_biome_tag", "operator": "==", "value": "monster"
                }
            }
        ]
    }
}
组件名称 描述
minecraft:spawns_on_surface 生物在地表生成
minecraft:spawns_on_underground 生物在地底生成
minecraft:brightness_filter 仅以特定亮度生成实体。 接受三个选项,minmaxadjust_for_weather。 亮度级别范围从 0 到 15。 如果 adjust_for_weather 设置为 true,则将考虑由于下雨和雷暴导致的亮度降低。
minecraft:difficulty_filter 难度选择器,minmax 定义了最低和最高难度。实体仅会在定义的难度内生成。
minecraft:weight 实体生成时的权重。 数字越大,生物生成的频率越高。
minecraft:herd 设置在同一生成规则上一起生成的实体数。也可以理解为群体生成。
minecraft:permute_type 给生成的实体一定概率变异为其他实体。
minecraft:biome_filter 群系选择器。仅会在通过条件的群系生成。

# 所有已知组件

下列是所有已知的用于控制实体生成的条件组件:

minecraft:weight
minecraft:density_limit
minecraft:spawns_on_block_filter
minecraft:spawns_on_block_prevented_filter
minecraft:spawns_above_block_filter
minecraft:herd
minecraft:permute_type
minecraft:brightness_filter
minecraft:height_filter
minecraft:spawns_on_surface
minecraft:spawns_underground
minecraft:spawns_underwater
minecraft:disallow_spawns_in_bubble
minecraft:spawns_lava
minecraft:biome_filter
minecraft:difficulty_filter
minecraft:distance_filter
minecraft:is_experimental
minecraft:world_age_filter
minecraft:delay_filter
minecraft:mob_event_filter
minecraft:is_persistent
minecraft:player_in_village_filter

我们仅需要对上述组件有一个大概印象就可以,除了一些比较特殊的 minecraft:is_persistent 之类的,我们在原版中看不到例子之外,其他的都可以在原版行为包中搜索到相关的应用。我们只对下面两个比较特殊常用的来做一些说明。

# spawns_above_block_filter

"minecraft:spawns_above_block_filter": {
    "blocks": "minecraft:stone",
    "distance": 10
}

这个组件会垂直检测设定距离内的方块,如果条件满足,则实体生成。生成在指定的方块上。

# spawns_on_block_prevented_filter

"minecraft:spawns_on_block_prevented_filter": [
    "minecraft:nether_wart_block",
    "minecraft:shroomlight"
]

正好与上面一个组件相反,该组件的作用是让实体永远不会在列表内的方块上生成。

# 代码生成

虽然原版的生成规则已经能够满足大部分的需求,但有时我们也会想要自己生成一些怪物以应对特殊情况。比如,在惊变中,血月会导致怪物的生成速度和数量大幅增加:

这可能就需要我们代码来执行额外的生成逻辑了。

# 一切的基础

生成怪物,目前官方只有一个 API (opens new window)CreateEngineEntityByTypeStr,用法也很简单:

import mod.server.extraServerApi as serverApi
ServerSystem = serverApi.GetServerSystemCls()
class MyServerSystem(ServerSystem):
    def createMob(self):
        # 在主世界(0,5,0)的位置创建一个朝向为(0, 0)的尸壳
        entityId = self.CreateEngineEntityByTypeStr('minecraft:husk', (0, 5, 0), (0, 0), 0)

# 示例:在玩家周围生成指定实体

API 很简单,最重要的就是对于坐标的选择了。我们想要实体生成在目标周围,但也要考虑一些特殊情况,比如当玩家在水中、空中、或者洞穴中能否支持生成。如果考虑得更加复杂一点,当前的控件是否支持实体的碰撞箱、光照强度等.....

这里提供一个简化之后的示例代码:

def SpawnEntityAround(self, targetId, spawnEntityIdentifier, num=1, minRadius=6, maxRadius=12):
    """
    生成实体在目标周围
    :param targetId:
    :param spawnEntityIdentifier:
    :param num:
    :param minRadius:
    :param maxRadius:
    :return:
    """
    entityList = []

    targetX, targetY, targetZ = CompFactory.CreatePos(targetId).GetFootPos()
    dimensionId = CompFactory.CreateDimension(targetId).GetEntityDimensionId()

    for _num in xrange(num):
        _randomPos = (
            targetX + random.randint(minRadius, maxRadius) * math.sin(random.uniform(0, 2 * math.pi)),
            targetY,
            targetZ + random.randint(minRadius, maxRadius) * math.cos(random.uniform(0, 2 * math.pi))
        )
        # 上面的 _randomPos 只是平面上的一个坐标,我们为了合理生成在地面上,需要 y 在 [-20, 20] 之间有一个合适的坐标
        _firstAirPos = self._RetFirstAirBlockPos(_randomPos, dimensionId, -20, 20)
        if _firstAirPos:
            entityId = self.CreateEngineEntityByTypeStr(spawnEntityIdentifier, _firstAirPos, (0, random.randint(-180, 180)),
                                                        dimensionId)
            entityList.append(entityId)
    return entityList

# 范围内的第一个空气方块
def _RetFirstAirBlockPos(self, basePos, dimensionId, step, maxStep):
    blockInfoComp = CompFactory.CreateBlockInfo(serverApi.GetLevelId())
    while step < maxStep:
        finalPos = (basePos[0], basePos[1] + step, basePos[2])
        blockDict = blockInfoComp.GetBlockNew(finalPos, dimensionId)
        step += 1
        if blockDict and blockDict['name'] == 'minecraft:air':
            return finalPos
    return None

整体逻辑也很简单,画个图来解释一下:

先在平面上选择一个随机坐标,然后再从 y 方向上下手,寻找第一个空气方块,这个就是适合实体(简化)的坐标。

# 课后作业

本次课后作业,内容如下:

  • 使用自定义的生成规则生成僵尸;
  • 使用自己的代码来自定义生成指定的生物;

实体生成规则

基础结构

人口控制

条件

所有已知组件

代码生成

一切的基础

示例:在玩家周围生成指定实体

课后作业