This commit is contained in:
boybook
2025-12-01 20:59:16 +08:00
parent 12738a142c
commit 760c2dd9ad
5535 changed files with 21070 additions and 2021 deletions

View File

@@ -0,0 +1,20 @@
---
front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg
hard: 入门
time: 5分钟
---
# 摘要
在接下来的这章中我们将一起学习自定义实体。充分利用第三方工具Blockbench自定义一个全新的实体。实体的完整Demo链接可以点击 [这里](https://g79.gdl.netease.com/addonguide-8.zip)下载。
- 在第一节(*使用Blockbench修改鸡的几何模型*我们将学习如何使用Blockbench修改一个模型换言之制作一个**几何****Geometry**)。
- 在第二节(*在Blockbench上为模型绘制鸭子贴图*我们将继续使用Blockbench为我们的几何模型“上色”即制作这个几何的**纹理**。
- 在第三节(*探索实体的资源控制*)中,我们将进一步探索我们新制作的实体的资源包,学习实体的**定义**、**动画**、**音效**和**动画控制器****Animation Controller**)及**渲染控制器****Render Controller**)。
- 在第四节(*探索实体的行为文件*)中,我们将探索该实体的行为包,了解实体的**描述**、**组件**、**组件组****Component Group**)和**事件****Event**)。
- 在第五节(*为实体添加音效*)中,我们将亲手为我们新定义的实体添加音效。
- 在第六节(*让实体在水面漂浮*)中,我们将为我们的实体添加行为组件。
- 在第七节(*为实体添加粒子*)中,我们将一起为这个新实体添加粒子效果。
- 在最后一节(*挑战:自定义水上坐骑*),我们将一起完成一个挑战,使我们的实体成为一个水上坐骑。
本章关键词Blockbench Minecraft Entity Wizard 水鸭 模型 几何 纹理 资源 材质 动画 动画控制器 渲染控制器 粒子 音效 格式版本 最低引擎版本段 短名称 挂接 骨骼 位置 旋转 尺度 通道 关键帧 状态机 状态 转移 附着物 组件 组件组 事件 运行时标识符 系统声音

View File

