486 lines
18 KiB
Markdown
486 lines
18 KiB
Markdown
---
|
||
front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg
|
||
hard: 进阶
|
||
time: 30分钟
|
||
---
|
||
|
||
# 探索实体的行为文件
|
||
|
||
在本节中,我们一起探索实体的行为文件。对于一个实体来说,在行为包中往往只有一个服务端定义文件起到定义它的作用。因此,我们着重考察这个**服务端定义文件**。
|
||
|
||
我们以我们自定义的水鸭实体的完整的服务端定义文件为例:
|
||
|
||
```json
|
||
{
|
||
"format_version": "1.16.0",
|
||
"minecraft:entity": {
|
||
"description": {
|
||
"identifier": "tutorial_demo:teal",
|
||
"runtime_identifier": "minecraft:chicken",
|
||
"is_spawnable": true,
|
||
"is_summonable": true,
|
||
"is_experimental": false
|
||
},
|
||
"components": {
|
||
"minecraft:type_family": {
|
||
"family": ["chicken", "mob"]
|
||
},
|
||
"minecraft:breathable": {
|
||
"total_supply": 15,
|
||
"suffocate_time": 0
|
||
},
|
||
"minecraft:collision_box": {
|
||
"width": 0.6,
|
||
"height": 0.8
|
||
},
|
||
"minecraft:nameable": {},
|
||
"minecraft:health": {
|
||
"value": 4,
|
||
"max": 4
|
||
},
|
||
"minecraft:hurt_on_condition": {
|
||
"damage_conditions": [
|
||
{
|
||
"filters": {
|
||
"test": "in_lava",
|
||
"subject": "self",
|
||
"operator": "==",
|
||
"value": true
|
||
},
|
||
"cause": "lava",
|
||
"damage_per_tick": 4
|
||
}
|
||
]
|
||
},
|
||
"minecraft:movement": {
|
||
"value": 0.25
|
||
},
|
||
"minecraft:damage_sensor": {
|
||
"triggers": {
|
||
"cause": "fall",
|
||
"deals_damage": false
|
||
}
|
||
},
|
||
"minecraft:leashable": {
|
||
"soft_distance": 4,
|
||
"hard_distance": 6,
|
||
"max_distance": 10
|
||
},
|
||
"minecraft:balloonable": {
|
||
"mass": 0.5
|
||
},
|
||
"minecraft:navigation.walk": {
|
||
"can_path_over_water": true,
|
||
"avoid_damage_blocks": true
|
||
},
|
||
"minecraft:movement.basic": {},
|
||
"minecraft:jump.static": {},
|
||
"minecraft:can_climb": {},
|
||
"minecraft:despawn": {
|
||
"despawn_from_distance": {}
|
||
},
|
||
"minecraft:behavior.float": {
|
||
"priority": 0
|
||
},
|
||
"minecraft:behavior.panic": {
|
||
"priority": 1,
|
||
"speed_multiplier": 1.5
|
||
},
|
||
"minecraft:behavior.mount_pathing": {
|
||
"priority": 2,
|
||
"speed_multiplier": 1.5,
|
||
"target_dist": 0,
|
||
"track_target": true
|
||
},
|
||
"minecraft:behavior.tempt": {
|
||
"priority": 4,
|
||
"speed_multiplier": 1,
|
||
"items": ["wheat_seeds", "beetroot_seeds", "melon_seeds", "pumpkin_seeds"]
|
||
},
|
||
"minecraft:behavior.random_stroll": {
|
||
"priority": 6,
|
||
"speed_multiplier": 1
|
||
},
|
||
"minecraft:behavior.look_at_player": {
|
||
"priority": 7,
|
||
"look_distance": 6,
|
||
"probability": 0.02
|
||
},
|
||
"minecraft:behavior.random_look_around": {
|
||
"priority": 8
|
||
},
|
||
"minecraft:physics": {},
|
||
"minecraft:pushable": {
|
||
"is_pushable": true,
|
||
"is_pushable_by_piston": true
|
||
},
|
||
"minecraft:conditional_bandwidth_optimization": {}
|
||
},
|
||
"component_groups": {
|
||
"minecraft:teal_baby": {
|
||
"minecraft:is_baby": {},
|
||
"minecraft:scale": {
|
||
"value": 0.5
|
||
},
|
||
"minecraft:ageable": {
|
||
"duration": 1200,
|
||
"feed_items": ["wheat_seeds", "beetroot_seeds", "melon_seeds", "pumpkin_seeds"],
|
||
"grow_up": {
|
||
"event": "minecraft:ageable_grow_up",
|
||
"target": "self"
|
||
}
|
||
},
|
||
"minecraft:behavior.follow_parent": {
|
||
"priority": 5,
|
||
"speed_multiplier": 1.1
|
||
}
|
||
},
|
||
"minecraft:teal_adult": {
|
||
"minecraft:experience_reward": {
|
||
"on_bred": "Math.Random(1,7)",
|
||
"on_death": "query.last_hit_by_player?Math.Random(1,3):0"
|
||
},
|
||
"minecraft:loot": {
|
||
"table": "loot_tables/entities/chicken.json"
|
||
},
|
||
"minecraft:breedable": {
|
||
"require_tame": false,
|
||
"breeds_with": {
|
||
"mate_type": "tutorial_demo:teal",
|
||
"baby_type": "tutorial_demo:teal",
|
||
"breed_event": {
|
||
"event": "minecraft:entity_born",
|
||
"target": "baby"
|
||
}
|
||
},
|
||
"breed_items": ["wheat_seeds", "beetroot_seeds", "melon_seeds", "pumpkin_seeds"]
|
||
},
|
||
"minecraft:behavior.breed": {
|
||
"priority": 3,
|
||
"speed_multiplier": 1
|
||
},
|
||
"minecraft:rideable": {
|
||
"seat_count": 1,
|
||
"family_types": ["zombie"],
|
||
"seats": {
|
||
"position": [0, 0.4, 0]
|
||
}
|
||
},
|
||
"minecraft:spawn_entity": {
|
||
"entities": {
|
||
"min_wait_time": 300,
|
||
"max_wait_time": 600,
|
||
"spawn_sound": "plop",
|
||
"spawn_item": "egg",
|
||
"filters": {
|
||
"test": "rider_count",
|
||
"subject": "self",
|
||
"operator": "==",
|
||
"value": 0
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"events": {
|
||
"from_egg": {
|
||
"add": {
|
||
"component_groups": ["minecraft:teal_baby"]
|
||
}
|
||
},
|
||
"minecraft:entity_spawned": {
|
||
"randomize": [
|
||
{
|
||
"weight": 95,
|
||
"trigger": "minecraft:spawn_adult"
|
||
},
|
||
{
|
||
"weight": 5,
|
||
"add": {
|
||
"component_groups": ["minecraft:teal_baby"]
|
||
}
|
||
}
|
||
]
|
||
},
|
||
"minecraft:entity_born": {
|
||
"remove": {},
|
||
"add": {
|
||
"component_groups": ["minecraft:teal_baby"]
|
||
}
|
||
},
|
||
"minecraft:ageable_grow_up": {
|
||
"remove": {
|
||
"component_groups": ["minecraft:teal_baby"]
|
||
},
|
||
"add": {
|
||
"component_groups": ["minecraft:teal_adult"]
|
||
}
|
||
},
|
||
"minecraft:spawn_adult": {
|
||
"add": {
|
||
"component_groups": ["minecraft:teal_adult"]
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
- `format_version`:实体的行为定义文件经过多次迭代,目前拥有多种格式版本,分别为`1.8.0`、`1.10.0`、`1.11.0`、`1.12.0`、`1.13.0`、`1.14.0`、`1.16.0`和`1.16.100`,我们依旧推荐使用最新的`1.16.0`或`1.16.100`。
|
||
- `minecraft:entity`:服务端实体的模式标识符,服务端实体定义文件中必须使用该标识符作为键名。其下通常由有`description`、`components`、`component_groups`和`events`对象。
|
||
|
||
## 理解实体行为的描述信息
|
||
|
||
实体的**描述**(**Description**)即实体的`description`对象。我们来依次看一下实体服务端定义文件的描述中都有哪些属性。下面是上述文件中的一个节选。
|
||
|
||
```json
|
||
"description": {
|
||
"identifier": "tutorial_demo:teal",
|
||
"runtime_identifier": "minecraft:chicken",
|
||
"is_spawnable": true,
|
||
"is_summonable": true,
|
||
"is_experimental": false
|
||
}
|
||
```
|
||
|
||
- `identifier`:字符串,实体的赋命名空间标识符,需要和客户端定义文件中的标识符保持一致。
|
||
- `runtime_identifier`:可选,字符串,实体的**运行时标识符**(**Runtime Identifier**,简称**运行时ID**)。运行时标识符只能填写一个原版的实体。当存在该标识符时,你的自定义实体便会以原版的实体为模板来建立。换句话说,你的实体将继承自原版的这个实体。所有的实体成员、旗标以及硬编码的行为都会在你的自定义实体上实现。比如,亡灵生物有着使用药水治愈会掉血,伤害会加血的行为,而且有着会因为亡灵杀手魔咒而受到更多伤害的行为,如果你也想让你的实体具备此类行为,可以将运行时标识符设置为`minecraft:zombie`或`minecraft:skeleton`来继承此类行为。
|
||
- `is_spawnable`:可选,布尔值,控制实体是否可以通过刷怪蛋生成或者自然生成。
|
||
- `is_summonable`:可选,布尔值,控制实体是否可以通过`/summon`命令召唤。
|
||
- `is_experimental`:可选,布尔值,控制实体是否是实验性实体,实验性实体必须在打开实验性玩法时才会被定义并出现在世界中。
|
||
- `animations`:可选,对象,实体的服务端动画或动画控制器。服务端实体也可以挂接动画或动画控制器,而此处就是服务端挂接动画或动画控制器的位置。实体的服务端动画或动画控制器主要用于控制“命令动画”,而非实体的模型动画。
|
||
- `scripts`:可选,对象,服务端Molang表达式伪脚本。同样,其下的`animate`数组用于条件控制播放动画或动画控制器。
|
||
|
||
## 理解实体行为的三种功能
|
||
|
||
除了实体的描述之外,我们可以看到实体定义中还有`components`、`component_groups`和`events`三种对象。它们分别代表实体行为的三种功能,分别是**组件**(**Component**)、**组件组**(**Component Group**)和**事件**(**Event**)。
|
||
|
||
### 组件
|
||
|
||
```json
|
||
"components": {
|
||
"minecraft:type_family": { // 第一个组件
|
||
"family": ["chicken", "mob"]
|
||
},
|
||
"minecraft:breathable": { // 第二个组件
|
||
"total_supply": 15,
|
||
"suffocate_time": 0
|
||
},
|
||
"minecraft:collision_box": { // 第三个组件
|
||
"width": 0.6,
|
||
"height": 0.8
|
||
},
|
||
"minecraft:nameable": {}, // 第四个组件
|
||
"minecraft:health": { // 第五个组件
|
||
"value": 4,
|
||
"max": 4
|
||
},
|
||
// ...
|
||
}
|
||
```
|
||
|
||
在前面的实体配置章节中,我们便已经了解了实体的组件。实体的组件就是就是实体各种行为的直接定义。定义在实体`components`对象中的组件会在实体一出现在世界中便开始生效,执行各自的功能,比如特性、导航和AI意向等。
|
||
|
||
我们可以通过我的世界开发工作台的编辑器来添加和修改组件,也可以参照[bedrock.dev上托管的附加包实体文档](https://bedrock.dev/zh/b/Entities)来通过直接修改JSON文件的方式修改组件。这两种方案是等价的。
|
||
|
||
实体的组件是赋予实体灵魂的重中之重,同时也是实现最全面、内容最庞大的功能,需要开发者们认真仔细地通过编辑器的辅助或通过文档来学习。
|
||
|
||
### 组件组
|
||
|
||
```json
|
||
"component_groups": {
|
||
"minecraft:teal_baby": { // 第一个组
|
||
"minecraft:is_baby": {}, // 第一个组中的第一个组件
|
||
"minecraft:scale": { // 第一个组中的第二个组件
|
||
"value": 0.5
|
||
},
|
||
"minecraft:ageable": { // 第一个组中的第三个组件
|
||
"duration": 1200,
|
||
"feed_items": ["wheat_seeds", "beetroot_seeds", "melon_seeds", "pumpkin_seeds"],
|
||
"grow_up": {
|
||
"event": "minecraft:ageable_grow_up",
|
||
"target": "self"
|
||
}
|
||
},
|
||
"minecraft:behavior.follow_parent": { // 第一个组中的第四个组件
|
||
"priority": 5,
|
||
"speed_multiplier": 1.1
|
||
}
|
||
},
|
||
"minecraft:teal_adult": { // 第二个组
|
||
"minecraft:experience_reward": {
|
||
"on_bred": "Math.Random(1,7)",
|
||
"on_death": "query.last_hit_by_player?Math.Random(1,3):0"
|
||
},
|
||
"minecraft:loot": {
|
||
"table": "loot_tables/entities/chicken.json"
|
||
},
|
||
"minecraft:breedable": {
|
||
"require_tame": false,
|
||
"breeds_with": {
|
||
"mate_type": "tutorial_demo:teal",
|
||
"baby_type": "tutorial_demo:teal",
|
||
"breed_event": {
|
||
"event": "minecraft:entity_born",
|
||
"target": "baby"
|
||
}
|
||
},
|
||
"breed_items": ["wheat_seeds", "beetroot_seeds", "melon_seeds", "pumpkin_seeds"]
|
||
},
|
||
"minecraft:behavior.breed": {
|
||
"priority": 3,
|
||
"speed_multiplier": 1
|
||
},
|
||
"minecraft:rideable": {
|
||
"seat_count": 1,
|
||
"family_types": ["zombie"],
|
||
"seats": {
|
||
"position": [0, 0.4, 0]
|
||
}
|
||
},
|
||
"minecraft:spawn_entity": {
|
||
"entities": {
|
||
"min_wait_time": 300,
|
||
"max_wait_time": 600,
|
||
"spawn_sound": "plop",
|
||
"spawn_item": "egg",
|
||
"filters": {
|
||
"test": "rider_count",
|
||
"subject": "self",
|
||
"operator": "==",
|
||
"value": 0
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
组件组是一种组件的“包裹”,每个组件组都是有一个对象组成的,组件组的键值是该组件组的标识符,而组件组的内容是由一个个组件构成的。组件组是为了可以动态地灵活地向实体中增减组件而产生的。比如上述例子,我们有`minecraft:teal_baby`和`minecraft:teal_adult`两个组件组。我们可以通过后面要介绍到的事件在实体时幼年状态时将`minecraft:teal_baby`组件组中的组件“打包”增加进实体的活动组件内,然后当实体是成年状态时,再将`minecraft:teal_baby`组件组中的组件“打包”取出,同时将`minecraft:teal_adult`组件组中的组件再“打包”放入活动组件。
|
||
|
||
换言之,组件组中的组件在实体进入游戏后并不会瞬间生效。只有当该组件组被添加到实体的活动组件列表中这些组件才能生效。当它们被取出时又会再次失效。这样可以做到实体组件的动态增删。
|
||
|
||
我们可以看到`minecraft:teal_baby`组件组中的`minecraft:scale`组件。这个组件代表着整个实体的尺度缩放。大家应该还记得上一节中我们曾经看到了这个水鸭实体幼年状态的动画吧,水鸭的头部会在幼年状态下增为原来的两倍。而此处我们可以看到整个水鸭将缩减为原来的0.5倍。所以整体效果其实会表现为水鸭的头部大小不变,而身形缩小一倍。这也是我们在我的世界中看到的各种幼年实体都普遍存在“头大体小”的样貌的原因。而这种“头大体小”其实也是符合常识的,毕竟动物的头骨大小一般都不会随着年龄增长而改变。通过灵活使用组件组,配合其他的功能比如动画,可以使我们的实体更加具有真实感或者其他你所设想的功能。
|
||
|
||
## 事件
|
||
|
||
```json
|
||
"events": {
|
||
"from_egg": {
|
||
"add": {
|
||
"component_groups": ["minecraft:teal_baby"]
|
||
}
|
||
},
|
||
"minecraft:entity_spawned": {
|
||
"randomize": [
|
||
{
|
||
"weight": 95,
|
||
"trigger": "minecraft:spawn_adult"
|
||
},
|
||
{
|
||
"weight": 5,
|
||
"add": {
|
||
"component_groups": ["minecraft:teal_baby"]
|
||
}
|
||
}
|
||
]
|
||
},
|
||
"minecraft:entity_born": {
|
||
"remove": {},
|
||
"add": {
|
||
"component_groups": ["minecraft:teal_baby"]
|
||
}
|
||
},
|
||
"minecraft:ageable_grow_up": {
|
||
"remove": {
|
||
"component_groups": ["minecraft:teal_baby"]
|
||
},
|
||
"add": {
|
||
"component_groups": ["minecraft:teal_adult"]
|
||
}
|
||
},
|
||
"minecraft:spawn_adult": {
|
||
"add": {
|
||
"component_groups": ["minecraft:teal_adult"]
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
实体的事件是用于在某些特定情况下触发特定功能的一种属性,事件可以由组件触发,动画或动画控制器触发,也可以由其他事件甚至其他实体的其他事件触发。还有一些事件是内置事件,可以由硬编码的部分触发。
|
||
|
||
事件一般用来增删组件组,有时候也可以用来触发其他事件。我们可以看到,下面的节选`from_egg`事件和`minecraft:spawn_adult`事件都可以分别添加一个组件组到活动组件列表中。
|
||
|
||
```json
|
||
"from_egg": {
|
||
"add": {
|
||
"component_groups": ["minecraft:teal_baby"]
|
||
}
|
||
}
|
||
```
|
||
|
||
```json
|
||
"minecraft:spawn_adult": {
|
||
"add": {
|
||
"component_groups": ["minecraft:teal_adult"]
|
||
}
|
||
}
|
||
```
|
||
|
||
下面的`minecraft:ageable_grow_up`可以同时增加一个和删除一个组件组。
|
||
|
||
```json
|
||
"minecraft:ageable_grow_up": {
|
||
"remove": {
|
||
"component_groups": ["minecraft:teal_baby"]
|
||
},
|
||
"add": {
|
||
"component_groups": ["minecraft:teal_adult"]
|
||
}
|
||
}
|
||
```
|
||
|
||
大部分事件都是由一个组件触发的。比如我们可以在实体的`minecraft:teal_baby`组件组中找到一个`minecraft:ageable`组件。它可以用于在实体到达特定的成长时间后触发`minecraft:ageable_grow_up`事件,而`minecraft:ageable_grow_up`事件可以将`minecraft:teal_baby`组件组中移除,并添加`minecraft:teal_adult`组件组。这意味着小水鸭将长成大水鸭。```
|
||
|
||
```json
|
||
"minecraft:teal_baby": {
|
||
// ...
|
||
"minecraft:ageable": {
|
||
"duration": 1200,
|
||
"feed_items": ["wheat_seeds", "beetroot_seeds", "melon_seeds", "pumpkin_seeds"],
|
||
"grow_up": {
|
||
"event": "minecraft:ageable_grow_up",
|
||
"target": "self"
|
||
}
|
||
},
|
||
// ...
|
||
}
|
||
```
|
||
|
||
还有一些组件可以用实体动画和动画控制器触发,这些动画和动画控制器必须是行为包中的定义的,且需要在`on_entry`或`on_exit`中直接使用`@s <namespace>:<event_identifier>`的格式。
|
||
|
||
有一些组件是其他组件触发的,比如`minecraft:entity_spawned`事件有95%的概率触发`minecraft:spawn_adult`事件,另外5%的概率添加一个`minecraft:teal_baby`组件组。这是利用`randomize`实现的。
|
||
|
||
```json
|
||
"minecraft:entity_spawned": {
|
||
"randomize": [
|
||
{
|
||
"weight": 95,
|
||
"trigger": "minecraft:spawn_adult"
|
||
},
|
||
{
|
||
"weight": 5,
|
||
"add": {
|
||
"component_groups": ["minecraft:teal_baby"]
|
||
}
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
还有一些便是**内置事件**(**Built-in Event**)。内置事件会在游戏特定的内部代码中触发,实体只需要定义好这些事件即可。这些事件无需由玩家手动触发。比如,上述的`minecraft:entity_spawned`和`minecraft:entity_born`就是两个内部事件,分别在实体由刷怪蛋生成/自然生成和实体被父母诞出的时候触发。当然,这并不意味着玩家不能将其手动触发,玩家依旧可以根据自己的意愿添加这些事件的其他触发途径。
|
||
|
||
当然,还有一些特殊事件,比如上述的`from_egg`事件。这个事件是硬编码的事件,只有当实体的运行时ID为`minecraft:chicken`时才会生效。这会使掷出的鸡蛋打碎时有概率生成该实体。
|
||
|
||
合理利用事件,可以使你的实体更加具有机动性,是你能够更好地丰富和完善其功能。
|