@@ -0,0 +1,152 @@
---
front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg
hard: 高级
time: 20分钟
---
# 使用Blockbench修改鸡的几何模型
Blockbench是一个社区开发的实体模型和动画制作软件拥有非常强大的功能和非常高的人气。我们如欲自定义一个简单的像素风模型的实体Blockbench是我们不二的选择。更有帮助的是在Blockbench中有一个微软官方制作的插件——**Minecraft Entity Wizard for Blockbench**(直译为**适用于Blockbench的我的世界实体向导**。这个插件可以极大地简化新手开发者的实体开发进程。通过这个向导我们可以快速以原版实体为模板创建一个新的实体并且直接利用Blockbench优异的模型和动画功能为该实体更改模型改变动画甚至绘制纹理。在此之后就可以直接将实体导出供游戏加载使用了。
因此在本节中我们便一起使用Minecraft Entity Wizard插件制作一个新的实体不妨以鸡为模板制作一个水鸭Teal绿头鸭实体。
## 安装Minecraft Entity Wizard插件
阅读过第三章的“社区工具介绍”的大家想必已经知悉了Blockbench的安装地址或网络应用网址。接下来我们默认大家已经安装好了Blockbench我们一起在Blockbench中安装微软官方的Minecraft Entity Wizard插件。
![](./images/8.1_file_addon.png)
我们在菜单栏中找到“**文件**”->“**插件...**”,点击该按钮打开插件的安装窗口。
![](./images/8.1_mc_entity_wizard.png)
在新的窗口中,我们点击上面的“**可用**”选项卡可以看到映入眼前的便是Minecraft Entity Wizard插件。我们点击这个插件面板右上角的“**+安装**”。
![](./images/8.1_file_addon_complete.png)
稍微等待几秒,我们便可以看到一个安装成功的提示消息。接下来,我们便可以在菜单栏的“**插件**”->“**Minecraft Entity Wizard**”来使用该插件了!
![](./images/8.1_addon.png)
## 创建名为水鸭的新实体
![](./images/8.1_let_s_go.png)
打开Minecraft Entity Wizard插件我们点击 **“Let's Go!”** **让我们开始吧!**),即可开始新建实体。在向导中,你可以使用底部的“**Next**”(**下一步**)和“‘**Back**”(**上一步**)按钮分别向后或向前翻动向导页面。
![](./images/8.1_mc_entity_wizard_name.png)
首先是“**Name**”(**命名**)选项卡,这里应该填写该实体的名字和标识符。
- **Display Name****显示名称**):该实体的显示名称,这会出现在刷怪蛋的名字上、聊天栏上和其他相关界面中。该名子自动支持本地化,所以请使用英文名称,随后开发者们可以创建`zh_CN.lang`文件来填入对应的中文名。
- **Identifier****标识符****标识符****Identifier**,简称**ID**)采用**赋命名空间标识符****Namespaced Identifier**,简称**NSID**)的格式,即`<namespace>:<identifier>`的格式。这里我们沿用`tutorial_demo`的命名空间,使用`tutorial_demo:teal`作为标识符。
![](./images/8.1_mc_entity_wizard_appearance.png)
点击“下一步”后进入“**Appearance**”(**外观**选项卡这里选择一个实体的几何和纹理作为你的实体的初始外观。因为要制作水鸭我们选择“Chicken”的几何和纹理。
![](./images/8.1_mc_entity_wizard_behavior.png)
接下来我们进入了“**Behavior**”(**行为**)选项卡。这里是说我们的新实体是使用和原来一样的行为还是使用另一个实体的行为。注意,某些行为往往和几何模型是绑定的,如果使用了文不对题的行为,可能会导致没有动画等一系列问题,所以我们这里建议初学者选择“**Same Behavior**”(**相同行为**)而非“**Different Behavior**”(**不同行为**)。
![](./images/8.1_mc_entity_wizard_spawn_egg.png)
第四步,我们进入了“**Spawn Egg**”(**刷怪蛋**)选项卡。这里有三种选择。
- **Colors****配色**):这是第一种方案,采用该方案可以使用**蒙版****Mask**)实体的刷怪蛋作为蒙版,自定义自己想要的颜色的刷怪蛋。我们的演示也是用这种方案。
- **Custom Texture****自定义纹理**):第二种方案允许开发者使用自己准备好的一个纹理贴图作为刷怪蛋贴图。
- **None****无**):没有刷怪蛋,该实体只能通过命令等方式召唤。
![](./images/8.1_mc_entity_wizard_export.png)
最后,我们进入了“**Export**”(**导出**)选项卡。这里我们将我们的实体进行导出。
- **Export to Folder****导出到文件夹** **国际版功能,不适用于中国版。** 只有客户端安装的Blockbench且电脑上同时存在国际版时才可用直接将包导出到国际版的开发文件夹`development_resource_packs``development_behavior_packs`
- **Integrate into Pack****集成到现有的包** **国际版功能,不适用于中国版。** 只有客户端安装的Blockbench且电脑上同时存在国际版时才可用将实体直接集成到国际版的工作文件夹内当前存在的包中。
- **Export as MCAddon****导出为MCAddon**通用的选项。由于Minecraft Entity Wizard不支持直接导出到中国版工作环境所以这个选项也**是我们此处希望使用的选项**,请确保选项切换至此。我们填写好“**Pack Name**”(**包的名称**)、“**Pack Author(s)**”(**包的作者**)和“**Pack Icon**”(**包的图标**),点击“**Export**”(**导出**按钮即可导出为一个MCAddon文件。也就是一个`.mcaddon`扩展名文件。
![](./images/8.1_mc_entity_wizard_next_steps.png)
导出成功后,我们来到最后的“**Next Steps**”(**后续步骤**选项卡。由于我们使用了Export as MCAddon所以事实上我们无需再有后续步骤就算我们点击“**Edit Model**”(**编辑模型**也无法继续在Blockbench中编辑模型因为我们已经将其导出为一个MCAddon文件。
![](./images/8.1_mc_entity_wizard_edit_model.png)
为了能够继续在Blockbench中编辑模型我们需要将本质是一个ZIP文件的MCAddon文件解压。我们不妨直接将其导入至我的世界开发工作台这样这个文件将会自动解压并进入我们的工作环境。
![](./images/8.1_mc_entity_wizard_discard.png)
暂时关闭Minecraft Entity Wizard。此时我们会遇到一个弹窗询问我们是否保留Minecraft Entity Wizard中的实体由于我们已经成功导出所以我们点击“**Discard**”(**丢弃**。如果你下次打开Minecraft Entity Wizard时还想继续使用这个实体那么你可以选择“**Keep**”(**保留**)。
### 导入至我的世界开发工作台并在Blockbench中重新打开模型
我们打开我的世界开发工作台,在“作品库”选项卡中的右上角点击“本地导入”。
![](./images/8.1_import.png)
我们选中“**复制文件到默认文件夹**”并找到我们刚才导出的包。编辑好作品名称后,点击“**导入**”按钮。
![](./images/8.1_open_directory.png)
此时,我们便可以在编辑器中看到我们的附加包了。找到它并**右键**或点击“**更多**”按钮,再点击“**打开目录**”按钮。此时我们能打开该包的目录。
![](./images/8.1_copy_address.png)
复制或记住其地址路径然后转到Blockbench点击菜单栏中的“文件”->“打开模型”,找到该路径。
![](./images/8.1_open_model.png)
定位到资源包下的`models/entity`文件夹,找到一个`geo.json`后缀的文件,即是我们的模型几何文件。双击打开该文件。此时,我们可以看见,我们目前“名为水鸭实为鸡”的实体出现在中央的**观察窗****Viewport**)上了。
![](./images/8.1_chicken.png)
## 使用“移动”、“尺寸”和“旋转”工具
在移动、缩放和旋转之前,我们应该先了解如何在观察窗中操作视图。而在操作视图之前,我们建议先将一种特殊的网格打开,这将方便我们直观地感受模型的大小。
找到“**文件**”->“**首选项**”->“**设置...**”,打开设置窗口。找到“**网格**”选项卡,点击“**大网格**”选项以打开。
![](./images/8.1_settings.png)
![](./images/8.1_grid.png)
然后我们便可以看到我们展开了一个更大的**网格线****Grid**。这个网格线便是方块网格线。网格线中的每个方形区域都是“一个方块大小”而中央的网格线中每个小方形都是“1/16个方块大小”在原版像素风格的我的世界中这边是一个像素的大小。
![](./images/8.1_chicken_with_block_grid.png)
至此,我们可以开始操作观察窗了。我们有三种基本操作方法。
- **鼠标左键**:拖动视图使你的**相机****Camera**)围绕中心进行旋转。相机在模型空间中的位置即是你的观察窗的位置,你可以认为你是通过这个相机来观察这只鸡的。
- **鼠标右键**:拖动视图使模型的**基面****Floor**)上下左右进行移动。基面即是鸡脚下的这个网格线所代表的面,是模型基准面。
- **鼠标滚轮**:缩放视图,即**拉近****Zoom in**)或**拉远****Zoom out**)你的相机相对于模型的位置。相当于相机的**变焦****Zoom**)。
现在,我们把注意力放在观察窗左上角的**工具栏**中。在工具栏的最左侧,我们有三个基础工具,分别是“**移动**”、“**尺寸**”和“**旋转**”工具。这三个工具大家看起来陌生,事实上我们在我的世界开发工作台的编辑器中便已经变相地接触过了。
首先,我们先了解到,一个模型中,不论是一个**立方体****Cube**)、一个**网格****Mesh**)还是一个**组****Group**)等等,我们都称之为一个**元素****Element**)。
![](./images/8.1_move.png)
![](./images/8.1_move_eg.png)
- **移动**:对一个元素进行**移动****Move**)。这类似于关卡编辑器中选区之后的移动。我们按住小坐标系中对应的轴就可以沿轴移动。
![](./images/8.1_resize.png)
![](./images/8.1_resize_eg.png)
- **尺寸**:对一个元素进行**缩放****Resize**)。这类似于关卡编辑器中选区之后对面的移动,即可以使选取的大小发生改变,只不过这里是拖动对应的轴来改变大小。
![](./images/8.1_rotate.png)
![](./images/8.1_rotate_eg.png)
- 对一个元素进行**旋转****Rotate**)。旋转的坐标系呈球形,其中心位于该元素的**轴心点****Pivot Point**,又译**枢轴点**)上。轴心点是一个元素旋转的相对位置点。在轴心点处拖动对应的轴即可进行相应方向的旋转。如图我们可以看到,鸡的嘴巴立方体的轴心点其实是在鸡的脚部中间。
## 在鸡模型的基础上重新设计水鸭模型
由于绿头鸭的一大特征是脖子很长,我们拉高鸡的脖子,将脖子`head`立方体的“位置”的Y由6增加到到9。同时鸭子没有鸡的红色肉裾因此我们去掉该红色的立方体。随后移动`beak`立方体(即喙)到靠近升高后的眼睛位置。
鸭子有比鸡更长的尾巴,因此我们在`body`组里放入两个新的立方体来模拟尾巴。将第一个立方体作为大尾巴,移动到(-3, 9, 4)的位置,缩放至(6, 2, 5),此时枢轴点位于(0, 0, 0)。将第二个立方体作为小尾巴,移动到(-2, 8, 4)的位置,缩放至(4, 1, 4),此时枢轴点位于(0, 0, 0)。
![](./images/8.1_teal_without_texture.png)
此时我们便完成了鸭子的几何!及时按`Ctrl+C`保存几何文件即可。但是,可以看到,我们的纹理由于我们的移动和缩放发生了奇怪的改变。不用担心,这是因为我们移动和缩放并不会自动改变纹理而导致的,所以下一节中,我们将一起来重新绘制新的纹理。

View File

@@ -0,0 +1,67 @@
---
front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg
hard: 高级
time: 15分钟
---
# 在Blockbench上为模型绘制鸭子贴图
在本节中我们一起为水鸭制作纹理贴图。在上一节中,我们已经制作好了水鸭的模型,但是遗憾的是,由于我们更改了一些立方体的面的大小,旧纹理已经不再适用了。我们接下来重新绘制一个纹理。
## 创建新的纹理贴图
我们定位到左侧的纹理栏,右击删掉原来的纹理贴图。我们可以看到,此时的水鸭变成了无纹理贴图的默认染色状态。
![](./images/8.2_delete_texture.png)
接下来,我们点击“**创建贴图**”按钮新建一个纹理贴图。
![](./images/8.2_create_texture.png)
![](./images/8.2_texture_template.png)
我们将“类型”改为“纹理模板”,这样我们创建一个每个立方体的每个面都已经自动染色的模板纹理文件。
![](./images/8.2_texture.png)
## 给水鸭上色、吸色与去色
现在,我们可以给水鸭上色了。我们在右上角的模式选择菜单中选择“画板模式”。
![](./images/8.2_paint_mode.png)
此时,我们的观察窗将出现像素网格线,右侧的模式面板将出现一个具有“**拾色器**”和“**调色板**”子面板的面板。同时,我们的鼠标指针在预览窗中将会变成一个“画笔”,通过我们的点击为每个像素**上色**。此时我们正在使用“**笔刷**”工具。
在屏幕右侧的“拾色器”子面板下,我们点击一个类似于吸管状的按钮,这是我们的“**拾色器**”。
![](./images/8.2_color_picker.png)
之后我们的鼠标指针将会变成一个拾色窗,供我们进行**拾色**。当我们点击鼠标时,鼠标所在位置的颜色将出现在右侧的拾色器面板上,供我们后续使用。
![](./images/8.2_color_picker_tool.png)
在预览窗上方的工具栏里,我们也能找到一个称为“**拾色器**”的工具,这个拾色器的功能与屏幕右侧的拾色器相同,但是可以供我们多次拾色。
![](./images/8.2_eraser.png)
在预览窗上方的工具栏里,我们可以选择“**橡皮擦**”工具。此时,我们的鼠标光标将具有擦除像素的能力,这是我们便可以进行**去色**了
## 使用镜面模式加快绘制速度
![](./images/8.2_mirror_paint.png)
我们在预览窗上方的工具栏中找到“**镜面绘画**”。点击该工具即可使其常驻地打开。此时我们再在预览窗中上色、去色时,镜面两侧的所有纹理都将不同发生变换。这样,我们的工作量便可以瞬间减少一倍,这能够方便我们更快速地完成绘制。
## 保存贴图
![](./images/8.2_texture_complete.png)
终于我们完成了贴图的绘制此时我们必须及时保存我们的纹理贴图否则一旦关闭Blockbench我们的成果将功亏一篑。
![](./images/8.2_texture_save.png)
![](./images/8.2_texture_saving.png)
找到纹理贴图右侧的保存按钮。直接替换掉我们之前的鸡的贴图即可再次打开Blockbench选择最近打开的文件水鸭我们可以看到完整的水鸭出现在了我们面前
![](./images/8.2_reopen.png)

View File

@@ -0,0 +1,350 @@
---
front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg
hard: 进阶
time: 30分钟
---
# 探索实体的资源控制
在本节中,我们将一起学习实体的客户端相关的各种文件、概念及其编写方式。
## 什么是实体定义文件
每个实体都需要通过“定义”才能被注册到游戏中供我们游玩实体定义文件便是供开发者们在自定义实体过程中进行“定义”操作的一个JSON文件。如果你已经阅读了前面的章节你便可以意识到实体定义文件是成对出现的在资源包中存在一个客户端定义文件而服务端中则有一个服务端定义文件。客户端定义文件中不存在实体的组件所以所有的组件代表着实体的各种行为都位于实体的服务端定义中。而实体的客户端定义文件主要用于注册实体的各种**资源控制****Resource Control**)功能,我们这里着重介绍实体的**客户端定义文件**。
我们一起来看我们上两节中制作的鸭子实体的客户端定义文件。为了更好地说明文件中的功能,我们补充了一些空字段。
```json
{
"format_version": "1.10.0", // 格式版本
"minecraft:client_entity": {
"description": {
"identifier": "tutorial_demo:teal", // 标识符
"min_engine_version": "1.12.0", // 最低引擎版本
"materials": {
"default": "chicken",
"legs": "chicken_legs"
}, // 材质
"textures": {
"default": "textures/entity/teal"
}, // 纹理
"geometry": {
"default": "geometry.teal"
}, // 几何
"animations": {
"move": "animation.teal.move",
"general": "animation.teal.general",
"look_at_target": "animation.common.look_at_target",
"baby_transform": "animation.teal.baby_transform"
}, // 动画和动画控制器
"scripts": {
"animate": [
"general",
{
"move": "query.modified_move_speed"
},
"look_at_target",
{
"baby_transform": "query.is_baby"
}
]
}, // 脚本
"render_controllers": ["controller.render.chicken"], // 渲染控制器
"particle_effects": {
}, // 粒子效果
"sound_effects": {
}, // 音效
"spawn_egg": {
"base_color": "#62c287",
"overlay_color": "#87692b"
} // 刷怪蛋
}
}
}
```
我们来依次研习客户端实体定义中的各个部分。首先,我们要明确一点,客户端实体定义文件是用于控制该实体的**资源****Resource**)的文件。这里的资源指的是广义的资源,即指代包括了纹理、模型、粒子、音效、动画在内的所有资源包内容。实体定义文件便是“统筹”这些内容的“司令官”。
- `format_version`:这是该实体文件的**格式版本****Format Version**。一个JSON文件的格式版本往往是用于决定该文件的JSON架构的一个字段。对于实体客户端定义文件来说它的格式有`1.8.0``1.10.0`两种版本。我们这里建议使用`1.10.0`版本,因为它包含了更多的功能,比如更简单的条件控制动画执行。
- `minecraft:client_entity`:客户端实体的模式标识符,客户端实体定义文件中必须使用该标识符作为键名。其下有且仅有`description`对象。
我们可以看到,`description`对象是是文件的主体,所有的客户端实体功能都定义于`description`对象下,该对象被称为实体客户端定义的**描述****Description**)。接下来,我们便想一起来学习`description`对象的各个字段的功能。不过在此之前,我们需要引入一对概念:**短名称****Short Name**)与**完整名称****Full Name**)。
**短名称**(又称**易记名称****Friendly Name**,或**实体内部名称****Entity Internal Name**)是实体客户端定义中使用的一种“简称”或“别名”。由于我们需要在动画控制器、渲染控制器等资源控制文件中频繁地引用一些资源,比如动画、粒子或纹理、几何模型,所以为了避免繁复地写入冗长的完整名称(如赋命名空间标识符),我们采用短名称来称呼各种资源。我们把将每种资源的标识符写在实体定义文件中的过程称为资源在实体上的**挂接****Attach**),因此每个完整名称在挂接到实体上时都会被赋予一个短名称,例如上述的纹理挂接`"default": "textures/entity/teal"`中,`textures/entity/teal`便是纹理的完整名称,对于纹理来说是完整的资源路径,而`default`便是短名称,是其他资源文件中将会用到的“快捷”名称。
下面我们依次介绍描述对象`minecraft:client_entity/description`中的字段。
- `identifier`:字符串,该实体的赋命名空间标识符,格式为`<namespace>:<identifier>`,需要和行为包中的服务端实体中的标识符相同。
- `min_engine_version`可选字符串该定义文件的最低引擎版本。最低引擎版本决定了引擎如何解析该定义文件。这个“如何解析”和格式无关指的是引擎识别文件中字段的方法比如使用何种Molang表达式的语法词法来解析文件中的Molang表达式。同时如果定义了该字段引擎解析时还会自动和清单文件中的最低引擎版本相比较只有最低引擎版本比清单文件中的最低引擎版本低的实体才会被成功定义。当出现多个同`identifier`的实体定义文件时,只有比清单文件中的最低引擎版本低且最接近它的实体会被成功解析并定义(即,取比清单文件中的最低引擎版本低的里面最高的那个来解析)。
- `materials`:可选,对象,其中每个字段都是`"short_name": "full_name"`的格式,该实体上挂接的所有**材质****Material**)。其中完整名称是`materials/entity.material`文件中定义的材质名。
- `textures`:可选,对象,其中每个字段都是`"short_name": "full/name"`的格式,该实体上挂接的所有**纹理****Texture**)。其中完整名称是纹理的相对于资源包根目录的相对路径(不带有纹理的扩展名)。
- `geometry`:可选,对象,其中每个字段都是`"short_name": "full.name"`的格式,该实体上挂接的所有模型的**几何****Geometry**)。其中完整名称是`models`文件夹中的文件里定义的标识符。
- `animations`:可选,对象,其中每个字段都是`"short_name": "full.name"`的格式,该实体上挂接的所有**动画****Animation**)和**动画控制器****Animation Controller**)。其中完整名称是`animations``animation_controllers`文件夹中的文件里定义的标识符。
- `scripts`可选对象该实体定义中的Molang表达式的伪脚本部分可以用于定义该实体的实体变量、初始化表达式、预动画表达式和最重要的动画播放条件表达式。示例中的`animate`数组便是动画播放条件表达式数组,对`animations`中定义的每个动画或动画控制器设定了播放条件。
- `render_controllers`:可选,数组,其中每个元素都是`"full.name"`的格式,该实体上挂接的所有**渲染控制器****Render Controller**),其完整名称是`render_controllers`文件夹中的文件里定义的标识符。注意,渲染控制器只有完整名称的挂接,没有短名称的定义。
- `particle_effects`:可选,对象,其中每个字段都是`"short_name": "my_namespace:full_name"`的格式,该实体上挂接的所有**粒子****Particle**)效果。其中完整名称是`particles`文件夹中的文件里定义的标识符。
- `sound_effects`:可选,对象,其中每个字段都是`"short_name": "full.name"`的格式,该实体上挂接的所有**音效****Sound Effect**)。其中完整名称是`sounds/sound_definitions.json`文件中定义的声音事件名。
- `spawn_egg`:可选,刷怪蛋图标或自定义颜色,不再赘述。
## 初步了解动画格式
我们先一起学习**动画****Animation**)文件。动画其实就是一个用于使实体“动起来”的功能,一个动画文件中可以定义多个动画。动画的基础格式如下:
```json
{
"format_version": "1.8.0", // 该动画的格式版本
"animations": {
"animation.some_entity.anim1": {
// ...
}, // 第一个动画
"animation.some_entity.anim2": {
// ...
}, // 第二个动画
"animation.some_entity.anim3": {
// ...
} // 第三个动画
}
}
```
我们可以看到,用于定义动画的`animations`字段是一个对象,对象中每个字段的键(例如`animation.some_entity.anim1`)就是一个动画的标识符,它的值是该标识符所定义的动画。动画使用的格式版本为`1.8.0`,目前动画只有这么一种格式版本。
我们继续以我们第一节中生成的水鸭实体中的动画为例。
```json
{
"format_version": "1.8.0",
"animations": {
"animation.teal.baby_transform": {
"loop": true,
"bones": {
"head": {
"scale": 2
}
}
},
"animation.teal.general": {
"loop": true,
"bones": {
"wing0": {
"rotation": [0, 0, "variable.wing_flap-this"]
},
"wing1": {
"rotation": [0, 0, "-variable.wing_flap-this"]
}
}
},
"animation.teal.move": {
"loop": true,
"anim_time_update": "query.modified_distance_moved",
"bones": {
"leg0": {
"rotation": ["math.cos(query.anim_time*38.17)*80.0", 0, 0]
},
"leg1": {
"rotation": ["math.cos(query.anim_time*38.17)*-80.0", 0, 0]
}
}
}
}
}
```
我们可以看到,这里定义了三个动画,分别是`animation.teal.baby_transform``animation.teal.general``animation.teal.move`,分别是水鸭的幼体成年时的缩放动画、常规的摆动翅膀动画和移动时的双脚摇摆动画。
首先我们可以看到,三个动画中的`loop`字段都为`true`,这代表他们是循环播放的。如果我们改为`false`,则该动画只会播放一次。当然,这个循环播放意味着动画会以其持续时间为周期反复播放,但是事实上,上述三个动画的持续时间都是“瞬间”,这是因为他们没有设置**关键帧**所以他们的持续时间都是单帧。此时的循环意味着每帧都播放一次该动画而动画中的Molang表达式也因此会每帧都重新计算一次确定新的值。
三个动画中的都存在`bones`字段,这里便是针对每个**骨骼****Bone**)的动画定义。这里的骨骼名称是和该实体的模型几何中的骨骼名一一对应的。不过值得注意的是,并不是每个骨骼都必须在这里分配一个动画,并且如果这里定义了某个不存在的骨骼的动画,这也无伤大雅,因为不存在的骨骼的动画会被默认忽视。
对于每个骨骼来说,比如`animation.teal.baby_transform`中的`head`骨骼,它们的**位置****Position**)、**旋转****Rotation**)和**尺度****Scale**)动画都可以分别被定义。我们分别用`position``rotation``scale`来定义这三种属性。位置便是该骨骼相对于轴心点的位置,旋转便是该骨骼相对于轴心点的旋转角,尺度便是该骨骼缩放的倍数。这是与模型几何中的这三种属性的意义相对应的,只不过通过这里的定义,这三种属性可以“动起来”。我们将位置、旋转和尺度称为实体的三种**通道****Channel**),而这里的分别定义这三种通道的值的过程称为**逐通道****Channel-wise**)的定义。同时,这里需要注意的是,和上面所说的骨骼一样,对于通道来说,也不是每种通道都必须得到定义。比如水鸭示例中,该动画就只定义了将其`head`骨骼的尺度通道更改为2。同时由于这里没有定义关键帧加之以存在动画循环所以结果是只要该动画还在播放中水鸭的头部的三个轴上的尺度就都会持续地每帧都会变为2倍的大小。
在上面,我们反复提到了一个词——**关键帧****Key Frame**。那么关键帧又是什么呢在水鸭的示例中我们并没有看到有关关键帧的信息这是因为Blockbench中导出的水鸭的三个动画都没有定义关键帧。我们现在将水鸭的第一个动画改为已定义了关键帧的形式再将其作为示例来考察。
```json
"animation.teal.baby_transform": {
"loop": true,
"bones": {
"head": {
"scale": {
"0.0": 2,
"0.5": 1,
"1.0": 2
}
}
}
}
```
现在我们可以看到尺度通道不再是一个值而是一个对象。对象的键名都是一个小数而值则是一个尺度值。此时我们便得到了一个带有关键帧的尺度通道。关键帧是一个在动画播放时间轴上以秒为单位的关键节点。我们可以定义数个关键帧来指定动画的某个骨骼的某个通道在某些时间点的状态而两个关键帧之间则默认采用线性插值的方式取值比如上述示例中0.25和0.75处的值都应该为2和1的中点值1.5。当然,如果定义了`lerp_mode`字段还可以采用其他插值方法比如Catmull-Rom平滑插值比如在0.5处采用平滑插值:`"0.5": {"post": 1, "lerp_mode": "catmullrom"}`。当定义了动画的关键帧之后动画的时长也不再是一帧的瞬间而是变为最大关键帧所代表的时间比如上述示例动画的时长为1.0s。
此时我们再回头看那些没有定义关键帧的动画。这些动画其实也是有关键帧的。引擎会在0.0处为这些动画定义长度为一帧的关键帧,因此这些动画自己的长度也就是一帧。但是只要配合上`loop`循环运行,便可以使其时刻保持动画的这一帧的状态。
另外,不管是单个通道直接设置的值,还是某个关键帧中的值,不管是通过一个值来同时设置三个轴向的状态,还是写成一个三元数组来分别设置各个轴向,我们都可以使用**Molang表达式****Molang Expression**)来指定这个值。比如上述的`variable.wing_flap - this``query.modified_distance_moved``math.cos(query.anim_time * 38.17) * 80.0`都是Molang表达式。他们通过查询实体的成员或旗标、引用全局参数、读取实体变量的值再配合上各种数学运算和数学函数来获取一个最终的输出值。这样的值可以脱离关键帧线性插值局限性的束缚使动画更加平滑和“有曲度”。关于Molang的更多内容我们将在第十二章中详细讲解。
实体的动画是逐骨骼定义的,而骨骼是逐通道定义的,通道是逐关键帧定义的。实体的这种动画定义方式我们称之为**层阶式****Hierarchical**)的。
```shell
EntityAnimation: 动画名
__BoneAnimation[]: 该动画使用的骨骼名
____AnimationChannel[]: 该动画播放的旋转、缩放和平移
______KeyFrame[]: 通道位于特定的时间点时位于的值
```
### 动画的播放
在实体的定义文件描述中的`scripts/animate`下定义了各个动画的播放条件。如果是直接写出了该动画的短名称,那么就意味着实体一旦出现在世界中便开始播放该动画。如果是使用了比如`{ "baby_transform": "query.is_baby" }`这种由Molang表达式控制的语句则意味着只有当Molang表达式返回为真时才会触发该动画的播放。比如这句话的意思便是“如果实体为幼年”`query.is_baby`返回1.0),则播放`baby_transform`动画。这种通过Molang表达式控制动画播放的功能称为动画的**条件控制****Conditional Control**)。
## 初步了解动画控制器
我们在上面学习了动画的条件控制,了解到我们事实上是可以使某个动画在某种特定的条件下触发的。那么既然如此,动画控制器的意义又在哪里呢?还有什么其他的控制方法使动画的播放更加高级么?答案是有的,那便是状态机。
在详细说明状态机之前,我们先设想一种情形。我们设想有一扇门,如果一个变量`flag``opened`且门关着,我们希望把门打开,如果`flag``closed`且门开着,我们希望把门关闭。我们可以将这个过程抽象为如下表述。
> 门有两个状态。状态1代表门关状态2代表门开。当门位于状态1时如果旗标`flag`更新为`opened`我们希望将门转移到状态2同时播放一个开门的动画。当门位于状态2时如果旗标`flag`更新为`closed`我们希望将门转移到状态1同时播放一个关门的动画。
这个过程可以用一张图来表示:
![](./images/8.3_finite_state_machine_example_with_comments.png)
这便是一个最简单的**状态机****State Machine**)。而**动画控制器****Animation Controller**)便是一种可以被挂接到我的世界实体上的状态机。状态机中每个情形都可以被称作一个**状态****State**),而状态机的运作,便是从一个状态转移到另一个状态,再从另一个状态转移到第三个状态的过程。这种过程我们称之为**状态转移****State Transition**)。在动画控制器中,每个状态都可以播放一个或多个动画,还可以播放粒子、特效和音效等。在服务端的动画控制器中,我们甚至可以在进入和离开状态时执行命令。实体的动画控制器通过状态机的运作模式给我们提供了无限的可能。
我们的水鸭实体并没有复杂到使用动画控制器的程度,因此我们使用原版铁傀儡的动画控制器作为示例:
```json
{
"format_version" : "1.10.0",
"animation_controllers" : {
"controller.animation.iron_golem.arm_movement" : {
"initial_state" : "default",
"states" : {
"attack" : {
"animations" : [ "attack" ],
"transitions" : [
{
"default" : "variable.attack_animation_tick <= 0.0"
}
]
},
"default" : {
"animations" : [ "move" ],
"transitions" : [
{
"attack" : "variable.attack_animation_tick > 0.0"
},
{
"flower" : "variable.offer_flower_tick"
}
]
},
"flower" : {
"animations" : [ "flower" ],
"transitions" : [
{
"attack" : "variable.attack_animation_tick > 0.0"
},
{
"default" : "variable.offer_flower_tick <= 0.0"
}
]
}
}
},
"controller.animation.iron_golem.move" : {
"initial_state" : "default",
"states" : {
"default" : {
"animations" : [
{
"walk" : "query.modified_move_speed"
},
"look_at_target"
]
}
}
}
}
}
```
和动画类似,一个动画控制器文件也可以定义多个动画控制器。比如,这里便定义了两个动画控制器:`controller.animation.iron_golem.arm_movement`控制手臂移动,而`controller.animation.iron_golem.move`控制整体的移动。动画控制器的格式版本有`1.8.0``1.10.0`,建议使用`1.10.0`格式的动画控制器。
`initial_state`代表了该控制器从播放开始的初始**默认状态**,在第一个控制器`controller.animation.iron_golem.arm_movement`中我们看到是`default`状态。`default`状态会播放动画`move`,从铁傀儡动画的定义文件中我们可以看到该动画是铁傀儡两条手臂对应的骨骼的移动。此处有两个状态转移条件,分别是`variable.attack_animation_tick > 0.0``variable.offer_flower_tick <= 0.0`。动画控制器**每帧**都会**自上而下**地按照顺序检查各个条件是否符合,一旦发现符合的条件便会开始转移,此时后面没有检查的条件将被跳过。
假设我们第一个条件满足,此时我们便转移到了`attack`状态。`default`状态的`move`动画将停止播放,转而播放此处定义的`attack`动画。当某时刻出现`variable.attack_animation_tick <= 0.0`为真时,我们便会重新转移回`default`状态。
接下来我们再关注第二个控制器`controller.animation.iron_golem.move`。我们可以看到此控制器的`default`状态会播放两个动画,`walk``look_at_target`。事实上,这两个动画是可以叠加的,而叠加的顺序是最上面的动画作为最底层,而最下面的状态作为最上层,即**最先读取到的动画会被放在最底层,后续动画依次向上叠加**,类似于计算机中的一个栈。如果上层的动画覆盖了下层同骨骼的动画,那么以上层动画为准进行播放。如果某些骨骼在上层没有动画,那么下层这些骨骼的动画就会播放。此时`walk`后面的Molang表达式将不再是条件控制的播放条件而是播放时传入的修饰符数值用于控制动画播放的速度和方向。我们可以通过这个值做到反向播放一个动画。
在动画控制器中我们可以将另一个动画控制器作为动画来播放,这因此扩展了动画的定义层阶。
### 动画控制器的播放
类似于动画,动画控制器也是需要放在实体定义文件描述中的`scripts/animate`下来播放。一个动画控制器一旦播放,将会自动进入它的默认状态,状态机便开始工作。和动画一样,动画控制器也可以使用条件控制。
## 初步了解渲染控制器
学习完毕动画和动画控制器,我们转向另一种类型的控制器,它控制的不是动画,而是模型、材质和纹理,它便是**渲染控制器****Render Controller**)。事实上,除了广义的资源之外,我们还有狭义的资源。狭义的资源便专指渲染控制器所控制的三种资源——模型**几何****Geometry**)、**材质****Material**)和**纹理****Texture**)。
由于我们的水鸭用到的渲染控制器是原版的控制器,并且极为简略,为了讲述方便,我们使用原版弩的**附着物****Attachable*****挂件***)的控制器作为示例。由于附着物也是一种客户端实体,所以其渲染控制器和实体的控制器别无二致。
```json
{
"format_version": "1.10.0",
"render_controllers": {
"controller.render.crossbow": {
"arrays": {
"textures": {
"array.crossbow_texture_frames": [
"texture.default",
"texture.crossbow_pulling_0",
"texture.crossbow_pulling_1",
"texture.crossbow_pulling_2",
"texture.crossbow_arrow",
"texture.crossbow_rocket"
]
},
"geometries": {
"array.crossbow_geo_frames": [
"geometry.default",
"geometry.crossbow_pulling_0",
"geometry.crossbow_pulling_1",
"geometry.crossbow_pulling_2",
"geometry.crossbow_arrow",
"geometry.crossbow_rocket"
]
}
},
"geometry": "array.crossbow_geo_frames[query.get_animation_frame]",
"materials": [
{ "*": "variable.is_enchanted ? material.enchanted : material.default" }
],
"textures": [
"array.crossbow_texture_frames[query.get_animation_frame]",
"texture.enchanted"
]
}
}
}
```
我们可以看到,渲染控制器也有格式版本。同动画控制器一样,渲染控制器有`1.8.0``1.10.0`两种格式版本,且我们推荐使用`1.10.0`。一个渲染控制器文件也可以定义多个渲染控制器,只不过该文件只定义了一个。我们专注于考察这一个渲染控制器`controller.render.crossbow`。我们可以看到有`arrays``geometry``materials``textures`四个字段。
`arrays`是一个可选字段可以用于定义其余三种资源的资源数组。资源数组的好处是我们可以在下面这三种资源的调用时使用一个索引值来取得资源这方便于我们进一步使用Molang表达式来进行资源控制。在`arrays`里我们可以再创建`geometry``materials``textures`三个字段,每个字段下又可以定义多个数组。定义的数组的格式全部都为`"array.<array_name>": [ /* some resource elements */ ]`格式。比如`geometry`的数组,我们在示例中看到其定义了`array.crossbow_geo_frames`数组,数组中的元素的格式都为`geometry.<short_name>`,其中`<short_name>`是我们在实体的客户端定义文件中定义的短名称。其他的数组也是类似。在示例中,我们看到其定义了`textures``geometry`数组,唯独没有定义`materials`数组,这是因为该附着物使用的材质非常简单,只有两种,无需使用数组大费周章。
下面便是几何、材质和纹理。对于**几何**每个渲染控制器只能定义一个或者说每一帧每个渲染控制器只能存在一个几何。因为几何代表着实体的模型而一个实体不可能同时具备多个模型。我们不存在“薛定谔的实体”。不过我们可以通过Molang表达式来做到动态切换模型比如上述例子中`array.crossbow_geo_frames[query.get_animation_frame]`便可以做到根据纹理动画的帧来切换对应的模型。
**材质**则不限制个数,因为一个模型具有不同的部位,而不同的部位则可能需要有多种渲染方式,自然就需要有多种材质。材质数组中每一个元素又都是一个对象,对象中只有一个键值对,那便是`"<bone>": <material>`格式的键值对。其中`<bone>`可以填写一个骨骼的名称,也可以填写一个骨骼的部分名称,其余部分使用通配符`*`来补充。比如`bone*`可以同时指`bone1``bone2`等骨骼,以此为它们同时赋予某个材质。单独一个`*`则可以代表全部的骨骼。
**纹理**和材质一样,可以存在多个纹理。毕竟纹理文件可能存在叠加和分层,有些纹理的上层是透明的,便可以露出下层的纹理;而有些纹理在特定的时候会不显示,也可以露出下层的纹理。示例中我们可以看到存在两层纹理,而且其中一层纹理还会随着`query.get_animation_frame`取值不同而改变。事实上,和动画控制器中的动画叠加播放一样,这里的纹理也是按照最上面的在最下层,最下面的在最上层的方式叠加的。这种叠加方式被称为**图层系统****Layer System**)。在我的世界开发中,很多地方都会用到这种图层系统。比如自定义模型的方块在物品栏中渲染时,各个骨骼的渲染顺序便遵循图层系统。再比如上面说的材质,其实当存在多个材质时,也是以图形系统的方式应用的。比如原版马的材质段落:
```json
"materials": [
{ "*": "Material.default" },
{ "TailA": "Material.horse_hair" },
{ "Mane": "Material.horse_hair" },
{ "*Saddle*": "Material.horse_saddle" }
],
```
首先是全部骨骼被应用为`default`材质,然后`TailA`骨骼应用材质`horse_hair`。此时`TailA`骨骼便不再使用`default`,因为顺序越往下应用的优先级越高。最后是一切名字中含有`Saddle`的骨骼被应用为`horse_saddle`,该材质会覆盖掉之前应用的所有材质,因为它在最上层。
在了解了动画、动画控制器和渲染控制器后,我们的实体资源文件的探索便将告一段落。而如果你还想进一步了解实体客户端的定义和资源控制器的格式,可以分别参考微软官方的附加包文档中的[活动对象资源定义模式](https://docs.microsoft.com/zh-cn/minecraft/creator/reference/content/schemasreference/schemas/minecraftschema_actor_resource_1.10.0)、[活动对象动画控制器模式](https://docs.microsoft.com/zh-cn/minecraft/creator/reference/content/schemasreference/schemas/minecraftschema_actor_animation_controller_1.10.0)和[渲染控制器模式](https://docs.microsoft.com/zh-cn/minecraft/creator/reference/content/schemasreference/schemas/minecraftschema_render_controller_1.8.0)。在下一节,我们一起来探索实体的行为文件,了解实体行为的作用机理。

View File

@@ -0,0 +1,485 @@
---
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`时才会生效。这会使掷出的鸡蛋打碎时有概率生成该实体。
合理利用事件,可以使你的实体更加具有机动性,是你能够更好地丰富和完善其功能。

View File

@@ -0,0 +1,146 @@
---
front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg
hard: 进阶
time: 25分钟
---
# 为实体添加音效
在本节中我们为实体添加一个音效。事实上由于我们的实体是使用Blockbench自动生成的所以其实已经默认配备了鸡的音效。我们接下来将鸡的音效中的步行音效替换为另一个原版实体的比如尸壳的步行音效。
## 导入音效资源
大家一定记得,我们在第一节中已经将我们的水鸭行为包导入了我的世界开发工作台。我们打开该附加包的编辑器,准备将尸壳的音效导入编辑器。
![](./images/8.5_import.png)
我们找到资源包的`sounds`文件夹,右击该文件夹,点击“**导入文件**”按钮。便可以打开导入窗口。
![](./images/8.5_husk_sounds.png)
我们找到原版模板资源包下的`sounds/mob/husk`文件夹,我们将所有的`step*.fsb`文件选中。这些文件便是尸壳的步行音效文件。
![](./images/8.5_imported.png)
此时,我们便可以在“资源管理”窗格中看到导入的文件了。但是,事实上,这些音效文件依旧存放在`sounds`文件夹的根目录中。我们并不推荐如此存放。音效文件和纹理文件类似,往往数量都非常庞大,所以我们需要有一个比较好的文件分类系统。直接存放在根目录不仅会使音效文件难易寻找,而且会使游戏无法处理同名的文件,是一种“坏文明”。我们可以看到,模板资源包中便提供了一种分类方法,我们比着葫芦画瓢,建立`sounds/mob/teal`文件夹,并将这些文件复制到该文件夹内。
![](./images/8.5_imported_modified.png)
这样,我们的音效文件就导入完毕了。
## 定义声音事件
导入好音效之后,我们定义这些音效的声音事件。只有有了声音事件,这些音效才有可能被实体引用,或者被命令播放。我们在`sounds`文件夹的根目录下建立`sound_definitions.json`文件,并填入以下内容。
```json
{
"format_version": "1.14.0",
"sound_definitions": {
"mob.teal.step": {
"category": "neutral",
"sounds": [
"sounds/mob/teal/step1",
"sounds/mob/teal/step2",
"sounds/mob/teal/step3",
"sounds/mob/teal/step4",
"sounds/mob/teal/step5"
]
}
}
}
```
声音定义文件的格式版本只有`1.14.0`,所以请使用该格式版本。`sound_definitions`字段下可以定义多个声音事件,这里我们只定义了一个标识符为`mob.teal.step`的事件。事件的`category`分类是`neutral`,意味着属于“有好生物”事件。这和游戏内声音设置屏幕中的音量滑块紧密相连。`sounds`可以是一个音效的相对路径,也可以一个数组,其内包含多个音效的相对路径。如果有多个音效,则播放时会随机播放一个。
这样,我们便定义好了声音事件。该声音事件已经可以由`/playsound`命令调用了。
## 使实体播放音效
为了使实体播放音效,我们需要将音效绑定到实体上。我们有两种绑定手段。
### 将声音事件绑定至系统声音
**系统声音****System Sound**),又称**存档声音****Level Sound**),是一系列挂钩在特定的硬编码事件上的声音触发器。我们可以通过资源包根目录的`sounds.json`文件将声音事件绑定在系统声音上。这样,这些声音事件便会根据游戏内情况而“独立自主”地播放。事实上,这是由于游戏本身会将绑定在系统声音上的添加至对应的系统声音事件中,从而无需开发者再做任何播放操作。系统声音类型可以在[枚举值列表](https://mc.163.com/mcstudio/mc-dev/MCDocs/2-ModSDK%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91/99-%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99/0-Minecraft%E6%9E%9A%E4%B8%BE%E5%80%BC%E6%96%87%E6%A1%A3.html#syssoundtype)中找到,把其中的驼峰命名改成蛇形命名即可,比如`ItemUseOn`对应的是`item_use_on`系统音效类型。
Blockbench创建的水鸭实体默认已经将鸡的音效绑定到了水鸭的系统声音上我们将`step`系统声音类型的绑定修改为我们刚刚定义的声音事件的标识符。
我们修改`sounds.json`文件如下:
```json
{
"entity_sounds": {
"entities": {
"tutorial_demo:teal": {
"events": {
"ambient": "mob.chicken.say",
"death": "mob.chicken.hurt",
"hurt": "mob.chicken.hurt",
"plop": "mob.chicken.plop",
"step": {
"pitch": 1,
"sound": "mob.teal.step", // 修改此处的chicken为teal
"volume": 0.25
}
},
"pitch": [0.8, 1.2],
"volume": 1
}
}
}
}
```
此时,我们在游戏中看到水鸭踱步时,听到的声音便会自动播放为我们定义的音效了。
### 用动画或动画控制器播放音效
除了绑定到系统音效,我们还可以使用动画或动画控制器播放音效。在每个动画中,我们可以使用和`bones`平级的`sound_effects`来定义需要播放的音效。每个音效都必须与一个关键帧对应,代表音效播放的时间点。在每个动画控制器中,我们可以使用和`animations``transitions`平级的`sound_effects`来定义需要播放的音效。
```json
"animation.teal.baby_transform": {
"loop": true,
"bones": {
"head": {
"scale": {
"0.0": 2,
"0.5": 1,
"1.0": 2
}
}
},
"sound_effects": {
"0.0": { "effect": "sound1" }, // 0.0时播放实体定义文件中的sound1短名称的音效
"0.5": [
{ "effect": "sound2" },
{ "effect": "sound3" }
] // 0.5时播放实体定义文件中的sound2和sound3短名称的音效
}
}
```
```json
{
"format_version" : "1.10.0",
"animation_controllers" : {
"controller.animation.teal.move" : {
"initial_state" : "default",
"states" : {
"attack" : {
"animations" : [ "attack" ],
"transitions" : [
{
"default" : "variable.attack_animation_tick <= 0.0"
}
],
"sound_effects": [
{ "effect": "sound1" }
] // 一进入该状态便播放实体定义文件中的sound1短名称的音效
},
// ...
}
}
}
}
```
我们可以看到,实体的动画控制器的 `sound_effects`只能做到在状态开始时播放音效。那么我们如何做到在状态结束时也可以播放音效呢?我们有两种途径,第一种是将音效定义在下一个状态的开始。但是这种方式的局限性太大。另一种途径则是使用和`animations``transitions``sound_effects`平级的`on_exit`触发命令。`on_exit`数组可以保证在状态结束时执行其中的命令,它的每一个元素都可以是一个字符串斜杠命令。此时我们便可以使用`/playsound`命令来播放声音。

View File

@@ -0,0 +1,367 @@
---
front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg
hard: 进阶
time: 25分钟
---
# 让实体在水中悬浮
我们知道,鸭子都喜欢在水面上游动。鸭子在游动的时候,虽然双脚在水中拍打,但是在水面上却极为平稳。成语“鸭子凫水”便意味着暗地使劲,明面上看不出来的行为。而我们目前的水鸭在水中漂浮时却仅仅是像其他生物一样,一上一下。在本节中,我们将我们的鸭子的行为改为在水面上持续悬浮。
## 在原版实体中找到对应的组件
我们在原版实体中回忆哪种实体存在悬浮在液体表面的行为。不难发现赤足兽便是一种可以在熔岩表面悬浮的实体。因此赤足兽的行为文件中必定存在一种可以用于悬浮的组件。我们打开赤足兽的行为文件来一探究竟。由于在水面漂浮应该是一种AI意向我们着重关注带有`behavior.`前缀的组件。
```json
"minecraft:behavior.rise_to_liquid_level": {
"priority": 0,
"liquid_y_offset": 0.25,
"rise_delta": 0.01,
"sink_delta": 0.01
},
"minecraft:behavior.move_to_lava": {
"priority": 7,
"search_range": 16,
"search_height": 10,
"goal_radius": 0.9,
"search_count": 30
},
"minecraft:behavior.random_stroll": {
"priority": 8,
"speed_multiplier": 0.8
},
"minecraft:behavior.look_at_player": {
"priority": 9,
"look_distance": 6.0,
"probability": 0.02
},
"minecraft:behavior.random_look_around": {
"priority": 10
},
"minecraft:behavior.panic": {
"priority": 3,
"speed_multiplier": 1.1,
"panic_sound": "panic",
"sound_interval": {
"range_min": 1.0,
"range_max": 3.0
}
},
"minecraft:behavior.tempt": {
"priority": 5,
"speed_multiplier": 1.2,
"items": [
"warped_fungus",
"warped_fungus_on_a_stick"
],
"tempt_sound": "tempt",
"sound_interval": {
"range_min": 2.0,
"range_max": 5.0
}
}
```
很快,我们便能通过其组件名的字面意思定位到`priority`优先级为0的那个组件`minecraft:behavior.rise_to_liquid_level`。事实上这个确实就是能够使赤足兽悬浮的AI意向。我们将其复制到鸭子中。
## 实现水鸭的行为
我们稍作修改,然后将其复制到水鸭的行为定义文件中。
```json
"minecraft:behavior.rise_to_liquid_level": {
"priority": 0,
"liquid_y_offset": -0.5,
"rise_delta": 0.01,
"sink_delta": 0.01
}
```
这并没有结束。因为我们知道水鸭之所以会在水面上上下浮动是因为另一个AI意向也在起作用那就是`minecraft:behavior.float`。我们删除这个AI意向。
最后,我们更改水鸭的导航组件。因为我们删除了`minecraft:behavior.float`水鸭的AI便不再要求水鸭持续浮在水面上所以我们在水鸭的导航组件`minecraft:navigation.walk`中加入`can_sink`并设置为`false`。这样,水鸭在水中便不会下沉。
```json
"minecraft:navigation.walk": {
"can_path_over_water": true,
"can_sink": false,
"avoid_damage_blocks": true
}
```
至此,我们从理论上边实现了水鸭的行为替换。
```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
},
"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
}
}
}
}
},
"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,
"can_sink": false, // 添加不会下沉
"avoid_damage_blocks": true
},
"minecraft:movement.basic": {},
"minecraft:jump.static": {},
"minecraft:can_climb": {},
"minecraft:despawn": {
"despawn_from_distance": {}
},
"minecraft:behavior.rise_to_liquid_level": {
"priority": 0,
"liquid_y_offset": -0.5,
"rise_delta": 0.01,
"sink_delta": 0.01
}, // 添加赤足兽的悬浮行为
"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": {}
},
"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"]
}
}
}
}
}
```
但是事实上,由于某些缺陷,我们当前的水鸭在实际游戏中依旧会无视`minecraft:behavior.rise_to_liquid_level`行为并给自己添加一个`minecraft:behavior.float`行为。这会导致我们的行为替换失败。这是由于我们的水鸭包是Blockbench生成的使用了微软最新的清单文件格式版本我们需要将清单文件中格式版本从2改为1才能消除这一隐患。
同样地我们在资源包中也需要将清单文件的格式版本从2替换为1否则将因为格式版本的不对应导致水鸭的贴图无法加载。由于格式版本的改变这将影响到最低引擎版本所以我们将资源包中的最低引擎版本删除否则将导致加载失败。
```json
{
"format_version": "1.10.0",
"minecraft:client_entity": {
"description": {
"identifier": "tutorial_demo:teal",
//"min_engine_version": "1.12.0",
"materials": {
"default": "chicken",
"legs": "chicken_legs"
},
"textures": {
"default": "textures/entity/teal"
},
"geometry": {
"default": "geometry.teal"
},
"animations": {
"move": "animation.teal.move",
//"general": "animation.teal.general",
"look_at_target": "animation.common.look_at_target",
"baby_transform": "animation.teal.baby_transform"
},
"scripts": {
"animate": [
//"general",
{
"move": "query.modified_move_speed"
},
"look_at_target",
{
"baby_transform": "query.is_baby"
}
]
},
"render_controllers": ["controller.render.chicken"],
"spawn_egg": {
"base_color": "#62c287",
"overlay_color": "#87692b"
}
}
}
}
```
同时,我们将`general`动画删除。`general`动画是硬编码的翅膀煽动动画,鸭子在水中或空气中都会播放该动画。但是,众所周知,鸭子游泳时翅膀是不煽动的,所以我们直接删除此动画。
![](./images/8.6_in-game.gif)
我们进入游戏可以看到我们鸭子确实拥有了悬浮的AI意向

View File

@@ -0,0 +1,95 @@
---
front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg
hard: 进阶
time: 20分钟
---
# 为实体添加粒子
在本节中,我们通过一个气泡粒子示例演示如何为实体添加粒子。我们为我们的水鸭添加一个在水中的气泡粒子。事实上,我们的水鸭在水中移动时本来就会产生少许粒子,这是硬编码的行为。但是,我们依旧可以为水鸭添加更多的气泡粒子。
## 定义粒子短名称
我们欲采用原版的国际版粒子发射器`minecraft:basic_bubble_particle_manual`作为我们的气泡粒子,所以我们在实体定义文件中定义这个粒子的短名称,以供我们后续使用。
```json
{
"format_version": "1.10.0",
"minecraft:client_entity": {
"description": {
"identifier": "tutorial_demo:teal",
"materials": {
"default": "chicken",
"legs": "chicken_legs"
},
"textures": {
"default": "textures/entity/teal"
},
"geometry": {
"default": "geometry.teal"
},
"animations": {
"move": "animation.teal.move",
"look_at_target": "animation.common.look_at_target",
"baby_transform": "animation.teal.baby_transform"
},
"scripts": {
"animate": [
{
"move": "query.modified_move_speed"
},
"look_at_target",
{
"baby_transform": "query.is_baby"
}
]
},
"particle_effects": {
"bubble": "minecraft:basic_bubble_particle_manual"
}, // 定义粒子短名称
"render_controllers": ["controller.render.chicken"],
"spawn_egg": {
"base_color": "#62c287",
"overlay_color": "#87692b"
}
}
}
}
```
这个粒子发射器根据定义,只会在自身位于水中时持续发射气泡粒子,因此我们不必担心空气中会出现“气泡”。
## 挂接粒子至动画控制器
我们可以使用动画或动画控制器来挂接粒子,这里我们选用动画控制器。我们在资源包的`animation_controllers`文件夹内创建一个新文件,命名为`teal.animation_controllers.json`。然后,我们在其中输入如下内容。
```json
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.teal.particle": {
"initial_state": "default",
"states": {
"default": {
"particle_effects": [
{
"effect": "bubble",
"locator": "lead"
}
]
}
}
}
}
}
```
由于我们希望粒子持续播放,我们只定义一个状态,并定义一个`particle_effects`字段。其中`effect`为粒子效果的短名称,而`locator`是个可选字段,为粒子挂接的**定位器****Locator**)。
![](./images/8.7_locator.png)
因为我们知道,我们的水鸭实体中存在一个用于挂接栓绳的定位器,为了演示方便,我们也将它用于自定义气泡粒子的定位器。也就是说,这个栓绳点一旦位于水中,水鸭将在水中产生大量气泡。
![](./images/8.7_in-game.png)
我们进入游戏自测,可以看到,果不其然,鸭子脖颈处的栓绳点在水下时便会发射出大量的气泡粒子!

View File

@@ -0,0 +1,313 @@
---
front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg
hard: 进阶
time: 30分钟
---
# 挑战:自定义水上坐骑
在本节中,我们一起来进行一个挑战,一起来自定义一个水上坐骑。在前面,我们已经制作出了一个非常完善的水鸭实体。目前,我们只需要为这个水鸭实体增加一些允许骑乘的相关组件即可完成挑战。
## 考查实体行为定义
我们先考查实体既有的定义文件。我们可以发现,实体中事实上已经有了一个可骑乘组件`minecraft:rideable`。它位于`minecraft:teal_adult`组件组中,内容为:
```json
"minecraft:rideable": {
"family_types": [
"zombie"
],
"seat_count": 1,
"seats": {
"position": [
0,
0.4,
0
]
}
}
```
我们可以很容易看出,这个组件是用于生成僵尸骑手的。我们只需要对其进行改变即可将其变为同时是用于玩家作为骑手的组件。
## 修改组件
我们通过查阅文档、参考其他实体的用法或查看编辑器中该组件的说明,可以轻易将其进行改造:
```json
"minecraft:rideable": {
"family_types": [
"zombie",
"player"
],
"interact_text": "action.interact.mount",
"seat_count": 1,
"seats": {
"position": [
0,
0.4,
0
]
}
}
```
我们可以通过在`family_types`字段中添加族的类型,然后通过`interact_text`来添加一个携带UI的交互按钮文本。这样我们的水鸭便可以在玩家与其交互时将玩家设为其骑手从而实现玩家骑乘在水鸭上的功能。
## 添加组件
但是,这并不意味着挑战便到此结束。因为目前我们的玩家还无法控制水鸭。我们骑在目前的水鸭上就好比没有萝卜钓竿就骑在猪上一样,无法控制方向,任凭其乱窜。我们需要添加一个控制组件。非常幸运的是,我们恰有一个可以通过方向键或方向按钮来控制实体移动的组件`minecraft:input_ground_controlled`。我们将其加在同样的位置。
```json
"minecraft:rideable": {
"family_types": [
"zombie",
"player"
],
"interact_text": "action.interact.mount",
"seat_count": 1,
"seats": {
"position": [
0,
0.4,
0
]
}
},
"minecraft:input_ground_controlled": {}
```
这样,我们的水鸭便成为了一个可骑乘可控制同时具备游泳能力的实体了。
![](./images/8.8_in-game.png)
完整的行为包定义代码如下:
```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
},
"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": {
"family_types": [
"zombie",
"player"
],
"interact_text": "action.interact.mount",
"seat_count": 1,
"seats": {
"position": [
0,
0.4,
0
]
}
},
"minecraft:input_ground_controlled": {},
"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
}
}
}
}
},
"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,
"can_sink": false,
"avoid_damage_blocks": true
},
"minecraft:movement.basic": {},
"minecraft:jump.static": {},
"minecraft:can_climb": {},
"minecraft:despawn": {
"despawn_from_distance": {}
},
"minecraft:behavior.rise_to_liquid_level": {
"priority": 0,
"liquid_y_offset": -0.5,
"rise_delta": 0.01,
"sink_delta": 0.01
},
"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": {}
},
"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"]
}
}
}
}
}
```