diff --git a/mconline/100-历史归档教程/10-addon教程/README.md b/mconline/100-历史归档教程/10-addon教程/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/README.md b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/1_1.jpg b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/1_1.jpg new file mode 100644 index 0000000..96a7137 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/1_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/2_1.jpg b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/2_1.jpg new file mode 100644 index 0000000..4c49ce7 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/2_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/3_1.jpg b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/3_1.jpg new file mode 100644 index 0000000..404b68a Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/3_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/4_1.jpg b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/4_1.jpg new file mode 100644 index 0000000..ccc492d Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/4_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/4_2.jpg b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/4_2.jpg new file mode 100644 index 0000000..32e3898 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/4_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/4_3.jpg b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/4_3.jpg new file mode 100644 index 0000000..427a9ac Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/4_3.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/4_4.jpg b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/4_4.jpg new file mode 100644 index 0000000..9702df3 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/4_4.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/4_5.jpg b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/4_5.jpg new file mode 100644 index 0000000..206ea1d Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/4_5.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/4_6.jpg b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/4_6.jpg new file mode 100644 index 0000000..0a895e6 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/4_6.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/4_7.jpg b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/4_7.jpg new file mode 100644 index 0000000..2b1e498 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/4_7.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/4_8.jpg b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/4_8.jpg new file mode 100644 index 0000000..505ce1b Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/4_8.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/5_1.jpg b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/5_1.jpg new file mode 100644 index 0000000..e18baaf Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/5_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/5_2.jpg b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/5_2.jpg new file mode 100644 index 0000000..b3610b4 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/5_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/5_3.jpg b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/5_3.jpg new file mode 100644 index 0000000..d3c03dc Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/images/5_3.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/课程01.认识主界面.md b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/课程01.认识主界面.md new file mode 100644 index 0000000..f022832 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/课程01.认识主界面.md @@ -0,0 +1,27 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/1_1.dc061f16.jpg +hard: 入门 +time: 5分钟 +--- + +# 认识主界面 + +#### 作者:境界 + + + +预期目标:熟悉MCSTUDIO的部分界面,以及开发一个自定义配方 + +![](./images/1_1.jpg) + + + +在开发者官网:[https://mc.163.com/dev/index.html](https://mc.163.com/dev/index.html),下载MCSTUDIO应用程序进行安装,然后双击MCSTUDIO图标,在登陆界面中输入开发者账号密码进行登录,开发者就可以看到MCSTUDIO的主界面了。 + + + +MCSTUDIO主界面分为上方的信息区域,侧边的导航区域,以及中间的内容区域。你可以在导航区域选择你需要跳转到其他功能页面的按钮。 + + + +作为新手开发者,本章节将引导你如何使用MCSTUDIO制作第一个玩法。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/课程02.新建玩法项目.md b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/课程02.新建玩法项目.md new file mode 100644 index 0000000..5feae3b --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/课程02.新建玩法项目.md @@ -0,0 +1,25 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/2_1.4e93e673.jpg +hard: 入门 +time: 5分钟 +--- + +# 新建玩法项目 + +#### 作者:境界 + + + +![](./images/2_1.jpg) + + + +进入MCSTUDIO后,系统默认停留在新建内容区域。当前工具只支持创建基岩版地图和基岩版Add-on。在基岩版组件-空白区域内,开发者可以选择创建地图模板或是Add-on模板。 + + + +它们的区别在于,创建地图模板后,你在编辑器内做的玩法,会与地图导出变成一个完整的玩法地图。建立Add-on模板时,最终只会导出Add-on玩法。 + + + +如果是一个新的Add-on玩法,开发者应当选择建立一个空白Add-on模板,点击启动编辑,就可以开始大展拳脚了。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/课程03.探索关卡编辑器.md b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/课程03.探索关卡编辑器.md new file mode 100644 index 0000000..033c496 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/课程03.探索关卡编辑器.md @@ -0,0 +1,46 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/3_1.32c9600e.jpg +hard: 入门 +time: 10分钟 +--- + +# 探索关卡编辑器 + +#### 作者:境界 + + + +![](./images/3_1.jpg) + + + +#### 什么是舞台? + +舞台是访问当前正出现在地图中的原有内容和新增内容的入口。当玩家出生在出生点附近时,世界会根据群系在玩家周围自然生成新的生物和方块。开发者可以通过舞台看到当前存在的生物种类,点击生物种类的折叠选项,还可以精确选择到某一个生物上,视角摄像机会自动定位到所选中的生物身上。 + +原有内容是地图中,由游戏世界自然生成的生物。新增内容则是开发者进入编辑器后,用编辑器工具放置的生物。 +在编辑器的中央,是一套内嵌在MCSTUDIO窗口上的游戏界面。开发者可以轻按鼠标左键选择在视角范围内的生物,这与在右侧区域里选择单一生物是同样的效果。因此,游戏世界本身也是舞台。 + + + +#### 移动与转移视角 + +在MCSTUDIO控制游戏界面内人物的移动方式和在游戏客户端上的方法大致相同。开发者在键盘上按WASD的行走按键控制人物的前后左右移动。 + +鼠标左键可以选择生物,鼠标右键可以旋转视角,鼠标中键滑轮可以拉伸视距。 + +如果双击空格,视角则会像创造模式下的玩家一样上升,按住shift键则会下降。 + +如果视角未移动,请将光标移向游戏界面区域即可。 + + + +#### 调试、保存与备份 + +当游戏界面处于编辑模式时,开发者是无法作为一个玩家与世界产生互动的。 + +这时候若想调试,就需要点击运行按钮。它会带领你进入游戏模式,及时获得玩法的测试反馈。 + +保存按钮提供保存功能,你也可以同时按住CRTL+S进行快捷的进度保存。 + +备份按钮提供克隆功能,它会将正在制作的内容再克隆一份。开发者可以稍后在主页面的基岩版组件中找到。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/课程04.自定义一种新的配方.md b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/课程04.自定义一种新的配方.md new file mode 100644 index 0000000..fbd3175 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/课程04.自定义一种新的配方.md @@ -0,0 +1,91 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/4_4.347fdf50.jpg +hard: 入门 +time: 10分钟 +--- + +# 自定义一种新的配方 + +#### 作者:境界 + + + +#### 通过编辑器新建配方 + +![](./images/4_1.jpg) + +移动鼠标点击添加组件,选择配方组件。 + + + +![](./images/4_2.jpg) + +配方组件默认会自带一个新增配方,若开发者需要新增更多配方,点击配方组件右侧的“+”号按钮即可新增配方。 + + + +#### 了解配方的各个属性 + +配方名称只会在编辑器的组件区域显示。因此掌握良好的命名习惯可以帮助开发者在下次进入编辑器时快速回忆起配方的功能。 + +![](./images/4_3.jpg) + +自定义ID是世界注册配方的唯一标识,若存在两个以上的配方标识一致,关联的配方会加载失败。因此命名可读性强的ID可以避免这一情况。注意:ID应以下划线/数字/英文为主,且对大小写不敏感,因此Aa和aa相同,开发者应以小写加下划线的格式为规范,例如:a_A,配方类型目前支持编辑有序合成、无序合成、熔炉配方。其中有序合成、无序合成适用在工作台中,熔炉配方适用于熔炉、高炉、篝火等中。 + +有序配方代表玩家需要按照格子顺序放好材料,工作台才会合成出配方道具。 + +无序配方代表玩家只需在格子上放好材料,工作台就会合成出配方道具。 + +配方结果是配方合成成功后,合成出的道具类型。 + +结果数量决定合成出的道具数量为几个。 + + + +#### 新建配方:可合成的命名牌 + +命名牌是游戏前期很难获得的道具,它只能在钓鱼、村民交易、和世界中的宝箱中找到。它可以给生物命名,防止生物被世界刷新掉。现在,通过MCSTUDIO的配方组件,开发者可以添加新配方来获得它。 + +![](./images/4_4.jpg) + + + +新增一个配方组件,将默认的配方名称改为自定义配方:命名牌。 + +这里将自定义ID改为crafting_custom_nametag,开发者也可以输入自己认为可读性强的ID命名。 + +配方类型选择有序配方,适用方块选择工作台。 + +![](./images/4_5.jpg) + + + +以上图例为配方构造示范,点击对应的网格,可以打开资源选择窗口。最快的方法是通过输入材料名称作为关键词搜素,选择好材料后,点击确认即可。 + +![](./images/4_6.jpg) + +点击配方结果右侧的文件夹按钮,打开资源选择窗口再选择命名牌。 + + + +#### 新建配方:可合成的生物蛋 + +生物蛋是只有在创造模式下才能获得的道具。手持生物蛋点击方块可以生成一个对应的生物。现在,通过MCSTUDIO的配方组件,开发者可以添加新配方来获得它。 + +新增一个配方组件,将默认的配方名称改为自定义配方:村民蛋。 + +这里将自定义ID改为crafting_custom_villager,开发者也可以输入自己认为可读性强的ID命名。 + +配方类型选择无序配方,适用方块选择工作台。 + +![](./images/4_7.jpg) + +以图例为配方成分示范,点击对应的“+”号按钮,选择成分右侧的文件夹按钮,可以打开资源选择窗口。最快的方法是通过输入材料名称作为关键词搜素。选择好材料后,点击确认即可。 + + + +![](./images/4_8.jpg) + + + +点击配方结果右侧的文件夹按钮,打开资源选择窗口选择生成村民。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/课程05.自测玩法,并在手机上运行它.md b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/课程05.自测玩法,并在手机上运行它.md new file mode 100644 index 0000000..3f6ea96 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第01章:用MCSTUDIO构建第一个玩法/课程05.自测玩法,并在手机上运行它.md @@ -0,0 +1,31 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/5_3.8964e499.jpg +hard: 入门 +time: 10分钟 +--- + +# 自测玩法,并在手机上运行它 + +#### 作者:境界 + + + +#### 自测玩法 + +![](./images/5_1.jpg) + +点击运行,编辑器会弹出客户端窗口,开发者就可以测试本地玩法了。 + +在玩法测试前,请记得随时保存当前的制作进度,避免遇到进度丢失的问题。 + + + +#### 在手机上运行Add-on + +![](./images/5_2.jpg) + +点击最近选项,你会看到刚刚制作的组件。鼠标移动到组件上点击更多,再点击发布,选择新建项目。接着根据发布资源的流程依次填写好相关内容,点击保存,再点击自测或提交审核。 + +![](./images/5_3.jpg) + +然后在MCSTUDIO-管理-测试版启动器下载手机版测试包,开发者就可以在手机测试版上体验到自己做的第一个玩法了。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/README.md b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_1.jpg b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_1.jpg new file mode 100644 index 0000000..3f70118 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_2.jpg b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_2.jpg new file mode 100644 index 0000000..ad43b1c Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_3.jpg b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_3.jpg new file mode 100644 index 0000000..ee9ae76 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_3.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_c.jpg b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_c.jpg new file mode 100644 index 0000000..739f474 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_c.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_dengl.jpg b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_dengl.jpg new file mode 100644 index 0000000..2fb413d Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_dengl.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_diushengwud.jpg b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_diushengwud.jpg new file mode 100644 index 0000000..e6d18ec Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_diushengwud.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_hb.jpg b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_hb.jpg new file mode 100644 index 0000000..297ba94 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_hb.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_hl.jpg b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_hl.jpg new file mode 100644 index 0000000..6ea1082 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_hl.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_j.jpg b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_j.jpg new file mode 100644 index 0000000..3d94321 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_j.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_mf.jpg b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_mf.jpg new file mode 100644 index 0000000..5767d9c Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_mf.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_mnjl.jpg b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_mnjl.jpg new file mode 100644 index 0000000..91d003c Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_mnjl.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_qukuai.jpg b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_qukuai.jpg new file mode 100644 index 0000000..3138616 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_qukuai.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_sdf.jpg b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_sdf.jpg new file mode 100644 index 0000000..7885bfa Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_sdf.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_shengwud.jpg b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_shengwud.jpg new file mode 100644 index 0000000..cf502cf Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_shengwud.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_tiej.jpg b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_tiej.jpg new file mode 100644 index 0000000..4cec510 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_tiej.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_wupinl.jpg b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_wupinl.jpg new file mode 100644 index 0000000..a8d46e8 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_wupinl.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_xz.jpg b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_xz.jpg new file mode 100644 index 0000000..d20d969 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_xz.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_ym.jpg b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_ym.jpg new file mode 100644 index 0000000..46445b2 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/images/1_ym.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/课程01.认识Minecraft世界.md b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/课程01.认识Minecraft世界.md new file mode 100644 index 0000000..2510747 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第02章:认识Minecraft世界/课程01.认识Minecraft世界.md @@ -0,0 +1,173 @@ +--- +front: https://nie.res.netease.com/r/pic/20220408/3dcdbaa9-9583-4921-a28d-33df67afe608.png +hard: 入门 +time: 20分钟 +selection: true +--- + +# 认识Minecraft世界 + +#### 作者:境界 + + + +在Minecraft世界中,构建世界的基本单位是方块(block)。建筑是由方块组成的,群系是由方块组成的,自然世界中的生物也是由模型中的方块构成的。 + +学会认识方块,代表你开始慢慢接受MC风格对自己创作的影响,也将会对你接下来的学习过程带来很大帮助。除此之外,世界还有许多延伸出来的内容,它们以各自的功能特色服务着这个世界。 + + + +#### 世界 + +世界即是一个游戏存档。它可能是你本地游玩的单人存档,是多人联机的云端存档,或是大型服务器联机的服务器存档。存档可以不局限于某一个终端,因此,你可以把服务器的存档放到单人上游玩,或者将单人的生存存档放到服务器上使用。 + +![](./images/1_1.jpg) + +理论上,在Minecraft世界中,玩家无法走遍世界的全部角落。因为这个世界没有边界,每个世界都会因“世界种子”而长得独一无二。世界种子由一串包含正负整数的数字字符组成,游戏会根据这个值来创建不同样子的世界。玩家可以通过存档的设置界面来分享种子,共享具有相同地貌模样的世界存档。 + + + +![](./images/1_2.jpg) + +当玩家不断冒险的时候,游戏会根据世界种子和算法生成玩家要探索的下一个区域。由于世界是无限大的,因此Minecraft世界使用区块概念来实现这一功能。 + + + +#### 区块 + +![](./images/1_qukuai.jpg) + +区块是由长宽各16格,高度255格的世界方块组成的区域。当玩家进入游戏时,他所出现在的位置一定坐落于世界的某个区块当中。 +通过设置世界存档的模拟距离,例如下图中的6个区块,则玩家所能看到的区域只有这6个区块大小。其他区块不会加载。只有当玩家移动到其他区块时,游戏才会开始加载那些区块, 并卸载此前的区块,达到流畅的游戏体验。 + +![](./images/1_mnjl.jpg) + + + +#### 生物群系 + +![](./images/1_3.jpg) + +生物群系会横跨多个区块。不同的生物群系有着不同的自然环境。如上图展示了沼泽、森林、平原三者交汇。沼泽的水犹如墨绿色,而森林和平原的水呈淡蓝色。两者在汇流处会有颜色的过渡。不仅如此,生物群系的种类还决定了它的海拔高度、植物群、地理特征、温度、湿度、天空、植被颜色、生物分布等。这种近似现实地球的气候区,又会因为“世界种子”而永不重样:玩家在同样的位置,会看到不一样的方块、不一样的生物,每个人在Minecraft世界的故事也因此而不同。 + + + +#### 方块 + +方块是我的世界的基本组成单位,大多数方块长宽高在1单位长度上。不同的方块具有不同的材质。一种方块可能存在多种方块状态,例如不同颜色的羊毛。同时一种方块可能可以贮存数据,例如箱子会保存箱子内的道具信息。 + + +![](./images/1_ym.jpg) + +例:不同颜色的羊毛 + + +![](./images/1_xz.jpg) + +例:一个大箱子拥有54格的储存空间,每格格子可以存放一种物品,每种物品都包含着复杂的物品信息,它们被以数据的格式保存在箱子方块中。 + + + +#### 生物 + +生物是在游戏内可见的、处于活动中的、拥有自主意识的实体统称。生物都具有自己独特的AI行为,受到游戏的调度,并影响着世界。 + +![](./images/1_hl.jpg) + +例:正在扑向鸡的狐狸! + + + +![](./images/1_mf.jpg) + +例:正在采花蜜的蜜蜂。 + + + +玩家是一种特殊的生物,他的行为来自玩家自身的意志,需要屏幕前的人来操控。与其他生物相比,玩家有着其他的作为Minecraft核心的玩法机制。如获取经验、合成道具、进食等。 + +![](./images/1_sdf.jpg) + +例:默认情况下,玩家拥有两款基础皮肤,Steve和Alex。在皮肤创作中,分别对应着粗手臂皮肤和细手臂皮肤。 + + + +#### 实体 + +实体是在游戏内具有典型行为的运动对象,它是生物的基类。在Minecraft世界中,并不是所有的实体都具有自主的AI,有些需要借由外力去操控,有些则会表现得像方块一样。 + + + +![](./images/1_c.jpg) + + + +例:船具有行驶速度、碰撞体积等典型行为,但它被放置于水上时不会自主滑动。需要由玩家控制来帮助其进行移动迁徙。 + + + +![](./images/1_hb.jpg) + +例:画是悬挂在墙上的装饰型实体,除观赏性外无自主行为。 + + + +#### 抛射物 + +抛射物是一种受外力被抛射至空中的实体,它们在飞行过程中会受到重力与摩擦力的影响。不同的抛射物可能具有不同的能力。 + +![](./images/1_j.jpg) + +例:基岩版世界种,除了普通的箭以外,还有16种带有状态效果的药水箭。玩家持弓蓄力的程度会直接影响到随后箭在空气中飞行的加速度。 + + + +#### 物品 + +物品是物品实体和物品道具的统称。物品道具通常只会出现在储存格,包括玩家背包栏和快捷栏、各种箱子、生物背包里等。物品道具通常包含三种行为,一种是方块物品,即在物品栏里是物品,放置时是方块。一种是放置后会变成实体,如船物品。最后一种是可以被使用,如武器、工具、食物、装备、合成材料等。 + +当生物或者玩家丢弃某件物品道具时,物品会以实体的方式存在于地面上,若它被丢在水里,则会受到水的浮力漂浮至水面上。当具有捡起物品能力的生物靠近这个物品实体时,实体会被清理,而生物的物品栏内会出现这个物品道具。 + + + +![](./images/1_tiej.jpg) + +例:作为实体形态的地狱合金剑被玩家靠近捡起来后,可以获得一个剑物品。它可以提高玩家8点攻击伤害,当玩家每次手持它并对目标挥出时生效。 + + + +![](./images/1_shengwud.jpg) + +例:作为实体形态的生物蛋被玩家靠近捡起来后,可以获得一个生物蛋物品。玩家右键地面可以召唤一只对应生物。 + + + +![](./images/1_dengl.jpg) + +例:作为实体形态的灯被玩家靠近捡起来后,可以获得一个灯物品。玩家右键地面可以一个灯方块。 + + + +#### 资源特效 + +资源特效代指Minecraft游戏中使用到的各类资源文件。在后续的章节中,会针对每一块内容制作单独列出所需的文件内容。 + +资源可以分为音效资源、贴图资源、模型资源等。其中中国版基岩版中,针对模型资源,开花组允许开发者使用骨骼模型来替换游戏中大部分生物的模型,进而实现不同的表现效果。 + +特效即是在Minecraft世界中模拟各种自然效果的游戏内容。如玩家在挖掘方块时洒落的方块屑粒,经过水面时溅起的水花等。在《原版粒子和特效粒子》章节中,我们还会更加深入地了解如何制作这一系列的内容。 + + + +#### UI界面 + +界面是在整个游戏流程中,世界和用户之间进行交互的管道。玩家可以通过触发UI来控制对应的游戏逻辑。在《进阶:创建界面》章节中,教程会带领开发者一起学习制作新的界面的方法。 + +![](./images/1_wupinl.jpg) + +例:电脑基岩版中,通过鼠标点击物品,在界面内将其拖曳至箱子内。 + + + +![](./images/1_diushengwud.jpg) + +例:在手游版中,通过手指点击快捷物品栏的格子来切换物品。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第03章:基本开发工具介绍/README.md b/mconline/100-历史归档教程/10-addon教程/第03章:基本开发工具介绍/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/10-addon教程/第03章:基本开发工具介绍/images/1_1.jpg b/mconline/100-历史归档教程/10-addon教程/第03章:基本开发工具介绍/images/1_1.jpg new file mode 100644 index 0000000..ce19dca Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第03章:基本开发工具介绍/images/1_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第03章:基本开发工具介绍/images/1_2.jpg b/mconline/100-历史归档教程/10-addon教程/第03章:基本开发工具介绍/images/1_2.jpg new file mode 100644 index 0000000..7db1e80 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第03章:基本开发工具介绍/images/1_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第03章:基本开发工具介绍/images/1_3.jpg b/mconline/100-历史归档教程/10-addon教程/第03章:基本开发工具介绍/images/1_3.jpg new file mode 100644 index 0000000..648aa77 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第03章:基本开发工具介绍/images/1_3.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第03章:基本开发工具介绍/images/1_4.jpg b/mconline/100-历史归档教程/10-addon教程/第03章:基本开发工具介绍/images/1_4.jpg new file mode 100644 index 0000000..0bc1d0a Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第03章:基本开发工具介绍/images/1_4.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第03章:基本开发工具介绍/课程01.基本开发工具介绍.md b/mconline/100-历史归档教程/10-addon教程/第03章:基本开发工具介绍/课程01.基本开发工具介绍.md new file mode 100644 index 0000000..1e7f0dc --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第03章:基本开发工具介绍/课程01.基本开发工具介绍.md @@ -0,0 +1,52 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/1_2.1dfacb37.jpg +hard: 入门 +time: 5分钟 +selection: true +--- + +# 基本开发工具介绍 + +#### 作者:境界 + + + +#### 开发工具 + +开发者在对玩法进行开发设计时,可能会脱离MCSTUDIO来编写更复杂的代码。趁手的工具可以让开发效率变得更高,这里给开发者提供了两种方向的选择: + +使用外部功能较全的文本编辑器。如:Visual Studio Code、NotePad++、手机上的相关软件,可以对基岩版原生的附加包(Add-on)进行编辑。附加包的相关解释可以查看教程《Addon-万象的基石》。 + +![](./images/1_1.jpg) + +另外,若是想要使用更多接口实现更复杂的模组玩法,则可以选择中国版的MODSDK框架,基于该框架可以使用Python语言进行模组开发。在开发过程中,建议开发者使用标准的IDE(集成开发环境),如:Pycharm。这样的工具可以帮助开发者在使用Python语言开发时提高效率。更多MODSDK框架的介绍,可以查看教程《初见MOD》。 + +![](./images/1_2.jpg) + + + +#### 美术工具 + +我的世界中,生物的模型、动画、贴图,以及物品、方块贴图等,这些都算在美术资源的范畴。 + +开发者在开发玩法功能时,需要各种美术资源。绘制简单的方块、物品贴图时,开发者可以选择自己常用的绘图软件。而制作像生物模型、方块模型这样的资源,则需要用到BlockBench。它是由国外个人开发者Jennis开发维护的一款针对我的世界美术制作的软件。 + +它可以满足我的世界基岩版的生物模型、方块模型、皮肤等资源的制作和绘制。同时可将制作好的生物模型在同一个软件里进行动画制作、粒子预览、音效演示等。 + +下载本地版:[https://blockbench.net/downloads/](https://blockbench.net/downloads/) + +WEB版入口:[https://web.blockbench.net/](https://web.blockbench.net/) + +![](./images/1_3.jpg) + + + +#### 地图工具 + +地图包括地形和建筑,开发者可以自行选择制作工具、资源中心的建筑辅助组件、或是使用MCSTUDIO的地图编辑器功能。 + +这里主要补充一种途径,方便开发者将JAVA版与基岩版地图格式互相转换的方法。MCCTOOLCHESTPE是由cynodontA开发维护的一款免费闭源的MC世界存档转化软件,支持Java版地图转基岩版、基岩版地图转Java。 + +下载入口:[http://mcctoolchest.com/download](http://mcctoolchest.com/download) + +![](./images/1_4.jpg) \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/README.md b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/1_1.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/1_1.png new file mode 100644 index 0000000..07143b3 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/1_1.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/2_1.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/2_1.png new file mode 100644 index 0000000..168d07b Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/2_1.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/2_10.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/2_10.png new file mode 100644 index 0000000..4388598 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/2_10.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/2_2.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/2_2.png new file mode 100644 index 0000000..c0725bf Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/2_2.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/2_3.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/2_3.png new file mode 100644 index 0000000..ae4c9c8 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/2_3.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/2_4.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/2_4.png new file mode 100644 index 0000000..2d11e6a Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/2_4.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/2_5.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/2_5.png new file mode 100644 index 0000000..aaa1921 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/2_5.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/2_6.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/2_6.png new file mode 100644 index 0000000..6b0c7b0 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/2_6.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/2_7.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/2_7.png new file mode 100644 index 0000000..6c39ffb Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/2_7.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/2_8.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/2_8.png new file mode 100644 index 0000000..f470289 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/2_8.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/2_9.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/2_9.png new file mode 100644 index 0000000..7bb1332 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/2_9.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/3_1.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/3_1.png new file mode 100644 index 0000000..3b684b8 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/3_1.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/3_2.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/3_2.png new file mode 100644 index 0000000..3087834 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/3_2.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/3_3.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/3_3.png new file mode 100644 index 0000000..cf40efb Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/3_3.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/3_4.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/3_4.png new file mode 100644 index 0000000..d430044 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/3_4.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/3_5.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/3_5.png new file mode 100644 index 0000000..edfbfc2 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/3_5.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/3_6.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/3_6.png new file mode 100644 index 0000000..7ebf6bc Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/3_6.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/3_7.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/3_7.png new file mode 100644 index 0000000..2f7a488 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/3_7.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/3_8.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/3_8.png new file mode 100644 index 0000000..d21e958 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/3_8.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/3_9.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/3_9.png new file mode 100644 index 0000000..f1e92a6 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/3_9.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_1.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_1.png new file mode 100644 index 0000000..6e70826 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_1.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_10.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_10.png new file mode 100644 index 0000000..0dcb304 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_10.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_11.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_11.png new file mode 100644 index 0000000..f221497 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_11.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_12.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_12.png new file mode 100644 index 0000000..a812d92 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_12.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_13.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_13.png new file mode 100644 index 0000000..83036d0 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_13.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_14.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_14.png new file mode 100644 index 0000000..6a436cb Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_14.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_15.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_15.png new file mode 100644 index 0000000..c14a258 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_15.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_16.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_16.png new file mode 100644 index 0000000..aeb587d Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_16.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_17.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_17.png new file mode 100644 index 0000000..9c006fc Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_17.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_18.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_18.png new file mode 100644 index 0000000..4851539 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_18.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_19.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_19.png new file mode 100644 index 0000000..55f19ac Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_19.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_2.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_2.png new file mode 100644 index 0000000..d66a2d1 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_2.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_20.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_20.png new file mode 100644 index 0000000..adf0185 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_20.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_21.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_21.png new file mode 100644 index 0000000..4a3032b Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_21.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_22.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_22.png new file mode 100644 index 0000000..d43b077 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_22.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_23.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_23.png new file mode 100644 index 0000000..7918d35 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_23.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_24.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_24.png new file mode 100644 index 0000000..c0aaf7c Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_24.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_25.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_25.png new file mode 100644 index 0000000..6f69372 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_25.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_26.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_26.png new file mode 100644 index 0000000..d037d68 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_26.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_27.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_27.png new file mode 100644 index 0000000..970e2c6 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_27.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_28.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_28.png new file mode 100644 index 0000000..97fe9da Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_28.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_29.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_29.png new file mode 100644 index 0000000..bff10cd Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_29.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_3.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_3.png new file mode 100644 index 0000000..d908481 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_3.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_30.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_30.png new file mode 100644 index 0000000..932b8c7 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_30.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_31.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_31.png new file mode 100644 index 0000000..f3c8aef Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_31.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_32.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_32.png new file mode 100644 index 0000000..66467f1 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_32.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_33.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_33.png new file mode 100644 index 0000000..45e1cca Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_33.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_34.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_34.png new file mode 100644 index 0000000..61dae7e Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_34.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_35.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_35.png new file mode 100644 index 0000000..b1c56ef Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_35.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_36.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_36.png new file mode 100644 index 0000000..85c78d9 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_36.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_37.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_37.png new file mode 100644 index 0000000..45afc1f Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_37.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_38.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_38.png new file mode 100644 index 0000000..49534c1 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_38.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_39.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_39.png new file mode 100644 index 0000000..56c72f6 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_39.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_4.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_4.png new file mode 100644 index 0000000..c3ab880 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_4.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_40.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_40.png new file mode 100644 index 0000000..60ff74b Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_40.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_41.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_41.png new file mode 100644 index 0000000..03b0192 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_41.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_42.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_42.png new file mode 100644 index 0000000..a0a0056 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_42.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_43.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_43.png new file mode 100644 index 0000000..dfeb61a Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_43.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_44.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_44.png new file mode 100644 index 0000000..0420e0c Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_44.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_45.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_45.png new file mode 100644 index 0000000..f0ba719 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_45.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_46.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_46.png new file mode 100644 index 0000000..fa08ec8 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_46.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_47.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_47.png new file mode 100644 index 0000000..96d630a Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_47.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_48.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_48.png new file mode 100644 index 0000000..ca8dfb9 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_48.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_49.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_49.png new file mode 100644 index 0000000..2955b06 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_49.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_5.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_5.png new file mode 100644 index 0000000..64258fe Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_5.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_50.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_50.png new file mode 100644 index 0000000..c6beab7 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_50.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_51.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_51.png new file mode 100644 index 0000000..8d7ea85 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_51.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_52.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_52.png new file mode 100644 index 0000000..c64913c Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_52.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_53.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_53.png new file mode 100644 index 0000000..f86c6b8 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_53.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_54.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_54.png new file mode 100644 index 0000000..651a576 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_54.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_55.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_55.png new file mode 100644 index 0000000..1b51efb Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_55.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_56.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_56.png new file mode 100644 index 0000000..80d5579 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_56.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_57.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_57.png new file mode 100644 index 0000000..6c44982 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_57.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_58.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_58.png new file mode 100644 index 0000000..b08c21b Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_58.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_59.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_59.png new file mode 100644 index 0000000..2e304cf Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_59.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_6.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_6.png new file mode 100644 index 0000000..3a02bb7 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_6.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_60.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_60.png new file mode 100644 index 0000000..d3ca600 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_60.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_61.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_61.png new file mode 100644 index 0000000..78c48fb Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_61.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_62.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_62.png new file mode 100644 index 0000000..09deda2 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_62.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_63.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_63.png new file mode 100644 index 0000000..04483b3 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_63.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_64.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_64.png new file mode 100644 index 0000000..a5cdb5f Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_64.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_65.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_65.png new file mode 100644 index 0000000..4f3df31 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_65.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_66.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_66.png new file mode 100644 index 0000000..ac7913a Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_66.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_67.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_67.png new file mode 100644 index 0000000..ea2136f Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_67.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_68.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_68.png new file mode 100644 index 0000000..283a6f2 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_68.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_69.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_69.png new file mode 100644 index 0000000..f20bc16 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_69.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_7.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_7.png new file mode 100644 index 0000000..50d4ace Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_7.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_70.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_70.png new file mode 100644 index 0000000..20dd29e Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_70.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_71.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_71.png new file mode 100644 index 0000000..9be62e9 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_71.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_72.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_72.png new file mode 100644 index 0000000..d3634ab Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_72.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_73.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_73.png new file mode 100644 index 0000000..2aeb4ba Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_73.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_74.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_74.png new file mode 100644 index 0000000..1424e5a Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_74.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_75.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_75.png new file mode 100644 index 0000000..9070370 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_75.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_76.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_76.png new file mode 100644 index 0000000..72701a2 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_76.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_77.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_77.png new file mode 100644 index 0000000..b30ddc1 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_77.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_78.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_78.png new file mode 100644 index 0000000..9f7cc5a Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_78.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_79.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_79.png new file mode 100644 index 0000000..fba4cd6 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_79.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_8.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_8.png new file mode 100644 index 0000000..8146ea0 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_8.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_80.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_80.png new file mode 100644 index 0000000..20f7341 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_80.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_81.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_81.png new file mode 100644 index 0000000..d158087 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_81.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_9.png b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_9.png new file mode 100644 index 0000000..0d1e589 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/images/5_9.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/课程01.指令系统,你的创作管家.md b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/课程01.指令系统,你的创作管家.md new file mode 100644 index 0000000..8232445 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/课程01.指令系统,你的创作管家.md @@ -0,0 +1,17 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/1_1.1a7c5b55.png +hard: 入门 +time: 5分钟 +--- + +# 指令系统,你的创作管家 + +#### 作者:境界 + + + +#### 什么是指令 + +指令是由使用者通过输入特定文本而激活的高级功能。在游戏中,冒险家可以通过指令达到一些作弊手段,而开发者可以利用指令达到一些更高级的做法,做出有趣好玩的地图。 + +![](./images/1_1.png) \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/课程02.如何使用指令.md b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/课程02.如何使用指令.md new file mode 100644 index 0000000..cd8ac20 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/课程02.如何使用指令.md @@ -0,0 +1,112 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/2_1.d438c68d.png +hard: 入门 +time: 20分钟 +--- + +# 如何使用指令 + +#### 作者:境界 + + + +#### 使用聊天框输入指令 + +在电脑客户端中,按下快捷键“T”或者“/”可以打开聊天窗口。按下“/”时会在输入栏内自动添加上命令前缀格式“/”,所以“/”是个很实用的快捷键。 + + +而在手游版中,需要点击屏幕上方的聊天按钮唤出聊天窗口。 + + +打开聊天窗口时,电脑客户端还支持用TAB补全命令。这意味着不需打出完整的命令格式,多用tab补全命令可以节省许多制作时间。 + +而手游版目前还不支持命令补全。 + + + +#### 通过命令方块执行指令 + +命令方块(Command Block)是一种用以执行命令的特殊方块。它不可以通过创造背包选取获得,只能在游戏内输入/give @s command_block获得。注意:需要开启设置-游戏中的启用命令方块选项。 + + + +![](./images/2_1.png) + +命令可以输入到命令输入框内。需要多重指令时,按enter键换行。按下+号可以放大输入框看到超出框外的指令。 + +![](./images/2_2.png) + +点击“脉冲/连锁/循环”选项可更改命令方块类型。默认为“脉冲”,命令方块改变类型时也会改变颜色。 + + + +![](./images/2_3.png) + +脉冲型命令方块是橙色的。这种方块是“标准”的命令方块,激活一次执行一次命令。 + +连锁型命令方块是绿色的。这种方块只在指向它的方块执行命令时才会执行命令。 + +循环型命令方块是紫色的。这种方块被激活时每一游戏刻都执行一次命令。 + + + +![](./images/2_4.png) + +打开“上一个输出”按钮,可以获得运行在该命令方块下的部分带有返回信息的指令的返回信息。例如:使用testblock指令检查某个坐标上的方块是否是某个种类,返回检测结果。 + + + +![](./images/2_5.png) + +点击“无条件/有条件的”选项来改变命令方块的条件制约行为: + +条件制约”:只有当前一个指向自己的命令方块执行指令成功时,后一个命令方块才会继续执行指令。 + +不受制约:该命令方块将忽视条件制约行为。 + + + +![](./images/2_6.png) + +激活命令方块有两种方式,分别是在方块界面中的红石选项里选择保持开启或者红石开启。 + +保持开启:会让命令方块一直保持在工作状态。 + +红石开启:则必须像红石机械一样激活才能执行指令。 + + + +以下是可以激活命令方块的红石物件: + +放在命令方块侧面的红石火把、红石块、阳光探测器、按钮等。 + +互相邻接的充能方块。 + +输出信号且指向向命令方块的红石比较器或红石中继器。 + +激活的且在命令方块四周的红石粉。 + +![](./images/2_7.png) + +指定循环型命令方块是否在激活后立即执行其指令,如果已禁用,则从激活时起延迟后第一次执行。 + + + +![](./images/2_8.png) + +对于脉冲型命令方块和连锁型命令方块,指定在被激活或触发后,执行指令之前延迟的游戏时间。对于循环型命令方块,指定其重新执行指令所延迟的游戏时间。对于脉冲型命令方块和循环型命令方块,延迟设置为“0”和“1”的效果相同,游戏将把“0”视为“1”。但是,对于连锁型命令方块,“0”和“1”是不同的。当设置延迟的命令方块被破坏后,未执行的指令将不会被执行。 + + + +#### 通过function执行指令 + +function是一种文件类型,它可由Add-on识别,作为一组指令集在游戏内被指令/function <文件名称>执行。文件的后缀名为.mcfunction。以下图为例,这里会执行行为包下,function文件夹里的test.mcfunction文件。 + +![](./images/2_9.png) + + + +未在正确路径下找到该文件时,则返回异常。 + +![](./images/2_10.png) + diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/课程03.理解指令参数.md b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/课程03.理解指令参数.md new file mode 100644 index 0000000..a2d4eb4 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/课程03.理解指令参数.md @@ -0,0 +1,303 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/3_1.21fa97ec.png +hard: 进阶 +time: 40分钟 +--- + +# 理解指令参数 + +#### 作者:境界 + + + +#### 什么是坐标参数 + +![](./images/3_1.png) + +上图示例提示下一类指令参数填写坐标参数。 + + + +绝对坐标:世界坐标被视作绝对坐标。例如,冒险家使用指令“/tp 128 64 128”时,会将人物传送到世界坐标为“x:128 y:64 z:128”的位置上。 + + + +相对坐标:相对坐标视为绝对坐标的偏移量。通常,冒险家可以用波浪符号~来指代坐标为当前所站的世界坐标。因此,可以在~符号旁添加计算式来达到坐标偏移的结果。例如,冒险家使用指令“/tp ~+1 ~-1 ~”时,会将他传送到距离当前世界坐标X轴正一格,Y轴负一格的位置上。 + + + +局部坐标:局部坐标为视线方向的偏移量。通常,冒险家可以用插入符号“^”来指代坐标为当前头部的方向坐标。 + +![](./images/3_2.png) + +与相对坐标不同的是,局部坐标以头部方向为基准点。例如,若冒险家希望让传送对象向面朝方向传送,就会用到插入局部坐标。还是以“tp”指令为例,冒险家使用指令“/tp ^ ^ ^+5”时,会把冒险家从当前位置向面朝方向的位置传送5格。 + + + +#### 什么是目标选择器 + +![](./images/3_3.png) + +上图示例提示下一类指令参数填写目标选择器。 + + + +在通常情况下,指令由使用者输入,再由使用者执行。如执行“/tp”指令可以传送执行者到一个坐标。 + +但在地图玩法中,往往执行指令的个体不单单是拥有具体名称的玩家,也有可能是地图中的其他生物。如何获取到这些生物,就需要学会如何使用目标选择器。 + + + +目标选择器可以根据使用者所选的变量,再用条件参数筛选出最后满足条件的一个或多个执行指令的个体。 + +目标选择器变量用于指定待选目标的大致分类。共有5种变量: + +``` +@p:选择最近的玩家 + +@r:选择随机的玩家 + +@a:选择所有玩家 + +@e:选择包括所有玩家在内的全部实体 + +@s:选择您自己 +``` + + + +![](./images/3_4.png) + +如果仅仅只输入选择器变量,是不足以满足开发者的各种定制需求。因此我的世界指令系统还为使用者提供了条件参数的功能,来筛选出目标个体。这里称之为目标选择器参数。在当前中国版中,共有12种类型: + +``` +x, y, z:坐标 + +r、rm:距离 + +dx, dy, dz:体积尺寸 + +scores:计分项分数 + +tag:记分板标签 + +c:数量 + +l、lm:经验等级 + +m:游戏模式 + +name:实体名称 + +rx、rxm:垂直旋转角度 + +ry、rym:水平旋转角度 + +type:实体类型 +``` + + + +通过特定格式@变量[<参数>=<值>,<参数>=<值>,<参数>=<值>,.....]来限制选取的群体,其中参数和值不区分大小写。 + + + +#### 坐标参数 + +坐标参数用以定义一个起始点,它常用的用法是和距离参数一起使用。例如,指令使用者希望以某个坐标点开始,让周围半径5格内的实体传送到自己身边。目标选择器可以这么做: + +![](./images/3_5.png) + +“x,y,z”三个参数决定世界的一个坐标,“r”代表距离范围内。“rm”则相反,代表距离范围外。两者结合使用,则可以获取从一个范围开始到另一个范围结束的实体目标。 + +因此,起始点的作用常用于配合距离参数来框选一个区域内的实体目标。 + + + +#### 体积尺寸 + +![](./images/3_6.png) + +体积尺寸参数用于定义一个一定长方体区域。它常与坐标参数一起使用,以坐标参数传入的世界坐标为基准点,向“X”方向(dx)延伸特定格数,向“Y”方向(dy)延伸特定格数,并向“Z”方向(dz)延伸特定格数。最后形成的长方体区域内,与之坐标重叠的实体便是筛选出的实体目标。例子中,指令使用者尝试以某个坐标点开始,让它延伸出5x5x5的立方体区域并将与之重叠的实体传送到自己身边。 + + + +#### 计分项分数参数 + +计分项分数参数允许开发者通过分数选择目标,生物的分数来自“scoreboard”指令,根据指定目标的分数过滤有多种写法,但基本格式是以@变量[scores={计分项=计分值}]为准: + +``` +@e[scores={myscore=10}]——选择所有记分项myscore分数为10的目标。 + +@e[scores={myscore=10..12}] — 选择所有记分项myscore分数为包含10到12之间的目标。 + +@e[scores={myscore=5..}] — 选择所有记分项myscore分数为5及以上的目标。 + +@e[scores={myscore=..15}] — 选择所有记分项myscore分数为15及以下的目标。 + +@e[scores={foo=10,bar=1..5}] — 选择所有记分项foo分数为10,且记分项bar分数为包含1到5之间的目标。 +``` + + + +#### 记分板 + +记分板标签参数允许开发者通过标签选择目标,生物的分数来自tag指令,基本格式是以@变量[tag=标签名称]为准: + +``` +@e[tag=标签名称]——选择所有带有“标签名称”的实体。 + +@e[tag=!标签名称]——选择所有没带有“标签名称”的实体。 + +@e[tag=]——选择所有没有标签的实体。 + +@e[tag=!]——选择所有带有标签的实体。 +``` + +允许目标选择器参数同时筛选多个标签的情况,被筛选出的目标个体必须满足所有标签参数的条件: + +``` +@e[tag=abc,tag=def]——选择所有同时带有“abc”和“def”标签的实体 + +@e[tag=abc,tag=!def]——选择所有同时带有"abc",但不带有"def"标签的实体 +``` + + + +#### 数量参数 + +数量参数用于所选的目标数量不超过给定数值,在基岩版中,默认下使用目标选择器变量“@p”和“@r”会限制获取的目标数量为“1”,那么冒险家就可以用数量参数来增加获取的数量。而在“@e”和“@a”中,数量参数传入正值会获取离冒险家最近的、满足筛选要求的目标实体,传入负值则相反,会获取满足筛选条件且离使用者最远的目标实体。 + +``` +@p[c=3]——获取离使用者最近的3名玩家 + +@a[c=-4]——选择离使用者最远的4名玩家 + +@r[c=2]——随机选择正在世界中的2名玩家 +``` + + + +#### 等级参数 + +等级参数用于筛选满足给定等级范围的目标实体。在基岩版中,有两个相关参数: +lm——等级超过给定值 + +l——等级小于给定值 + + + +例如: + +``` +@e[lm=1]——获取等级大于1的实体,由于只有玩家有等级概念,因此相当于获取等级大于1的玩家 + +@p[l=16]——获取离使用者最近,等级在16以内的玩家 + +@a[lm=8,l=16]——获取等级在8~16范围内的玩家 +``` + + + +#### 游戏模式参数 + +游戏模式参数用于筛选满足给定游戏模式的目标实体,在基岩版中,使用“m”来标识: + +“m”的值可以用0、s代表生存,1、c代表创造,2、a代表冒险模式。 + + + +例如: + +``` +@e[m=0]——获取生存模式的玩家实体 + +@a[m=c]——获取创造模式的玩家实体 + +@a[m=!2]——获取不为冒险模式的玩家实体 +``` + + + +#### 名称参数 + +名称参数用于筛选满足给定名字的目标实体,在基岩版中,使用name来标识: + +``` +@a[name=Steve]——获得世界中名为Steve的玩家实体 + +@a[name=!Steve]——获得世界中名称不为Steve的玩家群体 +``` + + + +#### 角度参数 + +通过垂直旋转角度参数,开发者可以筛选出给到角度范围内的目标实体,在基岩版中,使用两种参数来表示: + +rxm——角度超过给定值 + +rx——角度小于给定值 + +![](./images/3_7.png) + +例如: + +``` +@e[rxm=30,rx=60]——获取所有视线角度在30~60垂直角度内的实体目标 + +@e[rx=0]——获取所有视线角度与水平线持平的实体目标 + +@e[rxm=45]——获取所有视线角度大于45度的实体目标 +``` + + + +通过水平旋转角度参数,开发者可以筛选出给定角度范围内的目标实体,在基岩版中,使用两种参数来表示: + +rym——角度超过给定值 + +ry——角度小于给定值 + +![](./images/3_8.png) + +例如: + +``` +@e[rym=30,ry=60]——获取所有视线角度在30~60水平角度内的实体目标 + +@e[ry=0]——获取所有视线角度在0水平角度内的实体目标 + +@e[rym=45]——获取所有水平角度大于45度的实体目标 +``` + + + +#### 类型参数 + +类型参数可以帮助开发者筛选满足给定生物类型的目标实体,在基岩版中,使用type来表示: + +例如: + +``` +@e[type=cow]——获取世界中所有的牛实体 + +@e[type=!cow]——获取世界中不为牛类型的实体 + +@e[type=cow,type=chicken]——获取世界中牛类型和鸡类型的目标实体 +``` + + + +#### 什么是原始JSON文本 + +![](./images/3_9.png) + +在基岩版中,/tellraw指令使用原始json文本来显示文本信息结果。 + + + +``` +内的格式为{"rawtext":[{"text":""}]},其中<"text":> 后的双引号内,可以填写包含任意字符的文本信息。 +``` + diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/课程04.命令列表及概述.md b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/课程04.命令列表及概述.md new file mode 100644 index 0000000..1425a11 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/课程04.命令列表及概述.md @@ -0,0 +1,69 @@ +--- +front: https://nie.res.netease.com/r/pic/20210730/4ce20d4e-a4af-4d1f-a28a-036b122f1802.png +hard: 进阶 +time: 10分钟 +--- + +# 命令列表及概述 + +#### 作者:境界 + + + +| 指令名称 | 指令效果 | +| ---------------- | ------------------------------------------------------------ | +| /camerashake | 启用视角摇晃效果。 | +| /clear | 从玩家物品栏中删除物品。 | +| /clearspawnpoint | 清除世界中设置的重生点。 | +| /clone | 将特定区域的方块复制到另一处。 | +| /difficulty | 设置难度等级。 | +| /effect | 添加或移除状态效果。 | +| /enchant | 附魔玩家的物品。 | +| /event | 触发实体事件。 | +| /execute | 代表实体执行另一命令。 | +| /fill | 在某个区域填充特定方块。 | +| /fog | 用于管理玩家的迷雾设置。(1.16.100加入,当前中国版版本尚未实装) | +| /function | 运行一个保存在本地Add-on内的函数文件。 | +| /gamemode | 更改玩家的游戏模式。 | +| /gamerule | 更改或查询游戏规则。 | +| /give | 给予物品道具给玩家 | +| /help | 提供命令使用帮助。 | +| /kick | 将玩家踢出服务器。 | +| /kill | 清除实体(玩家、生物、掉落物等)。 | +| /list | 列出服务器中的玩家。 | +| /locate | 显示最近的给定特征结构的坐标。 | +| /me | 显示一条关于自己的信息。 | +| /mobevent | 控制或查询允许运行的生物事件。(如村庄战争事件) | +| /msg | /tell的替代命令,向另一玩家发送私信。 | +| /music | 允许玩家控制全局音乐。 | +| /particle | 在世界坐标上创建一个粒子。 | +| /playanimation | 用于使一个或多个实体运行一次性动画。 | +| /playsound | 用于播放一次音乐。 | +| /reload | 从硬盘中重新加载战利品表、进度和函数。 | +| /replaceitem | 替换物品栏中的物品。 | +| /ride | 用于使实体骑乘其他实体、停止实体的骑乘/使坐骑逐出自己的骑手、召唤坐骑/骑手。 | +| /say | 向多个玩家发送消息。 | +| /schedule | 安排函数在特定的游戏刻后运行。 | +| /scoreboard | 管理实体的计分板分数。 | +| /setblock | 设置某个坐标上的方块。 | +| /setworldspawn | 设置世界出生点。 | +| /spawnpoint | 为玩家设置出生点。 | +| /spreadplayers | 将实体传送到随机位置。 | +| /stopsound | 停止一个正在播放音效。 | +| /structure | 用于在不使用结构方块的情况下保存或加载结构。 | +| /summon | 生成实体。 | +| /tag | 修改、显示玩家或实体的标签。 | +| /teleport | /tp的替代命令,传送实体。 | +| /tell | 向另一玩家发送私信 | +| /tellraw | 向玩家显示JSON消息。 | +| /testfor | 统计符合给定条件的实体。 | +| /testforblock | 测定某方块是否在某位置。 | +| /testforblocks | 测定两个区域中的方块是否相同。 | +| /tickingarea | 添加、删除或列出常加载区域。 | +| /time | 更改或查询游戏中的世界时间。 | +| /title | 管理屏幕上的标题。 | +| /toggledownfall | 切换天气。 | +| /tp | 传送实体。 | +| /w | /tell的替代命令,向另一玩家发送私信 | +| /weather | 设置天气。 | +| /xp | 增加或减少经验。 | \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/课程05.常用指令及用法.md b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/课程05.常用指令及用法.md new file mode 100644 index 0000000..0ab2eec --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第04章:指令系统,你的创作管家/课程05.常用指令及用法.md @@ -0,0 +1,791 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/5_3.0efa798f.png +hard: 进阶 +time: 60分钟 +--- + +# 常用指令及用法 + +#### 作者:境界 + + + +#### 生物指令 + +生物指令主要汇整了与实体相关的指令用法。 + + + +#### summon指令的用法 + +``` +/summon指令,用于在世界中生成一个实体。在基岩版中,它有两种语法格式。 + +1./summon [spawnPos: x y z] [spawnEvent: string] [nameTag: string] + +2./summon [spawnPos: x y z] +``` + +其中entityType内填写生物的名称域,名称域是生物在世界中的唯一身份标签。例如牛的名称域是minecraft:cow,鸡的名称域是minecraft:chicken。如果是原版生物,minecraft:可以省略,而自定义生物则需要打出完整名称。 + +在Minecraft指令系统中,被“[ ]”包裹的参数为可省略参数,如在第一种写法里,若使用者给定生物类型后,没有填写后面三个参数,指令会正常执行并返回结果。spawnPos是坐标参数,它会默认选择在使用者的坐标上生成,而spawnEvent是生物事件,默认会执行minecraft:entity_spawned事件,如果nameTag是生物的命名,则会默认不命名。 + +在第二种写法中,默认要求使用者填写生物名称域和生物命名,同理,若不给定生成坐标,则默认生成在使用者所在的世界位置。 + +![](./images/5_1.png) + +![](./images/5_2.png) + +![](./images/5_3.png) + + + +#### kill指令的用法 + +``` +/kill指令,用于清理世界中的实体,包括玩家、生物、抛射物、掉落物等。在基岩版中,它有一种语法格式: + +/kill [target: Target] + +/kill的第一个参数是可省略参数,这意味着当使用者不给定目标选择器,则默认清理使用者自己。而给定目标选择器,则会清理筛选后的目标实体。 +``` + +![](./images/5_4.png) + + + +#### give指令的用法 + +``` +/give指令,用于给予玩家道具。在基岩版中,它有一种语法格式: + +/give [amount: int] [data: int] [components: json] + +/give只能给予玩家这一类实体,因此目标选择器变量的范围缩小在@p、@r、@a里。中,需要给定一个物品名称域,物品名称域和生物命称域属性相同,是作为某种物品在世界中的唯一身份标识。如苹果是minecraft:apple。如果给予的道具是原版物品,则minecraft:可省略,若给予的是自定义物品,则需要打出完整名称。 +``` + +[amount: int]是可省略参数,不填写的情况下,会默认给予一个道具,int可以简单理解为整数。在我的世界中,道具堆叠的数量一定是整数,例如,不存在具有1/2或者0.5个苹果。 + +[data:int]是可省略参数,不填写的情况下,默认给定0。data即物品的附加值,在原版道具中,许多物品带有附加值,以区分相同物品的不同特征。例如在游戏内,玩家可以收集到16种颜色的羊毛。因此附加值决定了羊毛的颜色。一般情况下,一种道具只有一类,因此附加值默认为0。 + +[components: json]是可省略参数,不填写的情况下,默认为无。它必须是一个json对象,作用是指定物品带有特殊的功能。它目前仅支持minecraft:can_place_on[决定方块物品可以放置在什么方块之上], minecraft:can_destroy[决定物品能够破坏什么样的方块], minecraft:item_lock[1.16.100加入,可以锁定物品无法丢弃、移动等],minecraft:keep_on_death[即使玩家死后重生,该物品也会得到保留]。 + +![](./images/5_5.png) + +![](./images/5_6.png) + + + +#### xp指令的用法 + +``` +/xp指令,用于增加或减少玩家经验值。它有两种语法格式: + +1. xp [player: target] + +2. xp L [player: target] +``` + +由于只有玩家这一实体才有经验值的概念,因此该指令只作用于玩家。 + +在第一种格式中,是必填参数,使用者输入正值增加经验值,输入负值减少经验值。经验值必须是整数且在0 ~ 2147483647之间,这是一个很庞大的范围,一般情况下,输入可接受的数值即可。[player: Target]是可省略参数,在不填写的情况下,指令生效在使用者本身。 + +在第二种格式中,与第一种格式的区别仅仅在填写完第一个参数时后面尾随一个大写的L。这是直接授予增加的经验等级而不再是经验值。在我的世界中,在一个等级内,经验值达到等级要求上限后会提升等级,这是等级和经验值的关系。同样,使用者输入正值增加经验等级,输入负值减少经验等级。等级必须是整数且在-2147483647 ~ 2147483647之间。 + +![](./images/5_7.png) + +![](./images/5_8.png) + + + +#### Tp指令的用法 + +``` +/tp指令,也可写成/teleport指令,是用于传送实体的指令。在基岩版中,它有9种语法格式: + +1. /tp [checkForBlocks: Boolean] + +2. /tp [checkForBlocks: Boolean] + +3. /tp [checkForBlocks: Boolean] + +4. /tp [yRot: value] [xRot: value] [checkForBlocks: Boolean] + +5. /tp facing [checkForBlocks: Boolean] + +6. /tp facing [checkForBlocks: Boolean] + +7. /tp [yRot: value] [xRot: value] [checkForBlocks: Boolean] + +8. /tp facing [checkForBlocks: Boolean] + +9. /tp facing [checkForBlocks: Boolean] +``` + + 代表需要给定一个坐标参数。 需要给定一个目标选择器。 + +[checkForBlocks: Boolean]是可省略参数,它接受true和false两个值。当它为true时,只会在传送坐标区域内没有与实体产生重叠的方块的情况下传送实体,否则执行失败,这是为了保证如传送玩家时,不希望他被传送进方块而受到窒息伤害的方法。若填写false,即无视这一情况,默认传送实体到指定目标。 + +[yRot: value]是可省略参数,它接受一个数值,这个整数要在-180.0~180.0之间,可以带有小数,也可以是整数。它的作用是在传送实体到目标地点时,同时旋转实体的水平角度。 + +[xRot: value]是可省略参数,它接受一个数值,这个整数要在-180.0~180.0之间,可以带有小数,也可以是整数。它的作用是在传送实体到目标地点时,同时旋转实体的垂直角度。 + +是一个需要给定值的参数,它接受一个坐标参数。它的作用是在传送实体到目标地点时,使目标面向某个坐标。 + + 是一个需要给定值的参数,它接受一个目标选择器变量。它的作用是在传送实体到目标地点时,使目标看向某个实体。 + +![](./images/5_9.png) + +![](./images/5_10.png) + + + +#### tag指令的用法 + +``` +/tag指令是用于给予实体标签的指令,在基岩版中,它有3种语法格式。 + +1. /tag add + +2. /tag list + +3. /tag remove +``` + +第一个格式中,主要介绍如何使用tag指令给实体目标添加标签。其中,给定一个目标选择器参数。< name>给定一个名字,这两个都是必填参数。 + +第二个格式中,主要介绍如何使用tag指令查询实体目标的标签。其中,给定一个目标选择器参数,它是一个必填参数。 + +第三个格式中,主要介绍如何使用tag指令给实体目标移除标签。其中,给定一个目标选择器参数。< name>给定一个名字,这两个都是必填参数。 + +![](./images/5_11.png) + +![](./images/5_12.png) + +![](./images/5_13.png) + +![](./images/5_14.png) + +![](./images/5_15.png) + +![](./images/5_16.png) + + + +#### testfor指令的用法 + +``` +/testfor指令,是用于检测并统计符合指定条件的实体(玩家、生物、物品等)。它有一种语法格式: + +1. /testfor +``` + +其中接受一个目标选择器参数,它是一个必填参数。 + +示例: + +``` +1.检测圆心为(128,64,128)、半径为5米的掉落物 + +/testfor @e[type=item,x=128,y=64,z=128,r=5] + +2.检测Steve是否在线 + +/testfor Steve +``` + +![](./images/5_17.png) + +![](./images/5_18.png) + + + +#### scoreboard指令的用法 + +``` +/scoreboard指令,是用于管理计分板系统的指令。在基岩版中,它有十二种语法格式: + +1. /scoreboard objectives add dummy [displayName:string] + +2. /scoreboard objectives list + +3. /scoreboard objectives remove + +4. /scoreboard objectives setdisplay [objective: string] [ascending:descending] + +5. /scoreboard objectives belowname [objective:string] + +6. /scoreboard players list [player: Target] + +7. /scoreboard players reset [objective:string] + +8. /scoreboard set|add|remove players + +9. /scoreboard players random + +10. /scoreboard operation <.selector:Target> +``` + + + +计分板系统是通过操作复杂的指令来追踪玩家分数的一种机制,根据玩家当前所带有的某种分数作为条件,可以用目标选择器来筛选出这一类玩家并执行相应指令。 +第一个格式中,主要介绍如何使用scoreboard指令给实体目标添加分数类型。其中, 给定一个分数名称。< displayName>给定一个显示名称,前者是必填参数,后者是选填参数。 + +第二个格式中,主要介绍如何使用scoreboard指令显示分数类型。 + +第三种格式中,主要介绍如何使用scoreboard指令移除分数类型。 + +第四种格式中,主要介绍如何使用scoreboard指令显示计分板,其中 是必填参数,显示在list【列表】时,计分板内容会出现在暂停界面右侧的玩家列表里,显示在sidebar【侧边栏】时,内容会以界面地方式呈现在玩家屏幕右侧。[objective: string] 是选填参数,当不填写这一项时,会清空已经显示的列表或侧边栏的内容,但不会影响本来的分数值。当填写这一项时,会在列表或侧边栏上显示这一数值类型的值。[ascending:descending]是选填参数,由于值必定是整数类型,因此可以进行上升或者下降排序。 + +第五种格式中,主要介绍如何使用scoreboard指令将分数显示在玩家名称下。其中[objective:string]是选填参数,当不填时,会清空之前显示在玩家名称下的分数;当填写时,会显示指定分数在玩家名称下。 + +第六种格式中,主要介绍如何使用scoreboard指令将玩家的分数值打印在聊天栏中。其中[player: Target]是选填参数,当不填写时,会打印全部正挂载在玩家身上的分数类型,若填写时,可以填写@a、@s以及指定玩家名称,以显示特定群体的玩家分数。 + +第七种格式中,主要介绍如何使用scoreboard指令将玩家的分数进行重置。其中 是必填参数,需要让指令知道要将哪类玩家的分数进行重置,[objective:string]是选填参数,代指分数类型。重置意味着将分数值进行清空,即不再带有某个分数类型的值。 + +第八种格式中,主要介绍如何使用scoreboard指令将玩家的分数进行设置、加、移除。其中 是必填参数,是分数类型,是值。set代指设置,即将玩家的分数设置成某个整数,而不进行任何数学计算,add代指添加,当玩家没有分数时,效果等同于“设置”,当玩家有分数时,效果等同于相加。remove是移除,当玩家没有分数时,分数会变成0 - 给定值,因此等同于设置值的负方向的值。当玩家有分数时,效果等同于相减。 + +第九种格式中,主要介绍如何使用scoreboard指令将玩家的分数进行随机区间的取值。其中 是玩家选择器,属于必填参数,是分数类型,属于必填参数, 分别代指最小值和最大值。 + +第十种格式中,主要介绍如何使用scoreboard指令将一位玩家的分数进行计算,并最终将目标玩家分数设置为计算结果。 是目标玩家选择器,最终改变分数的即是这个参数指定的那一类玩家。 是目标玩家的分数类型。 是运算符,一共包含%=[求模]、*=[相乘]、+=【相加】、-=【相减】、/=【相除】、<【若目标分数小于他人分数,则将目标分数设置为与他人分数相等】、=【将目标分数设置为与他人分数相等】、>【若目标分数大于他人分数,则将目标分数设置为与他人分数相等】、><【将目标分数与他人分数相互替换】。可以代指其他玩家选择器,同样,这个他人可以指向自己,即自己的分数与自己做计算。是分数类型。 + +![](./images/5_19.png) + +![](./images/5_20.png) + +![](./images/5_21.png) + +![](./images/5_22.png) + +![](./images/5_23.png) + +![](./images/5_24.png) + + + +#### execute指令的用法 + +``` +/execute允许使用者用该指令让特定类型的实体去执行另一条指令,由于在通常情况下,除了玩家外,其他生物是不会执行指令,所以这条指令十分有用。在基岩版中,它一共有两种语法格式: + +1. /execute + +2. /execute detect +``` + +第一个格式中,主要介绍如何使用execute指令让某一实体执行某一指令。其中 是目标选择器,筛选出来的实体将会是执行指令的对象, 是坐标参数,是要执行的指令。 + +第二个格式中,主要介绍如何使用execute指令让某一实体执行某一指令时,还要通过一道方块检测。 即要检测的方块坐标,即方块名称, 即方块附加值。 + +![](./images/5_25.png) + +![](./images/5_26.png) + + + +#### enchant指令的用法 + +``` +/enchant允许使用者用该指令让手上的可附魔物品附魔。需要注意的是,不同可附魔的物品可以附魔的能力不一定相同,若返回了一些红色提示信息,很可能是开发者将错误的附魔属性附魔到了手上的物品。例如,将三叉戟的特殊附魔属性附魔在了剑上。在基岩版中,它一共有两种语法格式: +1./enchant [level: int] + +2./enchant [level: int] +``` + +第一个格式与第二个格式不同的地方仅仅在enchantmentId和enchantmentName上,基岩版中一共有36种附魔,每种附魔对应一个附魔数字ID,也同时拥有一个附魔名字。其他参数上, 是目标选择器,是必填参数,[level: int]是附魔等级,是选填参数,当不填写时,只要拿着正确的物品,默认附魔等级为1的附魔属性。同时,受原版附魔等级限制,不同附魔超过一定等级是不可以用指令附魔的。 + +![](./images/5_27.png) + +![](./images/5_28.png) + +![](./images/5_29.png) + + + +#### effect指令的用法 + +``` +/effect允许使用者用该指令给予特定类型的实体一种药水效果。在基岩版中,它一共有两种语法格式: +1./effect clear + +2./effect [seconds:int] [amplifier:int] [hideParticles:Boolean] +``` + +第一个格式中, 是必填参数,但请不要被它的指令提示迷惑,它可以作用于所有生物上。clear即代表会移除该类目标身上的所有状态效果。 + +第二个格式中, 是状态效果,是必填参数,[seconds:int] 是选填参数,即状态效果的持续时间,[amplifier:int] 是状态效果的强度,也是选填参数,[hideParticles:Boolean]是布尔参数,即是否隐藏状态效果粒子,true为隐藏,false为不隐藏,默认为false。 + +![](./images/5_30.png) + +![](./images/5_31.png) + + + +#### clear指令的用法 + +``` +/clear允许使用者用该指令清除一个玩家背包内的物品。在基岩版中,它一共有一种语法格式: + +1./clear [player: target] [itemName: Item] [data: int] [maxCount: int] +``` + +第一个格式中,[player: target] 是选填参数,代指玩家类型选择器,因此只能填写@a、@s、玩家名称等,若不填写时,默认对象为执行玩家本人,同时,若不填写后面的其他参数,则会清空该名玩家背包的所有物品,包括装备栏、快捷栏、背包栏。[itemName: Item] 是物品名称,[data: int] 是物品附加值, [maxCount: int]是物品最大数量,当物品最大数量不指定时,指令匹配到的同类物品无论堆叠了多少数量都会一并清除。 + +![](./images/5_32.png) + +![](./images/5_33.png) + + + +### 世界指令 + +世界指令主要汇整了与游戏世界相关的指令用法。 + + + +#### time指令的用法 + +``` +/time允许使用者用该指令去改变游戏世界的时间,在我的世界中,24000代表一天会经过的游戏时间。在基岩版中,它一共有一种语法格式: + +1./time add + +2./time query + +3./time set + +4./time set +``` + +第一个格式中,代指时间长度,即添加多少时间到当前时间中。 + +第二个格式中,代表当天的游戏时间,gametime代表总的游戏时间,day代表这个世界过了几天。query即查询这些时间后得出的数据会显示在聊天框里。 + +第三种格式中,代指某一时间,即设置现在的时间为多少。 + +第四种格式中,代指某段时间,类型有day【白天】、night【黑夜】、noon【中午】、midnight【深夜】、sunrise【日出】、sunset【日落】。即设置当前时间为这6段时间中的一种。 + +![](./images/5_34.png) + +![](./images/5_35.png) + +![](./images/5_36.png) + + + +#### tickingarea指令的用法 + +``` +/tickingarea允许使用者用该指令去添加常加载区域,常加载区域意味着你所添加的区域将会持续加载,无论玩家离着多远,那块区域的方块状态、生物活动等都会照常运行。如植物生长、熔炉燃烧、生物繁殖等。在基岩版中,指令一次只能添加十个常加载区域在这个世界存档中。它一共有6种语法格式: + +1. /tickingarea add [name: string] + +2. /tickingarea add circle [name: string] + +3. /tickingarea remove + +4. /tickingarea remove + +5. /tickingarea remove_all + +6. /tickingarea list [all-dimensions: AllDimensions] +``` + +第一个格式中, 为坐标参数,即从某个坐标开始,到某个坐标结束的范围里为一个常加载区域,[name: string]为选填参数,即为该常加载区域设置一个名字。 + +第二个格式中,为坐标参数,即从某个坐标开始,到 某个半径范围外结束的一个圆形区域作为常加载区域,[name: string]为选填参数,即为该常加载区域设置一个名字。 + +第三种格式中,为必填参数,即常加载区域的名称。使用这个格式来移除该名称的常加载区域。 + +第四个格式中,为坐标参数,为必填参数,使用这个格式来移除以该坐标为起始点的常加载区域。 + +第五个格式中,使用这个格式一次性删除所有常加载区域。 + +第六个格式中,[all-dimensions: AllDimensions]为选填参数,即所有维度中的常加载区域,不填时只显示当前维度的常加载区域。 + +![](./images/5_37.png) + +![](./images/5_38.png) + +![](./images/5_39.png) + +![](./images/5_40.png) + + + +#### setworldspawn指令的用法 + +``` +/setworldspawn允许使用者用该指令去设置主世界的出生点。在基岩版中,它一共有一种语法格式: + +1. /setworldspawn [spawnPoint: x y z] +``` + +在这个唯一格式中,[spawnPoint: x y z]为出生点坐标参数,当不填时,默认设置当前使用者的坐标为出生点坐标。 + +![](./images/5_41.png) + +![](./images/5_42.png) + + + +#### spawnpoint指令的用法 + +``` +/spawnpoint允许使用者用该指令去设置某个玩家的出生点坐标。在基岩版中,它一共有一种语法格式: + +1. /spawnpoint [player: target] [spawnPos: x y z] +``` + +在这个唯一格式中,[player: target] 为玩家目标选择器参数,因此它只支持@a、@s、玩家名字等,当不填时,默认指定目标为执行玩家本人。[spawnPos: x y z]为出生点坐标参数,当不填时,默认设置当前使用者的坐标为出生点坐标。 + +![](./images/5_43.png) + +![](./images/5_44.png) + + + +#### spreadplayers指令的用法 + +``` +/spreadplayers允许使用者用该指令去分散某个区域内的实体。在基岩版中,它一共有一种语法格式: + +1./spreadplayers +``` + +在这个唯一格式中,< x> < z>为坐标参数的x轴、z轴, 为在每个被分散的目标之间的距离是多少。 为从中点开始,被分散的目标最终会在多大范围内,为实体目标选择器,因此不要被指令名称迷惑,它同时支持@a、@e、@s、玩家名称等目标。 + +![](./images/5_45.png) + +![](./images/5_46.png) + + + +#### locate指令的用法 + +``` +/locate允许使用者用该指令将离自己最近的原版特征建筑显示在聊天框内。在基岩版中,它一共有一种格式: + +1. /locate +``` + +在这个唯一格式中,为必填参数,指令提示会引导玩家去选填自己要找的特征。但需要注意的是,有些特征仅出现在特定群系的时候,使用者在其他群系是无法定位到这些特征的。如玩家在主世界寻找下界的特征等情况。 + +![](./images/5_47.png) + +![](./images/5_48.png) + + + +#### gamemode指令的用法 + +``` +/gamemode允许使用者用该指令去设置某位玩家的游戏模式。在基岩版中,它一共有两种语法格式: + +1./gamemode [gamemode: GameMode] [player:target] + +2./gamemode [gamemode:int] [player:target] +``` + +第一个格式中,[gamemode: GameMode]为模式类型的字符串,可以设置为生存模式、创造模式、冒险模式。[player:target]为玩家目标选择器参数,由于只有玩家才有游戏模式的概念,因此这里只能填写@a、@s、玩家名称等。 + +第二个格式中,[gamemode:int] 为模式类型对应的数值ID,0为生存模式,1为创造模式,2为冒险模式。 + +![](./images/5_49.png) + +![](./images/5_50.png) + +![](./images/5_51.png) + + + +#### gamerule指令的用法 + +``` +/gamerule允许使用者用该指令去设置世界的游戏规则。在基岩版中,它一共有两种语法格式: + +1. /gamerule [rule: BoolGameRule] [value:Boolean] + +2. /gamerule [rule: IntGameRule] [value:int] +``` + +第一个格式与第二个格式的区别在于,我的世界的有些游戏规则是只有是和否的选择,有些游戏规则则可以设置值规则。如是否生成怪物必定只有是和否的选项,出生范围则必定是可以设置值的规则。更多规则的概念,请参考minecraft wiki: + +[https://minecraft.gamepedia.com/Game_rule](https://minecraft.gamepedia.com/Game_rule) + +![](./images/5_52.png) + +![](./images/5_53.png) + + + +#### difficulty指令的用法 + +``` +/difficulty允许使用者用该指令去设置世界的游戏难度。在基岩版中,它一共有两种语法格式: + +1. /difficulty + +2. /difficulty +``` + +第一个格式与第二个格式的区别在于,我的世界一共有4种游戏难度,分别为和平、简单、普通、困难,因此它们除了带有完整名字和缩写名字的参数外,还允许以0~3的数值作为设置游戏难度的参数。 + +![](./images/5_54.png) + +![](./images/5_55.png) + + + +#### alwaysday指令的用法 + +``` +/alwaysday允许使用者用该指令去设置世界是否总是白天。在基岩版中,它一共有一种语法格式: + +1. /alwaysday +``` + +在这个唯一的格式中,它接收一个布尔值,即真(true)或者假(false)。设置为真时,则世界永远都是白天。设置为假时,世界会正常昼夜循环。 + +![](./images/5_56.png) + + + +#### weather指令的用法 + +``` +/weather允许使用者用该指令去设置世界的天气。在基岩版中,它一共有两种语法格式: + +1. /weather clear|rain|thunder + +2. /weather query +``` + +第一个格式中,clear代表清除当前的天气效果,若当前没有天气效果如下雨或者打雷,则不会有任何变化。rain代指将天气设置为雨天。thuner代指将天气设置为雷雨天。duration:int代指天气持续的时间,它接受一个整数值。 + +第二个格式中,query代表查询当前天气。 + +![](./images/5_57.png) + +![](./images/5_58.png) + + + +### 聊天指令 + +聊天指令主要汇整了与聊天相关的指令用法。 + + + +#### tellraw指令的用法 + +``` +/tellraw指令允许使用者发出一段json格式的信息给玩家。在基岩版中,它一共有一种语法格式: + +1. /tellraw + +第一个格式中, 为目标选择器,但一般情况下这里都会填写以玩家为类型的目标选择器,因为只有玩家才能看得到聊天信息。是一段被用来发给玩家的,以原始json消息为格式的信息。 +``` + +以消息“你好”为例,通常情况下使用其他聊天指令发给他人,或者使用聊天窗口聊天时,前面都会带有玩家名称。若使用者想要自行定制一个广播格式,则tellraw是非常好用的指令帮手。 +/tellraw @a {"rawtext":[{"text":"[广播站]你好"}]}将会给所有玩家发送一个以广播站为前缀,内容为你好的消息。 + +![](./images/5_59.png) + +![](./images/5_60.png) + + + +#### title指令的用法 + +``` +/title指令允许使用者发出一段纯文本或者json格式的信息给玩家。在基岩版中,它一共有三种语法格式: + +1. /title + +2. /title + +3. /title times +``` + +第一个格式中,为玩家目标选择器,为必填参数,clear为清空玩家当前正在播放的标题文本。reset为重置标题的时间配置。 + +第二个格式中,为玩家目标选择器,为必填参数, 为屏幕大标题、屏幕副标题、快捷栏上方标题三种类型,为必填参数,titleText接收纯文本或者json消息,为必填参数。 + +第三个格式中, 为渐进时间,为停留时间,为淡出时间。该格式可以用来设置标题的时间配置。 + +![](./images/5_61.png) + +![](./images/5_62.png) + +![](./images/5_63.png) + +![](./images/5_64.png) + + + +#### tell | msg | w指令的用法 + +``` +/tell | /msg | /w指令允许使用者对某个玩家进行悄悄话。在基岩版中,这三种命令的格式几乎相同: + +1. /tell | /msg | /w +``` + +在该格式中, 必须为玩家目标选择器,为必填参数,为一段纯文本。当指令使用成功时,一般以"你悄悄地对某人说:某段内容"为基准。 + +![](./images/5_65.png) + +![](./images/5_66.png) + + + +#### me指令的用法 + +``` +/me指令允许使用者对自己发送一条消息。在基岩版中,这个命令的格式只有一种:  + +1. /me +``` + +在该格式中,为一段纯文本,属于必填参数。当指令使用成功时,一般以"* 名字 某段内容"为格式显示在自己的聊天窗内。 + +![](./images/5_67.png) + +![](./images/5_68.png) + + + +### 方块指令 + +方块指令主要汇整了与方块相关的指令用法。 + + + +#### setblock指令的用法 + +``` +/setblock指令允许使用者设置某一个坐标点上的方块。在基岩版中,这个命令的格式只有一种: + +1. /setblock [tileData: int] [destroy|keep|replace] +``` + +在该格式下, 为一个坐标参数, 为方块名称, [tileData: int] 为方块附加值,[destroy|keep|replace]为方块替换模式,destroy为破坏上一个方块并设置指令的方块,会产生方块破坏粒子和声音。keep为值会替换非空气方块的方块。replace为直接用方块去替换上一个方块,而不产生破坏粒子和声音。默认情况下,若不填写方块替换模式,则为replace。 + +![](./images/5_69.png) + +![](./images/5_70.png) + + + +#### clone指令的用法 + +``` +/clone指令允许使用者将某个区域的方块复制到另一个区域。在基岩版中,这个命令的格式有两种: + +1. /clone [maskMode: MaskMode] [cloneMode: CloneMode] + +2. /clone filtered +``` + +在第一个格式中, 为起始坐标参数,为终止坐标参数, 为要粘贴的位置坐标参数。[maskMode: MaskMode] 为遮罩模式,一共有masked和replace两种遮罩模式,前者为仅复制选择区域的非空气方块,在粘贴时将保持原本会被替换为空气方块的区域不变。replace为复制选择区域的所有方块,它们主要作用在于是否过滤复制的方块,若未指定则默认为replace。[cloneMode: CloneMode]为复制模式,一共有force、normal、move三种模式,force为强制复制,即将一个区域拷贝一份粘贴到另一个区域。move可以理解为剪切,即将一个区域移动到另一个区域,源区域被带走的方块将会变成空气方块。normal则是不执行另外两种模式。 + +在第二个格式中,遮罩模式被改为过滤模式,并添加了方块参数和方块附加值参数,即意味着clone指令只会复制某一种符合了方块附加值和方块名称的方块,并根据复制模式的模式来粘贴到目的地区域。 + +![](./images/5_71.png) + +![](./images/5_72.png) + + + +#### fill指令的用法 + +``` +/fill指令允许使用者将某个区域用某种方块来填满。在基岩版中,这个命令的格式有两种: + +1. /fill [tileData: int] [oldBlockHandling: FillMode] + +2. /fill replace [replaceTileName: Block] [replaceDataValue: int] +``` + +在第一个格式中, 为要填充的区域的起始坐标, 为终止坐标, 为方块名称,[tileData: int] 为方块附加值,[oldBlockHandling: FillMode]为旧方块的填充模式,它一共有destroy、hollow、keep、outline、replace五种模式。destroy会破坏区域内的所有方块并在满足条件的情况下掉落部分方块的掉落物。hollow仅用指定方块填充区域最外层的方块,内部会被置换为空气方块。若填充区域没有内部(如体积小于3x3x3),作用则与replace相等。keep会仅用指定方块去替换区域内的空气方块,outline与hollow类似,但它不会置换内部位空气方块。replace则会替换区域内的所有方块,但不会掉落任何掉落物或产生方块破坏的音效、粒子效果等。 + +在第二个格式中,默认模式为replace模式,但多了两个参数[replaceTileName: Block] [replaceDataValue: int],即将某个区域内的符合被替换的方块名称和方块附加值的方块变为填充所需的方块。 + +![](./images/5_73.png) + +![](./images/5_74.png) + + + +#### testforblock指令的用法 + +``` +/testforblock指令允许使用者测试某个坐标上的方块是否与给定的指令方块参数匹配,并将结果信息打印在聊天栏。这个命令的格式有一种: + +1. /testforblock [dataValue: int] +``` + +在这个格式中, 为方块所在的坐标参数,为必填参数, 为方块名称,为必填参数,[dataValue: int]为方块附加值参数,为选填参数,若不填,当某种方块有多种附加值时则都会匹配得到。 + +![](./images/5_75.png) + +![](./images/5_76.png) + + + +### 音效指令 + +音效指令主要汇整了与音效相关的指令用法。 + + + +#### playsound指令的用法 + +``` +/playsound指令允许使用者对某类玩家在某个坐标上播放音效。这个命令的格式有一种: + +1. /playsound [player: target] [position: x y z] [volume: float] [pitch: float] [minimumVolume: float] +``` + +在这个格式中, 为必填参数,可以通过sound_definitions.json下的找到需要的音效资源键,它支持播放自定义音效。[player: target] 为选填参数,为玩家目标选择器,因此只能填写@a、@s、玩家名称等。 [position: x y z] 为选填参数,为音效播放的坐标,由于绝大多数的原版音效为3D音效,因此受距离关系,距离收到音效的玩家越远,该玩家听到的音量越小。[volume: float] 为声音音量,[pitch: float] 为声音音调,[minimumVolume: float]为最小音量。 + +![](./images/5_77.png) + +![](./images/5_78.png) + + + +#### stopsound指令的用法 + +``` +/stopsound指令允许使用者对某类玩家停止播放某个音效。这个命令的格式有一种: + +1. /stopsound [sound: string] +``` + +在这个格式中, 为必填参数,为玩家目标选择器,因此只能填写@a、@s、玩家名称等。 [sound: string]为音效资源键,若该参数不填,则默认停止所有正在播放的音效,包括背景音效等。 + +![](./images/5_79.png) + + + +### 特效指令 + +特效指令主要汇整了与特效相关的指令用法。 + + + +#### particle指令的用法 + +``` +/particle指令允许使用者在世界的某个坐标上播放一种粒子效果,该指令支持播放自定义粒子效果,粒子播放时对全部玩家可见。这个命令的格式有一种: + +1. /particle +``` + +在这个格式中, 为粒子名称域,为必填参数。需要认识更多的原版粒子,可以通过原版资源包目录下的particles文件夹中找到,由于部分粒子可能需要给定粒子参数,而当前粒子指令不支持传入粒子参数,所以有部分粒子存在播放不正确、无法正常播放的情况。为坐标参数,用来指定粒子生成在什么位置上,离该位置较远的玩家可能无法看到粒子效果。 + +![](./images/5_80.png) + +![](./images/5_81.png) \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/README.md b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/images/2_1.png b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/images/2_1.png new file mode 100644 index 0000000..d0bdf33 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/images/2_1.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/images/3_1.png b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/images/3_1.png new file mode 100644 index 0000000..6a247a1 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/images/3_1.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/images/3_2.png b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/images/3_2.png new file mode 100644 index 0000000..dbf56c8 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/images/3_2.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/images/3_3.png b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/images/3_3.png new file mode 100644 index 0000000..499634f Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/images/3_3.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/images/4_1.png b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/images/4_1.png new file mode 100644 index 0000000..66a7b48 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/images/4_1.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/images/4_2.png b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/images/4_2.png new file mode 100644 index 0000000..367a8c5 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/images/4_2.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/images/5_1.png b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/images/5_1.png new file mode 100644 index 0000000..4dbc344 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/images/5_1.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/images/7_1.png b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/images/7_1.png new file mode 100644 index 0000000..6164b35 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/images/7_1.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/images/7_2.png b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/images/7_2.png new file mode 100644 index 0000000..674a9cd Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/images/7_2.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/images/7_3.png b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/images/7_3.png new file mode 100644 index 0000000..4108873 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/images/7_3.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/images/7_4.png b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/images/7_4.png new file mode 100644 index 0000000..ce0079c Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/images/7_4.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/课程01.MCSTUDIO功能简介.md b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/课程01.MCSTUDIO功能简介.md new file mode 100644 index 0000000..b279026 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/课程01.MCSTUDIO功能简介.md @@ -0,0 +1,13 @@ +--- +front: https://nie.res.netease.com/r/pic/20210730/4ce20d4e-a4af-4d1f-a28a-036b122f1802.png +hard: 入门 +time: 5分钟 +--- + +# MCSTUDIO功能简介 + +#### 作者:境界 + + + +MC Studio是集开发者启动器、地图编辑器、关卡编辑器、逻辑编辑器、特效编辑器、云端测试平台等功能于一体的开发工具,能够为《我的世界》开发者开发和发布作品提供极大的便利。其中对初级开发者而言,关卡编辑器和地图编辑器是容易上手且轻松就能制作出玩法的工具,我们将在这一章程教给大家如何使用这两种编辑器。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/课程02.关卡编辑器.md b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/课程02.关卡编辑器.md new file mode 100644 index 0000000..301e4e8 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/课程02.关卡编辑器.md @@ -0,0 +1,41 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/2_1.f5da27b0.png +hard: 入门 +time: 10分钟 +--- + +# 关卡编辑器 + +#### 作者:境界 + + + +关卡编辑器是配置和管理附加包(Add-on)的中心,能够自定义维度、生物、物品、方块、配方等内容,通过组合这些游戏元素,形成自己独特的玩法。 + +对于不同阶段的开发者来说,使用关卡编辑器的推荐方式也不一样。 + +新手阶段:通过组件来搭建玩法,熟悉各种自定义游戏元素。 + +熟手阶段:通过关卡编辑器的资源管理了解Addon的文件构成,研究组件的功能实现原理,在组件的基础上尝试做一些文件级别的修改。 + +高手阶段:使用关卡编辑器快速搭建玩法原型,之后做深度的文件修改。 + + + +#### 各个模块的简单介绍 + +![](./images/2_1.png) + +菜单栏:“作品”中可设置Add-on的命名空间,“脚本”中可安装Mod SDK的脚本提示库,“窗口”中可设置不同分项窗口的可见性。左上角蓝色区域可以将MCStudio的关卡编辑器、地图编辑器、特效编辑器、界面编辑器它们来回切换。 + +组件:配置自定义的游戏元素,比如物品、生物、方块等等;通过不同的组件搭配形成玩法,上方的“添加组件”可以视为一个快捷按钮。 + +属性:当选择“组件”中的内容时,在属性面板中会显示相应的组件属性参数。 + +游戏内容:游戏内容运行着《我的世界》模拟玩家游戏环境,可编辑生物、坐标等,结果会实时反馈回游戏内。 + +舞台:显示当前游戏区域中的生物实体,也可以快捷定位到游戏内正存在的生物实体。 + +资源窗口:管理作品内的文件,通过双击文件图标可以打开文件, 文件格式转换的入口也都在这里。 + +快捷操作:包含“保存”、“备份”、“运行”等按钮,可快速保存、备份、测试玩法。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/课程03.使用组件.md b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/课程03.使用组件.md new file mode 100644 index 0000000..6062ae3 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/课程03.使用组件.md @@ -0,0 +1,181 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/3_1.3aa1459a.png +hard: 进阶 +time: 40分钟 +--- + +# 使用组件 + +#### 作者:境界 + + + + + +① 通过点击“添加组件”按钮,或是在组件区域内点击鼠标右键,就会弹出组件选项来添加组件。 + + + +![](./images/3_2.png) + +② 对单个组件点击右键,可以看到组件的简介内容。 + + + +![](./images/3_3.png) + +③添加完组件后,右下角的属性面板将会出现被添加组件的属性参数。 + + + +### 各类组件介绍 + +#### 世界类目: + +世界类目:包括相机和地形改变相关的组件,详细组件说明如下: + +①基础组件:该组件可以设置世界选项、作弊选项等,与在游戏客户端中的设置-选项里的内容无差。许多地图玩法中,可能会需要一个总是白天的地图,或者不刷怪的地图,这时就可以在这里进行设置了。 + +②相机组件:该组件可以设置玩家的视角,游戏中带有三种视角,第一人称视角、第三人称后视角、第三人称前视角,即组件选项中的原版视角。而改为俯视角时,玩家视角会被改成第三人称视角,并且视角角度可以自由定义,例如MOBA游戏中的俯视角,2D平面跑酷的侧视角,都可以实现。默认情况下,切换为自定义视角时,应不允许玩家切换视角。 + +③资源点:该组件可以设置世界中的某个位置不断生成资源道具,风靡全球的MC小游戏起床战争就用到了资源点的理念。 + +④地图复原:该组件可以设置在创建游戏时或开始游戏时保存一片区域的方块,在结束游戏时进行场景复原。当场景内会由玩家因素造成场地破坏时,就可以用到这个组件。 + +⑤安全区:该组件可以将一个随时间向内聚拢的方形区域外的方块设置成其他方块,适合需要引导玩家向内移动的诱因玩法。如,在出生点周围100格外,不断向内涌入岩浆,直到比赛分出胜负时停止。 + + + +#### 维度类目: + +维度类目:包括生物群系、自定义特征、生物生成规则等维度相关的功能,详细组件说明如下: + +①自定义维度:该组件可以创建一个除主世界、下界、末地以外的新维度。由于群系设定下,新的维度的群系与主世界的群系呈对应关系,因此在没有自定义新维度群系的情况下,主世界与新维度的地貌相差无几。 + +②自定义群系:该组件可以创建一个在新维度中的群系。在网易版自定义群系中,群系必须继承原版群系,无法添加自定义新群系,且只能作用于新维度中。地形高度栏目下,可以设置群系的高度,在一般情况下使用原版的预设种类即可。若对于地形生成算法小有研究的开发者,亦可以尝试使用随机参数来自定义群系高度。地表栏目下,可以设置一个群系从下层到上层的方块种类。生成规则会控制群系在不同温度下的生成概率,在我的世界中,每个地图种子会被划分为海洋和陆地,并决定不同地区的温度,就好像地球上的温带一样,不同温带下的群系会呈现出不同的地形及气候等特征。不仅如此,当群系遇到高度变化时,可以设置要被转化的群系。如平原遇到山地变化时,会生成森林和森林丘陵。海洋遇到岸滨变化时,会生成沙滩和平原。最后,群系标签可以将指定的自定义名称标签加入到自定义群系中,用来作为新生物出现的群系条件,这个会在生成规则组件中做额外说明。 + +③自定义生物生成规则:该组件可以创建一个新的生物生成规则。顾名思义,新的规则是用来划分生物可以出现的条件。其中,生物必须来自生物组件所创建的自定义生物,若开发者想要使用该组件来设置手动写的自定义生物的生成规则,可以简单生成一个模板生物后,引用该自定义生物,并在附加包文件内将identifier字段指向的生物ID改为手动写的生物ID。 + +④自定义特征:该组件可以创建一个新的自定义特征,特征可以用来自定义地下矿簇、地表植物树木等,原版世界里稀有的化石堆、苔石堆,都用到了特征功能。当前编辑器只支持将地图编辑器导出的方块结构转化成特征,如需了解更多自定义特征功能,请参考minecraft wiki或游戏客户端目录/data/definitions/features文件夹。 + +⑤自定义特征规则:该组件可以创建一个新的特征规则,用来划分编辑器所创建的自定义特征的生成规则。生成时机用来决定特征出现的时机,原版世界中,花草树木一般在表层时机出现,矿物在地下时机出现。指定群系标签可以让特征出现在带有对应群系标签的群系内,该标签适用自定义群系内的标签。分布状态有均匀分布和所有区块分布,所有区块分布下,特征会在满足群系条件的情况下,尝试在每个区块都生成一次。均匀分布下,特征会在满足群系条件的情况下,尝试在多个区块间隔下生成。 + +⑥自定义传送门:该组件可以设置一个通向另一个维度的传送门。当前编辑器只允许设置立式传送门,即类似下界传送门。一般可容纳玩家穿过的传送门高度在4格宽,5格高。传送门方块需要使用自定义方块进行额外定义,才会从下拉选项内弹出选项。 + + + +#### 玩家类目: + +玩家类目:包括技能、排行榜等和玩家相关的功能,详细组件说明如下: + +①基础属性:该组件可以快速设置玩家的基础属性,并将最后结果生效于进入世界的全部玩家身上。 + +②初始物品:该组件可以在玩家进入游戏时给予指定出生道具。 + +③初始装备:该组件可以在玩家进入游戏时自动穿戴指定装备。 + +④有限复活:该组件可以设置玩家能够复活的次数,当超过复活次数时,会被传送到游戏区域外,非常适合用来制作带有有限复活条件的地图玩法。 + +⑤技能:该组件可以简单地自定义一些基础型的技能,目前可以定义给予状态效果技能,和发射弹射物技能。 + +⑥个人排行榜:该组件可以追踪一些玩家数据并对其进行排行,显示在游戏地图内。 + +⑦旧版初始物品:该组件可以通过填写对应的原版物品ID,让玩家进入游戏时得到指定的出生道具,若希望给予自定义物品,请使用初始物品功能。 + +⑧旧版初始装备:该组件可以通过填写对应的原版物品ID,让玩家进入游戏时得到穿戴指定的原版装备,若希望给予自定义装备,请使用初始装备功能。 + + + +#### 生物类目: + +生物类目:用于添加自定义生物,详细组件说明如下: + +①生物:该组件可以添加自定义生物,创建自定义生物时,会在资源包和行为包上生成几个文件,而生物行为最终会放置在行为包/entites文件夹内。目前生物功能不支持添加自定义的生物模型、动画,以及更复杂的生物行为组合,但使用该功能可以快速生成生物模板,方便熟练的开发者在之后进行文本级的深度自定义。除此之外,当开发者需要放置自定义生物到地图内时,需要先点击左侧组件中的坐标符号将放置功能解锁,方可放置生物至指定位置。 + + + +#### 物品类目: + +物品类目:用于添加食物、工具、装备、武器等自定义物品,详细组件说明如下: + +①普通物品:该组件可以添加自定义的普通道具、生物蛋道具、消耗性道具等。 + +②武器和工具:该组件可以添加自定义的剑武器和基于四种原版工具的新工具。 + +③盔甲:该组件可以添加对应玩家四个装备槽的自定义装备。同时,若开发者越来越熟练后,可以使用文本级的编辑将装备换成自定义3D模型版本的装备。 + + + +#### 方块类目: + +方块类目:用于添加自定义方块,详细组件说明如下: + +①自定义方块:该组件可以添加自定义的基础方块、刷怪笼方块和传送门方块。除此之外,当开发者需要放置自定义方块到地图内时,需要先点击左侧组件中的坐标符号将放置功能解锁,方可放置方块至指定位置。 + + + +#### 配方类目: + +配方类目:用于添加自定义配方,详细组件说明如下: + +①自定义配方:该组件可以添加自定义的熔炉配方和工作台配方。支持将配方的合成材料或结果产出设置为由物品组件和方块组件添加的自定义物品或者方块。 + + + +#### 队伍类目: + +队伍类目:用于添加队伍配置,详细组件说明如下: + +①队伍:该组件可以添加队伍并对队伍内的成员进行分数跟踪。 + + + +#### 经济类目: + +经济类目:可配置掉落、交易、商店等物品流通相关的功能,详细组件说明如下: + +①货币:该组件可以为地图玩法上的玩家新增流通的货币单位。 + +②掉落:该组件可以为自定义生物添加上自定义的掉落物。掉落物内可以包含多个物品的随机组合,支持自定义物品、装备、方块等。 + +③交易:该组件可以为自定义生物添加上类似村民的交易列表。 + +④出售商店:该组件可以为地图上正在被加载的实体添加上出售商店的功能。 + +⑤购买商店:该组件可以为地图上正在被加载的实体添加上购买商店的功能。 + + + +#### 剧情类目: + +剧情类目:可配置对话和任务,详细组件说明如下: + +①对话:该组件可以为地图上的实体添加对话选项,基于多个条件下,出现对话的时机也都可以不同。 + +②任务:该组件可以为地图上的实体添加可被玩家领取的任务。领取任务时,会对开发者提供多种领取条件的自定义属性,和任务完成时的道具奖励。 + + + +#### 流程类目: + +流程类目:包括开始游戏、结束游戏等控制游戏流程的组件,详细组件说明如下: + +①开始游戏:该组件可以为地图玩法添加开始游戏的条件。满足组件属性内自定义的多个条件后,游戏才会开始。 + +②结束游戏:该组件可以为地图玩法添加结束游戏的条件。满足组件属性内自定义的多个条件后,游戏才会结束。 + + + +#### 玩法元素类目: + +玩法元素类目:包括塔防、起床战争等特定玩法所需的组件,详细组件说明如下: + +①床:该组件可以为一个队伍内的成员设置一个公共出生点的坐标,当床被破坏时,可以自定义成员被淘汰后所在的位置,主要对应起床战争的内容。 + +②塔防大本营:该组件可以设置玩家需要保护的大本营,主要对应塔防玩法。 + +③攻击手:该组件可以设置自定义的塔防单位为玩家保护大本营,主要对应塔防玩法。 + +④生物点:该组件可以设置自定义的生物点坐标,利用地形的设计加上巡逻路径组件,可以设置一条塔防玩法中敌对生物的移动路径。 + +⑤巡逻路径:该组件可以为生物点的生物设置一条行径路径,主要对应塔防玩法。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/课程04.资源管理:转化方块模型.md b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/课程04.资源管理:转化方块模型.md new file mode 100644 index 0000000..33b2c39 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/课程04.资源管理:转化方块模型.md @@ -0,0 +1,26 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/4_1.f0b4b002.png +hard: 进阶 +time: 10分钟 +--- + +# 资源管理:转化方块模型 + +#### 作者:境界 + + + +在下方的资源管理列表里,会存在一个常用目录的折叠选项,对models选项进行右键,可以看到有转化Blockbench Java方块模型的选项。点击进去选择提前存好的bbmodel工程文件即可。 +为了让开发者能够少踩一些坑,这里收集了一些常见的无法导入的报错信息,方便开发者自己检查。 + + + + + +①该模型不是一个有效的自由模型项目:虽然选项上写的是Blockbench Java方块模型,但开发者需要在创建模型时将工程文件选择为自由模型工程,才能满足导入的要求。并且Java版方块模型的旋转角度是有限制的,使用自由模型时则没有旋转角度的限制。 + + + +![](./images/4_2.png) + +②该模型的贴图不是16x16大小,由于16*16的方块贴图对于游戏资源占用最小,官方建议开发者对方块模型的贴图尺寸进行更好的优化,从而将贴图限制在16x16。但有一些小技巧可以让超过16x16分辨率的模型被转化成功,即打开blockbench的工程文件,点击文件->项目,将贴图高度和宽度设置为16x16即可。但为了避免给低配终端玩家带来不好的游戏体验,这里还是建议开发者对方块贴图进行尺寸修改。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/课程05.地图编辑器.md b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/课程05.地图编辑器.md new file mode 100644 index 0000000..a8657e2 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/课程05.地图编辑器.md @@ -0,0 +1,22 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/5_1.1b182f91.png +hard: 入门 +time: 15分钟 +--- + +# 地图编辑器 + +#### 作者:境界 + + + +地图编辑器是针对基岩版(C++版)的地图编辑工具,界面分为提供基础功能的菜单栏、集成各种工具及属性的工具栏、进行地图绘制修改的操作区三个部分。 + +目前地图编辑器提供了笔刷工具、地形工具、橡皮工具、选取工具等等常用的便捷工具,开发者可以通过这些工具在操作区像使用绘图软件一样,对地图存档进行修改与绘制。本章节会简单地为组件开发者提供如何导出特征结构的方法,方便将其应用到自定义特征当中。 + + + +![](./images/5_1.png) + +进入地图编辑器时,默认工具会选择选取工具,若当前工具不是选取工具,可以点击便捷工具栏改变工具类型,接着选取完区域范围后,点击保存为结构,为其命名一个名称即可。 + diff --git a/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/课程06.简易教学①:制作新颜色的玻璃方块.md b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/课程06.简易教学①:制作新颜色的玻璃方块.md new file mode 100644 index 0000000..a5145c3 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/课程06.简易教学①:制作新颜色的玻璃方块.md @@ -0,0 +1,23 @@ +--- +front: https://nie.res.netease.com/r/pic/20210730/4ce20d4e-a4af-4d1f-a28a-036b122f1802.png +hard: 入门 +time: 15分钟 +--- + +# 简易教学①:制作新颜色的玻璃方块 + +#### 作者:境界 + + + +①把当前编辑器切换至关卡编辑器,若界面已在关卡编辑器可以不执行此操作。 + +②在组件面板右键添加方块组件,将渲染材质设置为半透明。 + +③将新颜色的玻璃贴图通过“资源管理”下的常用目录下textures\blocks,右键点击导入文件,把贴图导入进blocks文件夹即可。 + +④将玻璃贴图从“资源管理”中拖至“属性栏”模型与贴图的上面方块贴图、下面方块贴图、和侧面方块贴图,其他属性使用默认属性值。 + +⑤点击自定义方块组件右侧的放置按钮,解锁放置操作,若遇到弹出窗口提示需要重新刷新游戏,点击重新加载即可。 + +⑥最后点击编辑器右上角的“运行”按钮,在游戏的创造模式建筑分类里面可以找到刚刚添加的自定义方块。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/课程07.简易教学②:制作一条跑酷赛道.md b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/课程07.简易教学②:制作一条跑酷赛道.md new file mode 100644 index 0000000..a8bdf60 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第05章:MCSTUDIO功能简介/课程07.简易教学②:制作一条跑酷赛道.md @@ -0,0 +1,49 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/7_1.6371e1df.png +hard: 进阶 +time: 30分钟 +--- + +# 简易教学②:制作一条跑酷赛道 + +#### 作者:境界 + + + +跑酷赛道示例包下载:下载[示例包](https://g79.gdl.netease.com/guidedemo-case5.zip)。 + + + + + +一条跑酷赛道需要至少一个起点和终点,根据赛道的长度,可能还需要添加标记进度点,避免因为跑酷容错率小而浇灭玩家对地图玩法的热情。当玩家从跑酷地图上跌落下去时,应在一定下落高度前将玩家传送回上一个进度点。因此,本小节的简易教学将会使用多种颜色的羊毛作为建筑材料,攻击手为陷阱元素来教开发者如何制作一个不寻常的跑酷赛道。 + + + +![](./images/7_2.png) + +①使用粉色羊毛为玩家搭建一个出生点,使用紫色、深蓝色、蓝色、青色、绿色、黄色、橙色羊毛铺起赛道,用红色羊毛作为终点。 + + + +![](./images/7_3.png) + +②以搭建高度开始,往下十格以后的高度是会将玩家拉回复活点的高度,该功能需要使用命令方块配合。在粉色区域下方放置一个循环型命令方块。条件设置为无条件,红石选择保持始终开启。将指令内容设置为/tp @a[x=-13,dx=26,y=0,dy=40,z=0,dz=100,m=!c] 0 47 8。这会将筛选出所有玩家中,在x坐标从-13到13,y坐标从40到0,z坐标从0到100且不是创造模式的玩家,传送到起始点。因此在这个范围内的玩家若从跑酷位置掉落后,则会马上被传送到安全位置。传送时会在聊天信息里出现指令结果信息,若希望提示信息消失,可以使用gamerule指令的commandblockoutput、sendcommandfeedback选项设置为false。 + +③在终点位置的羊毛下方,放置一个循环型命令方块。条件设置为无条件,红石选择保持始终开启。将指令内容设置为/title @a[x=5,dx=-12,y=47,dy=10,z=74,dz=14,m=!c,tag=!success] title 恭喜你完成比赛。这会筛选出在进入红色羊毛区域内的玩家,并对他们发送比赛完成的信息。接着,在该命令方块指向的方向上,放置一个紧贴着该命令方块的连锁型命令方块。条件设置为无条件,红石选择保持始终开启。将指令内容设置为/tag @a[x=5,dx=-12,y=47,dy=10,z=74,dz=14,m=!c] add "success"。根据之前的教程可以知道,当循环型命令方块执行一次比赛完成的title指令后,紧接着会执行连锁型命令方块内的指令,并添加success这个标签,这样循环型命令方块的指令内容只会对该名通关玩家执行一次。 + +④在拉回玩家的循环型命令方块指向的方向上,放置一个紧贴着该命令方块的连锁型命令方块。条件设置为无条件,红石选择保持始终开启。将指令内容设置为/tag @a[x=-13,dx=26,y=0,dy=40,z=0,dz=100,m=!c] remove "success"。因此,当玩家从赛道跌落后被拉回来时,会尝试清除success标签,达到重复游玩的目的。 + +⑤在靠近终点位置设立两个橙色羊毛塔,这里将会作为攻击手所在的位置,会对靠近该区域的玩家进行骚扰攻击。 + +⑥将地图导入至MCSTUDIO,使用组件功能-玩家-基础属性,将玩家复活点设置在粉色羊毛的中心点。 + +⑦新增生物组件,将生物继承流浪者,资源设置为流浪者,并放大流浪者大小体型,将两种速度皆设置为0,因为我们希望它是站桩输出的远程射手。新增一个名为player标签的攻击类型,这样流浪者会对玩家进行攻击,其他属性由开发者自行决定。 + + + +![](./images/7_4.png) + +⑧点击组件-生物-攻击手右侧的位置符号按钮,解锁放置攻击手新生物的功能。将攻击手生物放在两个橙色柱子上,对临近终点的玩家进行骚扰性攻击。 + +⑨在运行前必须保存当前的制作进度,点击右上角快捷按钮的“保存”按钮后,就可以点击运行进入游戏测试啦! \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/README.md b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/3_1.png b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/3_1.png new file mode 100644 index 0000000..1e43412 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/3_1.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/4_1.png b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/4_1.png new file mode 100644 index 0000000..987d5f9 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/4_1.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/4_2.png b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/4_2.png new file mode 100644 index 0000000..5608962 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/4_2.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/4_3.png b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/4_3.png new file mode 100644 index 0000000..222f4c5 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/4_3.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/4_4.png b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/4_4.png new file mode 100644 index 0000000..cf51b5d Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/4_4.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/4_5.png b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/4_5.png new file mode 100644 index 0000000..cb97bf7 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/4_5.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/4_6.png b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/4_6.png new file mode 100644 index 0000000..b5de53b Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/4_6.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/4_7.png b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/4_7.png new file mode 100644 index 0000000..bdb8db5 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/4_7.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/4_8.png b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/4_8.png new file mode 100644 index 0000000..c76c755 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/4_8.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/4_9.png b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/4_9.png new file mode 100644 index 0000000..030b363 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/4_9.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/5_1.png b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/5_1.png new file mode 100644 index 0000000..1cf0c1e Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/5_1.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/5_2.png b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/5_2.png new file mode 100644 index 0000000..c98dfb3 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/5_2.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/5_3.png b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/5_3.png new file mode 100644 index 0000000..951ed18 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/5_3.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/6_1.png b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/6_1.png new file mode 100644 index 0000000..b0e6b16 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/6_1.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/7_1.png b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/7_1.png new file mode 100644 index 0000000..b735e82 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/7_1.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/8_1.png b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/8_1.png new file mode 100644 index 0000000..03ebada Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/8_1.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/8_2.png b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/8_2.png new file mode 100644 index 0000000..f24f636 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/images/8_2.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/课程01.认识Add-on.md b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/课程01.认识Add-on.md new file mode 100644 index 0000000..47ee396 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/课程01.认识Add-on.md @@ -0,0 +1,18 @@ +--- +front: https://nie.res.netease.com/r/pic/20210730/4ce20d4e-a4af-4d1f-a28a-036b122f1802.png +hard: 入门 +time: 10分钟 +--- + +# 认识Add-on + +#### 作者:境界 + + + +附加包(Add-on)是面向Minecraft全版本自定义的坚实一步,它受Mojang AB和微软的官方支持。 + +目前版本允许冒险家去更改他们的世界,这些功能都可以通过资源包和行为包来实现。与传统上,用脚本、编程手段去修改游戏内容不同,附加包提供了一套更加浅显易懂、灵活组合的方法。它使用JSON这种数据交换格式语言,甚至某种程度上,它并不具备完整的编程语言所应有的特性,因此开发者需要掌握的知识点更少,要了解的关键词也更少。 + +附加包功能的真正强大之处在于,降低了开发者对编程能力要求的前提下,还提供了远超以往Minecraft版本的自定义内容。 +在本章程中,将会带领开发者初步认识附加包能够为我们的创意做些什么。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/课程02.支持的功能.md b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/课程02.支持的功能.md new file mode 100644 index 0000000..43684ff --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/课程02.支持的功能.md @@ -0,0 +1,89 @@ +--- +front: +hard: 入门 +time: 15分钟 +--- + +# 支持的功能 + +#### 作者:境界 + + + +附加包(Add-on)能做到的层面有许多,我们在这里将它支持的功能拆分成以下几个大类: + +①自定义生物 + +②自定义物品 + +③自定义方块 + +④自定义配方 + +⑤自定义音乐 + +⑥自定义战利品 + +⑦自定义维度群系 + +⑧自定义状态效果 + + + +#### 自定义生物 + +生物包括原版生物和由开发者添加的原创生物。 + +一个生物至少会包括一个生物蛋、一张贴图纹理、一个几何体模型以及数个动画。打个比方,我们在游戏内遇到生物鸡时,它带有黄色的脚丫和红色的冠,它行走张望的表现来自移动动画和向某处看的动画。打开创造背包,可以看到有个鸡的生物蛋。相应的,在添加新的自定义生物时,这些内容也能一一进行定制化。 + + + +#### 自定义物品 + +一般来说,物品包括道具、装备、武器工具三种类型。 + +除了装备支持3D模型以外,在当前版本中,道具和武器暂不支持3D形式,并且物品支持自定义的内容较为有限,一些不太常见的功能,如支持副手手持的物品,或者是当作箭矢消耗的物品,还不能实现。 + +能够自定义的物品功能都较为基础,我们将道具分为以下三个种类:一般性道具、功能性道具、消耗性道具。一般性道具通常会作为其他道具的合成消耗品,如原版的烈焰棒,鹦鹉螺等,实质拿在手上并没有太大的作用。功能性道具通常被看作是对于冒险家生存有帮助的道具,如末影珍珠等。消耗性道具主要集中在食物上,如牛肉、金苹果等。 + +自定义装备支持设置四种槽类型,头盔、铠甲、护腿、鞋子,也支持设置相应的防御力和附魔等级。 + +自定义武器工具支持远程武器、锄、镐、铲、斧和近战武器。针对武器工具,还可以设置工具的等级和速度,而近战武器,还可以设置武器的伤害。 + + + +#### 自定义方块 + +自定义方块是继承了部分原版方块的功能,可由开发者自定义的新型方块。 + +由于《我的世界》的方块有许多特性,并且会随版本更新而添加更多新特性,因此绝大多数方块的功能无法被添加到自定义方块上。在中国版上,开发者除了可以自定义简单的单格方块外,通过开花组的努力,还可以给方块自定义模型、碰撞箱,以及部分特殊功能性方块的功能等。 + + + +#### 自定义配方 + +自定义配方功能允许开发者自定义合成工作台配方、熔炉配方、酿造台配方、切石机配方等,但目前熔炉配方不支持获得经验,要注意一下。 + + + +#### 自定义音乐 + +自定义音乐是指开发者可以通过放入文件后缀为ogg、wav格式的音乐文件到附加包内,通过实体生物、背景音乐、音乐指令等方式为冒险家呈现更加独特、美妙的音乐体验。 + + + +#### 自定义战利品 + +自定义战利品功能允许开发者修改世界中的宝箱、生物方块的掉落物、或者钓鱼奖励等。 + + + +#### 自定义维度群系 + +自定义维度群系允许开发者新增除主世界、下界、末地以外的额外维度,同时搭配自定义群系可以为冒险家创造更加别致的生态面貌,高度提升游戏的体验。 + + + +#### 自定义状态效果 + +自定义状态效果允许开发者通过与MODSDK的配合,为冒险家增加、增益、减益buff效果。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/课程03.你的第一个Add-on.md b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/课程03.你的第一个Add-on.md new file mode 100644 index 0000000..c70c36a --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/课程03.你的第一个Add-on.md @@ -0,0 +1,80 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/3_1.ad6421a3.png +hard: 入门 +time: 15分钟 +--- + +# 你的第一个Add-on + +#### 作者:境界 + + + +附加包(Add-on)由Resource Pack(以下称为资源包)和Behavior Pack(以下称之为行为包)组成,资源包加载在客户端中,里面涵盖的资源包括生物模型、动画、粒子、方块贴图、生物贴图、物品贴图、方块模型、音效等。而行为包更多处理服务端的任务,如生物的行为、物品的功能、战利品的配置、生物在世界中的生成规则、群系维度的配置等。 + +在做一个附加包时,开发者应当十分注重工程内每个文件夹的命名,因为它需要根据指定的文件夹引入相应的配置文件,同时在资源包和行为包中都需要在根目录下有一个入口文件,其格式为json,来告诉游戏是哪个附加包,在这里我们通常会命名它为manifest.json。只要在工程内的资源包和行为包创建了这样的文件,即使没有自定义的游戏内容,它也是一个附加包了。 + + + +#### 入口文件manifest.json + +如果使用国际版Add-on附加包模板的开发者,需要注意一下微软将manifest内的format_version参数提升为2了,而在中国版1.16.10里,还可以继续使用兼容性format_version:1的参数,Name通常指代附加包的名字,亦或是作品名称,description意为简介描述,在这里开发者可以简单地写上团队名称等内容。 + +UUID 是指Universally Unique Identifier,翻译为中文是通用唯一识别码,UUID 的目的是让每个附加包内的所有元素都能有唯一的识别信息。如此一来,每个人都可以创建与其它人不冲突的 UUID,且不需考虑附加包创建时的冲突问题。开发者可以通过网上自由分享的uuid生成器页面进行UUID的生成,资源包和行为包一共至少需要四个UUID,所以通常情况下要一次性生成四个UUID。 + +![](./images/3_1.png) + + + +```json +{ + + "format_version": 1, + + "header": { + + "name": "Design Res", + + "description": "By Design Team", + + "uuid": "21de7661-def9-46e8-b841-2435bb0b1492", + + "version": [ + + 0, + + 0, + + 0 + + ] + + }, + + "modules": [ + + { + + "type": "resources",//type改为data时,则变为行为包的manifest + + "uuid": "182664d3-80cc-476a-8043-183afad03acc", + + "version": [ + + 0, + + 0, + + 0 + + ] + + } + + ] + +} +``` + +上图是一个简单的资源包manifest的书写格式,与资源包不同的是,行为包的manifest文件里,type内的resourece字段需要改成data字段即可。 + diff --git a/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/课程04.开始认识行为包.md b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/课程04.开始认识行为包.md new file mode 100644 index 0000000..6783b7b --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/课程04.开始认识行为包.md @@ -0,0 +1,96 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/4_1.590e60eb.png +hard: 入门 +time: 20分钟 +--- + +# 开始认识行为包 + +#### 作者:境界 + + + +行为包内运作的内容是运行在服务端上的。 + +服务端可以理解为一个公共环境,它会向服务端内所有的玩家客户端提供各种服务。例如不同生物会出现在不同的群系,不同种类的生物有不一样的行为,它们又会各自掉落不一样的战利品,这些都是服务端去实施的行为。 +在我的世界中,服务端可以简单理解为一个游戏存档,当玩家自己单人游玩,服务端运行在本地,它的延迟最低,对于玩家影响也最小。在联机大厅游戏中,服务端是房主,其他玩家进入房间会受到主机玩家的终端配置和网络延迟影响,因此游戏体验相较于单人游戏时,可能表现得不太稳定。而在网络游戏里,服务端则是一台远程主机,所有玩家连接进入这个世界都不需要去承担服务端带来的运行计算压力。这是三者之间的区别,因此,开发者在设计行为包的时候,应当遵守第一原则,即玩家在本地游玩时,就要有流畅的游戏体验。否则开发者将这样的附加包放进联机游戏和网络游戏当中,都会带来不流畅的游戏体验。 + + + +### 行为包的工程文件结构 + +#### entities[生物行为文件夹] + +在原版游戏包里,这里存放着所有原版生物的行为文件,是开发者认知生物行为的最佳场所。同时,我们可以在这里重新修改原版生物的行为,如让鸡可以像末影人一样传送,或者末影人可以像鸡一样每隔一段时间生下一颗鸡蛋。当我们组合新的原创生物时,它们的行为文件也会放在这里。 + +![](./images/4_1.png) + + + +#### loot_tables[掉落物文件夹] + +在原版行为包里,这里存放着所有原版的宝箱战利品,生物战利品,钓鱼战利品等。通过编辑行为,我们可以将生物及自定义方块的战利品表指向存放在这里的任何一种配置文件里,达到自定义掉落物的效果。 + +![](./images/4_2.png) + + + +#### trading[村民交易表文件夹] + +在原版行为包里,这里存放着所有原版游戏中,旧村民和新村民的交易表。旧村民交易表在trading目录下,新村民交易表在trading目录内的economy_trades中。一般来说,我们要修改原版村民的交易表时,优先修改的都是新村民的交易内容。并且,我们也可以将新的交易表文件放在这里,让带有交易行为的其他生物能够读取这些交易表。 + +![](./images/4_3.png) + + + +#### spawn_rules[生物生成规则文件夹] + +在原版行为包里,这里存放着所有原版生物的生存规则,若你想要重新编写原版生物的生成规则,可以直接在这里修改。当你正在修改自定义生物的生成规则时,你的新生物生成规则也应该放在这里。 + +![](./images/4_4.png) + + + +#### recipes[配方文件夹] + +在原版行为包里,这里存放着所有原版的配方。在1.16.10的中国版中,开发者应在模组文件夹内将文件夹名改成netease_recipes,才能在不开实验玩法的条件下使用这项功能。 + +![](./images/4_5.png) + + + +#### items[物品文件夹] + +在原版行为包里,这里存放着大部分原版食物的行为文件。在1.16.10的中国版中,开发者应在模组文件夹内将文件夹名改成netease_items_beh,才能在不开实验玩法的条件下使用自定义物品功能。 + +![](./images/4_6.png) + + + +#### blocks[方块文件夹] + +原版行为包目前没有包含任何一个原生方块的行为,开发者需要通过后续的教程以及开发者技术中心的文档、minecraft wiki作为学习参考的对象。在1.16.10中国版中,开发者应在模组文件夹内将文件夹名改成netease_blocks,才能在不开实验玩法的条件下使用自定义方块功能。 + + + +#### features[特征文件夹] + +在原版行为包里,这里存放着大多数原版群系中的特征。包括矿石分布特征、植被分布特征等。在1.16.10中国版中,开发者应在模组文件夹内将文件夹名改成netease_features,才能在不开实验玩法的条件下使用自定义特征功能。 + +![](./images/4_7.png) + + + +#### feature_rules[特征规则文件夹] + +在原版行为包里,这里存放着大多数原版群系中的特征规则。特征规则将决定特征出现的条件。在1.16.10中国版中,开发者应在模组文件夹内将文件夹名改成netease_feature_rules,才能在不开实验玩法的条件下使用自定义特征规则功能。 + +![](./images/4_8.png) + + + +#### Biomes[群系文件夹] + +在原版行为包里,这里存放着所有原版群系设定。在1.16.10中国版中,开发者应在模组文件夹内将文件夹名改成netease_biomes,才能在不开实验玩法的条件下使用自定义群系功能。 + +![](./images/4_9.png) \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/课程05.定义生物行为的三种结构.md b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/课程05.定义生物行为的三种结构.md new file mode 100644 index 0000000..7a592f2 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/课程05.定义生物行为的三种结构.md @@ -0,0 +1,185 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/5_1.e886f00f.png +hard: 进阶 +time: 20分钟 +--- + +# 定义生物行为的三种结构 + +#### 作者:境界 + + + +开发者第一次点开生物行为文件时,常常会对component_groups、components、events三种结构感到困惑。这三种结构是组成行为包里生物行为的基础结构。本章将会带领开发者逐一梳理这几种结构的不同作用。 + + + +#### components【组件行为】 + +组件是构建我的世界基岩版实体的属性集合。 + +它是由Mojang和微软提供给开发者使用的,并且不允许开发者自创新的组件,当开发者想要给实体添加行为时,可以通过添加组件到组件行为对象中来让实体行为表现得更加复杂多样。 + +例如,当我们希望某种生物会爬行,我们可以通过添加"minecraft:can_climb": {}来实现。所有的组件格式都以"minecraft:<组件名称>": {<组件设定>}为基准,不同的组件带有不同的设定。 + +![](./images/5_1.png) + +``` +{ + "format_version":"1.16.0", + "minecraft:entity":{ + "description":{ + }, + "component_groups":{ + }, + "components":{ + "minecraft:health":{ + "value":4, + "max":4 + } + }, + "events":{ + } + } +} +``` + +上图是原版鸡生物的部分行为组成,其中minecraft:health组件会影响实体的血量,在图片中,鸡的生命值和最大生命值被设置为4点,即两颗心。 + + + +#### component_groups【组件组】 + +组件组将多个组件分组在一起,就像一个文件夹一样,因此多个组件组内可以包含多个组件,并且可以在events事件内添加和删除它们,以创建自定义生物行为的组合。 + +在组件组内放入的组件不会自动添加到生物实体上,在事件未被添加进生物行为前,它们不会做任何事。只有当事件被触发后,对应的事件将组件会变为工作状态,它才会开始影响生物的行为。 + +![](./images/5_2.png) + +``` +{ + "format_version":"1.16.0", + "minecraft:entity":{ + "description":{}, + "component_groups":{ + "minecraft:chicken_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 + } + } + }, + "components":{}, + "events":{} + } +} +``` + +上图是原版鸡的部分组件组,其中官方对组件组的命名格式为"minecraft:<命名空间>:chicken_baby<名称>",开发者可以自由定义命名空间和名称,如"design:custom_abc"。在chicken_baby中,微软将minecraft:is_baby组件放在这里,通过事件来宣告某只鸡是否为幼年鸡。 + + + +#### events【事件】 + +事件是用于添加或删除组件组的特殊语法,通过这些改变,我们可以为实体创建动态行为。 + +![](./images/5_3.png) + +在上图中,原版鸡使用minecraft:ageable_grow_up事件来删除chicken_baby组,添加新的chicken_adult组,来将一只幼年鸡变为成年鸡。 + +``` +{ + "format_version":"1.16.0", + "minecraft:entity":{ + "description":{ + + }, + "component_groups":{ + + }, + "components":{ + + }, + "events":{ + "from_egg":{ + "add":{ + "component_groups":[ + "minecraft:chicken_baby" + ] + } + }, + "minecraft:entity_spawned":{ + "randomize":[ + { + "weight":95, + "remove":{ + + }, + "add":{ + "component_groups":[ + "minecraft:chicken_adult" + ] + } + }, + { + "weight":5, + "remove":{ + + }, + "add":{ + "component_groups":[ + "minecraft:chicken_baby" + ] + } + } + ] + }, + "minecraft:entity_born":{ + "remove":{ + + }, + "add":{ + "component_groups":[ + "minecraft:chicken_baby" + ] + } + }, + "minecraft:ageable_grow_up":{ + "remove":{ + "component_groups":[ + "minecraft:chicken_baby" + ] + }, + "add":{ + "component_groups":[ + "minecraft:chicken_adult" + ] + } + } + } + } +} +``` + + + +#### 如何触发事件 + +原版中有许多组件会触发事件,这些组件可能在一开始就被添加进了组件行为,然后在不同的条件下触发事件,来实现复杂多样的生物行为。并且所有实体会有一些共同事件,这些事件是由游戏引擎控制触发的,如minecraft:entity_spawned,它会在生物第一次生成时触发。当然,在之后开发者学会MODSDK后,也可以通过生物事件接口来触发生物的事件。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/课程06.基础的实体生成规则.md b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/课程06.基础的实体生成规则.md new file mode 100644 index 0000000..e6b2304 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/课程06.基础的实体生成规则.md @@ -0,0 +1,70 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/6_1.3ddaf093.png +hard: 进阶 +time: 15分钟 +--- + +# 基础的实体生成规则 + +#### 作者:境界 + + + +实体生成规则用来控制任一实体在世界中生成的逻辑。 + +目前常用于生物的生成,生物是实体这一理念的延伸,我们可以说鸡、牛、猪是实体,也是生物,但矿车、船只是实体不是生物。因此船、矿车这种实体不应在自然界中生成,而鸡、牛、猪等生物需要在世界中生成。 +本章我们将会教给大家如何简单地改变原版的生物生成规则。 + + + +#### 修改尸壳的生成规则 + +在原版的生成规则中,尸壳这一种攻击性生物只会出现在沙漠中,我们希望它能生成在每个攻击性生物都会出现的地方,而不仅仅是沙漠,请看下面的分析: + + + + + +``` +{ + "format_version": "1.8.0", + "minecraft:spawn_rules": { + "description": { + "identifier": "minecraft:husk", + "population_control": "monster" + }, + "conditions": [ + { + "minecraft:spawns_on_surface": {}, + "minecraft:brightness_filter": { + "min": 0, + "max": 7, + "adjust_for_weather": true + }, + "minecraft:difficulty_filter": { + "min": "easy", + "max": "hard" + }, + "minecraft:weight": { + "default": 240 + }, + "minecraft:herd": { + "min_size": 2, + "max_size": 4 + }, + "minecraft:biome_filter": { + "test": "has_biome_tag", "operator": "==", "value": "desert" + } + } + ] + } +} +``` + + + +①当前生物生成规则的format_version停留在1.8.0版本。 + +②identifier指向生物的名称,population_control是指由游戏引擎控制的生物数量,这里可以写上的字段有animal、underwater_animal、monster、ambient。因此在基岩版中,存在着4个无法直接确定生物数量的维度。打个比方,几乎绝大多数动物都会在亮度7~15的时候出现,而绝大多数攻击性生物都会在0~7的时候出现。因此animal的数量控制了白天时候玩家所看到的生物数量,monster控制了黑夜时分玩家所看到的生物数量。如果同时允许animal的数量和monster的数量在同一个时间内一起出现,可能会出现由于世界中生物实体数量生成过多,所导致的游玩体验降低的风险,请开发者斟酌使用。 + +③生物生成主要是由群系过滤器控制,biome_filter下接收一个或多个带有群系标签(has_biome_tag)的过滤条件,在尸壳的源文件中可以看到value值的参数是desert,因此将desert改为monster后,就可以让尸壳出现的规律和僵尸一样了。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/课程07.简易教学①:修改铁傀儡的基础属性.md b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/课程07.简易教学①:修改铁傀儡的基础属性.md new file mode 100644 index 0000000..68fed90 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/课程07.简易教学①:修改铁傀儡的基础属性.md @@ -0,0 +1,56 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/7_1.59202254.png +hard: 进阶 +time: 10分钟 +--- + +# 简易教学①:修改铁傀儡的基础属性 + +#### 作者:境界 + + + +在《开始认识行为包》章节已知,通过游戏根目录/data/behavior_packs/vanilla/entities中可以找到原版铁傀儡的行为文件。开发者也可以下载原版Add-on附加包模板,通常压缩包内会携带behavior_packs文件夹,与前面提到的游戏根目录下所寻找的行为文件夹相对应。 + + + +![](./images/7_1.png) + + + +``` +{ + "format_version": "1.13.0", + "minecraft:entity": { + "description": { + }, + + "component_groups": {}, + + "components": { + "minecraft:collision_box": { + "width": 1.4, + "height": 2.9 + }, + "minecraft:health": { + "value": 100, + "max": 100 + }, + "minecraft:movement": { + "value": 0.25 + } + }, + + "events": { + } + } +} +``` + + + +①:minecraft:collision_box是控制一个生物碰撞盒子的组件。碰撞盒子决定其他生物距离该类生物多远会产生推挤碰撞,以及可以在多大范围内能伤害到这类生物。其中width决定这个碰撞盒子的宽度,height决定它的高度。 + +②:minecraft:health是管理一个生物生命值的组件。value的参数代表生物的生命值,max代表生物的最大生命值。 + +③:minecraft:movement是控制生物的速度组件。其中value的参数代表速度。我的世界基岩版速度换算公式为:movement[速度值] * speed_multiplier[部分行为的速度加成] * friction[方块的摩擦力,默认是0.6] * 20 tick[游戏时间]。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/课程08.简单教学②:修改牛的掉落物为鸡的掉落物.md b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/课程08.简单教学②:修改牛的掉落物为鸡的掉落物.md new file mode 100644 index 0000000..5bb82ad --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第06章:认识Addon/课程08.简单教学②:修改牛的掉落物为鸡的掉落物.md @@ -0,0 +1,50 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/8_1.325675fb.png +hard: 入门 +time: 10分钟 +--- + +# 简单教学②:修改牛的掉落物为鸡的掉落物 + +#### 作者:境界 + + + +在《开始认识行为包》章节已知,通过游戏根目录/data/behavior_packs/vanilla/entities中可以找到原版牛的行为文件。开发者也可以下载原版Add-on附加包模板,通常压缩包内会携带behavior_packs文件夹,与前面提到的游戏根目录下所寻找的行为文件夹相对应。 + + + +![](./images/8_1.png) + + + +``` +{ + "format_version": "1.16.0", + "minecraft:entity": { + "description": { + }, + "component_groups": { + + "minecraft:cow_adult": { + "minecraft:loot": { + "table": "loot_tables/entities/cow.json" + } + } + }, + + "components": { + }, + + "events": {} + } +} +``` + + + +![](./images/8_2.png) + + + +①:minecraft:loot是管理生物掉落物表的组件。table的参数指向loot_tables文件夹下的一个表文件路径。根据路径提示打开loot_tables文件夹,再打开entities文件夹,可以看到里面存放了所有实体的掉落品文件,其中也包括生物鸡的掉落物品文件。将loot_tables/entities/cow.json改为loot_tables/entities/chicken.json即可完成把牛的掉落物修改为鸡的掉落物。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/README.md b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/10_1.jpg b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/10_1.jpg new file mode 100644 index 0000000..76b86e1 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/10_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/10_2.jpg b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/10_2.jpg new file mode 100644 index 0000000..9a8293a Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/10_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/10_3.jpg b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/10_3.jpg new file mode 100644 index 0000000..fb97f71 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/10_3.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/10_4.jpg b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/10_4.jpg new file mode 100644 index 0000000..05a18bd Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/10_4.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/11_1.jpg b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/11_1.jpg new file mode 100644 index 0000000..8071566 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/11_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/11_2.jpg b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/11_2.jpg new file mode 100644 index 0000000..940360a Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/11_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/11_3.jpg b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/11_3.jpg new file mode 100644 index 0000000..9ea86a8 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/11_3.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/11_4.jpg b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/11_4.jpg new file mode 100644 index 0000000..24346e5 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/11_4.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/11_5.jpg b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/11_5.jpg new file mode 100644 index 0000000..9255733 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/11_5.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/11_6.jpg b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/11_6.jpg new file mode 100644 index 0000000..e1cc7b6 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/11_6.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/14_1.jpg b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/14_1.jpg new file mode 100644 index 0000000..2fc08fa Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/14_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/15_1.jpg b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/15_1.jpg new file mode 100644 index 0000000..cddcaf4 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/15_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/1_1.png b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/1_1.png new file mode 100644 index 0000000..5b3b6e0 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/1_1.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/1_2.png b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/1_2.png new file mode 100644 index 0000000..312d469 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/1_2.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/1_3.png b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/1_3.png new file mode 100644 index 0000000..ca0034d Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/1_3.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/1_4.png b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/1_4.png new file mode 100644 index 0000000..0a25c5c Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/1_4.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/1_5.png b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/1_5.png new file mode 100644 index 0000000..6f07cef Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/1_5.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/1_6.png b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/1_6.png new file mode 100644 index 0000000..305c7d0 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/1_6.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/5_1.jpg b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/5_1.jpg new file mode 100644 index 0000000..26fdec4 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/5_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/6_01.png b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/6_01.png new file mode 100644 index 0000000..a821d4f Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/6_01.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/6_1.jpg b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/6_1.jpg new file mode 100644 index 0000000..10918e2 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/6_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/6_2.jpg b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/6_2.jpg new file mode 100644 index 0000000..e69c680 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/6_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/6_3.jpg b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/6_3.jpg new file mode 100644 index 0000000..3e845e0 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/6_3.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/7_01.png b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/7_01.png new file mode 100644 index 0000000..d914d3b Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/7_01.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/7_1.jpg b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/7_1.jpg new file mode 100644 index 0000000..80a33f8 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/7_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/7_2.jpg b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/7_2.jpg new file mode 100644 index 0000000..6aef222 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/7_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/8_1.jpg b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/8_1.jpg new file mode 100644 index 0000000..3f9885a Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/images/8_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程01.认识自定义生物.md b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程01.认识自定义生物.md new file mode 100644 index 0000000..e249e2a --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程01.认识自定义生物.md @@ -0,0 +1,72 @@ +--- +front: +hard: 入门 +time: 15分钟 +--- + +# 认识自定义生物 + + + +#### 作者:境界 + + + +#### 什么是自定义生物 + +自定义生物功能为开发者提供了全新的思路来丰富我的世界基岩版的生物种类。在过去,修改生物AI行为和形象,对于MC新手开发者是极其复杂的事情。它不仅需要开发者对编程语言有较高的掌握和了解,可能还会对一些编程算法知识有额外要求。现在,开发者只需要学会掌握一种文本编辑器,对于JSON语言有一定的了解,便可以与美术配合、甚至是靠单人来创造一个新的生物。 + + + +### Add-on对自定义生物的功能分配 + +#### 资源包 + +在我的世界基岩版中,资源包负责在游戏里渲染生物,同时会根据生物行为的改变而产生相应的视觉效果的反馈。 + +#### 行为包 + +行为包则控制生物在游戏里表现的行为,同时会提醒资源包在合适的契机配合播放什么样的动画和特效等。 + + + +#### 如何在资源包中定义自定义生物 + +资源包运行在客户端中,是游戏渲染生物材质、动画、模型等的入口。以下将材质包主目录简写为RP。 + + + +自定义生物的定义文件,会放置在:RP/entity文件夹中。 + +![](./images/1_1.png) + + + +自定义生物的动画文件,会放置在:RP/animations文件夹中。 + +![](./images/1_2.png) + + + +自定义生物的模型文件,会放置在:RP/models/entity文件夹中。 + +![](./images/1_3.png) + + + +自定义生物的渲染控制器文件,会放置在:RP/render_controllers文件夹中。 + +![](./images/1_4.png) + + + +自定义生物的动画控制器文件,会放置在:RP/animation_controllers文件夹中。 + +![](./images/1_5.png) + + + +自定义生物的音效文件,会放置在:RP/sounds文件夹中。 + +![](./images/1_6.png) + diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程02.生物定义文件.md b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程02.生物定义文件.md new file mode 100644 index 0000000..7fd4a75 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程02.生物定义文件.md @@ -0,0 +1,133 @@ +--- +front: +hard: 进阶 +time: 15分钟 +--- + +# 生物定义文件 + + + +#### 作者:境界 + + + +生物资源定义是在客户端中完成的,在这一类文件里,开发者可以定义生物的纹理、动画、模型、音效、粒子的短名称,在有需要的时候还能控制动画的播放时序。这种类型的文件称为生物定义文件,它位于RP/entity中,格式后缀为.json + +``` +{ + "format_version":"1.10.0", + "minecraft:client_entity":{ + "description":{ + "identifier":"minecraft:pig", + "materials":{ + "default":"pig" + }, + "textures":{ + "default":"textures/entity/pig/pig", + "saddled":"textures/entity/pig/pig_saddle" + }, + "geometry":{ + "default":"geometry.pig" + }, + "animations":{ + "setup":"animation.pig.setup.v1.0", + "walk":"animation.quadruped.walk", + "look_at_target":"animation.common.look_at_target", + "baby_transform":"animation.pig.baby_transform" + }, + "scripts":{ + "animate":[ + "setup", + { + "walk":"query.modified_move_speed" + }, + "look_at_target", + { + "baby_transform":"query.is_baby" + } + ] + }, + "render_controllers":[ + "controller.render.pig" + ], + "spawn_egg":{ + "texture":"spawn_egg", + "texture_index":2 + } + } + } +} +``` + + + +#### format_version + +format_version是指代定义内容的版本号,目前有1.8.0和1.10.0两个选择。1.8.0和1.10.0是自定义生物功能实现的两个重大改变版本,在基岩版1.8.0首次实装了自定义生物的功能,而在1.10.0的版本里,基岩版再次重新整理了自定义生物的写法格式。一般情况下,使用最新的1.10.0的格式,是现在自定义生物的通用规范。 + + + +#### identifier + +identifier意为生物的标识符。它由命名空间和名称组成。在游戏中使用/summon召唤实体指令时,以minecraft:为开头的原版生物可以直接用它们的名称作为实体召唤的对象。而自定义生物就需要在名字前再添加一个命名空间,可以帮助游戏区分例如:两个组件都加入了一种名为duck的鸭子生物,区分它们来自哪个组件就是命名空间的工作。 + + + +#### material + +material意为材质,是控制实体的呈现方式。游戏中的一些生物带有发光、透明的纹理部分,需要指定贴图纹理的材质才能正常生效。 + + + +#### texture + +texture意为贴图纹理,它控制实体的纹理。例如木头的木纹理,鸡身上的羽毛等。 + + + +#### geometry + +geometry意为模型几何体。它控制实体长什么样子。 + + + +#### animations + +animations意为动画,这里会放置一系列的动画资源和控制动画播放时机的动画控制器。 + + + +#### scripts/animate + +scripts/animate意为播放根动画。动画或动画控制器会放置在这里。 + + + +#### render_controller + +render_controller意为渲染控制器。它是一种由开发者定义生物该如何渲染的方法。通过渲染控制器可以设置生物的几何体、材质、贴图、和部位隐藏显示等。注意:这里的几何体、材质、贴图等词,代表渲染生物需要哪些关键信息。如原版猪需要展示两种形态,根据放鞍或者不放鞍的状态显示带有鞍或者不带鞍的贴图,则可以在渲染控制器中进行切换,并由开发者定义入口,再由生物定义文件中指向完整的资源路径。 + + + +#### particle_effects + +particle_effects意为粒子效果。这里会放置一系列需要用到的粒子资源,开发者可以使用原版粒子亦或是自定义粒子。 + + + +#### sound_effects + +sound_effects意为音效效果。这里会放置一系列需要用到的音效资源,开发者可以使用原版音效亦或是自定义音效。 + + + +#### spawn_egg + +spawn_egg意为生物蛋,是我的世界召唤生物的一种方式。开发者可以定义生物蛋引用的贴图资源。 + + + +#### enable_attachable + +enable_attachable意为显示身上穿戴物。在玩家、部分凋零生物身上都有这个属性。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程03.动画格式.md b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程03.动画格式.md new file mode 100644 index 0000000..ccc34b3 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程03.动画格式.md @@ -0,0 +1,81 @@ +--- +front: +hard: 进阶 +time: 15分钟 +--- + +# 动画格式 + + + +#### 作者:境界 + + + +动画格式是指由基岩版团队为生物实体开发的数据结构,是写入JSON文件并以JSON语法呈现的。若开发者是使用Blockbench这样的软件制作的美术资源,是可以不用特地了解这些结构的。本章教程更多是为同时兼具美术和开发的个人开发者进行一些知识上的梳理和归纳,展示图中可能对一些非关键信息进行了折叠。 + + + +#### format_version + +format_version是指代定义动画文件的版本号,目前只有1.8.0这一格式版本号。 + + + +#### Animation. + +每个动画资源都必须以animation.为固定前缀。后面的名称可以以生物名称加动画名称的 组合。 + + + +#### loop + +loop意为动画播放的循环格式。如果只播放一次,则loop属性可以不填;若循环播放,loop值为true。若动画结束后停留在最后一帧,值为"hold_on_last_frame"。 + + + +#### animation_length + +animation_length意为动画时长。 + + + +#### bones + +bones意为每个骨骼动画信息的集合。在里面保存着所有生物骨骼的位移信息、旋转信息、缩放信息。其中里面的每个属性键都对应着生物几何体的骨骼名称。对应的值内共包含rotation旋转、position位置、scale缩放三种动画形式,每个形式内再包含着相应的动画信息。 + + + +``` +{ + "format_version": "1.8.0", + "animations": { + "animation.fallout_cow.dying": { + "animation_length": 0.52, + "bones": { + "body": { + "rotation": { + "0.0": [0, 0, 0], + "0.52": [0, 0, -5] + }, + "position": { + "0.0": [0, 0, 0], + "0.52": [-2, -11, 0] + } + } + } + } + } +} +``` + + + +如上面代码所示: + +"0.0"->“0.52”这个时间段,body骨骼绕Z轴旋转-5角度,位置以骨骼位置向X轴左边偏移2个单位,向Y轴向下偏移11个单位,同时最后再放大1.5倍。 + + + +当前中国版的动画效果都是线性动画效果,即动画的表现形式为匀速。曲线变化动画效果目前还在实验玩法当中,开发者可以在单人模式上看到效果,但无法将组件用于联机,请等待基岩版团队后续开发计划。 + diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程04.渲染控制器.md b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程04.渲染控制器.md new file mode 100644 index 0000000..5e8087e --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程04.渲染控制器.md @@ -0,0 +1,92 @@ +--- +front: +hard: 进阶 +time: 15分钟 +--- + +# 渲染控制器 + + + +#### 作者:境界 + + + +渲染控制器帮助开发者控制生物如何渲染在游戏世界中的表现。它与动画控制器可以被视为自定义生物最难的两个部分。原版生物中,不是所有生物都只有单一表现。例如村民战争更新后,村民根据群系拥有不同皮肤,热带鱼拥有数千种组合,狼在生气时会红眼等,这些都是由渲染控制器所控制,同时又以JSON结构存放在游戏本地文件当中。除了阅读教程之外,原版文件同样是天然而又深度的教材,希望开发者能在学习过程中对于原版文件进行额外的知识补充。 + +``` +{ + "format_version":"1.8.0", + "render_controllers":{ + "controller.render.default":{ + "geometry":"Geometry.default", + "materials":[ + { + "*":"Material.default" + } + ], + "textures":[ + "Texture.default" + ], + "overlay_color":{ + "r":0, + "g":0, + "b":0, + "a":0 + }, + "on_fire_color":{ + "r":0, + "g":0, + "b":0, + "a":0 + }, + "is_hurt_color":{ + "r":0, + "g":0, + "b":0, + "a":0 + } + } + } +} +``` + +#### format_version + +format_version是指代定义渲染控制器文件的版本号,目前只有1.8.0这一格式版本号。 + + + +#### controller.render. + +每个控制器都必须以controller.render.为固定前缀。后面的名称可以以生物名称作区隔。 + + + +#### material + +material意为材质。同样,值写为"Material.材质键",其中材质键为英文字符,会对应生物定义文件中,material对象下的键,它的值则会引向材质资源。 + + + +#### texture + +texture意为贴图纹理。同样,值写为"texture.贴图键",其中贴图键盘为英文字符,会对应生物定义文件中,贴图对象下的键,它的值则会引向贴图纹理资源。 + + + +#### overlay_color + +overlay_color意为叠加颜色。类似于图像处理软件的颜色叠加模式,其中r代表红色,g代表绿色,b代表蓝色,a代表透明程度。RGB是色彩中的三原色。在控制器中,值是由颜色分量除以255得出的。例如,RGB(255,0,0)表示红色,则在上述颜色值内填上"r": 1.0,"g": 0.0,"b": 0.0,"a": 1.0。 + + + +#### on_fire_color + +on_fire_color意为生物实体着火时的颜色。在MC中默认为红色,若不自定义此项则会继承原版生物着火的颜色,自定义形式同overlay_color。 + + + +#### is_hurt_color + +is_hurt_color意为生物实体受伤时的颜色。在MC中默认为红色,若不自定义此项则会继承原版生物受伤的颜色,自定义形式同overlay_color。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程05.动画控制器.md b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程05.动画控制器.md new file mode 100644 index 0000000..bb2c629 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程05.动画控制器.md @@ -0,0 +1,100 @@ +--- +front: +hard: 进阶 +time: 20分钟 +--- + +# 动画控制器 + + + +#### 作者:境界 + + + +动画控制器掌管生物播放动画的时机。一个生物实体可以同时加载多个动画资源,也可以同时加载多个动画控制器。动画控制器实际上是一个状态控制机,它在资源包上会用来播放动画,它在行为包上用来执行指令,或者指令“动画”。 + +浅粗地了解状态控制机,它实际上是一个特殊的逻辑管理模式,每个状态都有两个属性: + +1)在当前状态下做什么; + +2)如何转移到其他状态; + +状态机之所以有用,是因为它们使开发者能够自然地将动画分解成逻辑流程,每个状态都处理自己的动画和逻辑。 + +![](./images/5_1.jpg) + + + +例如,假设开发者要为自定义生物松鼠设置一个休息-移动动画,则它包含两种状态: + +休息状态和移动状态,可视化流程图如上方所示。 + +``` +{ + "format_version":"1.10.0", + "animation_controllers":{ + "controller.animation.squirrel.general":{ + "initial_state":"default", + "states":{ + "default":{ + "blend_transition":0.1, + "blend_via_shortest_path":true, + "animations":[ + "idle" + ], + "transitions":[ + { + "move":"query.is_moving" + } + ] + }, + "move":{ + "animations":[ + "move" + ], + "transitions":[ + { + "default":"!query.is_moving" + } + ] + } + } + } + } +} +``` + + + +#### format_version + +format_version是指代定义动画控制器文件的版本号,目前有1.8.0、1.10.0这两个格式版本号。1.10.0的版本里,基岩版再次重新整理了自定义生物的动画控制器写法格式。一般情况下,使用最新的1.10.0的格式,是现在自定义生物的通用规范。 + + + +#### controller.animation. + +每个控制器都必须以controller.animation.为固定前缀。后面的名称可以以生物名称作区隔。 + + + +#### initial_state + +initial_state是指动画控制器的初始状态,默认不写时,默认值为default。 + + + +#### states + +states是状态的集合,在里面会包含动画集合,切换状态集合。其中动画集合可以同时放置多个动画,它们会在切换到该状态时马上播放。如:松鼠摇头待机动画和松鼠摇尾巴待机动画。切换状态集合内,会有存放其他状态的对象数据,如上图中:切换到移动状态时,必须满足生物移动的条件。 + +最后是两个特殊属性,blend_transition和blend_via_shortest_path。 + +blend_transition意为让动画状态切换更加顺滑,合理分配该值可能会做出更加顺畅的动画切换效果,它的值取0.0~1.0之间。 + + + +#### blend_via_shortest_path + +blend_via_shortest_path意为是马上切换过渡到下个状态,根据开发者需求而定。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程06.绑定动画粒子和动画控制粒子.md b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程06.绑定动画粒子和动画控制粒子.md new file mode 100644 index 0000000..f9a59e6 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程06.绑定动画粒子和动画控制粒子.md @@ -0,0 +1,135 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/6_1.6892d54c.jpg +hard: 进阶 +time: 20分钟 +--- + +# 绑定动画粒子和动画控制粒子 + + + +#### 作者:境界 + + + +粒子可以做出非常出彩的效果,让生物的视觉体验提高几个层次。在游戏中,墨鱼喷墨和烈焰人蓄力发射烈焰弹时的火焰都归于粒子效果的范畴。开发者可以在生物定义文件中引用粒子,再通过动画和动画控制器两种方式来播放粒子。粒子还可以绑定在生物的几何体模型上,根据生物的骨骼位置、旋转角度进行相对计算,是更加进阶的粒子使用。在本章中会教开发者如何让粒子跟随生物实体,实现一个简单的效果。 + +![](./images/6_01.png) + + + +原版的粒子可以在RP/particles文件夹中找到,以中国版当前版本1.21(1.16.10)中,一共有113个预定义好的原版粒子,开发者可以通过研读其中的结构设定,来实现自己的新粒子。 + +![](./images/6_1.jpg) + + + +1)打开Blockbench,导入原版猪的模型和贴图。 + +2)点击动画模式,新建一个动画。 + +![](./images/6_2.jpg) + + + +3)点击动画,动画效果,打开动画特效面板。 + +![](./images/6_3.jpg) + + + +4)在效果栏列插入帧,同时在左侧关键帧文字上方选择导入文件,选择所需的粒子。同时填入在生物定义文件预先索引好的粒子短名称。 + +5)拖动鼠标即可预览粒子。 + +``` +{ + "format_version":"1.8.0", + "animations":{ + "animation.pig.particle":{ + "particle_effects":{ + "0.0":{ + "effect":"smoke" + } + } + } + } +} +``` + + + +6)保存好动画文件,可以看到,在动画格式中新增了一个particle_effects对象,内容是时间差和它对应的值,值内包含着粒子效果的短名称,将新建的动画在生物定义文件中定义好后,若动画配置正确,游戏内粒子就会正常播放了。 + +7)在Blockbench当中,一帧内只能添加一种粒子效果。若需要同时播放好几种粒子效果,则需要将粒子效果对象改为粒子效果数组,再手动填入每一个粒子对象。 + +如: + +``` +{ + "format_version":"1.8.0", + "animations":{ + "animation.pig.particle":{ + "particle_effects":{ + "0.0":[ + { + "effect":"smoke" + }, + { + "effect":"flame" + } + ] + } + } + } +} +``` + +``` +{ + "format_version":"1.10.0", + "animation_controllers":{ + "controller.animation.blaze.flame":{ + "initial_state":"default", + "states":{ + "default":{ + "transitions":[ + { + "flaming":"query.is_charged" + } + ] + }, + "flaming":{ + "particle_effects":[ + { + "effect":"charged_flames" + } + ], + "transitions":[ + { + "default":"!query.is_charged" + } + ] + } + } + }, + "controller.animation.blaze.move":{ + "initial_state":"default", + "states":{ + "default":{ + "animations":[ + "move", + "look_at_target" + ] + } + } + } + } +} +``` + + + +与绑定动画粒子相比,动画控制器粒子可能更为简单。在每个状态下新建一个粒子效果集合,即"particle_effects",它同样可以同时添加多种粒子效果。 + +与绑定动画粒子不同的是,当动画控制器切换到带有粒子效果的状态时,粒子会在第一时间播放,想要延迟粒子的播放则只能手动修改粒子的发射时间。而动画粒子可以根据动画达到一定时间才会播放一次粒子效果。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程07.绑定动画音效及动画控制器音效.md b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程07.绑定动画音效及动画控制器音效.md new file mode 100644 index 0000000..8788a50 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程07.绑定动画音效及动画控制器音效.md @@ -0,0 +1,127 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/7_2.d721173b.jpg +hard: 进阶 +time: 20分钟 +--- + +# 绑定动画音效及动画控制器音效 + + + +#### 作者:境界 + + + +音效是可以提升游戏沉浸感、真实感的一种方法。在游戏里,玩家除了能够听到背景音乐、环境音乐,每种生物也都有自己的专属声音。例如羊吃草,蜜蜂嗡嗡嗡。一种生物如果在每个行为表现上都有自己的音效,会让玩家觉得生物更加生动。因此,本章将会教给开发者如何通过动画和动画控制器来给生物添加上音效。 + +![](./images/7_01.png) + + + +原版的音效可以在RP/sounds中找到,由于MC原版音效使用的是商业音效引擎FMOD,正常情况下是无法试听音效。但开发者可以在同目录下的sound_definitions.json文件中,找到音效的资源名称和资源路径。通过一定的比较,提取出适合的音效作为练习绑定生物音效的素材。 + +![](./images/7_1.jpg) + + + +1)打开Blockbench,导入原版猪的模型和贴图。 + +2)点击动画模式,新建一个动画。 + +3)点击动画,动画效果,打开动画特效面板。 + +4)在效果栏列插入帧,同时在左侧关键帧文字下方效果输入在生物定义文件中定义好的音效名称。 + +![](./images/7_2.jpg) + + + +5)进入游戏,测试音效播放情况。 + +6)保存好动画文件,可以看到,在动画格式中新增了一个sound_effects对象,内容是时间差和它对应的值,值内包含着音效效果的名称,将新建的动画在生物定义文件中定义好后,若动画配置正确,游戏内音效就会正常播放了。 + +``` +{ + "format_version":"1.8.0", + "animations":{ + "animation.pig.particle":{ + "sound_effects":{ + "0.0":{ + "effect": “smoke” + } + } + } + } +} +``` + + + +7)在Blockbench当中,一帧内只能添加一种音效效果。若需要同时播放好几种音效效果,则需要将音效效果对象改为音效效果数组,再手动填入每一个音效对象。 + +如: + +``` +{ + "format_version":"1.8.0", + "animations":{ + "animation.pig.particle":{ + "sound_effects":{ + "0.0":[ + { + "effect":"jiliguala" + }, + { + "effect":"yiyiyaya" + } + ] + } + } + } +} +``` + + + +与绑定动画音效相比,动画控制器音效可能更为简单。在每个状态下新建一个音效效果集合,即"sound_effects",它同样可以同时添加多种音效效果。 + +``` +{ + "format_version":"1.10.0", + "animation_controllers":{ + "controller.animation.blaze.flame":{ + "initial_state":"default", + "states":{ + "default":{ + "transitions":[ + { + "flaming":"query.is_charged" + } + ] + }, + "flaming":{ + "sound_effects":[ + { + "effect":"balabala" + } + ], + "particle_effects":[ + { + "effect":"charged_flames" + } + ], + "transitions":[ + { + "default":"!query.is_charged" + } + ] + } + } + } + } +} +``` + + + +与绑定动画音效不同的是,当动画控制器切换到带有音效效果的状态时,音效会在第一时间播放,并且播放结束后就不会重新播放。若音效绑定在动画内并且动画被设置为循环开启,则音效会在指定的时间间隔不断重复播放,直到动画结束为止。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程08.继承与复用思维.md b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程08.继承与复用思维.md new file mode 100644 index 0000000..0d8ade3 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程08.继承与复用思维.md @@ -0,0 +1,23 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/8_1.17f29d6f.jpg +hard: 入门 +time: 10分钟 +--- + +# 继承与复用思维 + + + +#### 作者:境界 + + + +新手开发者在制作自定义生物时,往往带有很多奇妙的点子,但在实际操作过程中,难免遇到暂时无法攻克的问题。正所谓“千里之行始于足下”,学会使用原版生物资源来制作一些带有额外行为、功能、特效的生物,往往是一个能够不断积累经验,又不容易遇到挫败感的方法。 + +当制作一个新的生物时,一个可靠的逻辑链参考可如以下所示: + +![](./images/8_1.jpg) + + + +在制作新的自定义生物时,最好的学习资料来自原版的资源包。最好的仿照生物,也同样来自于原版世界中的原版生物。学习是一个不断积累,不断解决困难的过程。只要锲而不舍,则金石可镂。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程09.制作一只小水鸭.md b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程09.制作一只小水鸭.md new file mode 100644 index 0000000..ac2024f --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程09.制作一只小水鸭.md @@ -0,0 +1,15 @@ +--- +front: +hard: 入门 +time: 5分钟 +--- + +# 制作一只小水鸭 + + + +#### 作者:境界 + + + +水鸭是一个很经典的例子。鸭子和鸡的形态特征十分相似,但它又有独立的行为,如浮在水面上而又移动速度快等特点。根据继承与复用思维,这是一个很好的针对初级开发者适用的生物类型题材。因此本章内容教程会教给开发者如何自定义一个新生物:水鸭。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程10.利用Blockbench修整鸡的模型.md b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程10.利用Blockbench修整鸡的模型.md new file mode 100644 index 0000000..3c5daa3 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程10.利用Blockbench修整鸡的模型.md @@ -0,0 +1,63 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/10_2.aa637b4c.jpg +hard: 进阶 +time: 20分钟 +--- + +# 利用Blockbench修整鸡的模型 + + + +#### 作者:境界 + + + +![](./images/10_1.jpg) + + + +![](./images/10_2.jpg) + + + +在Blockbench中导入鸡的模型,从中可以看到它一共被分为6个骨骼。 + +1)body:身体 + +2)head:头部 + +3)leg0:右脚 + +4)leg1:左脚 + +5)wing0:右翼 + +6)wing1:左翼 + +这样的骨骼分组十分符合我们对新生物水鸭的需求。对照现实中的绿头水鸭,只需提取鸭头、鸭嘴、羽毛特征,进行个人风格化的修整,即可制作完一只水鸭模型。 + + + +![](./images/10_3.jpg) + + + +1)打开blockbench,选择基岩版模型。 + +2)点击文件,选择打开模型,找到鸡的模型文件(通常在客户端文件中的data/resource_packs/vanilla/models/entity/chicken.geo.json中),选择geometry.chicken,再点击导入。 + +3)点击body骨骼,按下快捷键R用鼠标向X轴正旋转90度,或是在元素面板中的旋转单位里,在X轴输入90,矫正模型。 + +4)拉高鸡的脖子,由于绿头鸭的一大特征是脖子很长,我们将脖子高度由6拉到9。同时鸭子没有鸡的红脖子,因此去掉红色的方块,随后移动beak骨骼(即喙)到靠近升高后的眼睛位置。 + +5)鸭子有比鸡更长的尾巴,因此我们在body骨骼里放入两个新的方块来模拟尾巴。将大尾巴移动到x为-3,y为12,z为-2的位置,将方块放大至长度为6,高度为2,宽度为5。将第二个方块作为小尾巴,位置放在x为-2,y为12,z为0的位置。将尺寸拉至4格长,1格高,4格宽。 + +![](./images/10_4.jpg) + + + +![](./images/11_1.jpg) + + + +6)最后我们的水头鸭会长成这样,不用担心,现在看起来贴图纹理很怪异的原因,是因为原来贴图上的像素点无法对应新增的方块面积,而变成了透明区域。再下一章我们会带大家进行贴图的绘制。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程11.利用BlockBench为鸭画上贴图.md b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程11.利用BlockBench为鸭画上贴图.md new file mode 100644 index 0000000..e98f202 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程11.利用BlockBench为鸭画上贴图.md @@ -0,0 +1,53 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/11_5.2f677828.jpg +hard: 进阶 +time: 25分钟 +--- + +# 利用BlockBench为鸭画上贴图 + + + +#### 作者:境界 + + + +![](./images/11_1.jpg) + + + +修改完鸡的模型后,我们在右上角将blockbench列为画板模式,指针变成笔刷,点击方块区域即可上色。 + +![](./images/11_2.jpg) + + + +1)首先删掉原来的贴图,点击创建贴图,将贴图名称改为green_duck,勾选模板,接着一直点击确定。 + +![](./images/11_3.jpg) + + + +![](./images/11_4.jpg) + + + +2)首先调整喙的颜色,水鸭的喙更加鲜艳,因此可以选择更鲜艳(饱和度高)的黄色,阴影选择稍微浅的土黄色。 + +![](./images/11_5.jpg) + + + +2)绿水鸭最显眼的是它的绿冠头,用笔刷给它画上深绿色。脖子上还有个白色红领巾,再下一层给它刷上白色。 + +4)接着将胸口,后背和大尾巴刷成棕色,小尾巴和其他身体画成白色。 + +5)让脚方块的前面,上面、侧面和一小部分的背面变透明,使用画板模式的橡皮擦即可。其他部分画成黄色。 + +6)加上一些阴影,一只继承了鸡骨骼的水鸭模型就做好了。 + +![](./images/11_6.jpg) + + + +7)最后一步,选择文件->导出,选择导出基岩版模型,之后在自定义水鸭时还会用到它。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程12.继承鸡的动画.md b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程12.继承鸡的动画.md new file mode 100644 index 0000000..10084c8 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程12.继承鸡的动画.md @@ -0,0 +1,75 @@ +--- +front: +hard: 进阶 +time: 20分钟 +--- + +# 继承鸡的动画 + + + +#### 作者:境界 + + + +将green_head_duck.geo.json放入材质包中,models文件夹的entity文件夹内。 + +将green_head_duck.png放入材质包中,textures文件夹内的entity文件夹里。 + +在材质包中的entity文件夹里,新建一个green_head_duck.entity.json文件。 + +``` +{ + "format_version": "1.10.0", + "minecraft:client_entity": { + "description": { + "identifier": "design:green_head_duck", + "render_controllers": [ + "controller.render.default" + ], + "textures": { + "default": "textures/entity/green_head_duck" + }, + "geometry": { + "default": "geometry.green_head_duck" + }, + "materials": { + "default": "entity_alphatest" + }, + "animations": { + "move": "animation.chicken.move", + "look_at_target": "animation.common.look_at_target" + }, + "scripts": { + "animate": [ + { "move": "query.modified_move_speed" }, + "look_at_target" + ] + }, + "spawn_egg": { + "base_color": "#256143", + "overlay_color": "#dd9238" + } + } + } +} +``` + + + +1)首先我们将名称域定义为design:green_head_duck。 + +2)接着我们使用原版默认的基础渲染控制器,"controller.render.default",它会控制生物渲染成一份由一个模型、一张贴图、一份材质组合的生物形象。 + +3)接着在textures中,我们将贴图资源路径指向存放在textures/entity里的green_head_duck贴图,请注意在这里,你不需要写出贴图文件后缀名。 + +4)在geometry中,我们将模型资源指向前面做好的几何体模型上。在项目一开始时,我们导出的模型是由鸡模型魔改而成,因此,打开水鸭模型时,模型的名称域依旧是geometry.chicken,为了避免资源冲突,需要提前将这里改为geometry.green_head_duck。 + +5)在materials中,将材质设置为entity_alphatest,它将允许生物使用带有透明通道的贴图。 + +6)在animations下,加载鸡的移动动画和注视动画。 + +7)在scripts/animate下,将动画加载上去。 + +8)在spawn_egg里,我们新增两个属性键值,一个是"base_color",值为"#256143",这是代表着一种深绿色的颜色色量,而值代表蛋的基础颜色。新增一个“overlay_color“,值为"#dd9238",而值代表蛋的斑点颜色。 + diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程13.加一点点鸭子音效.md b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程13.加一点点鸭子音效.md new file mode 100644 index 0000000..86be83d --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程13.加一点点鸭子音效.md @@ -0,0 +1,81 @@ +--- +front: +hard: 进阶 +time: 15分钟 +--- + +# 加一点点鸭子音效 + + + +#### 作者:境界 + + + +音效位于RP/sounds文件夹中,因此自定义的音效同样需要放在这里,开发者可以用文件夹进行区隔。自定义的音效文件使用的是.ogg和.wav两种格式。在追求质量下,.wav比.ogg的音质更好,但.ogg比.wav更节省空间,由开发者的自身考量去决定使用什么样的格式。 + +``` +{ + "mob.green_head_duck.ambient": { + "category": "neutral", + "sounds": [ + { + "name": "sounds/mob/green_head_duck/ambient", + "volume": 0.7 + } + ] + } +} +``` + + + +开发者需要在sounds文件夹下放入一个sound_definitions.json文件。我们定义了一个资源路径"mob.green_head_duck.ambient",我们设置了音效种类(category)为neutral(自然),在1.16.200以前,设定音效种类对于音效是否正常播放关系不大。在1.16.200版本后,开发者可以通过设置里根据音效种类调整对应的音量大小。 + +sounds数组里会存放每个音效的资源地址和音量大小、播放距离等,一般不设置音量大小(volume)的话,默认是音效的正常音量。 + +``` +{ + "entity_sounds": { + "entities": { + "design:green_head_duck": { + "volume": 1.0, + "pitch": [0.8, 1.2], + "events": { + "ambient": "mob.green_head_duck.ambient" + } + } + } + } +} +``` + + + +回到材质包主目录下的sounds.json文件,这个文件同样需要手动创建。进入文件后,定义结构格式为: + +``` +{ + "entity_sounds": { + "entities": {} + } +} +``` + + + +其中entities内放入对应的生物名称域,我们的水鸭名称域为design:green_head_duck,因此对象名称就以"design:green_head_duck"为准,它里面的属性包含了音量,音调,和音效播放事件。在修改了音频、音效本身的音量和音调等效果后,还可以在这里重新修订一次。事件内包含着许多生物在不同状态下会播放的音效。 + +常用的参数选项一般有以下几个: + +ambient自然音效,即待机时播放的音效。 + +hurt受伤音效,即受伤时播放的音效。 + +death死亡音效,死亡时播放的音效。 + +step行走音效,行走时播放的音效。 + +attack攻击音效,攻击时的音效。 + +shoot射击音效,设计时播放的音效。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程14.让水鸭活动起来.md b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程14.让水鸭活动起来.md new file mode 100644 index 0000000..3cc4c74 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程14.让水鸭活动起来.md @@ -0,0 +1,144 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/14_1.199826f4.jpg +hard: 进阶 +time: 25分钟 +--- + +# 让水鸭活动起来 + + + +#### 作者:境界 + + + +在材质包完成了水鸭的一切工作后,开发者回到行为包上。在这里,我们可以使用原版的鸡行为去掉一部分多余的内容,增加一些新的行为,来模拟出水鸭的行为状态。 + +``` +{ + "format_version": "1.16.0", + "minecraft:entity": { + "description": { + "identifier": "design:green_head_duck", + "is_spawnable": true, + "is_summonable": true, + "is_experimental": false, + "runtime_identifier": "design:green_head_duck" + } + }, + "component_groups": { + }, +} + "components": { + "minecraft:type_family": { + "family": [ "duck", "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:behavior.rise_to_liquid_level": { + "priority": 0, + "liquid_y_offset": -0.5, + "rise_delta": 0.01, + "sink_delta": 0.01 + }, + "minecraft:leashable": { + "soft_distance": 4.0, + "hard_distance": 6.0, + "max_distance": 10.0 + }, + "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.panic": { + "priority": 1, + "speed_multiplier": 1.5 + }, + "minecraft:behavior.tempt": { + "priority": 4, + "speed_multiplier": 1.0, + "items": [ + "wheat_seeds", + "beetroot_seeds", + "melon_seeds", + "pumpkin_seeds" + ] + }, + "minecraft:behavior.random_stroll": { + "priority": 6, + "speed_multiplier": 1.0 + }, + "minecraft:behavior.look_at_player": { + "priority": 7, + "look_distance": 6.0, + "probability": 0.02 + }, + "minecraft:behavior.random_look_around": { + "priority": 8 + }, + "minecraft:physics": { + }, + "minecraft:pushable": { + "is_pushable": true, + "is_pushable_by_piston": true + } + }, + + "events": { + } + } +} +``` + + + +1)将format_version格式设置为最新的1.16.0,水鸭作为一个带有生物蛋的生物,我们将is_summonable(可用指令生成的)设置为true(真),is_spawnable(可用生物蛋)设置为true(真),is_experimental(是否是实验玩法打开才能看到的生物)设置为false(否)。同时将runtime_identifier指向自己。 + +2)行为组内的组合都清空,同时在原本鸡的行为上,加入”minecraft:behavior.rise_to_liquid_level“行为,这是一个来自炽足兽的新行为。可以让生物在水面上下起伏,就像水上的鸭子一样。 + +3)其中行为内部属性里,priority设置为0,即最高级优先使用的行为。liquid_y_offset设置为-0.5。rise_delta是上升增量,sink_delta是下降增量,来模拟鸭子的上下起伏。这里都设置为0.01。这样一个可爱的绿头鸭就做好了。 + +![](./images/14_1.jpg) \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程15.练习1-水鸭浮在水上时的泡泡粒子.md b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程15.练习1-水鸭浮在水上时的泡泡粒子.md new file mode 100644 index 0000000..f8c8734 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程15.练习1-水鸭浮在水上时的泡泡粒子.md @@ -0,0 +1,134 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/15_1.379b8dfc.jpg +hard: 进阶 +time: 15分钟 +--- + +# 练习1:水鸭浮在水上时的泡泡粒子 + + + +#### 作者:境界 + + + +``` +{ + "format_version": "1.10.0", + "minecraft:client_entity": { + "description": { + "identifier": "design:green_head_duck", + "render_controllers": [ + "controller.render.default" + ], + "textures": { + "default": "textures/entity/green_head_duck" + }, + "geometry": { + "default": "geometry.green_head_duck" + }, + "materials": { + "default": "entity_alphatest" + }, + "animations": { + "move": "animation.chicken.move", + "look_at_target": "animation.common.look_at_target" + }, + "scripts": { + "animate": [ + { "move": "query.modified_move_speed" }, + "look_at_target" + ] + }, + "spawn_egg": { + "base_color": "#256143", + "overlay_color": "#dd9238" + } + } + } +} +``` + + + +使用前面的知识,我们知道今天要播放粒子,首先要把粒子资源加载在生物定义文件中。 + +1)在description内新增particle_effects,引用原版粒子"minecraft:basic_bubble_particle_manual",同时将粒子短名称写为bubble。 + +2)在动画控制器文件夹内新增一个控制绿头水鸭播放粒子的动画控制器,由于我们希望水鸭不停播放这个粒子,因此只需定义一个状态即可。 + +3)将动画控制器加载到生物定义文件的动画资源键中,接着在scripts/animate中的根动画加载这个动画控制器。 + +4)该粒子的设定下,释放的特效只会在水中有效,因此在游戏内,可以看到只有绿头水鸭在水里才会冒出泡泡粒子。 + + + +``` +{ + "format_version": "1.10.0", + "animation_controllers": { + "controller.animation.green_head_duck.particle": { + "initial_state": "default", + "states": { + "default": { + "particle_effects": [ + { + "effect": "bubble" + } + ] + } + } + } + } +} +``` + + + +``` +{ + "format_version": "1.10.0", + "minecraft:client_entity": { + "description": { + "identifier": "design:green_head_duck", + "render_controllers": [ + "controller.render.default" + ], + "textures": { + "default": "textures/entity/green_head_duck" + }, + "geometry": { + "default": "geometry.green_head_duck" + }, + "materials": { + "default": "entity_alphatest" + }, + "particle_effects": { + "bubble": "minecraft:basic_bubble_particle_manual" + }, + "animations": { + "move": "animation.chicken.move", + "look_at_target": "animation.common.look_at_target", + "particle": "controller.animation.green_head_duck.particle" + }, + "scripts": { + "animate": [ + { "move": "query.modified_move_speed" }, + "particle", + "look_at_target" + ] + }, + "spawn_egg": { + "base_color": "#256143", + "overlay_color": "#dd9238" + } + } + } +} +``` + + + +现在,一个在水中移动会不停冒水泡泡的绿头水鸭就做好了。 + +![](./images/15_1.jpg) \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程16.练习2-会攻击侵犯领地的凶狠水鸭.md b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程16.练习2-会攻击侵犯领地的凶狠水鸭.md new file mode 100644 index 0000000..3bb5f62 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第07章:自定义生物/课程16.练习2-会攻击侵犯领地的凶狠水鸭.md @@ -0,0 +1,171 @@ +--- +front: +hard: 进阶 +time: 20分钟 +--- + +# 练习2:会攻击侵犯领地的凶狠水鸭 + + + +#### 作者:境界 + + + +凶狠水鸭示例包下载:下载[示例包](https://g79.gdl.netease.com/guidedemo-case7v2.zip)。 + +``` +{ + "format_version": "1.16.0", + "minecraft:entity": { + "description": { + "identifier": "design:green_head_duck", + "is_spawnable": true, + "is_summonable": true, + "is_experimental": false, + "runtime_identifier": "design:green_head_duck" + }, + "component_groups": { + }, + + "components": { + "minecraft:breathable": { + "total_supply": 15, + "suffocate_time": 0 + }, + "minecraft:collision_box": { + "width": 0.6, + "height": 1.0 + }, + "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:behavior.rise_to_liquid_level": { + "priority": 0, + "liquid_y_offset": -0.5, + "rise_delta": 0.01, + "sink_delta": 0.01 + }, + "minecraft:leashable": { + "soft_distance": 4.0, + "hard_distance": 6.0, + "max_distance": 10.0 + }, + "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.panic": { + "priority": 1, + "speed_multiplier": 1.5 + }, + "minecraft:behavior.tempt": { + "priority": 4, + "speed_multiplier": 1.0, + "items": [ + "wheat_seeds", + "beetroot_seeds", + "melon_seeds", + "pumpkin_seeds" + ] + }, + "minecraft:type_family": { + "family": [ "duck", "mob" ] + }, + "minecraft:behavior.melee_attack": { + "priority": 3, + "speed_multiplier": 1, + "track_target": false + }, + "minecraft:behavior.nearest_attackable_target": { + "priority": 2, + "reselect_targets": true, + "must_see": false, + "entity_types": [ + { + "filters": { + "all_of": [ + { "test": "is_family", "subject": "other", "value": "mob" }, + { "test": "is_family", "subject": "other", "operator": "not", "value": "duck"} + ] + }, + "max_dist": 5 + } + ] + }, + "minecraft:attack": { + "damage": 1 + }, + "minecraft:behavior.random_stroll": { + "priority": 6, + "speed_multiplier": 1.0 + }, + "minecraft:behavior.look_at_player": { + "priority": 7, + "look_distance": 6.0, + "probability": 0.02 + }, + "minecraft:behavior.random_look_around": { + "priority": 8 + }, + "minecraft:physics": { + }, + "minecraft:pushable": { + "is_pushable": true, + "is_pushable_by_piston": true + } + }, + + "events": { + } + } +} +``` + + + +攻击侵犯领地的行为,可以想象成为靠近水鸭多近会引起水鸭的攻击。因此在设置一个生物拥有近战能力的时候,需要用三个行为进行组合: + +1)minecraft:attack,它的damage值可以接受一个数组,也可以接受一个整数数值。数组情况下,可以让伤害在数组内由大到小取一个随机伤害,而一个固定数值则代表的是固定伤害。 + +2)minecraft:behavior.nearest_attackable_target,它的entity_types接受一连串的生物目标,我们在目标选择器中,同时将目标设置为mob,但又不是duck,即所有带有mob标签却不带有duck标签的生物。max_dist代表在多远距离内会被当作目标,must_see意为是否必须看到才会当作目标,reselect_targets会在一群目标里不断选择离自己最近的目标。 + +3)minecraft:behavior.melee_attack,它的speed_multiplier接收一个浮点数值,即保持进攻状态时,速度会给予多少的加成,1为100%,则保持原速,1.5则是150%。track_target接收一个布尔值,若设置为true则会不停跟着目标,即使走出攻击范围,false则会在目标离开攻击范围后停止行为。 + + + +特别注意:由于minecraft:type_family组件用来标记生物的家族类型,原版鸡携带的家族标签为"mob“和"chicken",理论上,所有原版的生物都带有这个mob标签,我们将水鸭的标签保留了mob标签,也是为了允许其他原版生物在处理一些需要判断生物标签的行为,如寻找目标的时候,能够将水鸭纳入目标范围内,更加的贴近原版的生态。除此之外,我们将chicken改为duck,一个是强调实体携带duck标签,另一个是为了在自身寻找目标的行为当中,将同为duck标签的实体剔除出目标范围。若保持原来鸡的写法,则会变成需要剔除chicken标签的实体,那样就会包括了鸡这类生物,不符合实际需求。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第08章:自定义武器工具/README.md b/mconline/100-历史归档教程/10-addon教程/第08章:自定义武器工具/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/10-addon教程/第08章:自定义武器工具/images/1_1.jpg b/mconline/100-历史归档教程/10-addon教程/第08章:自定义武器工具/images/1_1.jpg new file mode 100644 index 0000000..c093b8a Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第08章:自定义武器工具/images/1_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第08章:自定义武器工具/课程01基础武器工具.md b/mconline/100-历史归档教程/10-addon教程/第08章:自定义武器工具/课程01基础武器工具.md new file mode 100644 index 0000000..8996a8f --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第08章:自定义武器工具/课程01基础武器工具.md @@ -0,0 +1,47 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/1_1.3a2ff48f.jpg +hard: 进阶 +time: 10分钟 +--- + +# 基础武器工具 + + + +#### 作者:境界 + + + +在当前的中国版版本下,剑、镐、锄、斧、铲包含在自定义物品内容范围里。因此它们除了支持自定义物品的所有特性之外,还具有工具的相关功能,属于特殊的自定义物品。本章将会教给开发者如何使用MCSTUDIO自定义武器和工具道具。 + +![](./images/1_1.jpg) + + + +1)进入逻辑编辑器,右键左侧“组件”面板,依次选择添加、物品、武器和工具。 + +2)点击面板下的“+”可以增加多个装备。 + +3)点击武器和工具下的子项,界面右下角的属性面板会出现自定义工具的内容选项。 + +4)游戏内名字输入口提供显示武器工具名称的功能。 + +自定义ID是物品的唯一性名称,必须为英文,在游戏中使用give、replaceitem等指令时会用到; + +在创造物品栏显示:默认打勾,一般情况下都使用默认选项,会将物品注册到创造背包内,若取消则不显示在创造背包中; + +贴图:将贴图通过拖曳的方式导入资源管理器中的textures/items文件夹内,再导入至提示入口即可; + +耐久:即物品工具使用的耐久; + +攻击伤害:给予装备攻击伤害值,必须填入整数; + +附魔能力:是为盔甲、工具、武器以及书添加一个或多个魔咒的游戏机制,这些魔咒可以添加或增强物品的特殊能力和效果。 + +类型:支持剑、镐、锄、斧、铲,类型会决定物品可以附魔的种类。 + +工具等级:对于镐,对应挖掘等级,即当使用镐挖掘某些方块时,镐子的挖掘等级大于等于方块的挖掘等级,才会产生掉落物。并且该值与铁砧的修复材料有关。当等级为0时,速度为2对应木板,否则对应金锭;当等级为1时,对应石头;当等级为2时,对应铁锭;当等级为3时,对应钻石;当等级大于3时,无法用铁砧修复。 + +对于原版镐子,木镐与金镐是0,石镐是1,铁镐是2,钻石镐是3; + +挖掘工具基础速度:对采集工具生效,表示挖掘方块时的基础速度; \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第08章:自定义武器工具/课程02.简易教学①-没有耐久的镐子.md b/mconline/100-历史归档教程/10-addon教程/第08章:自定义武器工具/课程02.简易教学①-没有耐久的镐子.md new file mode 100644 index 0000000..24ecfc2 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第08章:自定义武器工具/课程02.简易教学①-没有耐久的镐子.md @@ -0,0 +1,49 @@ +--- +front: +hard: 进阶 +time: 10分钟 +--- + +# 简易教学①:没有耐久的镐子 + + + +#### 作者:境界 + + + +``` +{ + "format_version":"1.10", + "minecraft:item":{ + "components":{ + "minecraft:max_damage":10, + "minecraft:max_stack_size":1, + "netease:weapon":{ + "attack_damage":10, + "enchantment":10, + "level":0, + "speed":1, + "type":"pickaxe" + } + }, + "description":{ + "category":"Equipment", + "identifier":"design:custom_hatchet", + "register_to_create_menu":true + } + } +} +``` + + + +1)进入关卡编辑器,创建武器和工具组件。 + +2)将工具类型改为镐。 + +3)通过资源管理器进入行为包/netease_items_beh(网易版物品)文件夹,找到自定义物品文件。 + +4)默认情况下,由编辑器生成的物品设定文件会自带"minecraft:max_damage"键对,这是掌管道具耐久的组件,我们将这一行删掉。 + +5)进入游戏,可以看到镐子在挖掘过程中不产生耐久消耗了。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第08章:自定义武器工具/课程03.简易教学②-附魔超好的武器.md b/mconline/100-历史归档教程/10-addon教程/第08章:自定义武器工具/课程03.简易教学②-附魔超好的武器.md new file mode 100644 index 0000000..13c4f3e --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第08章:自定义武器工具/课程03.简易教学②-附魔超好的武器.md @@ -0,0 +1,18 @@ +--- +front: +hard: 进阶 +time: 10分钟 +--- + +# 简易教学②:附魔超好的武器 + + + +#### 作者:境界 + + + +1)进入关卡编辑器,创建武器和工具组件。 +2)将类型选择为剑,这样物品才能附上武器的附魔。 +3)将附魔等级设置为15,这是原版下界合金装备的附魔等级。 +4)进入游戏,可以看到武器在附魔台上更加容易出现好的附魔。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/README.md b/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/images/1_1.jpg b/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/images/1_1.jpg new file mode 100644 index 0000000..680d547 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/images/1_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/images/1_2.jpg b/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/images/1_2.jpg new file mode 100644 index 0000000..f3f5c05 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/images/1_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/images/1_3.jpg b/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/images/1_3.jpg new file mode 100644 index 0000000..b1e9a8a Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/images/1_3.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/images/1_4.jpg b/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/images/1_4.jpg new file mode 100644 index 0000000..cbef847 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/images/1_4.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/images/2_1.jpg b/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/images/2_1.jpg new file mode 100644 index 0000000..7fe735d Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/images/2_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/images/2_2.jpg b/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/images/2_2.jpg new file mode 100644 index 0000000..12bc4b8 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/images/2_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/images/2_3.jpg b/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/images/2_3.jpg new file mode 100644 index 0000000..aa2a3d3 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/images/2_3.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/images/4_1.jpg b/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/images/4_1.jpg new file mode 100644 index 0000000..a048541 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/images/4_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/images/4_2.jpg b/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/images/4_2.jpg new file mode 100644 index 0000000..10e3775 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/images/4_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/images/5_1.jpg b/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/images/5_1.jpg new file mode 100644 index 0000000..90fc10a Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/images/5_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/images/5_2.jpg b/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/images/5_2.jpg new file mode 100644 index 0000000..ed9a28d Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/images/5_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/课程01.认识自定义方块.md b/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/课程01.认识自定义方块.md new file mode 100644 index 0000000..a4d62f1 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/课程01.认识自定义方块.md @@ -0,0 +1,89 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/1_1.43173d4d.jpg +hard: 进阶 +time: 15分钟 +--- + +# 认识自定义方块 + + + +#### 作者:境界 + + + +方块是我的世界中元素构成的基本单位。大到地表、群系、植被,小到工作台、机器、篝火,全部都是以方块的形式去演绎。自定义方块为开发者提供了一条更方便的途径管道来定制内容玩法。诚然,当前自定义方块内容只提供了部分功能,并不是所有原版方块的细节都可以通过自定义方块来实现的。请期待在接下来的更新,由中国版和基岩版团队提供更加多元的方案吧!本章节将会阐述,自定义方块现在能够做到什么。 + + + +#### 自定义方块组件 + +目前自定义方块支持使用基岩版1.16.10提供的原版方块组件功能和中国版提供的额外方块组件功能。我们可以使用文本编辑器或者MCSTUDIO来进行制作。 + + + +#### MCSTUDIO + +![](./images/1_1.jpg) + + + +1)进入关卡编辑器,右键组件面板,创建方块组件。可以通过右边“+”号增加多个自定义方块。然后再点击右边的解锁按钮,在编辑器页面上左键放置自定义方块,按ESC退出放置模式。注意:每当开发者修改了自定义方块信息,需根据提示弹窗重新加载世界,更改后的信息才会生效。 + +![](./images/1_2.jpg) + + + +2)当前编辑器支持自定义方块的组件内容为: + +#### 游戏内名称: + +修改方块物品显示在物品栏里的名称。 + +#### 自定义方块ID: + +是该方块在游戏内的唯一性名称,必须只能带有英文和下划线。为了避免与其他模组、原版方块产生ID冲突,我们应当使用模组名称+下划线+方块名称的方式命名最为保险。 + +![](./images/1_3.jpg) + + + +#### 在创造物品栏显示: + +勾选选项会将方块注册到construction栏,即建筑方块分页下的创造物品栏内。 + +#### 方块类型: + +当前可选的有普通、传送门、刷怪箱三种。 + +#### 渲染属性: + +渲染属性下的四个组件功能分别是吸光度(可理解为透光度,值越大,则越不透光)、亮度(可理解为发光,值在0.0~1.0之间,值越大,在夜晚发光程度越大)、渲染材质(由方块贴图纹理决定,若贴图内带有透明图层,则选择透明材质,若有半透明图层,则选择半透明材质,否则选择不透明材质)、地图颜色(方块在地图里显示的颜色)。 + +![](./images/1_4.jpg) + + + +#### 模型与贴图: + +模型有两种类型,一种是简单立方体,即占据世界内一格高、一格宽、一格长的方块模型,默认情况下不需要指定方块模型;一种是自定义方块模型,需要开发者提供自定义方块模型文件。选择简单立方体时,若勾选上侧面使用同种贴图的话,开发者只需提供存放在资源管理器内,textures文件夹下的blocks文件夹内的上面、侧面、底面三类贴图纹理文件,若取消勾选,则可以指定方块的上下左右前后面,总共达6面的贴图。贴图随机纹理会帮助方块在游戏内被放置后,随机旋转设定好的那一面的贴图,在原版世界中使用到这个功能的方块有树叶、泥土、草方块等,随机旋转会在合适的条件下给玩家带来更好的视觉效果,而不容易产生审美疲劳。 + +#### 声音: + +指方块的音效,包括生物踩在上面和破坏时的音效,方块音效属于硬编码的范畴,当前无法自定义方块音效,所以我们必须使用原版的方块音效。 + +#### 挖掘属性: + +硬度决定了方块的破坏时间,硬度越大,方块需要挖掘的时间也越久,而高效挖掘工具可以设置成原版的镐、锄、斧、铲四种类型,同时支持这四种类型的自定义物品工具。限定工具等级掉落会影响方块是否掉落,它会添加一格工具等级的条件,高于工具等级才能挖掘后掉落方块掉落物。掉落可以指向组件面板建立的掉落物表。是否实心则会影响该方块是否会窒息生物,以及使用自定义方块模型时,取消一些难看的阴影。寻路时被当作障碍物选项,勾选时则会被生物当作是障碍物跳到上方走动,不勾选则会当作空气方块无视其存在来移动。 + +#### 形状: + +面向决定方块是否是四面向或者六面向方块,设置为四面向方块,根据玩家放置的四个不同角度(东西南北)来改变方块朝向,六面向则多了上和下两个面。 + +实体碰撞箱会检测实体的体积是否与方块体积重叠,一般情况下实体会被挡在方块碰撞箱之外,若实体碰撞箱和实体重叠,有可能造成窒息伤害! + +射线碰撞箱会检测玩家的视线是否与方块体积重叠,例如,绝大多数的方块的射线碰撞箱都是一个格子,因此当视线移动到格子之内时,这个方块就能够被挖掉! + +#### 方块实体: + +打开后可以让自定义方块保存复杂数据结构的功能。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/课程02.制作方块的特殊模型.md b/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/课程02.制作方块的特殊模型.md new file mode 100644 index 0000000..a278fc1 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/课程02.制作方块的特殊模型.md @@ -0,0 +1,45 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/2_1.54b5ffe9.jpg +hard: 进阶 +time: 10分钟 +--- + +# 制作方块的特殊模型 + + + +#### 作者:境界 + + + +在制作方块模型前,首先要新建工程。但是必须提前告知的是,中国版的方块模型文件与原版的模型文件中的部分键对格式稍有出入,开发者可以在中国版开发者文档中自行比较。为了方便开发者,开花组团队通过Blockbench的自由模型格式开发了转化器,我们可以通过McStudio的特效编辑器下的资源管理面板,直接转化自由模型的工程文件为自定义方块模型文件,十分的方便。 + + + +#### BlockBench的工程选择 + +![](./images/2_1.jpg) + + + +1)打开Blockbench,从新建栏目下选择自由模型。 + +![](./images/2_2.jpg) + + + +2)项目中的文件名和模型ID指定好后,不勾选BoxUV,并将贴图宽度改为16x16。 + +3)进入项目后可以看到坐标系和网格,N方向即朝北方向。需要注意的是,从示例图可以看到红蓝线,它们的交点是方块的左上角。因此,方块模型需要从这里开始,向右下角伸展,进行素材搭建,而不是在网格范围内搭建。虽然这与正常的建模思维会有一定冲突,但是开发者也可以选择先在网格中央建好方块模型后,再往右下角移动即可。同样的,与建模自定义生物模型一样,网格长宽都有16格,16格在世界中代表一格方块格子。 + +4)每个立方体最后都会被骨骼包裹,这是建造我的世界模型的基本概念,与自定义生物模型无差。不过,由于目前自定义方块模型尚未支持动画,因此所有的骨骼锚点都需要设置在坐标0,0,0的位置上,否则对方块骨骼进行旋转时,可能会遇到预览效果与游戏中实际效果有偏差的情况!与此同时,方块模型中的立方体和骨骼都不支持膨胀功能,遇到立方块面重叠的问题时,当前只能通过移动立方体或者挤压/放大立方体大小来解决! + + + +#### 方块模型的贴图规范 + +![](./images/2_3.jpg) + + + +与生物模型的UV展开图方式不同的是,我们建议开发者建模自定义方块时取消生物模型的Box UV,转而使用以设置每一个方块面东、西、南、北、上、下的方式,并且贴图格式大小设置为16x16。如果遇到一张分辨率在16x16的纹理画布上无法满足开发者对模型的细节要求,可以在工程里再次新建一张16x16的贴图,这样的建模限制是为了引导开发者积极优化方块模型的资源消耗,毕竟玩家的硬件资源是有限的。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/课程03.自定义特殊方块.md b/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/课程03.自定义特殊方块.md new file mode 100644 index 0000000..bd1fbb3 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/课程03.自定义特殊方块.md @@ -0,0 +1,169 @@ +--- +front: +hard: 进阶 +time: 15分钟 +--- + +# 自定义特殊方块 + + + +#### 作者:境界 + + + +原版的部分方块具有特殊功能,开花组为开发者提供了方块继承的形式来继承这些特殊方块的能力,本章将教给开发者如何自定义红石元件方块和刷怪箱方块。由于McStudio当前尚不支持完整自定义这两种特殊方块的功能,这里会使用JSON语法来演示。 + + + +#### 自定义红石元件方块 + +``` +{ + "format_version": "1.16.0", + "minecraft:block": { + "description": { + "identifier": "design:redstone_consumer", + "register_to_creative_menu": true + }, + "components": { + "minecraft:destroy_time": 2.0, + "minecraft:map_color": "#ffffff", + "netease:tier": { + "digger": "pickaxe", + "level": 0 + }, + "netease:redstone": { + "type": "consumer" + } + } + } +} +``` + + + +1)1.16.0是当前下界更新的自定义方块内容格式,所以我们在format_version里填1.16.0 + +2)identifier是自定义方块的名称域,这里可以由开发者自己定义。register_to_creative_menu是管理方块是否注册到创造背包里。 + +3)components下的参数会储存方块的各项功能。主要来看netease:redstone组件,它可以传入两种类型,这里我们传入consumer,这样该方块即可接收红石信号,成为红石元件方块。 + +``` +{ + "format_version": [ + 1, + 1, + 0 + ], + "design:redstone_producer": { + "textures": "gold_block", + "sound": "metal" + }, + "design:redstone_consumer": { + "textures": "gold_block", + "sound": "metal" + } +} +``` + + + +4)最后,还应在blocks.json下进行如下配置,示例图中将方块贴图设置为金方块贴图,则6个面都是金方块面,走在该方块上和破坏方块时产生的音效类型是金属音效。 + + + +#### 自定义红石信号源方块 + +``` +{ + "format_version": "1.16.0", + "minecraft:block": { + "description": { + "identifier": "design:redstone_producer", + "register_to_creative_menu": true + }, + "components": { + "minecraft:destroy_time": 2.0, + "minecraft:map_color": "#ffffff", + "netease:tier": { + "digger": "pickaxe", + "level": 0 + }, + "netease:redstone": { + "type": "producer", + "strength": 10 + } + } + } +} +``` + + + +1)1.16.0是当前下界更新的自定义方块内容格式,所以我们在format_version里填1.16.0 + +2)identifier是自定义方块的名称域,这里可以由开发者自己定义。register_to_creative_menu是管理方块是否注册到创造背包里。 + +3)components下的参数会储存方块的各项功能。主要来看netease:redstone组件,它可以传入两种类型,这里我们传入producer,这样该方块即可接发送红石信号,strength掌管红石信号的强度,即信号每传递一格,强度会降低1点。 + +4)最后,还应在blocks.json下进行如下配置,示例图中将方块贴图设置为金方块贴图,则6个面都是金方块面,走在该方块上和破坏方块时产生的音效类型是金属音效。 + + + +#### 自定义刷怪箱方块 + +``` +{ + "format_version": "1.16.0", + "minecraft:block": { + "description": { + "identifier": "design:chicken_mob_spawner", + "register_to_creative_menu": true, + "base_block": "mob_spawner" + }, + "components": { + "minecraft:block_light_absorption": 0, + "netease:tier": { + "digger": "pickaxe", + "level": 0 + }, + "netease:render_layer": { + "value": "alpha" + }, + "netease:mob_spawner": { + "type": "minecraft:chicken" + }, + "minecraft:block_light_emission": 1.0, + "minecraft:destroy_time": 2.0, + "minecraft:map_color": "#ffffff" + } + } +} +``` + + + +1)1.16.0是当前下界更新的自定义方块内容格式,所以我们在format_version里填1.16.0 + +2)identifier是自定义方块的名称域,这里可以由开发者自己定义。register_to_creative_menu是管理方块是否注册到创造背包里。在这里还要额外指定base_block键对,将方块继承刷怪箱即"mob_spawner"。 + +3)components下的参数会储存方块的各项功能。主要来看netease:mob_spawner组件,它里面的键类型会指向一种生物名称域,该组件功能同时支持自定义生物和MC原版生物。 + +``` +{ + "format_version": [ + 1, + 1, + 0 + ], + "design:chicken_mob_spawner": { + "textures": "mob_spawner", + "sound": "metal" + } +} +``` + + + +4)最后,还应在blocks.json下进行如下配置,示例图中将方块贴图设置为原版刷怪箱贴图。则6个面都是刷怪箱子贴图,走在该方块上和破坏方块时产生的音效类型是金属音效。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/课程04.简易教学①-制作会发光的地灯.md b/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/课程04.简易教学①-制作会发光的地灯.md new file mode 100644 index 0000000..f25a35d --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/课程04.简易教学①-制作会发光的地灯.md @@ -0,0 +1,101 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/4_1.fd4b17c4.jpg +hard: 进阶 +time: 15分钟 +--- + +# 简易教学①:制作会发光的地灯 + + + +#### 作者:境界 + + + +#### 自定义方块行为包 + +``` +{ + "format_version": "1.16.0", + "minecraft:block": { + "description": { + "identifier": "design:lamp", + "register_to_creative_menu": true + }, + "components": { + "minecraft:block_light_absorption": 0, + "netease:tier": { + "digger": "pickaxe", + "level": 0 + }, + "netease:render_layer": { + "value": "alpha" + }, + "minecraft:block_light_emission": 1.0, + "minecraft:destroy_time": 2.0, + "minecraft:map_color": "#ffffff" + } + } +} +``` + +1)1.16.0是当前下界更新的自定义方块内容格式,所以我们在format_version里填1.16.0 + +2)identifier是自定义方块的名称域,这里可以由开发者自己定义。register_to_creative_menu是管理方块是否注册到创造背包里。 + +3)components下的参数会储存方块的各项功能。 + +-将透光度调为0,减少环境光的影响。 + +-将该方块设置为被木镐以上等级的镐子挖掘有速度加成。 + +-由于使用了带有透明图层的贴图,将该方块的渲染材质设置为透明。 + +-将发光度设置为1.0,即发最强的光。 + +-将方块破摔时间设置为2.0 + +-自定义方块在地图上显示的颜色,这里可以由开发者自己定义。 + +![](./images/4_1.jpg) + + + +#### 自定义方块资源包 + +1)在resourcepack/blocks.json中,添加该方块的名称域与模型资源名称,名称来自resourcepack/models/netease_block/内的模型文件里的名称域。将走在方块上和破坏方块的音效设置为玻璃音效。 + +``` +{ + "format_version": [ + 1, + 1, + 0 + ], + "design:lamp": { + "netease_model": "design:lamp", + "sound": "glass" + } +} +{ + "format_version": "1.13.0", + "netease:block_geometry": { + "bones": [ + … + ], + "description": { + "identifier": "design:lamp", + "textures": [ + "design:lamp" + ], + "use_ao": false + } + } +} +``` + + + +#### 实际游戏效果 + +![](./images/4_2.jpg) \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/课程05.简易教学②-制作一个百宝箱.md b/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/课程05.简易教学②-制作一个百宝箱.md new file mode 100644 index 0000000..18ffa3d --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第09章:自定义方块/课程05.简易教学②-制作一个百宝箱.md @@ -0,0 +1,108 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/5_1.ca4249e1.jpg +hard: 进阶 +time: 15分钟 +--- + +# 简易教学②:制作一个百宝箱 + + + +#### 作者:境界 + + + +百宝箱示例包下载:下载[示例包](https://g79.gdl.netease.com/guidedemo-case9.zip)。 + +#### 自定义方块行为包 + +``` +{ + "format_version": "1.16.0", + "minecraft:block": { + "description": { + "identifier": "design:treasure_chest", + "register_to_creative_menu": true + }, + "components": { + "minecraft:block_light_absorption": 0, + "netease:tier": { + "digger": "pickaxe", + "level": 0 + }, + "netease:face_directional": { + "type": "direction" + }, + "minecraft:block_light_emission": 0.0, + "minecraft:destroy_time": 2.0, + "minecraft:loot": "loot_tables/design_treasure_chest.json", + "minecraft:map_color": "#ffffff" + } + } +} +``` + + + +1)1.16.0是当前下界更新的自定义方块内容格式,所以我们在format_version里填1.16.0 + +2)identifier是自定义方块的名称域,这里可以由开发者自己定义。register_to_creative_menu是管理方块是否注册到创造背包里。 + +3)components下的参数会储存方块的各项功能。 + +-将透光度调为0,减少环境光的影响。 + +-将该方块设置为被木镐以上等级的镐子挖掘有速度加成。 + +-将该方块设置为四面向方块,因为我们希望玩家放置时,正面始终朝向玩家。 + +-将发光度设置为0,即不发光,该组件也可以省略。 + +-将方块破摔时间设置为2.0 + +-自定义一个战利品表,将路径设置到该战利品表上,战利品表内会放置一种组合,破坏宝箱时会掉落一组金锭。 + +-自定义方块在地图上显示的颜色,这里可以由开发者自己定义。 + +![](./images/5_1.jpg) + + + +#### 自定义方块资源包 + +1)在resourcepack/blocks.json中,添加该方块的名称域与模型资源名称,名称来自resourcepack/models/netease_block/内的模型文件里的名称域。将走在方块上和破坏方块的音效设置为木头音效。 + +``` +{ + "format_version": [ + 1, + 1, + 0 + ], + "design:treasure_chest": { + "netease_model": "design:treasure_chest", + "sound": "wood" + } +} +{ + "format_version": "1.13.0", + "netease:block_geometry": { + "bones": [ + … + ], + "description": { + "identifier": "design:treasure_chest", + "textures": [ + "design:treasure_chest" + ], + "use_ao": false + } + } +} +``` + + + +#### 实际游戏效果 + +![](./images/5_2.jpg) \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/README.md b/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/images/2_1.jpg b/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/images/2_1.jpg new file mode 100644 index 0000000..ebe18ec Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/images/2_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/images/3_1.jpg b/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/images/3_1.jpg new file mode 100644 index 0000000..728ab7d Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/images/3_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/images/4_1.jpg b/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/images/4_1.jpg new file mode 100644 index 0000000..72100e7 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/images/4_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/images/4_2.jpg b/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/images/4_2.jpg new file mode 100644 index 0000000..9f95377 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/images/4_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/images/6_1.jpg b/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/images/6_1.jpg new file mode 100644 index 0000000..7a73a5a Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/images/6_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/images/7_1.jpg b/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/images/7_1.jpg new file mode 100644 index 0000000..a6dbcd2 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/images/7_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/课程01.认识MODSDK.md b/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/课程01.认识MODSDK.md new file mode 100644 index 0000000..1aebb6c --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/课程01.认识MODSDK.md @@ -0,0 +1,132 @@ +--- +front: +hard: 进阶 +time: 20分钟 +--- + +# 认识MODSDK + + + +#### 作者:境界 + + + +#### 什么是Mod + +原生的AddOn方式可以改变很多原版游戏内容,以及添加自定义的游戏内容,但一般无法实现一些较复杂的逻辑,比如右键自定义方块时,会给予物品。这就需要使用脚本来实现了。 + +在我的世界中国版中,我们使用python来编写mod脚本。 + +开花组用MODSDK封装了很多的游戏事件与组件,游戏事件是指游戏内触发了某个操作或达到某一种条件(例如玩家右键了自定义方块),可以通知到我们的代码,进而实现我们的逻辑。组件是我们封装好的游戏引擎接口,可以用来设置或获取某些数据(例如操作玩家背包、放置方块),或者执行一些特殊的功能。 + +脚本开发就是一个监听事件与调用组件的过程,配合自己的逻辑来实现很酷的玩法。想要了解当下最新的事件和接口文档,请翻阅官方文档网址。 + +#### Mod是如何工作的 + +MOD加载时与附加包类似,存在着服务端运行和客户端单独运行的内容,因此在MOD文档中也清晰地分离了服务端和客户端各自的事件和接口。我们常见的,如玩家的血量、饥饿、饱和度等信息,以及世界上分布的生物,它们所在的坐标、行为、属性等,都属于公共资源的范畴,即它们会放在服务端上运行,每当玩家需要使用到这些数据时,再由客户端向服务端请求后获得这些数据。因此,生成一个实体生物的接口会在服务端上使用,因为它是公共资源,它是同步的。而生成一种特效的接口则在客户端上使用,因为就像原版粒子资源放在资源包,即客户端上运行的道理类似,某些粒子只有在一名玩家达到某个条件时才会显示,亦或是这样的资源在不同的玩家面前都有不同的表现。 + +因此常有玩家会说,联机房间开启时,某些#模组的表现不再像单人游玩时那么稳定。其中一个原因便是房主会同时承担客户端和服务端的计算压力,又会不断接受其他房间内玩家请求数据的压力。这与单人游玩需要的数据传输量有着天壤之别,并且服务端和客户端传输时需要网络通信,当房主的网络环境与其他联机成员的环境相差较大时,也容易造成成员卡顿而体验不佳。因此,优化的任务永远是摆在所有开发者面前最重要的大关。能够优化的越好,触达的玩家自然也越多,模组的受众也会越来越多! + + + +#### 开发环境搭建 + +开发环境搭建主要分为两个,一个是Python的安装,一个是工具的安装。前者可以理解为MOD工作需要一个编程语言环境,就像我的世界PC JAVA版在运行前一定要安装JAVA语言环境一样。后者可以理解为生产环境的搭建,即需要一系列配套的工具进行MOD的编程作业。 + +Python安装需要进入Python官网下载,选择Python2的版本【[https://www.python.org/downloads/](https://www.python.org/downloads/)】。同时根据自己电脑是64位还是32位选择下载安装文件,安装时需要选择将python添加到环境变量中,其余按照提示点下一步即可。 + +生产环境需要配置生产工具,集成开发环境(IDE)可以很好帮助我们编写代码,对于python来说,pycharm与vscode都是很不错的IDE,这里我们以pycharm为例。进入pycharm的官网下载页,选择Community版本下载,然后按照提示点下一步安装即可。 + +Mod SDK补全可以允许代码编辑器根据上下文自动补全输入内容。 + +在MCSTUDIO->编辑器->关卡编辑器环境中,点击菜单栏【工具】→【安装1.22.0 stable补全库】即可自动安装,若提示报错,请重点检查是否将python添加到环境变量中了。 + + + +#### Python入门 + +python是一门很容易入门的编程语言,没有接触过python的开发者可以先浏览[https://www.liaoxuefeng.com/wiki/897692888725344](https://www.liaoxuefeng.com/wiki/897692888725344)的下面这些页面,跟着上面的示例代码来了解python: + +第一个Python程序 + +使用文本编辑器 + +Python基础 + +数据类型和变量 + +条件判断和循环 + +使用dict + +函数 + +调用函数 + +定义函数 + +函数的参数 + +模块 + +使用模块 + +面向对象编程 + +类和示例 + + + +#### 为自定义模组添加脚本 + +①打开教程附录的示例文件夹,可以看到文件夹内仅包含Beh(Behavior)行为包的内容。在中国版我的世界中,必须注意:在行为包内添加entities文件夹才能被识别为行为包,即使所有格式正确的话,没有这个文件夹也会识别失败。 + +②除了entities文件夹外,有一个叫做TutorialStepOneScripts的文件夹,它用来放所有MOD脚本的文件。其中一个行为包可以放置多个脚本文件夹,但必须以xxxScripts为结束。 + +③在文件夹内新建一个modMain.py文件,它被视为模组的入口文件。目前无法由开发者决定入口文件的名称,因此这属于必须遵循的规范。并且在文件夹内还需要新建一个__init__.py的文件,这是为了让这个文件夹变成一个可以被脚本引擎导入的模块。如果有按照之前推荐的python线上教程跟着学的话,会在学到一定程度后理解这一点,如果还没学到,就仅记这一点,在模组文件夹和它的子文件夹内都需要新建这么一个__init__.py文件。 + +④使用任何一种文本编辑器或者IDE打开文件,可以看到里面的代码内容,它们此时的功能只会在游戏加载时输出日志到控制台上,额外的介绍会在下一个部分。 + +⑤将TutorialModBeh这个文件夹用zip进行压缩,导入进MCSTUDIO看看吧! + + + +#### 脚本结构 + +①紧接着在TutorialBeh文件夹内看第二个脚本文件夹,即TutorialStepTwoScripts。 +②与TutorialStepTwoScripts文件夹不同的是,在这里我们正式加入了两个自己的文件,一个是用来作为服务端系统的ServerSys.py,另一个是用来作为客户端系统的ClientSys.py。前者代码逻辑只会运行在服务端,后者的代码逻辑只会运行在客户端。也请开发者不要在客户端导入服务端的接口,反之亦然,这会造成模组运行不正常的风险。 + +③ServerSys可由开发者自创一个新类,在没有学习到面向对象的环节时,可以简单理解为自创一个系统入口,这个入口是个类。它需要继承官方系统类才能使用MODSDK的接口,监听游戏内的事件。ClientSys亦然。我们将打印信息搬到两个系统类里,在之后打开游戏时,正常运行下会在控制台打印这两个信息。 + +④回到modMain.py内,我们在上方通过MODSDK提供的代码提示包,导入了两个新模块。将服务端模块命名为ServerApi,将客户端模块命名为ClientApi,并在服务端的入口函数内注册了xxxSys.py内的系统类。我们MOD的工作逻辑也将会在那里进行。 + + + +#### 命名建议 + +为了方便开发者们统一书写规范,避免不必要的冲突风险。开花组建议采用团队名称[Scripts],例如SDKTeamTestScripts,来规范每个脚本目录文件夹的名称。 + + + +#### 如何调试 + +Mod代码目前不支持断点调试,因此只能通过在不同的地方打Log来调试代码。打Log可以使用 print 也可以使用 logging模块(后面阅读AwesomeMod时会看到)。Log会显示在”脚本测试日志“窗口中,脚本的Log最好加上特殊的前缀方便查找。 + +推荐使用mod_log模块打印日志。 + +目前Mod代码支持热更新调试,在运行游戏进行开发测试时,增加,删除,修改相关本地Python文件后,回到Studio或者编辑器界面,Studio会自动热更新修改的内容,并在”脚本测试日志“窗口中提示“正在重新加载相关的Python模块”。 + + + +#### 开始行动吧 + +首先阅读系统简介,事件简介,组件简介,然后从modMain.py入手,阅读TutorialMod的代码与注释。 + +完全理解后,在服务端事件,以及服务端组件中找几个自己感兴趣的,尝试添加到mod里,看能不能生效吧! + +掌握了事件与组件的用法后,可以从官方示例里寻找自己感兴趣的示例模组进行练习,或者继续研习开发者教程。 + +熟悉了modsdk的用法后,可以继续阅读这个网站的“高级特性”,“面向对象编程”,“常用内建模块”,有助于使用python编写出更复杂的玩法逻辑。 + +遇到自身没法解决的问题时,及时和官方沟通反馈(可通过MC Studio内嵌开发者论坛或网页),或者在论坛内尝试寻求他人的帮助。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/课程02.MOD第一步:监听事件.md b/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/课程02.MOD第一步:监听事件.md new file mode 100644 index 0000000..b491680 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/课程02.MOD第一步:监听事件.md @@ -0,0 +1,23 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/2_1.b074023e.jpg +hard: 进阶 +time: 15分钟 +--- + +# MOD第一步:监听事件 + + + +#### 作者:境界 + + + + +①在示例目录下新建TutorialStepThree目录,根据StepTwo的写法进行模仿。 + +![](./images/2_1.jpg) + + + +②在_ init _函数下,将脚本引擎的命名空间和系统名称赋值给两个变量,使用ListenForEvent监听玩家聊天事件,并指定回调函数。回调函数的作用是,每当玩家打出聊天内容时,这个回调函数就会被调用,里面的相关代码就会执行。根据文档的信息可得知,它最后会回传一个包含6个参数的字典。因此在定义回调函数的时候,除了保证第一个参数为对象本身之外,还需要再额外增加一个参数,为了方便理解,我们将第二个参数命名为event。之后在函数体下,我们打印一下这个event,并在游戏内看到最终的效果。 + diff --git a/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/课程03.MOD第二步:创建组件.md b/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/课程03.MOD第二步:创建组件.md new file mode 100644 index 0000000..a16beab --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/课程03.MOD第二步:创建组件.md @@ -0,0 +1,25 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/3_1.795ccf68.jpg +hard: 进阶 +time: 15分钟 +--- + +# MOD第二步:创建组件 + + + +#### 作者:境界 + + + +①创建组件前,先来确认一下要实现的功能。由于我们执行的环境是在玩家聊天时,因此可以假定一个目标,我们希望通过取得玩家聊天输入的信息变成玩家名称的前缀。 + +![](./images/3_1.jpg) + + + +②通过获得字典内数据的语法,将玩家ID和聊天内容赋值给两个变量:“player_id”和“message”。其中“player_id”为玩家在世界的唯一ID,也可以理解为是一种身份标识。通常在创建组件的时候,我们要通过MODSDK的接口让某个玩家执行某种操作,就需要在创建组件时传入玩家ID。 + +③根据存档可知,服务端上有个设置玩家前缀名称和后缀名称的接口。我们通过创建这个接口,根据代码提示需要传入四个参数,即前缀、前缀字颜色、后缀、后缀字颜色。将“message”传入第一个参数位置,其它三个位置留''空字符串”。 + +④最后,将接口执行后回传的执行结果传给“result”变量,并将结果打印在控制台上,在游戏内看到最终的效果。由于主机玩家不会看到自己的名称,开发者可以通过双开客户端的方式来用房客的视角看噢! \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/课程04.MOD第三步:服务端与客户端通信.md b/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/课程04.MOD第三步:服务端与客户端通信.md new file mode 100644 index 0000000..5627021 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/课程04.MOD第三步:服务端与客户端通信.md @@ -0,0 +1,30 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/4_1.ca250d44.jpg +hard: 进阶 +time: 20分钟 +--- + +# MOD第三步:服务端与客户端通信 + + + +#### 作者:境界 + + + +①由于服务端和客户端属于两个不同的容器,对应支持的接口也各有不同。常有开发者问如何在服务端上使用客户端的方法,或者客户端上使用服务端的方法,但这实际上是行不通的,因为两个容器都各自有自己的轨道,为了让轨道之间可以产生合作,这就需要用到通信了。 + +②通信即是让服务端或者客户端将需要对方需要用到的数据以Python的基本数据类型进行包装,服务端通过NotifyToClient、BroadcastToAllClient,客户端通过NotifyToServer来传递。其中BroadcastToAllClient是广播到所有玩家客户端上,NotifyToClient是发送给一名指定的客户端玩家,NotifyToServer是发送到服务端上。 + +![](./images/4_1.jpg) + + + +③还记得在监听事件的时候需要在第一个参数和第二个参数传入命名空间和系统名吗?并且注册系统的时候,我们就已经为客户端系统和服务端系统命名了,因此在一端上接收另一端的自定义事件,就是用modMain.py内注册系统时写下的命名。 + +![](./images/4_2.jpg) + + + +④在示例中,我们在聊天事件内新增代码,使用条件语句判断聊天信息,传到客户端后让玩家改变自己的人称视角。 + diff --git a/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/课程05.MOD第四步:UI与服务端客户端通信.md b/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/课程05.MOD第四步:UI与服务端客户端通信.md new file mode 100644 index 0000000..c16a38e --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/课程05.MOD第四步:UI与服务端客户端通信.md @@ -0,0 +1,24 @@ +--- +front: +hard: 进阶 +time: 10分钟 +--- + +# MOD第四步:UI与服务端/客户端通信 + + + +#### 作者:境界 + + + +#### 什么是UI + +UI是玩家与游戏交互的界面。比如在世界中有许多方块带有特殊的功能,使用这些方块时,游戏会弹出UI交互界面,来引导玩家进行下一步动作。使用MCSTUDIO的界面编辑器可以快速进行界面制作。界面的样式文件最后也会用json的文件格式保存在附加包的资源包中。 + + + +#### 与客户端/服务端通信 + +UI自身也运行在客户端上,因此一般情况下,在UI文件内都可以使用客户端的接口。若需要调用客户端系统的方法,可以使用ClientApi.GetSystem接口来获得客户端系统,同时它会携带NotifyToServer接口,因此也可以直接将UI的数据传送到服务端上。 + diff --git a/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/课程06.简易教程①:击退棒.md b/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/课程06.简易教程①:击退棒.md new file mode 100644 index 0000000..264b9a7 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/课程06.简易教程①:击退棒.md @@ -0,0 +1,26 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/6_1.3bed48ba.jpg +hard: 进阶 +time: 20分钟 +--- + +# 简易教程①:击退棒 + + + +#### 作者:境界 + + + +击退棒示例包下载:[下载示例包](https://g79.gdl.netease.com/guidedemo-case10.zip)。 + +①在看到一些竞技性的MC小游戏玩法,或者一些整蛊搞笑的模组时,我们常常能看到一种赋予了木棒超高击退效果的玩法。我们现在就来复刻一下吧! + +②根据文档可知,脚本引擎存在一个玩家攻击实体事件。首先我们监听一下这个事件,并获取被伤害的实体ID。判断玩家手持物品是否是木棒,是则执行接下来的逻辑。 + +![](./images/6_1.jpg) + + + +③我们获得玩家的头部转向,将头部转向转化成方向向量。最后,创建一个Action组件,给予受害者实体一个瞬时的击退效果。进入游戏并查看效果吧! + diff --git a/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/课程07.简易教程②:传送点.md b/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/课程07.简易教程②:传送点.md new file mode 100644 index 0000000..7471014 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第10章:你的第一个MOD/课程07.简易教程②:传送点.md @@ -0,0 +1,28 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/7_1.2616ac65.jpg +hard: 进阶 +time: 15分钟 +--- + +# 简易教程②:传送点 + + + +#### 作者:境界 + + + +传送点示例包下载:下载[示例包](https://g79.gdl.netease.com/guidedemo-case10.zip)。 + +①我的世界地图面积辽阔,通过模组为玩家提供传送点的功能,可以方便玩家在地图内快速移动。 + +![](./images/7_1.jpg) + + + +②下载示例包在MCStudio中导入进游戏,通过聊天窗口输入“传送点设置”,即可把当前坐标设置为传送点,通过聊天窗口输入“传送点传送”,无论走的多远都可以把自己传送回来。 + + + +③由于目前中国版尚未支持自定义指令,我们通过监听玩家聊天的信息,来模拟指令效果,并执行玩家标记传送点、管理传送点、传送至传送点的逻辑。在简易教程里,我们只为玩家提供一个传送点,只在一次游戏房间内有效,离开房间后就会消失,并在匹配聊天内容成功后将玩家传送过去。 + diff --git a/mconline/100-历史归档教程/10-addon教程/第11章:更强大的自定义生物/README.md b/mconline/100-历史归档教程/10-addon教程/第11章:更强大的自定义生物/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/10-addon教程/第11章:更强大的自定义生物/images/1_1.png b/mconline/100-历史归档教程/10-addon教程/第11章:更强大的自定义生物/images/1_1.png new file mode 100644 index 0000000..216a081 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第11章:更强大的自定义生物/images/1_1.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第11章:更强大的自定义生物/images/1_2.png b/mconline/100-历史归档教程/10-addon教程/第11章:更强大的自定义生物/images/1_2.png new file mode 100644 index 0000000..b5d5f44 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第11章:更强大的自定义生物/images/1_2.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第11章:更强大的自定义生物/images/2_1.png b/mconline/100-历史归档教程/10-addon教程/第11章:更强大的自定义生物/images/2_1.png new file mode 100644 index 0000000..407e797 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第11章:更强大的自定义生物/images/2_1.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第11章:更强大的自定义生物/images/2_2.png b/mconline/100-历史归档教程/10-addon教程/第11章:更强大的自定义生物/images/2_2.png new file mode 100644 index 0000000..99b6a8f Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第11章:更强大的自定义生物/images/2_2.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第11章:更强大的自定义生物/课程01.行为与动画组合.md b/mconline/100-历史归档教程/10-addon教程/第11章:更强大的自定义生物/课程01.行为与动画组合.md new file mode 100644 index 0000000..26c165c --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第11章:更强大的自定义生物/课程01.行为与动画组合.md @@ -0,0 +1,35 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/1_1.b3d4b5ee.png +hard: 进阶 +time: 20分钟 +--- + +# 行为与动画组合 + + + +#### 作者:境界 + + + +我们希望生物会因某种行为而发生动作上的改变,这种改变更多是暴露在外给玩家看到的。比如希望生物在攻击时会表现攻击动画,希望它们在水面上移动时会做出游泳动画。在基岩版中,使用Molang语法配合动画控制器可以做出丰富的动画组合。Molang语言是基岩版独有的一种简易的表达式语言,它主要作用于计算和获取游戏系统内的属性值,由于设计的比较底层,因此在语言优化上的优势远远大于脚本引擎。能够从游戏内获得的属性绝大多数服务于自定义实体、自定义物品、自定义方块上,这些属性又由query函数所返回,它们就像MODSDK上的接口,根据不同query函数所服务的对象,可以返回它们携带的属性。例如我想知道某只羊是否是幼体,可以在molang中读取query.is_baby函数来获得羊是否是幼体。更多的query函数表可以从Minecraft wiki上找到! + + + +#### 行为与动画示例① + +![](./images/1_1.png) + + + +①羊在幼体时,它看起来头特别的大,比成年的羊还大。这是因为羊这个生物注册了一个放大头部位的动画,判断羊是幼体使用了query.is_baby函数。从羊的生物定义文件中可以看到,放大羊头的动画资源名称为baby_transform,而baby_transform的播放条件是query.is_baby必须为1,文档告知这个函数只会返回1真或者0假。在Molang中,布尔值由数字代替,不为0为真,0为假。因此,{动画: molang语法}即告知游戏,当某只羊是幼体时,则放大头部的动画会播放。这里的molang语法简写为"query.is_baby",相当于若query.is_baby不为0,则为真。 + + + +#### 行为与动画示例② + +![](./images/1_2.png) + + + +①猪在移动的时候,用到了walk这个动画,它被注册在了猪的定义文件里。同样使用molang语法,根据文档可以知道query.modified_move_speed会根据生物的速度返回0~1之间单位化取值,若速度慢慢地走出来,则返回1,若因为部分组件行为使用了速度调整的键对,可能不会刚好满足1,但是由于大于0的缘故,最后在上图中,每当猪移动的时候都会返回真,则播放walk动画。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第11章:更强大的自定义生物/课程02.控制器与生物事件.md b/mconline/100-历史归档教程/10-addon教程/第11章:更强大的自定义生物/课程02.控制器与生物事件.md new file mode 100644 index 0000000..d419843 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第11章:更强大的自定义生物/课程02.控制器与生物事件.md @@ -0,0 +1,37 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/2_1.5731dfa4.png +hard: 进阶 +time: 20分钟 +--- + +# 控制器与生物事件 + + + +#### 作者:境界 + + + +在前面的章节中,我们曾有提到动画控制器是存放在资源包的,因为它用来控制动画的状态切换。但实际上,行为包上也可以存在动画控制器,所存放的文件夹和大部分结构上的写法都没有太大的变化。但这又是用来做什么的呢?实际上,有许多开发者曾经希望,能够获得实体的状态后去执行行为包内的事件,亦或是直接从实体身上执行某段指令。由于这些内容都必须在服务端上运行,因此基岩版团队也为开发者在行为包上开放了另一种动画控制器,来根据生物的状态变化时,去执行一组指令或者触发生物的某个事件。 + +由于原版的实体都没有用到这个功能,它的格式和用途只存在于文档当中,因此寻找参考对象比较困难,以下整理了两种用途,为开发者指点迷津。 + + + +#### 行为包控制器的写法 + +![](./images/2_1.png) + + + +① 在行为包根目录中创建animation_controllers的文件夹,控制器文件都会放在这里。 + +②根据上图可以看到与资源包类似,除了在每个状态下可以放置animations动画集合以外,还有两个新的集合,一个是“on_entry”,一个是“on_exit”,它们分别会在进入该状态下后,和离开该状态下前去执行里面的内容。内容可以填写一组指令,或者"@s 事件名称"的格式来执行一个使用了该动画控制器的实体的行为事件。 + +③在上图中,我们根据生物在发现目标的时候,给予一格10秒的1级强力状态效果,并在生物失去目标的时候,清理掉身上全部的状态效果。 + +![](./images/2_2.png) + + + +④ 在生物行为文件里,注册动画控制器,并在scripts/animate下持续执行控制器,同样的执行控制器前,也可以额外通过Molang条件判断控制器是否需要执行。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第11章:更强大的自定义生物/课程03.Add-on与Mod协同工作.md b/mconline/100-历史归档教程/10-addon教程/第11章:更强大的自定义生物/课程03.Add-on与Mod协同工作.md new file mode 100644 index 0000000..c44c472 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第11章:更强大的自定义生物/课程03.Add-on与Mod协同工作.md @@ -0,0 +1,24 @@ +--- +front: +hard: 进阶 +time: 5分钟 +--- + +# Add-on与Mod协同工作 + + + +#### 作者:境界 + + + +当前中国版,有多种方式使MOD与附加包协同工作。 + +①使用GetEntitiesAround,可以传入过滤器来筛选出范围内想要的实体。 + +②使用TriggerCustomEvent,可以执行某个生物行为定义的生物事件。 + +③监听EntityDefinitionsEventServerEvent事件,可以监听到游戏内的某个实体触发了某个事件。 + +④使用GetMolangValue,可以在客户端上获取某个实体的query函数值。 + diff --git a/mconline/100-历史归档教程/10-addon教程/第11章:更强大的自定义生物/课程04.简易教程①-制作带有完整攻击动画的生物.md b/mconline/100-历史归档教程/10-addon教程/第11章:更强大的自定义生物/课程04.简易教程①-制作带有完整攻击动画的生物.md new file mode 100644 index 0000000..8eedc14 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第11章:更强大的自定义生物/课程04.简易教程①-制作带有完整攻击动画的生物.md @@ -0,0 +1,30 @@ +--- +front: +hard: 进阶 +time: 30分钟 +--- + +# 简易教程①:制作带有完整攻击动画的生物 + + + +#### 作者:境界 + + + +高级史莱姆示例包下载:下载[示例包](https://g79.gdl.netease.com/guidedemo-case11.zip)。 + +①附录的附加包内包含一只高级史莱姆生物。让生物带有一种完整攻击动画在概念中一般是指,生物的攻击会因为动画效果带有延迟性。假如史莱姆在对目标进行进攻的时候,我们不希望它在达到攻击距离时,就对目标造成伤害。我们更希望它携带一种比较好的攻击动画,在动画的某一刻再打出那道伤害。幸运的是,原版的劫掠兽携带着这样的行为,并且它也用到了这样的动画效果。因此制作带有完整攻击动画的效果就变得简单起来。 + + + +②让实体带有攻击行为前,必须至少添加一种寻路组件行为、移动组件行为,一个移动速度组件行为和一个攻击力组件行为。寻路和移动组件行为打开后,攻击行为才会有效。移动组件行为和移动速度组件行为使生物能够移动到目标身边。攻击力组件行为添加上后,生物近战才会携带伤害能力。 + + + +③在高级史莱姆行为中,我们添加了minecraft:behavior.delayed_attack,即延迟攻击组件行为。详细的属性注释已经写在了组件行为旁边。 + + + +④接着切换到资源包,打开动画控制器,可以看到高级史莱姆的动画控制器文件。里面已经包含着一个专属于高级史莱姆的攻击动画控制器,使用“molang语法”前,根据文档提示可知,延迟攻击组件行为所搭配的“query”函数叫做“query.is_delayed_attacking”,当生物做出攻击动作时,它会返回“1.0”,即真,否则在其他时间下返回“0.0”,即假。因此,攻击动画使用的循环模式为单次,并在“query.is_delayed_attacking”返回“0.0”,即攻击结束时,切换回初始状态。 + diff --git a/mconline/100-历史归档教程/10-addon教程/第11章:更强大的自定义生物/课程05.简易教程②-制作一种带有范围攻击的生物.md b/mconline/100-历史归档教程/10-addon教程/第11章:更强大的自定义生物/课程05.简易教程②-制作一种带有范围攻击的生物.md new file mode 100644 index 0000000..d21ba40 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第11章:更强大的自定义生物/课程05.简易教程②-制作一种带有范围攻击的生物.md @@ -0,0 +1,21 @@ +--- +front: +hard: 进阶 +time: 30分钟 +--- + +# 简易教程②:制作一种带有范围攻击的生物 + + + +#### 作者:境界 + +#### + +高级史莱姆示例包下载:下载[示例包](https://g79.gdl.netease.com/guidedemo-case11.zip)。 + +①再次打开附录包内的高级史莱姆行为。我们开启了一个组件组,里面包含了一个“knockback_roar”组件行为,它来自劫掠兽,是劫掠兽吼叫时产生范围伤害和击退的组件行为。非常适合用来制作范围攻击。详细的组件键对属性已经用注释写好了。我们来关注一下如何在合适的时机将这个组件组加入进高级史莱姆的活动当中。  + + + +②还记得本章前面提到的控制器与生物事件吗?我们曾经写过一个例子,是在生物发现目标时给予一种状态效果,在失去目标时取消状态效果。同样,我们也可以将这个模式进行改造,让高级史莱姆在发现目标时对周围产生一次战吼的范围攻击,并在攻击结束后开始朝玩家方向移动。因此行为包的控制器内已经写好了一个控制范围攻击触发的控制器。“query.has_target”由文档可知,当实体发现目标时返回“1.0”,即真,反之返回“0.0”,即假。因此切换到“roar”吼叫状态时,必须满足发现目标。紧接着我们在“on_entry”里执行一条指令,会触发一次全屏信息公告。接着触发在高级史莱姆行为内定义好的开始吼叫事件。当吼叫结束时,我们又通过吼叫组件的“on_roar_end”触发一次移除吼叫组件的事件,这样生物就会开始朝目标靠近,直到目标消失后,控制器切换回普通状态,就会等待下一个目标发现时,继续战吼,形成一个生动行为的闭环。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/README.md b/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/images/2_1.jpg b/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/images/2_1.jpg new file mode 100644 index 0000000..7396213 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/images/2_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/images/3_1.jpg b/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/images/3_1.jpg new file mode 100644 index 0000000..4a2d8c0 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/images/3_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/images/4_1.jpg b/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/images/4_1.jpg new file mode 100644 index 0000000..a9bbaf7 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/images/4_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/images/5_1.jpg b/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/images/5_1.jpg new file mode 100644 index 0000000..f96523e Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/images/5_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/images/6_1.jpg b/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/images/6_1.jpg new file mode 100644 index 0000000..b4279b6 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/images/6_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/images/luck_bonusrolls_quality.png b/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/images/luck_bonusrolls_quality.png new file mode 100644 index 0000000..c8af65f Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/images/luck_bonusrolls_quality.png differ diff --git a/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/课程01.更完善的自定义掉落物.md b/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/课程01.更完善的自定义掉落物.md new file mode 100644 index 0000000..b81061f --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/课程01.更完善的自定义掉落物.md @@ -0,0 +1,23 @@ +--- +front: +hard: 进阶 +time: 5分钟 +--- + +# 更完善的自定义掉落物 + + + +#### 作者:境界 + + + +自定义掉落物不仅仅能掉落生物相关物品,在合理的配置与想象力的发挥下,开发者可以利用掉落物池、掉落物功能、掉落物条件、特殊的掉落物入口等方法实现多样的战利品系统。。本章将逐一拆解每个方法的细节。 + + + +#### 以下是自定义掉落物时,需要注意的几个点: + +- 崩溃:当掉落物配置文件中带有错误的语法时,一旦在生成带有装备的生物、击杀生物、破坏自定义方块、打开被改动了战利品的奖励箱、钓鱼、与生物交互产生该文件的掉落物时,游戏会立即崩溃并且不提供任何错误提示。 +- 装备战利品:只有原版的鞘翅、头盔、铠甲、护腿、鞋子、掠夺者旗帜可以被生物装备;无法设置生物装备生物头颅、自定义装备、雕刻过的南瓜;可以被玩家副手手持的物品例如:盾牌等,也可以被生物通过装备战利品副手持有。 +- 附魔:设定掉落物附魔时,无法给予没有附魔能力的道具附魔。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/课程02.战利品池.md b/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/课程02.战利品池.md new file mode 100644 index 0000000..bee0d07 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/课程02.战利品池.md @@ -0,0 +1,65 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/2_1.eeeb242d.jpg +hard: 进阶 +time: 15分钟 +--- + +# 战利品池 + + + +#### 作者:境界 + + + +我们以原版僵尸的战利品池(pools)为例,它与其他生物的战利品配置不同的是,在战利品池中一共有两种组合。 + +![](./images/2_1.jpg) + + + +#### rolls + +rolls可以理解为投掷骰子的次数,它接受整型数值和包含最小次数和最大次数的随机区间,因此当次数等于1时,可以从战利品入口处筛选一次战利品。若大于1次时,可以筛选多次战利品。而当写成区间对象时,则会在最小次数和最大次数之间取一个数值,来筛选几次战利品。 + + + +#### type + +type一共有三种参数,item代表战利品为物品,empty代表战利品为空,loot_table则会将该战利品指向其他战利品配置文件中。 + +当type为item时,可以通过name指定物品名称,格式为命名空间:标识符。weight是权重的意思。 + +当type为loot_tables时,可以通过name指定战利品文件路径,路径名一般以loot_tables开头。weight是权重的意思。 + +当入口存在多个战利品时,权重越高的越容易中到。name在类型为empty下无效。 + + + +在每个战利品中依旧可以开启一个战利品池,当战利品配置表被游戏触发时,会依次从最头部的战利品池内筛选出一个战利品,紧接着若在战利品内还有一个战利品池,则会继续筛选下去。这种方法会在需要玩家生成某件物品时,若还要再生成另一件物品时会非常好用。 + + + +如果战利品池里有两次以上的筛选组合,在没有加入条件前,全部会根据rolls的数值执行次数。若在筛选组合内加入条件,则必须满足条件后才可以筛选其中的战利品。 + +### 补充内容: +我的世界中国版自2.1版本增加玩家的幸运属性,因此战利品表也同步支持quality和bonus_rolls两个字段,如下图所示。 +>此部分内容不在原版僵尸的战利品表内,需要开发者自行配置战利品表实现 + +![luck_bonusrolls_quality](./images/luck_bonusrolls_quality.png) + +luck:幸运值属性,与Java版的幸运值类似,属于玩家属性的一种,能够配合战利品表的quality和bonus_rolls字段改变战利品池的投掷次数和每个物品的抽取权重,最终实现幸运值越高,掉落物品更丰富和宝贵的效果。 + +quality:配合幸运值属性增加此物品的权重,值只能为整数,可以为负数,计算公式如下 + + weight = weight + luck*quality + +bonus_rolls:配合幸运值属性增加战利品投掷的次数,值可为浮点数,可以为负数,等同于在rolls的次数基础上增加额外的抽取次数。计算公式如下: + + 最终投掷次数=rolls+luck*bonus_rolls(结果向下取整,若最终投掷次数≤0则不掉落) + +>注意事项1:幸运属性的影响范围仅限被玩家击杀的生物和钓鱼的战利品表(掉落表),暂不支持自然生成的容器(箱子),破坏方块的掉落物,猪灵的以物易物的战利品表。 + +>注意事项2:钓鱼时的战利品还可以被海之眷顾的附魔效果影响,但是这并不会提升玩家的幸运值属性 + +>注意事项3:掠夺附魔效果额外得到的数量会受最终投掷次数影响而成倍增长,例如:受幸运值影响最终投掷次数为3,则掠夺附魔效果会额外得到的数量基准为3,附魔等级每增加一级,将额外得到3个物品,因此请避免将bonus_rolls值设置过大。 diff --git a/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/课程03.战利品功能.md b/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/课程03.战利品功能.md new file mode 100644 index 0000000..c1aa745 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/课程03.战利品功能.md @@ -0,0 +1,252 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/3_1.ea877fdd.jpg +hard: 进阶 +time: 20分钟 +--- + +# 战利品功能 + + + +#### 作者:境界 + + + +![](./images/3_1.jpg) + + + +战利品功能可以赋予战利品更加强大的能力,以僵尸正常掉落战利品的情况为例,functions内定义了两个功能:set_count和looting_enchant,前者是设置掉落腐肉时的数量,后者是被抢夺附魔赋予的额外数量。min是最小值,而max是最大值,数量会在这区间内随机产生。 + + + +除此之外,原版还有非常多的功能以供选择。 + + + +#### 与附魔相关的有: + +enchant_with_levels:通过附魔等级给予附魔。可以设置为附魔等级设置固定数值或随机区间。 + +```json +{ + "function": "enchant_with_levels", //功能名称 + "treasure": false, //若为真,则赋予稀有的附魔 + "levels": 1 //附魔等级 +} +``` + +```json +{ + "function": "enchant_with_levels", //功能名称 + "treasure": false,//若为真,则赋予稀有的附魔 + "levels": {"min":1, "max": 2}//附魔等级 +} +``` + + + +enchant_randomly:随机附魔。 + +```json +{ + "function": "enchant_randomly",//功能名称 + "treasure": false,//若为真,则赋予稀有的附魔 +} +``` + + + +enchant_random_gear:给予随机的护甲附魔。 + +```json +{ + "function": "enchant_random_gear", //功能名称 + "chance": 0 //概率,0.0~1.0区间,1.0=100% +} +``` + + + +specific_enchants:指定附魔和等级(等级可以超过原版上限,但游戏内只会显示原版的最高等级)。 + +```json +{ + "function": "specific_enchants", + "enchants": [ + { + "id": "aqua_affinity", + "level": 1 + } + ] +} +``` + + + +#### 与物品相关的有: + +set_data://指定物品附加值,如羊毛附加值15为黑色羊毛。 + +```json +{ + "function": "set_data", + "data": 15 +} +``` + + + +set_damage:指定物品百分比耐久度,1=100%的剩余耐久度,0为没有剩余耐久度,取值在0.0~1.0之间。 + +```json +{ + "function": "set_damage", + "damage": 1 +} +``` + + + +set_data_from_color_index:根据minecraft:color行为设置对应的附加值,原版使用minecraft:color行为的生物有羊,因此会根据羊的羊毛颜色掉落对应的羊毛。 + +```json +{ + "function": "set_data_from_color_index" +} +``` + + + +random_aux_value:会给予一个非方块物品随机的附加值。 + +```json +{ + "function": "random_aux_value", + "values": { + "min": 2, + "max": 9 + } +} +``` + + + +set_book_contents:会给予一本写好的书书名、作者、以及书本内容。 + +```json +{ + "function": "set_book_contents", + "title": "1", + "author": "1", + "pages": [ + "1111", + "2222" + ] +} +``` + + + +fill_container:会掉落或者给予一个箱子方块、发射器方块、漏斗方块一个自定义的战利品配置表。 + +```json +{ + "function": "fill_container", + "loot_table": "loot_tables/table.json" +} +``` + + + +furnace_smelt:会掉落一个带有熔炉配方的道具的配方结果,如牛肉变烤牛肉;条件目前只有on_fire着火和on_ground着地两种。 + +```json +{ + "function": "furnace_smelt", + "conditions": [ + { + "condition": "entity_properties", + "entity": "this", + "properties": { + "on_fire": true + } + } + ] +} +``` + + + +set_banner_details:设置旗帜的细节。 + +```json +{ + "function": "set_banner_details", + "conditions": [ + { + "condition": "entity_properties", + "type": 1 + } + ] +} +``` + + + +exploration_map:设置地图变为探险家地图,其中destination的目标值为原版/locate指令的参数,包括 "monument", "mansion", "village", "stronghold", "temple", "ruins", "shipwreck", "pillageroutpost", "buriedtreasure", "mineshaft", "endcity", "fortress", "ruinedportal", "bastionremnant"。 + +```json +{ + "function": "exploration_map", + "destination": "monument" +} +``` + + + +random_block_state:随机选取一个block state值,当设置的物品为方块物品且含有该值的话,如 "coral_color", "flower_type", "sapling_type",更多blockstate请参考wiki:[https://minecraft.gamepedia.com/Block_states](https://minecraft.gamepedia.com/Block_states)。 + +```json +{ + "function": "random_block_state", + "values": { + "min": 1, + "max": 1 + } +} +``` + + + +set_lore:设置物品词缀。 + +```json +{ + "function": "set_lore", + "lore": [ "111" ] +} +``` + + + +set_name:设置物品名称。 + +```json +{ + "function": "set_name", + "name": "design" +} +``` + + + +set_actor_id:设置生物蛋物品的生物ID,会掉落该生物的生物蛋。 + +```json +{ + "function": "set_actor_id", + "id": "minecraft:zombie" +} +``` + diff --git a/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/课程04.战利品条件.md b/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/课程04.战利品条件.md new file mode 100644 index 0000000..38792cd --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/课程04.战利品条件.md @@ -0,0 +1,61 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/3_1.ea877fdd.jpg +hard: 进阶 +time: 20分钟 +--- + +# 战利品条件 + + + +#### 作者:境界 + + + +![](./images/4_1.jpg) + + + +当开发者希望筛选组合必须满足条件时才会掉落战利品,可以使用战利品条件功能。 + +```json + { + "conditions": [ + { + "condition": "has_mark_variant",//当该生物的minecraft:mark_variant值为几时,条件通过,可以筛选 + "value": 1 + }, + { + "condition": "entity_properties",//当该生物因着火或者落地而被击败时 + "value": { + "on_ground": false, + "on_fire": false + } + }, + { + "condition": "killed_by_player",//当该生物是被玩家击败的 + }, + { + "condition": "killed_by_player_or_pets",//当该生物是被玩家或玩家的宠物击败的 + }, + { + "condition": "random_chance_with_looting",//当随机值满足时,且掠夺附魔会增加随机概率,取值范围在0.0~1.0 + "chance": 0.0, + "looting_multiplier": 0.0 + }, + { + "condition": "random_difficulty_chance",//当难度为下列某一难度时,值越大,越容易被筛选到,取值范围在0.0~1.0 + "default_chance": 0, //若没有设置游戏难度,则此段生效 + "peaceful": 0,//若游戏难度为和平难度 + "easy": 0,//若游戏难度为简单难度 + "normal": 0,//若游戏难度为正常难度 + "hard": 0//若游戏难度为困难难度 + }, + { + "condition": "random_regional_difficulty_chance", //将最大区域难度随机机会满足指定值时,取值范围在0.0~1.0 + “max_chance”: 0.0 + } + ] +} +``` + diff --git a/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/课程05.简易教学①自定义掉落一种颜色的羊毛.md b/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/课程05.简易教学①自定义掉落一种颜色的羊毛.md new file mode 100644 index 0000000..f803957 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/课程05.简易教学①自定义掉落一种颜色的羊毛.md @@ -0,0 +1,57 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/4_1.af85ce66.jpg +hard: 进阶 +time: 10分钟 +--- + +# 简易教学①:自定义掉落一种颜色的羊毛 + + + +#### 作者:境界 + + + +![](./images/5_1.jpg) + + + +1)创建一个在行为包主目录中,loot_tables文件夹下的战利品配置文件。 + + + +2)根据内容格式写入 + +``` +{ + "pools": [ + { + "rolls": 1, + "entries": [] + } + ] +} +``` + +由于只掉落一种颜色的羊毛,筛选次数写1即可 + + + +3)插入战利品内容 + +``` +{ + "pools": [ + { + "rolls": 1, + "entries": [ + {“type”: "item", "name": "minecraft:wool","function": [{"function": "set_data","data":15}]} + ] + } + ] +} +``` + + + +将战利品类型设置为物品,将物品名称设置为minecraft:wool,最后再加入战利品功能,设置物品附加值为15,这样就会在战利品出现时,出现一个黑色羊毛。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/课程06.简易教学②自定义多次筛选同个奖励池.md b/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/课程06.简易教学②自定义多次筛选同个奖励池.md new file mode 100644 index 0000000..d09e03e --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第12章:更完善的自定义掉落物/课程06.简易教学②自定义多次筛选同个奖励池.md @@ -0,0 +1,72 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/6_1.b9815060.jpg +hard: 进阶 +time: 10分钟 +--- + +# 简易教学②:自定义多次筛选同个奖励池 + + + +#### 作者:境界 + + + +![](./images/6_1.jpg) + + + +1)创建一个在行为包主目录中,loot_tables文件夹下的战利品配置文件。 + + + +2)根据内容格式写入 + +``` +{ + "pools": [ + { + "rolls": { + "min":1, + "max":2 + }, + "entries": [] + } + ] +} +``` + +由于我们希望多次筛选同一个战利品池,但希望是在一个随机的区间里,因此筛选次数最小值填1,最大次数填2,则最终筛选次数会在1~2次之间。 + + + +3)插入战利品内容 + +``` +{ + "pools":[ + { + "rolls":{ + "min":1, + "max":2 + }, + "entries":[ + { + "type":"item", + "name":"minecraft:diamond_sword", + "weight":1 + }, + { + "type":"item", + "name":"minecraft:stone", + "weight":4 + } + ] + } + ] +} +``` + + + +投放两个物品类型的战利品,将物品名称设置为minecraft:diamond_sword钻石剑和minecraft:stone圆石。同时更加昂贵的钻石剑的权重被设置为了1,普通的圆石设置为4,这样单次抽取战利品的概率里,钻石剑抽中的概率为1/ (1 + 4) * 100% = 25%。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/README.md b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/1_1.jpg b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/1_1.jpg new file mode 100644 index 0000000..8caca42 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/1_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/2_1.jpg b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/2_1.jpg new file mode 100644 index 0000000..ce93767 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/2_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/2_2.jpg b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/2_2.jpg new file mode 100644 index 0000000..836f69e Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/2_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/2_3.jpg b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/2_3.jpg new file mode 100644 index 0000000..97d6011 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/2_3.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/2_4.jpg b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/2_4.jpg new file mode 100644 index 0000000..94cd145 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/2_4.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/2_5.jpg b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/2_5.jpg new file mode 100644 index 0000000..587102d Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/2_5.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/2_6.jpg b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/2_6.jpg new file mode 100644 index 0000000..f424f82 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/2_6.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/2_7.jpg b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/2_7.jpg new file mode 100644 index 0000000..cacb571 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/2_7.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/3_1.jpg b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/3_1.jpg new file mode 100644 index 0000000..0927a8b Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/3_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/3_2.jpg b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/3_2.jpg new file mode 100644 index 0000000..20c3ff6 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/3_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/3_3.jpg b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/3_3.jpg new file mode 100644 index 0000000..c6201e9 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/3_3.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/4_1.jpg b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/4_1.jpg new file mode 100644 index 0000000..129de2e Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/4_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/4_10.jpg b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/4_10.jpg new file mode 100644 index 0000000..937517b Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/4_10.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/4_2.jpg b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/4_2.jpg new file mode 100644 index 0000000..cff93c4 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/4_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/4_3.jpg b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/4_3.jpg new file mode 100644 index 0000000..97a2598 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/4_3.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/4_4.jpg b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/4_4.jpg new file mode 100644 index 0000000..20e6f1c Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/4_4.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/4_5.jpg b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/4_5.jpg new file mode 100644 index 0000000..e41caed Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/4_5.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/4_6.jpg b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/4_6.jpg new file mode 100644 index 0000000..6b6cc52 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/4_6.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/4_7.jpg b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/4_7.jpg new file mode 100644 index 0000000..bddcfb5 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/4_7.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/4_8.jpg b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/4_8.jpg new file mode 100644 index 0000000..843e8ce Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/4_8.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/4_9.jpg b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/4_9.jpg new file mode 100644 index 0000000..e27ce04 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/4_9.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/5_1.jpg b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/5_1.jpg new file mode 100644 index 0000000..83c41f9 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/5_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/5_2.jpg b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/5_2.jpg new file mode 100644 index 0000000..961d538 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/5_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/5_3.jpg b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/5_3.jpg new file mode 100644 index 0000000..ade9b2e Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/5_3.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/5_4.jpg b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/5_4.jpg new file mode 100644 index 0000000..de99706 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/5_4.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/5_5.jpg b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/5_5.jpg new file mode 100644 index 0000000..098efef Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/images/5_5.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/课程01.自定义方块实体.md b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/课程01.自定义方块实体.md new file mode 100644 index 0000000..23faba6 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/课程01.自定义方块实体.md @@ -0,0 +1,56 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/1_1.be3fdadc.jpg +hard: 进阶 +time: 15分钟 +--- + +# 自定义方块实体 + + + +#### 作者:境界 + + + +自定义方块实体是一种可以保存复杂数据结构的方块类型。与方块状态不同的概念是,方块状态更多是保存一种单一的属性。以基岩版的铁砧为例,它带有一个方块状态:"damaged"来代表铁砧的损坏程度,它接受四个值,broken(损坏的)、slightly_damaged(轻微受损的)、undamaged(完好的)、very_damaged(严重受损的)。而方块实体则可以保存更复杂的数据内容,如一个箱子方块里保存了一个物品数据。因此,方块实体就是方块的内容,方块状态更多看作是方块的属性。注意:自定义方块实体功能为中国版特供,所以开发者无法用同样的格式在国际板上自定义方块实体。 + + + +#### 如何自定义方块实体 + +![](./images/1_1.jpg) + + + +``` +{ + "format_version": "1.16.0", + "minecraft:block": { + "description": { + }, + "components": { + "netease:block_entity": { + "tick": true, + "movable": false + } + } + } +} +``` + +在自定义方块的components下,开发者添加一个"netease:block_entity"的组件,其中有两个键对,"tick"设置为true时,当玩家进入方块tick范围时,该方块每秒会发送20次ServerBlockEntityTickEvent事件,设置为false时,则不会像脚本层发送事件。 + +"movable"用来设置该自定义方块实体是否可以被活塞推动。由于基岩版的设定,所有原版方块实体都可以被活塞推动,这里也为自定义方块实体提供了这一项功能。 + + + +#### tick与不tick + +在需要方块保存数据而不需要实时更新方块内的数据时,可以选择将tick设置为false。 + +例如:玩家右键交互一个自定义方块实体,每20下会随机掉落一个战利品。这里的方块实体保存数据只是为了记录玩家点击自己的次数,因此可以将tick设置为false。 + + + +而当需要设置tick为true时,往往该自定义方块实体需要不断更新自身的数据,例如自定义一个熔炉方块,当方块内有燃料并且正在烧东西时,方块实体自身需要维护一个不断更新的燃烧物品时间,则可以将该方块实体的tick设置为true。 + diff --git a/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/课程02.自定义作物.md b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/课程02.自定义作物.md new file mode 100644 index 0000000..beff2d2 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/课程02.自定义作物.md @@ -0,0 +1,260 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/2_1.8a46d947.jpg +hard: 进阶 +time: 25分钟 +--- + +# 自定义作物 + + + +#### 作者:境界 + + + +在《我的世界》中,种植一个作物需要一颗种子。因此从开发的角度来说,自定义作物需要用到一个种子道具和一个自定义方块。实现自定义作物的常用方法一共有两种,一种可以通过纯组件方式实现,一种则需要用到MODSDK,本章将会为开发者提供这两种思路去实现自己的自定义作物。 + + + +#### 开始前的准备 + +#### 自定义种子 + +![](./images/2_1.jpg) + + + +自定义一个种子道具,设置它的最大堆叠数为64个。加入"minecraft:seed"组件,其中有两个键对,"crop_result"指向种植的方块,这里命名为"design:custom_crop_0",它将作为未成熟的自定义作物方块的名称域备用。"plant_at"指向可以种植的方块,这里设置只能种植在灰化土上。 + +``` +{ + "format_version": "1.16.0", + "minecraft:item": { + "description": { + "identifier": "design:custom_crop_item", + "register_to_create_menu": true, + "category": "Items" + }, + "components": { + "minecraft:max_stack_size": 64, + "minecraft:seed": { + "crop_result": "design:custom_crop_0", + "plant_at": ["podzol"] + } + } + } +} +``` + + + +#### 自定义农作物方块 + +![](./images/2_2.jpg) + + + +``` +{ + "format_version": "1.16.0", + "minecraft:block": { + "description": { + "identifier": "design:custom_crop_0", + "register_to_creative_menu": true + }, + "components": { + "minecraft:block_light_absorption": 0, + "minecraft:destroy_time": 0.0, + "minecraft:loot": "loot_tables/empty.json", + "netease:render_layer": { + "value": "alpha" + }, + "netease:aabb": { + "collision": { + "min": [0.0, 0.0, 0.0], + "max": [0.0, 0.0, 0.0] + }, + "clip": { + "min": [0.0, 0.0, 0.0], + "max": [1.0, 0.125, 1.0] + } + }, + "netease:may_place_on": { + "block": ["minecraft:podzol"] + } + } + } +} +``` + + + +自定义未成熟的作物方块行为,用"minecraft:block_light_absorption"设置它的透光度为0,这样方块不会在世界中产生难看的阴影。用netease:aabb将碰撞箱和射线检测碰撞箱适配实际方块的形象。用netease:render_layer将方块材质设置为透明材质。用"minecraft:destroy_time"设置它的破坏时间为0.0,用"minecraft:loot"将掉落物指向原版的空战利品表,即不掉落任何掉落物。用“netease:may_place_on”设置方块只能生长在灰化土上,不是灰化土则自毁。 + + + +![](./images/2_3.jpg) + + + +自定义未成熟的作物方块材质,设置它被破坏的音效为草的音效,与原版作物方块被破坏的音效一致。将它的方块形状设置为"cross_texture",这样方块就会像原版的作物方块一样呈现一个交叉形状,适合对方块模型没有特殊要求的开发者。 + + + +``` +{ + "format_version": [ + 1, + 1, + 0 + ], + "design:custom_crop_0": { + "blockshape": "cross_texture", + "sound": "grass", + "textures": "design:custom_crop_0" + } +} +``` + + + +![](./images/2_4.jpg) + + + +``` +{ + "resource_pack_name": "vanilla", + "texture_name": "atlas.terrain", + "texture_data": { + "design:custom_crop_0": { + "textures": "textures/blocks/wheat_stage_0" + } + } +} +``` + + + +最后在textures/terrain_texture.json文件(如果没有就新建)里,设置作物的资源键和资源路径。 + + + +![](./images/2_5.jpg) + + + +在自定义方块内加入"netease:transform"组件,"condition"内放置方块的条件,brightness光照控制方块在什么光照条件下生长,random_tick_count为转化需要的随机tick次数,surrounding为防转化需要的周围方块,radius是方块半径。result是方块转化后的结果方块。 + +``` +{ + "format_version":"1.16.0", + "minecraft:block":{ + "description":{ + "identifier":"design:custom_crop_0", + "register_to_creative_menu":true + }, + "components":{ + "minecraft:block_light_absorption":0, + "minecraft:destroy_time":0, + "minecraft:loot":"loot_tables/empty.json", + "netease:render_layer":{ + "value":"alpha" + }, + "netease:aabb":{ + "collision":{ + "min":[0,0,0], + "max":[0,0,0] + }, + "clip":{ + "min":[0,0,0], + "max":[1,0.125,1] + } + }, + "netease:may_place_on":{ + "block":[ + "minecraft:podzol" + ] + }, + "netease:transform":{ + "conditions":{ + "brightness":{ // 农作物生长必须满足的光照条件 + "max":15, + "min":9 + }, + "random_tick_count":{ // 转化需要的随机tick次数 + "value":2 + }, + "surrouding":{ //转化需要的周围方块 + "value":"minecraft:podzol", + "radius":1 //半径 + } + }, + "result":"minecraft:wheat" //转化为哪种方块 + } + } + } +} +``` + + + +#### 脚本实现自定义方块 + + + +![](./images/2_6.jpg) + + + +``` +{ + "format_version": "1.16.0", + "minecraft:block": { + "description": { + "identifier": "design:custom_crop_0", + "register_to_creative_menu": true + }, + "components": { + "minecraft:block_light_absorption": 0, + "minecraft:destroy_time": 0.0, + "minecraft:loot": "loot_tables/empty.json", + "netease:render_layer": { + "value": "alpha" + }, + "netease:aabb": { + "collision": { + "min": [0.0, 0.0, 0.0], + "max": [0.0, 0.0, 0.0] + }, + "clip": { + "min": [0.0, 0.0, 0.0], + "max": [1.0, 0.125, 1.0] + } + }, + "netease:may_place_on": { + "block": ["minecraft:podzol"] + }, + "netease:random_tick": { + "enable": true, + "tick_to_script": true + }, + "netease:block_entity": { + "tick": false, + "movable": false + } + } + } +} +``` + + + +在自定义方块内加入"netease:random_tick"组件,其中enable为是否启用random_tick随机时间,tick_to_script为触发随机时间时,是否传递BlockRandomTickServerEvent事件到脚本层。启动随机时间组件后,方块会在每一个随机间隔内将事件发送到脚本层,为了达到更加拟真的效果,我们加入方块实体组件,这样可以用来保存方块状态,进行一个生长值的模拟。 + +![](./images/2_7.jpg) + + + +在示例图中,我们通过监听BlockRandomTickServerEvent来判断是否可以转换到下一个阶段的自定义农作物,判断条件不限定于netease:transform中描述的光照、tick数量以及周边环境。同时,我们还可以借助blockEntityData组件来存储数据。最后满足条件结果,会将自定义作物方块转化为成熟的小麦,当然,也可以替换成开发者自定义的作物。 + diff --git a/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/课程03.自定义管线方块.md b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/课程03.自定义管线方块.md new file mode 100644 index 0000000..904f338 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/课程03.自定义管线方块.md @@ -0,0 +1,55 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/3_1.f808b90b.jpg +hard: 进阶 +time: 15分钟 +--- + +# 自定义管线方块 + + + +#### 作者:境界 + + + +管线方块作为一些知名模组的物质传输方块,常常出现在玩家面前。中国版当前支持制作根据接触其他方块的面显示对应连接部位的方块模型。注意:自定义连接功能为中国版特供,所以开发者无法用同样的格式在国际版为自定义方块添加该功能。 + + + +#### 资源上的定义 + +![](./images/3_1.jpg) + + + +打开附件里bbmodel文件夹内的管道方块模型,可以看到连接6个面的方块骨骼和内部的方块位置。 + +root是管道的中心骨骼,当四面都没有可连接的方块时,应当只显示root骨骼。当方块的南面有可连接的方块时,应显示root和south骨骼,其他亦然。 + +捋顺模型的制作思路后,还要注意的是,为了让游戏知道模型在哪个面连接到相应方块时显示哪一面的接口,还需要在最终的模型文件上进行配置。 + +![](./images/3_2.jpg) + + + +如上图所示,我们需要在骨骼名称的同级下打开键对“enable”,值为molang语法:query.is_connect(接触面)。其中接触面即方块朝向的固定值。“down”是管道下方的骨骼,与此同时,我们在它的骨骼同级下加入"enable": "query.is_connect(0)",即仅当下方有可连接方块时,才显示该骨骼所携带的方块。 + + + +#### 功能上的定义 + + + +![](./images/3_3.jpg) + + + +在自定义方块内加入"netease:connection"组件,"blocks"内放置连接的方块,只有连接到这些方块,模型才会显示出连接过去的一部分。 + +同时,自定义方块模型会携带一部分阴影,需要将"netease:solid"的值设置为false才能关闭阴影。 + +由于方块拥有了比基本方块更小的体积,我们需要将方块的射线检测和碰撞体积都契合它真实的模型。 + +首先将aabb组件内的collision碰撞体积和clip射线检测体积都改为数组形式,这样方块才能拥有多重的体积。 + +通过blockbench的世界坐标和方块位置、体积求得每个骨骼的实际体积,需要再加入enable键对来帮助游戏控制每个连接部位的显示与否。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/课程04.简易教程①制作一个挂载果实的树.md b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/课程04.简易教程①制作一个挂载果实的树.md new file mode 100644 index 0000000..933a369 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/课程04.简易教程①制作一个挂载果实的树.md @@ -0,0 +1,416 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/4_10.f2c5d857.jpg +hard: 进阶 +time: 35分钟 +--- + +# 简易教程①:制作一个挂载果实的树 + + + +#### 作者:境界 + + + +挂载苹果示例包下载:下载[示例包](https://g79.gdl.netease.com/guidedemo-case13.zip) 。 + +#### 增加苹果方块 + +![](./images/4_1.jpg) + + + +#### 实现代码逻辑如下: + +![](./images/4_2.jpg) + + + +``` +{ + "format_version": "1.16.0", + "minecraft:block": { + "description": { + "identifier": "design:custom_apple", + "register_to_creative_menu": true + }, + "components": { + "minecraft:block_light_absorption": 0, + "minecraft:destroy_time": 1.0, + "netease:render_layer": { + "value": "alpha" + }, + "netease:aabb": { + "collision": { + "min": [0.3125, 0.375, 0.3125], + "max": [0.6875, 1.0, 0.6875] + }, + "clip": { + "min": [0.3125, 0.375, 0.3125], + "max": [0.6875, 1.0, 0.6875] + } + }, + "minecraft:loot": "loot_tables/custom_apple.json", + "netease:solid": { + "value": false + }, + "netease:pathable": { + "value": true + } + } + } +} +``` + + + +1)首先我们将方块的透光率设置为0,即将minecraft:block_light_absorption值写为0。这是由于苹果方块用到自定义模型,游戏内的日夜光照会对模型进行阴影覆盖,我们希望方块受到阴影的程度的影响小一些,因此将值设置为0。开发者也可以根据自己的实际情况,在0~15间内进行微调,这是透光率组件允许接受的数值范围。 + +2)接着用netease:render_layer将方块材质设置为透明材质,以允许方块模型使用带有透明图层的贴图。 + +3)之后在netease:aabb里,将碰撞体积和射线检测贴准苹果方块真实的样子。组件中,每1点对应blockbench建模空间中16格,也就是每0.0625的长度对应blockbench内的一格空间。 + +4)接着将方块设置为非实心方块,这是由于我们希望避免玩家与苹果方块接触时,偶尔会因为碰撞盒重叠出现窒息扣血的情况。 + +5)接着将方块指向自定义的苹果战利品表,使玩家在挖掉苹果方块后会掉落苹果物品。 + +6)最后,将方块设置为生物无视其继续循着自己的寻路路径走。 + + + +#### 添加苹果战利品表 + +![](./images/4_3.jpg) + + + +1)添加战利品池,设置筛选次数为1次,战利品入口设置为只有苹果这一个物品。 + +``` +{ + "pools": [ + { + "rolls": 1, + "entries": [ + { + "type": "item", + "name": "minecraft:apple" + } + ] + } + ] +} +``` + + + +#### 添加苹果方块资源文件 + +![](./images/4_4.jpg) + + + +``` +{ + "format_version": "1.13.0", + "netease:block_geometry": { + "bones": [ + ], + "description": { + "identifier": "design:custom_apple", + "textures": [ + "design:custom_apple" + ], + "use_ao": false + } + } +} +``` + + + +![](./images/4_5.jpg) + + + +``` +{ + "format_version": [ + 1, + 1, + 0 + ], + "design:custom_apple": { + "netease_model": "design:custom_apple", + "sound": "wood" + } +} +``` + + + +1)使用MCSTUDIO导出苹果方块工程文件,获得json模型文件 + +2)将模型放入材质包/models/netease_block文件夹内 + +3)将材质放入材质包/textures/blocks文件夹内 + +4)新建terrain_texture.json文件,列入资源键和路径 + +5)将资源键写入模型文件中,将环境光遮罩关闭("use_ao": false) + +6)在材质包/blocks.json内配置苹果方块的模型和破坏音效 + + + +#### 制作特征 + +实现游戏内自然放置苹果方块,一共有三个维度。分别要用到单方块特征、检索特征、和总和特征。其中单方块特征负责设定苹果方块放置的规则、检索特征帮助苹果方块寻找放置的位置、总和特征会作为最后打包的部分被特征规则所使用。详细的细节请看以下: + + + +#### 单方块特征 + +1) 首先在行为包根目录下的netease_features文件夹内新建custom_apple_feature.json的特征,特征类型为单方块特征。基本格式如下: + +``` +{ +    "format_version":"1.14.0", +    "minecraft:single_block_feature":{ +        "description":{ +            "identifier":"特征的identifier,以命名空间:标识符为格式,其中命名空间后面的标识符还必须与文件名称匹配"" +        }, +        "places_block":"方块", +        "enforce_placement_rules":false, +        "enforce_survivability_rules":false, +        "may_place_on":[ +            "方块" +        ], +        "may_replace":[ +            "方块" +        ] +    } +} +``` + + + +2)接着,我们需要希望单方块特征会放置自定义的苹果方块。一共有两种写法,即只指定方块类型和指定方块类型加方块状态。由于自定义的苹果方块不存在任何方块状态,因此可以用places_block: “design:custom_apple”的方式。在未来开发者如果想要放置一些带有方块状态的,例如原版方块。则可以参考下面放置成熟小麦的例子: + +``` +“places_block”: { + “name”: “minecraft:wheat”, +“states”: { + “growth”: 7 +} +} +``` + + + +3) 之后,为了能够保证苹果方块放置的其中之一条件必须是所替换的方块只能是空气方块,我们在may_replace_on这个属性集合内放入minecraft:air。从常理上,只有当放置规则在放置苹果方块时,满足了单方块特征里只能替换掉空气方块的规则,最后苹果方块才能放置成功。否则如果是替换任何一种方块,则在游戏内会让玩家察觉到非常不适的视觉效果。例如,苹果方块占用了叶子的位置,将叶子替换成了自己。 + +4) 紧接着,为了实现能够让果实挂载在树叶底下。我们要加入may_attach_to属性字段,并告知单方块特征,果实会挂载在叶子的下面,即方块的顶面top会贴在原版的树叶方块,即树叶方块的下面。auto_rotate字段用来决定是否自动旋转方块来符合贴近规则,这里我们填写false假。示例教程里,我们单独挑出橡木树叶和桦木树叶作为例子。 + +5) 最后是enforce_placement_rules和enforce_survivability_rules字段,前者会触发方块自我检查是否定义了方块放在什么方块边上的行为,后者会触发方块自我检查是否定义了方块可以在什么方块上存活保留的行为。由于自定义方块功能当前没有存活条件的行为,我们将此enforce_survivability_rules字段设置为false,enforce_placement_rules设置为true。 + +![](./images/4_6.jpg) + + + +``` +{ + "format_version": "1.14.0", + "minecraft:single_block_feature": { + "description": { + "identifier": "design:custom_apple_feature"//特征名称域 + }, + "places_block": { + "name": "design:custom_apple",//方块名称域 + "states": {} + }, + "enforce_placement_rules": true,//是否根据放置规则放置特征,这里要写为true真 + "enforce_survivability_rules": false, + "may_attach_to": { + "auto_rotate": false, + "min_sides_must_attach": 1, + "top": [ + { + "name": "minecraft:leaves",// 方块必须上部贴住橡树树叶才会生成 + "states": { + "old_leaf_type": "oak" + } + }, + { + "name": "minecraft:leaves",// 方块必须上部贴住桦树树叶才会生成 + "states": { + "old_leaf_type": "birch" + } + } + ] + }, + "may_replace": [//只会在有空气的位置替换成苹果方块 + "minecraft:air" + ] + } +} +``` + + + +#### 检索特征 + +![](./images/4_7.jpg) + + + + 1)首先在行为包根目录下的netease_features文件夹内新建custom_apple_search_feature.json的特征,特征类型为检索特征。基本格式如下: + +``` +{ + "format_version":”1.14.0”, + "minecraft:search_feature": { + "description": { + "identifier": "特征的identifier,以命名空间:标识符为格式,其中命名空间后面的标识符还必须与文件名称匹配"" + }, + "places_feature": "特征identifier", + "search_volume": { + "min": [ -3, -3, -3 ], + "max": [ 3, 3, 3 ] + }, + "search_axis": "-y", + "required_successes": 3 +} +``` + + + +2)然后我们将检索特征下所放置的特征挂载为前面设定的苹果单方块特征。 + +3)紧接着,我们将检索特征会寻找的放置坐标范围,这需要用到search_volume对象,在min和max中进行调整。min代表左下角起点,max代表右上角最大点。这样拉出来的空间是一个方型。我们希望在之后和原版数特征组合用总和特征打包起来后,检索特征会检索前一个树木特征放下来后,从它的特征放置点下,在最小点到最大点的空间内进行偏移,寻找空间内是否有树叶,并且必须满足前面单方块特征的条件,包括苹果方块只能替换空气方块,以及放置单方块特征时必须是树叶下面的坐标点。这样子特征联动的效果就打通了。 + +4)接着,我们将寻找的位置从这个空间开始出发,以最小点【0,0,0】,即检索特征被放置的坐标点开始,以Y轴往上,进行向x轴正8格,Y轴正16格,Z轴正8格的方向去寻找树叶。当然,在search_axis字段内,不仅允许从Y轴往上的方向去寻找,也可以从其他方向去找,这其中包括-x、+x、-y、+y、-z、+z。 + +5)最后,required_successes字段用来指定寻找到的合适放置苹果方块的坐标究竟要达到多少个,整个特征才会正确放置。如果最后找到的坐标不满足要求的数量,则整个特征就会放置失败。在许多情况下这个也属于情理之中,因为有些树可能紧靠在一些突起的地貌环境,树叶的数量不足以挂接这么多的苹果,亦或是总和特征中放置的树木大小本来就小,承载不了要求的最低数量的苹果方块。但这也让环境变得更加多样变化与不同了。 + + + +``` +{ + "format_version": "1.14.0", + "minecraft:search_feature": { + "description": { + "identifier": "design:custom_apple_search_feature"//自定义名称域 + }, + "places_feature": "design:custom_apple_feature",//放置的规则为前面自定义的单方块特征 + "search_volume": {//寻找的位置是从放置坐标开始,位移X轴正8格、Y轴16格、Z轴8格内的体积内 + "min": [ 0, 0, 0 ], + "max": [ 8, 16, 8 ] + }, + "search_axis": "+y",//寻找的位置从设定的点开始,以Y轴往上 + "required_successes": 5 + //至少要有5个可放置苹果的位置,该特征才会放置成功 + } +} +``` + + + +![](./images/4_8.jpg) + + + +#### 总和特征 + +1) 在行为包根目录下/netease_features文件夹内新建oak_tree_with_custom_apple_feature.json的特征,特征类型为总和特征。它的基本格式如下: + +``` +{ + "format_version": “1.14.0”, + "minecraft:aggregate_feature": { + "description": { + "identifier": "特征的identifier,以命名空间:标识符为格式,其中命名空间后面的标识符还必须与文件名称匹配" + }, + "features": [ + “特征1”, + “特征2” + ] + } +} +``` + + + +2)这里我们将写好的检索特征和原版的橡木树木特征放在里面。需要注意的是,在features内写好的特征,所处于的顺序不代表它们最终放置的顺序。因此有时候可能是先尝试放置了树木特征后再尝试放置苹果,有时候则相反。因此通过这个特征所放置的橡木不一定全部都挂接着苹果,因为先尝试放置苹果的检索特征,大概率会导致检索特征放置失败,因为可能树木特征还没有来得及放下去,所放置的坐标点此时可能光秃秃一片。但这也符合常理,毕竟不是所有野外的树木都挂载着果实。如果开发者希望做到必须挂接着果实的树木,可以期待后续的教程更新中,关于 队列特征的内容。 + +``` +{ + "format_version": "1.14.0", + "minecraft:aggregate_feature": { + "description": { + "identifier": "design:oak_tree_with_custom_apple_feature" + }, + "features": [ + "minecraft:oak_tree_feature", + "design:custom_apple_search_feature" + ] + } +} +``` + + + +#### 特征规则 + +![](./images/4_9.jpg) + + + +1)首先在行为包根目录下/netease_feature_rules文件夹内新建overworld_apple_oak_tree_feature.json的特征规则。 + +2)接着我们需要设定特征放置的群系条件,通过conditions对象,我们希望整个主世界的群系都能够长出带有苹果方块的橡木。但请不用担心这会让我们在例如海洋、冰原群系也看得到它,因为原版的橡木特征里会携带着橡木能够生长在什么方块上的规则,因此奇怪的群系是看不到苹果树的。每个主世界群系都会携带overworld的标签,因此只要判断是否携带overworld标签,就能够放置在整个主世界维度当中。 + +3)最后我们在分布规则distribution里,在iterations中写下数字1,即每一个区块尝试放置一次。在区块内放置时,我们希望特征遵守先确定好X和Z轴,再决定Y轴的规则,我们希望X和Z坐标取从区块起始坐标开始,随机偏移0~16格的范围内的其中一个点。在Y轴在X和Z轴点确定下,这个点位置最高的非实心方块Y轴坐标点。保证树木会放置在地面上,而不是可能在地面中或者地面下。 + + + +``` +{ + "format_version": "1.14.0", + "minecraft:feature_rules": { + "description": { + "identifier": "design:overworld_apple_oak_tree_feature", + "places_feature": "design:oak_tree_with_custom_apple_feature" + }, + "conditions": { + "placement_pass": "surface_pass", + "minecraft:biome_filter": [ + { + "test": "has_biome_tag", + "operator": "==", + "value": "overworld" + } + ] + }, + "distribution": { + "iterations": 1, + "x": { + "distribution": "uniform", + "extent": [ 0, 16 ] + }, + "y": "query.heightmap(variable.worldx, variable.worldz)", + "z": { + "distribution": "uniform", + "extent": [ 0, 16 ] + } + } + } +} +``` + + + +#### 效果图如下: + +![](./images/4_10.jpg) + diff --git a/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/课程05.简易教程②制作一个加速作物生长的火把方块.md b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/课程05.简易教程②制作一个加速作物生长的火把方块.md new file mode 100644 index 0000000..4ef421f --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第13章:更强大的方块/课程05.简易教程②制作一个加速作物生长的火把方块.md @@ -0,0 +1,169 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/5_5.d0bc082b.jpg +hard: 进阶 +time: 25分钟 +--- + +# 简易教程②:制作一个加速作物生长的火把方块 + + + +#### 作者:境界 + + + +加速火把示例包下载:下载[示例包](https://g79.gdl.netease.com/guidedemo-case13.zip) 。 + +#### 火把方块示意图: + + +![5_1](./images/5_1.jpg) + + + +#### 增加火把方块行为包 + +![](./images/5_2.jpg) + + + +``` +{ + "format_version": "1.16.0", + "minecraft:block": { + "description": { + "identifier": "design:custom_torch", + "register_to_creative_menu": true + }, + "components": { + "minecraft:block_light_absorption": 0, + "minecraft:block_light_emission": 1.0, + "minecraft:destroy_time": 1.0, + "netease:render_layer": { + "value": "alpha" + }, + "netease:aabb": { + "collision": { + "min": [0.4375, 0.0, 0.4375], + "max": [0.5625, 0.6875, 0.5625] + }, + "clip": { + "min": [0.4375, 0.0, 0.4375], + "max": [0.5625, 0.6875, 0.5625] + } + }, + "netease:solid": { + "value": false + }, + "netease:pathable": { + "value": true + }, + "netease:block_entity": { + "tick": true, + "movable": false + } + } + } +} +``` + + + +1)首先我们将方块的透光率设置为0,即将minecraft:block_light_absorption值写为0。这是由于火把方块用到自定义模型,游戏内的日夜光照会对模型进行阴影覆盖,我们希望方块受到阴影的程度的影响小一些,因此将值设置为0。开发者也可以根据自己的实际情况,在0~15间内进行微调,这是透光率组件允许接受的数值范围。 + +2)接着使用minecraft:block_light_emission组件,将发光程度调成1.0,使火把会在游戏内产生发光效果。开发者也可以根据自己的实际情况,在0.0~1.0间内进行微调,这是发光组件允许接受的数值范围。 + +2)接着用netease:render_layer将方块材质设置为透明材质,以允许方块模型使用带有透明图层的贴图。 + +3)之后在netease:aabb里,将碰撞体积和射线检测贴准火把方块真实的样子。组件中,每1点对应blockbench建模空间中16格,也就是每0.0625的长度对应blockbench内的一格空间。 + +4)接着将方块设置为非实心方块,这是由于我们希望避免玩家与苹果方块接触时,偶尔会因为碰撞盒重叠出现窒息扣血的情况。 + +5)紧接着,我们使用一个方块自定义的小技巧,即不设置方块的掉落物。那么在游戏内,方块默认会掉落方块物品本身。 + +6)最后,将方块设置为生物无视其继续循着自己的寻路路径走。 + + + +#### 增加火把方块材质包 + +1)使用MCSTUDIO导出火把方块工程文件,获得json模型文件 + +2)将模型文件的贴图资源指向原版的火把贴图"torch_on",将环境光遮罩关闭("use_ao": false) + +3)在材质包/blocks.json内配置火把方块的模型和破坏音效 + + + +#### 脚本实现自定义方块 + +![](./images/5_3.jpg) + + + +``` +{ + "format_version": "1.16.0", + "minecraft:block": { + "description": { + "identifier": "design:custom_torch", + "register_to_creative_menu": true + }, + "components": { + "netease:block_entity": { + "tick": true, + "movable": false + } + } + } +} +``` + + + +在自定义方块内加入"netease:block_entity"组件,其中设置tick为真,movable为假。加速火把方块会在每一秒发送20次事件到脚本层,即 每一次事件间隔为1 / 20 = 0.05秒。 + +![](./images/5_4.jpg) + + + +在示例图中,我们通过监听ServerBlockEntityTickEvent来判断是否可以将小麦变成下一个生长程度,判定条件为小麦是否是成熟状态,注意本加速火把示例中,只对小麦种子加速有效果,其他种子无效果。 + +``` + def torch_grow(self, event): + x = event['posX'] # 获取坐标X + y = event['posY'] # 获取坐标Y + z = event['posZ'] # 获取坐标Z + block_name = event['blockName'] + if block_name != 'design:custom_torch': # 是否为自定义加速方块 + return + dim_id = event['dimension'] # 获取维度ID + block_comp = serverApi.GetEngineCompFactory().CreateBlockInfo(serverApi.GetLevelId()) + block_state_comp = serverApi.GetEngineCompFactory().CreateBlockState(serverApi.GetLevelId()) + block_entity_comp = serverApi.GetEngineCompFactory().CreateBlockEntityData(serverApi.GetLevelId()) + block_entity_data = block_entity_comp.GetBlockEntityData(dim_id, (x, y, z)) # 获取方块数据 + tick = block_entity_data['tick'] # 获取方块刷新次数 + if not tick: + tick = 0 + tick += 1 # 加速火把方块会在每一帧自增一次刷新次数 + block_entity_data['tick'] = tick + if tick % 20 != 0: # 根据文档可知事件会在一秒刷新20次,当刷新次数与20求模为0时,即过了一秒钟 + return + # 运用列表推导式,获得[(x - 1, y, z - 1), (x, y, z - 1), (x + 1, y, z - 1), (x + 1, y, z + 1), (x, y, z), + # (x - 1, y, z + 1), (x, y, z + 1), (x - 1, y, z), (x + 1, y, z)]的坐标列表 + grow_poses = [(x + x_offset, y, z + z_offset) for x_offset in xrange(-1, 2) for z_offset in xrange(-1, 2)] + grow_poses.remove((x, y, z)) # 由于坐标(x, y, z)是加速火把,所以移除这个坐标 + for grow_pos in grow_poses: + block = block_comp.GetBlockNew(grow_pos, dim_id) + if block and block['name'] == 'minecraft:wheat': # 如果是小麦 + block_state = block_state_comp.GetBlockStates(grow_pos, dim_id) + if block_state['growth'] < 7: # 且小麦还未成熟,即成长值小于7 + block_state['growth'] += 1 # 增加一级成长值 + block_state_comp.SetBlockStates(grow_pos, block_state, dim_id) +``` + + + +#### 最终效果如下: + +![](./images/5_5.jpg) \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/README.md b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/2_1.jpg b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/2_1.jpg new file mode 100644 index 0000000..22963a3 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/2_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/3_1.jpg b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/3_1.jpg new file mode 100644 index 0000000..7bd321f Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/3_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/3_2.jpg b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/3_2.jpg new file mode 100644 index 0000000..7e5d2e2 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/3_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/3_3.jpg b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/3_3.jpg new file mode 100644 index 0000000..89521e3 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/3_3.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/3_4.jpg b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/3_4.jpg new file mode 100644 index 0000000..9c86c5d Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/3_4.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/3_5.jpg b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/3_5.jpg new file mode 100644 index 0000000..d497119 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/3_5.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/3_6.jpg b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/3_6.jpg new file mode 100644 index 0000000..9c51455 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/3_6.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/3_7.jpg b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/3_7.jpg new file mode 100644 index 0000000..046ffdb Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/3_7.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/3_8.jpg b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/3_8.jpg new file mode 100644 index 0000000..7a34104 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/3_8.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/3_9.jpg b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/3_9.jpg new file mode 100644 index 0000000..80d1c3a Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/3_9.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/4_1.jpg b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/4_1.jpg new file mode 100644 index 0000000..ef3a7b6 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/4_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/4_2.jpg b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/4_2.jpg new file mode 100644 index 0000000..77be23d Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/4_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/5_1.jpg b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/5_1.jpg new file mode 100644 index 0000000..4d545fb Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/5_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/5_2.jpg b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/5_2.jpg new file mode 100644 index 0000000..87780ab Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/5_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/5_3.jpg b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/5_3.jpg new file mode 100644 index 0000000..6177a61 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/images/5_3.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/课程01.创造一个全新维度.md b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/课程01.创造一个全新维度.md new file mode 100644 index 0000000..350650c --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/课程01.创造一个全新维度.md @@ -0,0 +1,16 @@ +--- +front: +hard: 入门 +time: 5分钟 +--- + +# 创造一个全新维度 + + + +#### 作者:境界 + + + +维度是我的世界中玩家生存的领域,我的世界一共有主世界、下界、末地三个维度,它们带有各自不同的地貌、生物分布、特征规则。在当前中国版中,我们可以使用附加包的方式添加新的维度,改变新维度的群系地貌、特征分布来定制一个新的玩法体验。需要注意的是,自定义群系当前与自定义维度捆绑,若开发者想要添加或者修改主世界的群系,需要打开实验玩法才能使用,而实验玩法将带来不稳定的因素。因此强烈建议将自定义群系交给新的维度去使用。 + diff --git a/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/课程02.自定义维度.md b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/课程02.自定义维度.md new file mode 100644 index 0000000..4dfe1dd --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/课程02.自定义维度.md @@ -0,0 +1,29 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/2_1.c8367cf7.jpg +hard: 进阶 +time: 10分钟 +--- + +# 自定义维度 + + + +#### 作者:境界 + + + +自定义维度在刚推出时,开花组只开放了ID为3~20的维度世界,这17个世界不需要开发者新建文件来生成,只需配置好相应的传送逻辑即可前往。面对庞大的开发者数量,这种规模远远无法满足所有的组件。因此在之后的更新中,我们需要使用附加包的方式添加新的维度ID让游戏能够开通去往那个维度的入口,这里使用mcstudio的关卡编辑器可以快速帮助初级开发者建立新的文件,扫除一开始理解上的困难,让我们来看一下。 + +![](./images/2_1.jpg) + + + +在关卡编辑器中,右键组件区域,指针指向维度,创建自定义维度组件、自定义传送门组件、方块组件。 + +①点击维度的子选项,可以看到右下角属性面板上可以自定义维度ID,维度ID的取值范围是0~int的最大值【一般为2147483647】。每次新建立一个维度组件时,都会帮我们默认填好一次随机的维度ID,可以不再去修改。 + +②点击方块组件的“+”号,至少创建两个自定义方块,将方块类型都设置为传送门。将其中一个自定义方块设置为去向传送门方块,将维度ID设置为新维度的ID。另一个自定义方块设置为返向传送门方块,将维度ID设置为主世界的ID,即0。 + +③在自定义传送门组件中,将传送门类型改为双向传送门,并将传送门方块设置为去向传送门方块,反向传送门方块设置为返回时的传送门方块。需要注意的是,当前传送门的边框方块还不支持自定义的普通方块,只能使用原版方块。 + +④右上角点击保存,运行进行测试。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/课程03.自定义群系.md b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/课程03.自定义群系.md new file mode 100644 index 0000000..3240150 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/课程03.自定义群系.md @@ -0,0 +1,95 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/3_1.3b916957.jpg +hard: 进阶 +time: 25分钟 +--- + +# 自定义群系 + + + +#### 作者:境界 + + + +附加包允许自定义群系群落,在中国版可以使用特定的目录结构来自定义某个维度的群系。自定义群系时必须使用继承关系,即每个群系都有一个对应的原版群系。虽然无法添加新的群系,但通过改变表面高度、气候调整、重新分配生物群落的稀有性,这些维度依然能变得与众不同。例如,使用新的树类型或方块建筑结构来激进地改变一个群系,添加新的自定义矿物方块到洞穴中等。群系要掌握的知识点如下: + +①群系继承 + +②群系高度 + +③群系气候 + +④群系表面结构 + +⑤屏蔽原版群系特征 + +⑥自定义生物生成 + + + +#### 群系继承 + +![](./images/3_1.jpg) + +![](./images/3_2.jpg) + + + +自定义生物群系必须继承一个原版群系。在继承的时候,若没有复写原来群系的组件设定,则会默认使用原版群系的设定。原版群系的文件内容可以在modpc客户端根目录/data/definitions/biomes找到。 + +在MCSTUDIO中,选择继承群系的选项会继承一个原版群系。 + +在文本级编辑中,需要在identifier键对同级下新增一个inherts键对,值内容为一个原版群系名称。 + + + +#### 群系高度 + +![](./images/3_3.jpg) + + + +群系的高度设定只能针对overworld进行自定义,换句话说,即高度设定必须给定到主世界存在的群系才会表现正确。像下界群系的高度设定由专门的下界群系生成器控制,由于中国版目前无法对下界群系进行自定义,这里不再赘述。而末地由于是一个虚空环境,该组件行为也不对末地产生效果。 + +![](./images/3_4.jpg) + + + +群系高度可以设置为预设高度和噪声参数的高度,在通常情况下,使用预设高度可以降低学习难度和快速对自定义群系高度进行成型。而在使用噪声参数需要注意以下几个点: + +噪声参数noise_params为一个二维数组,第一个值表示生物群落的平均高度。公式为:f(值)=(高度 -67)/ 16。因此,将此值设置为1将会产生高度大约为83的平均高度,类似丘陵群系中的山丘高度,将此值设置为-2会形成深海表面,并且处于海平面以下。群系的最大平均高度最多只能达到高度128以下。因此噪声参数数组的第一个数值在3.8125以上作用效果将不会表现出来。数组的第二个值确定高度变化,输入负值通常表现得非常不稳定,建议不要这样设置。值0将使地形变化较小,但不能使其完全平坦。值等于0.125将产生特别平滑的地形,大于此值开始形成悬崖、海湾和凹陷。像针叶林群系这样的极端群系高度始于0.25。设置较为激进的数值会让玩家在群系生存和移动产生困扰。 + +在目前关卡编辑器中,高度类型和噪声参数可以混用在一起,但实际上在群系生成的管道中,两者只能存在一个。要么是使用高度类型要么使用噪声参数类型。 + + + +#### 群系气候 + +群系气候组件会调节任何一个我的世界群系,包括在末地也存在气候。气候主要包括三个部分,downfall、temperature、snow_accumulation。Downfall取值区间在0.0~1.0间,若超过1.0会取1.0,它主要作用在降水与降雪中,可以理解为降水量或者降雪量。调整downfall并不会直接明显地表现在降水粒子和降雪粒子中,如果要看出它的效果,比较具有参考性地可以观察降雪时雪方块堆积的程度,坩埚收集水的速度,以及钓鱼时鱼出现的时间。Temperature是基础温度,给定基础温度不代表群系每个地方都是这个温度,温度还会随着群系高度变高而降低。温度公式为:T(实际温度) = 基础温度 - ((高度 - 63) / 600)。63为世界中海平面的高度,在原版设定中,温度为0.15时是冻结的气候,即会形成雪。因此假设我们给予一个0.2温度的气候,在0.15的温度时的高度群系会积雪,则高度为93格高。snow_accumulation为雪的堆积。 + +在通常情况下,开发者继承原来的群系都带有原本的温度,若需要自定义这些参数,需要进行文件级别的修改,而目前关卡编辑器尚不支持。 + + + +#### 群系地表 + +群系地表包含了群系在生成时的地表方块结构,可以自定义群系的浅表方块、中层方块、深层方块、水底方块、水底高度。 + + + +#### 屏蔽原版特征 + +由于自定义群系继承于原版群系,因此像森林的桦树、平原的橡树、热带草原的金合欢树,都会一起出现在新的群系里。为了屏蔽掉这些原版的特征,需要使用一个特定的组件行为屏蔽掉。 + +打开指定自定义群系的文本内容,在components下加入"minecraft:ignore_automatic_features"即可。 + +但有部分特征无法使用该组件行为去除,如村庄、沙漠神殿、林中府邸等。 + + + +#### 自定义生物生成 + +生物生成在前面的章节有提到,生成在群系的条件是根据群系标签来决定的。因此在自定义群系中,会自带原来群系的标签,这会导致一些原版生物也出现在新的自定义群系中,若要屏蔽这些生物,可以使用MODSDK处理。 + +在新的群系添加新的标签可以让自定义的生物生成在这里,要做到这样的效果,只需在关卡编辑器的群系标签列表里新增标签名字即可。或者在群系文件中的components下加入"标签名": {}。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/课程04.自定义特征.md b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/课程04.自定义特征.md new file mode 100644 index 0000000..d66a6d4 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/课程04.自定义特征.md @@ -0,0 +1,119 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/3_5.08de570e.jpg +hard: 进阶 +time: 35分钟 +--- + +# 自定义特征 + + + +#### 作者:境界 + + + +群系中会存在一些自然的植被、矿物、建筑,它们都用到了特征功能。在关卡编辑器中,当前只支持使用中国版的建筑模板特征。为了让开发者了解更多的特征放置组合,有以下几个特征适合了解并掌握: + +①矿物特征 + +②散布特征 + +③总和特征 + +④单方块特征 + +⑤建筑模板特征 + +想要了解更多的原版特征和写法,可以打开modpc根目录/definitions/features中查看。 + + + +#### 矿物特征 + +矿物特征用来替换地表下的方块为矿物方块。它是相对容易掌握的一种特征,写法格式并不复杂。 + +![](./images/3_5.jpg) + + + +①Identifier是特征的唯一名称域,其中以namespace:identifier的格式为基准。并且identifier必须与文件名字一样,否则在modpc客户端下会测试出断言错误并强制关闭客户端。 + +②count是放置的次数。 + +③places_block接收一个方块内容,若方块不需要指明方块数据,如羊毛的颜色等,则可以直接以"places_blocks": "方块名字"代替。否则需要查询方块数据后根据上图示例的格式来写。 + +![](./images/3_6.jpg) + + + +④may_replace下接收一组方块内容,即会被矿物方块取代的方块类型。 + + + +#### 单方块特征 + +单方块特征用来放置一种方块,并可以决定它放置时替换的方块是什么。 + +![](./images/3_7.jpg) + + + +①identifier是特征的唯一名称域,其中以namespace:identifier的格式为基准。并且identifier必须与文件名字一样,否则在modpc客户端下会测试出断言错误并强制关闭客户端。 + +②places_block接收一个方块内容,若方块不需要指明方块数据,如羊毛的颜色等,则可以直接以"places_blocks": "方块名字"代替。否则需要查询方块数据后根据上图示例的格式来写。 + +③enforce_survivablility_rules会强制特征放置时遵守方块能存活下来的方块,如草方块无法存活在沙子上,因此当这个键对设置为真时,则草方块特征不会放在沙子上。 + +④enforce_placement_rules会强制特征方式时遵守方块放置的规则。 + +⑤may_replace下接收一组方块内容,即会被该方块取代的方块类型。 + + + +#### 总和特征 + +总和特征用来放置一组特征,即包括在features下内的特征都会按照随机顺序放置。 + +![](./images/3_8.jpg) + + + +① identifier是特征的唯一名称域,其中以namespace:identifier的格式为基准。并且identifier必须与文件名字一样,否则在modpc客户端下会测试出断言错误并强制关闭客户端。 + +②early_out是提前退出的意思,有first_success和first_failure两种类型,即若放置的第一个特征成功或者失败时,则特征放置结束。 + +③features下接收一组特征,它们会共用一个放置特征的位置。即若该总和特征放置在世界坐标(63,63,63)上时,集合内的特征放置位置都会从这里开始。 + +小贴士:在上图中,集合内放置了两个特征,分别是原版蕨方块的上部分和下部分,由于我的世界中超过两格的方块都是由两种方块组成的。因此在示例的写法中,由于原版的蕨方块上部要有下部分方块存在才能放置。因此若一开始放置了蕨方块上部特征,则触发第一次放置就失败的规矩而提前退出。 + + + +#### 散布特征 + +散布特征用来再次将另一个特征进行偏移及调整放置次数。 + +![](./images/3_9.jpg) + + + +①identifier是特征的唯一名称域,其中以namespace:identifier的格式为基准。并且identifier必须与文件名字一样,否则在modpc客户端下会测试出断言错误并强制关闭客户端。 + +① iterations为放置次数,因此,若特征规则里使用了某个定义了额外放置次数的散布特征,最后的放置次数为:特征规则的放置次数 x 散布特征的放置次数。 + +③coordinate_eval_order是寻找放置坐标的依次顺序,坐标系有x、y、z三种轴,因此存在xzy、xyz、yxz、yzx、zxy、zyx等组合。 + +④x为特征规则放置时,所偏移的x系数。若不使用随机分布distribution,则可以使用"x": 整数值来给定偏移的格数。 + +⑤y为特征规则放置时,所偏移的y系数。若不使用随机分布distribution,则可以使用"y": 整数值来给定偏移的格数。 + +⑥z为特征规则放置时,所偏移的z系数。若不使用随机分布distribution,则可以使用"z": 整数值来给定偏移的格数。 + +⑦places_feature为放置的特征 + +小贴士:在上图中,原版将高度两格的蕨特征重新用散布特征包装起来再让特征规则放置,这样是因为散布特征可以对特征进行再放置,若开发者希望放一朵花、或一堆聚在一起的方块,可以先用散布特征将方块以比如0到8的xz区间聚拢在一起,被特征规则放置后就可以看到它们被集中放置在一片区域里了。 + + + +#### 建筑模板特征 + +建筑模板特征用来放置一块由结构方块或地图编辑器导出的方块结构,适合用来放一些具有成型价值的建筑模板。建筑模板特征可以直接使用地图编辑器将地图内的建筑大观进行切割、打包导出。最后使用维度大类下的自定义特征组件,将结构文件从资源管理器拖入属性面板即可。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/课程05.简易教程① 放置在高于水面的特征.md b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/课程05.简易教程① 放置在高于水面的特征.md new file mode 100644 index 0000000..88dff98 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/课程05.简易教程① 放置在高于水面的特征.md @@ -0,0 +1,27 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/4_1.f29b0af7.jpg +hard: 进阶 +time: 15分钟 +--- + +# 简易教程① 放置在高于水面的特征 + + + +#### 作者:境界 + + + +①原版世界的海平面在Y轴63格及以下,因此在放置某些自定义方块植物进入世界时,我们不希望它出现水面上。这里就用单方块组合和特征规则来展示如何放置一个高于水面的特征。 + +![](./images/4_1.jpg) + + + +② 创建一个单方块特征,并将放置的方块类型设置为南瓜。 + +![](./images/4_2.jpg) + + + +③新建一个特征规则,允许特征放置在全群系内,高度必须在64格以上而不包括64格,否则将特征放置在0格以下,即虚空位置。这里运用到了一种三元运算符,可以简单理解为条件 ? 满足的话执行这里 : 不满足的话执行这里。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/课程06.简易教程② 放置树建筑的模板特征.md b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/课程06.简易教程② 放置树建筑的模板特征.md new file mode 100644 index 0000000..3b5e83c --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第14章:创造一个全新维度/课程06.简易教程② 放置树建筑的模板特征.md @@ -0,0 +1,31 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/5_1.c8a98ae5.jpg +hard: 进阶 +time: 15分钟 +--- + +# 简易教程② 放置树建筑的模板特征 + + + +#### 作者:境界 + + + +![](./images/5_1.jpg) + + + +① 进入地图编辑器,随机挑选一颗树进行选取,点击保存结构进行保存,记得要勾选去除空气方块。 + +![](./images/5_2.jpg) + + + +②新建自定义特征组件,将结构下拉栏打开,选取刚刚保存的结构 + +![](./images/5_3.jpg) + + + +③新建自定义特征分布组件,将特征指向前面新建的特征,其余属性默认不变。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/README.md b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/1_1.jpg b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/1_1.jpg new file mode 100644 index 0000000..e9bcfb8 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/1_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/1_2.jpg b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/1_2.jpg new file mode 100644 index 0000000..3a91f04 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/1_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/1_3.jpg b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/1_3.jpg new file mode 100644 index 0000000..d64fe57 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/1_3.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/1_4.jpg b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/1_4.jpg new file mode 100644 index 0000000..b8f1ebd Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/1_4.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/2_1.jpg b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/2_1.jpg new file mode 100644 index 0000000..674d7d1 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/2_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/2_2.jpg b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/2_2.jpg new file mode 100644 index 0000000..c9cafbd Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/2_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/2_3.jpg b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/2_3.jpg new file mode 100644 index 0000000..128f357 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/2_3.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/3_1.jpg b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/3_1.jpg new file mode 100644 index 0000000..e4cc524 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/3_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/4_1.jpg b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/4_1.jpg new file mode 100644 index 0000000..227b15f Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/4_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/4_2.jpg b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/4_2.jpg new file mode 100644 index 0000000..d9e9b21 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/4_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/4_3.jpg b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/4_3.jpg new file mode 100644 index 0000000..e67a8c2 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/4_3.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/4_4.jpg b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/4_4.jpg new file mode 100644 index 0000000..2a4ff85 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/4_4.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/5_1.jpg b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/5_1.jpg new file mode 100644 index 0000000..4418358 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/5_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/5_2.jpg b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/5_2.jpg new file mode 100644 index 0000000..c022f03 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/images/5_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/课程01.自定义远程武器.md b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/课程01.自定义远程武器.md new file mode 100644 index 0000000..fccc070 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/课程01.自定义远程武器.md @@ -0,0 +1,69 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/1_1.48802b15.jpg +hard: 进阶 +time: 30分钟 +--- + +# 自定义远程武器 + + + +#### 作者:境界 + + + +目前,我的世界中国版支持以弓为主的自定义远程武器。使用附加包可以添加新的弓道具以及弓的手持位置与拉伸动画。使用弓的功能逻辑则会依赖MODSDK,我们将这两个部分各自拆解一下。 + + + +#### 材质包配置 + +![](./images/1_1.jpg) + + + +①自定义弓的命名必须在符号“:”后以bow结尾,这样子才会在接下来为物品添加使用弓动画组件行为时,动画会正常播放。 + +②minecraft:icon会指向一个贴图资源短名路径,它会由开发者自己在textures文件夹下的item_texture.json定义。 + +③minecraft:use_animation会为物品在玩家使用时做出某个动画,这里我们添加bow这个值,以便玩家拉伸弓的时候默认下会慢慢移动,并且视角缩小。 + +④netease:frame_animation内有三个键对,frame_count指的是弓箭拉伸时一共有多少张序列图会播放,在原版的弓中,玩家拉伸弓到最底一共会播放三张贴图。 + + + +#### 行为包配置 + +![](./images/1_2.jpg) + + + +①必须将custom_item_type设置为ranged_weapon + +②minecraft:max_damage用来设置弓的最大耐久值。 + +③minecraft:use_duration用来设置弓的可拉伸蓄力的使用时间,我们需要将时间设置的足够长,以免出现在一段时间内出现重复蓄力的问题。 + +④minecraft:max_stack_size用来设置弓的堆叠数量,弓属于武器装备范畴,因此建议一个栏中只能堆叠一个。 + +⑤minecraft:hand_equipped用来将自定义弓在第三人称的渲染形象和原版弓一致。 + +⑥netease:render_offsets用来设置自定义弓在第一人称的手持位置,其中controller_position_adjust用来控制位置,controller_rotation_adjust用来控制角度,controller_scale用来控制大小缩放。 + + + +#### 发射投掷物功能 + +![](./images/1_3.jpg) + + + +①监听ItemReleaseUsingServerEvent事件 + +②根据蓄力的时间,由开发者决定根据时间的长短来划分箭的伤害,以达到模拟出原版弓箭发射时的动态伤害效果。 + +![](./images/1_4.jpg) + + + +③判断玩家的游戏模式,若为创造模式,不消耗弓的耐久和箭的数量的前提下,即可发射箭。若非创造模式,则会搜索背包内是否有箭道具,有则消耗并扣除一点弓耐久,否则直接返回,不执行后面发射箭的逻辑。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/课程02.自定义武器词条.md b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/课程02.自定义武器词条.md new file mode 100644 index 0000000..ce51a23 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/课程02.自定义武器词条.md @@ -0,0 +1,65 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/2_2.044dfe8c.jpg +hard: 进阶 +time: 20分钟 +--- + +# 自定义武器词条 + + + +#### 作者:境界 + + + +物品包含词缀,当玩家点选物品时,词缀会包含物品的一些信息,如武器的附魔、伤害等。由于当前接口限制,无论是附加包自带的接口还是中国版MODSDK自带的接口,都无法一开始就自定义词缀注册在道具上,如从创造背包选取下来的道具等。以下优先教给大家如何自定义武器词缀。 + + + +#### 利用战利品功能添加词缀 + +![](./images/2_1.jpg) + + + +使用战利品表的set_lore功能,可以将词缀写入武器道具内。但是新增的词缀不会覆盖原来的词缀。 + +战利品表可以随组件行为、村民交易等地方一起使用。 + +#### 效果图: + +![](./images/2_2.jpg) + + + +#### 使用MODSDK添加词缀 + +物品信息字典带有一对customTips的数据,里面会包含着使用接口所改变的物品词缀信息。 + +使用生成物品或者是ChangePlayerItemTipsAndExtraId接口可以改变物品词缀信息。其中词缀改变时,是直接覆盖掉原来的词缀。这一点与战利品表的词缀添加功能有着明显不同。 + +同时,customTips支持自定义格式,包含四种自带格式: + +【%name%:物品名】 + +【%category%:物品类型】 + +【%enchanting%:附魔属性】 + +【%attack_damage%:攻击伤害】 + +自带格式可以与自定义文本自由组合,顺序可以打乱,物品的自定义格式的文本不存在时不予显示。 + +自带格式的字符串采用原版的显示格式,物品名前面不带换行符,物品类型、附魔属性前面自带一个换行符,攻击伤害前面自带两个换行符。 + +举个例子: + +``` +head%name%after%category%%enchanting%/nnewline%attack_damage%/n/nend +``` + + + +#### 效果如下: + +![](./images/2_3.jpg) \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/课程03.自定义3D防具模型.md b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/课程03.自定义3D防具模型.md new file mode 100644 index 0000000..dc73fdc --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/课程03.自定义3D防具模型.md @@ -0,0 +1,33 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/3_1.f375f404.jpg +hard: 进阶 +time: 15分钟 +--- + +# 自定义3D防具模型 + + + +#### 作者:境界 + + + +目前中国版与国际版一样,支持自定义防具模型。同时防具在被附魔后,模型也会呈现附魔的流光效果。这里会教大家解读防具资源包内容的数据格式,在本大章的最后会提供一个防具模型给开发者,尝试自己导入进去。 + + + +#### 数据格式解析 + +![](./images/3_1.jpg) + + + +①自定义防具定义文件,需要放在资源包下的attachables文件夹内。 + +②format_version有1.8.0和1.10.0两种格式,理论上支持粒子和动画。但由于硬编码缘故,无法对模型进行绑定定位器,且防具模型只支持方块自旋转,不支持骨骼旋转,所以若使用粒子和动画在防具资源上可能会得出错误的效果。 + +③identifier即相对应的物品ID,使用自定义防具时,需要将identifier和对应的物品设置为同一个ID。 + +③scripts中的parent_setup,是用于配合官方商店中,由官方推出的3D皮肤包的显示与隐藏。若玩家装备了3D头饰皮肤,则定义了parent_setup的头部防具将不予以显示。 + +④其他四项里,除了geometry、textures的default键指向自定义的防具贴图和模型资源以外,其他可以保持不做更改。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/课程04.简易教程① 增加一顶帽子装备.md b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/课程04.简易教程① 增加一顶帽子装备.md new file mode 100644 index 0000000..b608a16 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/课程04.简易教程① 增加一顶帽子装备.md @@ -0,0 +1,38 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/4_4.554e8eb8.jpg +hard: 进阶 +time: 15分钟 +--- + +# 简易教程① 增加一顶帽子装备 + + + +#### 作者:境界 + + + +渔夫帽示例包下载:下载[示例包](https://g79.gdl.netease.com/guidedemo-case15.zip)。 + +#### 注册物品 + +![](./images/4_1.jpg) + + + +#### 添加模型 + +![](./images/4_2.jpg) + + + +#### 定义行为 + +![](./images/4_3.jpg) + + + +#### 效果图如下: + +![](./images/4_4.jpg) + diff --git a/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/课程05.简易教程② 增加武器吸血属性.md b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/课程05.简易教程② 增加武器吸血属性.md new file mode 100644 index 0000000..b819611 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第15章:制作更好的装备/课程05.简易教程② 增加武器吸血属性.md @@ -0,0 +1,31 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/5_2.4fbaff36.jpg +hard: 进阶 +time: 20分钟 +--- + +# 简易教程② 增加武器吸血属性 + + + +#### 作者:境界 + + + +吸血剑示例包下载:下载[示例包](https://g79.gdl.netease.com/guidedemo-case15.zip)。 + +#### 代码逻辑 + +![](./images/5_1.jpg) + + + +①监听ServerChatEvent和PlayerAttackEntityEvent,通过输入聊天信息“吸血剑”,获得带有吸血词缀的钻石剑。 + +②在玩家攻击实体的事件回调函数内,获取手持物品并匹配词缀信息,然后通过attr组件来获得玩家生命值,并设置新的生命值。 + + + +#### 效果图如下: + +![](./images/5_2.jpg) \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/README.md b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/2_1.jpg b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/2_1.jpg new file mode 100644 index 0000000..9cee83b Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/2_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/2_2.jpg b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/2_2.jpg new file mode 100644 index 0000000..5ce1af4 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/2_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/2_3.jpg b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/2_3.jpg new file mode 100644 index 0000000..ae2d268 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/2_3.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/2_4.jpg b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/2_4.jpg new file mode 100644 index 0000000..41c456c Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/2_4.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/3_1.jpg b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/3_1.jpg new file mode 100644 index 0000000..b8b10fe Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/3_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/3_2.jpg b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/3_2.jpg new file mode 100644 index 0000000..8c7157b Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/3_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/4_1.jpg b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/4_1.jpg new file mode 100644 index 0000000..3b6a51b Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/4_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/5_1.jpg b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/5_1.jpg new file mode 100644 index 0000000..9e57fda Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/5_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/5_2.jpg b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/5_2.jpg new file mode 100644 index 0000000..d26ae20 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/5_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/5_3.jpg b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/5_3.jpg new file mode 100644 index 0000000..40d53a4 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/5_3.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/6_1.jpg b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/6_1.jpg new file mode 100644 index 0000000..8f2375c Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/6_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/6_2.jpg b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/6_2.jpg new file mode 100644 index 0000000..c393619 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/6_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/6_3.jpg b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/6_3.jpg new file mode 100644 index 0000000..54133e1 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/images/6_3.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/课程01.认识界面控件.md b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/课程01.认识界面控件.md new file mode 100644 index 0000000..f64a067 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/课程01.认识界面控件.md @@ -0,0 +1,109 @@ +--- +front: +hard: 进阶 +time: 20分钟 +--- + +# 认识界面控件 + + + +#### 作者:境界 + + + +UI界面是由一个个界面控件组合而成,常见的控件有按钮、文字板、网格、输入框等。目前MCSTUDIO支持使用界面编辑器来制作UI,同时可以根据不同分辨率来模拟UI在手机、PC电脑、平板上的效果,最大程度去适配不同终端的屏幕。因此本章会通过界面编辑器来带开发者了解界面控件,对于常用的控件能够获得基本的认识。 + + + +#### 画布 + +画布可以视为界面的入口,一张画布包含着多个属性,同时将界面放入画布内容中,还可以直接适配异形屏,最大程度地使界面在多种屏幕上都能正常使用。 + +#### 画布属性有以下几种: + +①始终支持按键,即这个界面生成时,若玩家按到如移动按键等,人物也会响应按键效果产生移动。 + +②创建时运行游戏,即这个界面生成时,界面背后的游戏世界是否照常在屏幕上渲染。 + +③点击穿透,即这个界面生成时,点击后是否会同样在下方的旧界面上响应到这个输入。例如,我们在HUD界面(带有移动键、快捷栏的界面)上生成了另一个新界面,手指点击屏幕时会先点到新界面,而这个属性若勾选起来,则下方的HUD界面也会响应,即可能隔着一个界面还是能点到移动键等。 + +④代替暂停界面,即这个界面生成后,若在允许弹出暂停界面的情况下,将此属性勾选,则无法弹出暂停界面。 + +⑤限制光标移动范围,即这个界面生成后,PC电脑上光标只会在界面范围内出现。勾选后非常适合用来将界面与电脑基岩版适配。 + +⑥强制持续渲染,即这个界面生成后,在电脑资源占用过高时依旧会渲染,不勾选的话,在电脑运算压力过大时,可能不会渲染。 + +⑦继承基类画布,即界面使用了被绝大多数原版界面使用的画布属性。 + + + +#### 画板 + +面板可以视为界面里一个被分割的区块,是一个区域部分。想象一下,在一个画布中,我们希望在左上角横铺四个按钮,让它们紧贴在一起。比较寻常的方法是使用界面编辑器在画布控件下新建四个按钮,并将它们依次移动到左上角。有时我们希望在控件结构里隐藏按钮显示其他区域,或者在代码里隐藏这几个按钮,可能一下子就要多写很多额外的代码。而通过把控件归纳到面板下,移动面板的位置,面板所包含的控件会一并移动,隐藏面板时,也会一并消失。它更像是电脑文件管理系统的文件夹。学会使用面板,可以将一个界面的功能分开成好几个独立的区域,也可能会让控件结构和在后续的代码编写中起到更加简洁、方便的效果噢! + + + +#### 按钮 + +按钮可以被玩家点击,在界面编辑器中,通过设置按钮大小和不同按入状态的贴图,可以做出许多丰富的样式。同时编辑器还为开发者在按钮内包装了一个文本,若开发者选择不将文本画在按钮贴图内时,可以选择使用自定义按钮文本的功能。默认情况下按钮贴图会指向无,即使用原版的常用按钮贴图。若使用原生贴图,则会打开原版textures文件夹,点击ui文件夹后可以看到所有的原版贴图,方便开发者选择自己心仪的原版资源。而若选择自定义的话,则会下方资源管理器中,出现由开发者导入的新贴图。 + +#### 需要注意以下几点: + +①按钮使用的贴图一共要用到三种,分别是普通、悬浮、按下,普通即是看到按钮默认下的状态,悬浮是指针放在按钮上的状态,按下则是点击按钮的状态。 + +②九宫格指九宫格拉伸原理,它是处理贴图纹理在游戏界面减少资源占用中应用到的一种高级方法。更多详细内容可以[查看官方教程](http://mc.163.com/mcstudio/mc-dev/MCDocs/2-ModSDK%E6%A8%A1%E7%BB%84%E5%BC%80%E5%8F%91/04-Mod%E5%BC%80%E5%8F%91%E8%A7%84%E8%8C%83/2-%E8%B4%B4%E5%9B%BE%E8%A7%84%E8%8C%83%E5%8F%8A%E4%B9%9D%E5%AE%AB%E6%A0%BC%E4%BD%BF%E7%94%A8.html)。 + + + +#### 图片 + +图片控件会显示一张图片。需要将图片类型选择原生或者自定义的方式,来选择需要使用的资源。下方的UV起点和UV尺寸,前者会决定图片从哪个端点开始,尺寸决定从UV起点开始,向下延长宽度,向右延长长度。在上一章自定义粒子的时候,我们使用原版粒子贴图可以看到基岩版将所有常用粒子放在一张128x128的尺寸贴图上,使用uv起点和尺寸就是决定显示它的某一个区域。 + + + +#### 文本 + +文本控件顾名思义,就是用来显示文本。文本支持文字投影,文字颜色、字号、行间距、字对齐等功能。所有原版界面显示的文本都依赖它,包括输入框这样的控件,本质上也是将玩家输入的内容以文本的形式呈现出来,是非常重要的控件。 + + + +#### 文本输入框 + +文本输入框允许玩家输入文本进入框内,同时通过代码监听绑定输入框交互和绑定输入框内容,可以知道在脚本内获得文本输入框的内容。需要注意的是,一个新的界面若用到了文本输入框,则会阻止其他输入,如主界面的移动、跳跃按钮等。因此使用到文本输入框的界面,我们应将它处理为一个静止游戏界面。就像原版的聊天界面、背包界面一样。 + + + +#### 开关 + +开关控件常用来决定一个功能或者状态是开启还是关闭。它只带有真假两种状态,在原版的设置界面中大量用到了开关控件。因此在决定一个新界面是否需要用到这样的控件,就要清楚自己是否需要设置一种开启或者关闭的功能。通过代码监听绑定开关交互和按钮状态,可以知道某个开关的状态。 + + + +#### 纸娃娃 + +纸娃娃控件用来渲染一个实体模型到屏幕上。在编辑器中当前只支持显示玩家模型和FBX 三维模型,使用MODSDK可以更加方便地设置模型的缩放大小、实体类型、沿Y轴的旋转角度等。非常适合用来做展示实体生物的界面。 + + + +#### 物品渲染 + +物品渲染控件用来渲染一个物品在界面上。在原版界面里,它常被应用在需要显示物品的地方。如背包界面、附魔台界面、铁砧界面等。这些界面中允许玩家放入物品的物品栏,本质上就是一个按钮下挂载了一个物品渲染控件。当按钮被点选时,按钮呈现按住状态,若里面存在物品时,则在它挂载下的物品渲染控件显示一个物品道具。 + + + +#### 进度条 + +进度条控件常用来显示某种流程的进度。比如某种机器的工作进度、某个状态的变动程度等等。进度条的贴图一般使用一张完整的长贴图,使用modsdk可以对进度条控件设置切割图片的程度,以1.0为一个完整的长度,若通过接口设置切割程度为0.1,则只会显示10%的进度条。 + + + +#### 滚动列表 + +滚动列表常用来滑动一块内容区域,这个内容可以是文本输入框、图片、网格等。想象一下,原版的创造背包栏,物品从头到尾排序下来,显示的按钮数量之多远远超过了屏幕可以容纳的范围。使用滚动列表可以只显示背包栏中间的一块区域,其他部分通过滚动内容区域的形式慢慢向上呈现。 + + + +#### 网格 + +网格控件可以帮助开发者将其他画布的控件内容绑定在网格控件上。例如,原版的大箱子界面内置了一个网格控件,网格的格数大小是从左往右数9格,从上往下数6行,并将网格控件里的内容指向物品栏按钮。这方便开发者统一管理一序列重复的控件,而不需要专门为了实现类似的功能,真的去手动新建多个控件并计算它们之间的间隔排列组合。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/课程02.使用UI编辑器绘制基础界面.md b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/课程02.使用UI编辑器绘制基础界面.md new file mode 100644 index 0000000..f4a6ef0 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/课程02.使用UI编辑器绘制基础界面.md @@ -0,0 +1,51 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/2_1.00b439ab.jpg +hard: 进阶 +time: 20分钟 +--- + +# 使用UI编辑器绘制基础界面 + + + +#### 作者:境界 + + + +①进入工程后,左上角的编辑器选项选择UI编辑器。 + +![](./images/2_1.jpg) + + + +②UI内容最后会被放入一个JSON文件,它位于资源包的ui文件夹内。因此我们在左下角的界面文件列表右侧,点击“+”号新建一个界面文件,命名为custom_ui + +![](./images/2_2.jpg) + + + +③界面文件新建后,会自动在左上角的控件结构中,新增一个名为main的主画布,这个名字可以任意更改,在后续使用MODSDK声明界面的画布入口时,填对即可。为了统一辨识度,我们将在下面的内容中,对每个控件进行严格的命名,比以节点代称,方便开发者能够更好的阅读。 + +④在main节点下,新建一个面板控件,并将它的名称改为entry_panel,即入口面板。 + +⑤将entry_panel节点设置为125px长,125px宽,点击属性中的锚点,将锚点设置为左上角。它会自动对齐左上角,而无需通过拖曳的方式来对齐。 + +⑥在入口面板节点下挂载一个图片控件,我们将其命名为color_image。向下拉,将使用贴图设置为原生,轻击原生按钮入口,会跳出一个电脑的资源管理器,它会自动定位到原版资源文件夹下的textures文件夹,我们选择ui文件夹,并找到white_background.png贴图,选择它。 + +⑦在color_image节点属性下拉到最低,找到图片适配选项,将九宫格类型选择原版类型,在上、下、左、右四个栏的任意一栏将0像素点写为1像素点。这样贴图资源将会与节点大小匹配,平铺整个控件。 + +![](./images/2_3.jpg) + + + +⑧将图片节点拉为100px长,100px宽,锚点的父节点和子节点都选择向中间上部对齐。 + +⑨在entry_panel节点下,新建一个名为black_button按钮,将大小设置为50px长,20px宽。此时按钮内文字会超过按钮的大小,显示不全,在black_button节点属性下,将文字字号设置为8号。并将文字设置为:按我黑色。 + +![](./images/2_4.jpg) + + + +⑩在控件结构面板下,右键black_button节点,复制一次。紧接着选择entry_panel节点,粘贴上去。并将新的按钮控件名称改为white_button。将文字设置为:按我白色。 + +⑪将black_button、white_button节点锚点对齐中间下部,将两个按钮平行分开。在后面使用MODSDK创建界面时,我们将会学习如何绑定按钮,通过代码让白色贴图在黑色和白色之间来回变化。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/课程03.在游戏内弹出界面.md b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/课程03.在游戏内弹出界面.md new file mode 100644 index 0000000..4bee96d --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/课程03.在游戏内弹出界面.md @@ -0,0 +1,31 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/3_1.7d892d10.jpg +hard: 进阶 +time: 15分钟 +--- + +# 在游戏内弹出界面 + + + +#### 作者:境界 + + + +①切换回关卡编辑器,在资源管理器面板下,选择行为包,右键打开行为包所在的文件夹。在文件夹内新建一个脚本文件夹工程。  + +②由于UI界面运行在客户端,并且界面功能仅需调用客户端接口,因此我们只注册一个客户端系统。 + +③新建一个CustomUi.py文件,导入ScreenNode类。新增一个Main类,并继承ScreenNode。 + +![](./images/3_2.jpg) + + + +④监听UiInitFinished事件,在回调函数内使用注册UI接口,UI必须注册才能创建。其中第一个参数是命名空间,第二个参数是UI名称,第三个参数是ui节点路径。ui节点路径的格式与注册系统类似,即"脚本文件夹.CustomUi.Main"。第四个是ui json文件入口,即"custom_ui.main"。 + +![](./images/3_1.jpg) + + + +⑤使用客户端的PushScreen接口,第一个参数即UI的命名空间,第二个参数即UI名称,分别对应注册UI的前两个参数。进入游戏后,查看效果。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/课程04.绑定界面控件和脚本.md b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/课程04.绑定界面控件和脚本.md new file mode 100644 index 0000000..4a68c15 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/课程04.绑定界面控件和脚本.md @@ -0,0 +1,23 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/4_1.2374ef65.jpg +hard: 进阶 +time: 20分钟 +--- + +# 绑定界面控件和脚本 + + + +#### 作者:境界 + + + +①在 __ init __ 下新建四个实例变量,分别对应继承基础画布后出现的主节点路径,主节点路径下的color_image路径,主节点路径下的black_button路径,主节点路径下的white_button路径。 + +②重写Main类的Create方法,使用AddTouchEventHandler接口添加两个按钮的回调,第一个参数是路径,第二个是回调函数名。 + +![](./images/4_1.jpg) + + + +③在回调函数内,将color_image图片设置为黑色或白色,其中白色的rgb值为255,255,255,除以255后为0.0,0.0,0.0。黑色的rgb值为0,0,0,除以255后为1.0,1.0,1.0。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/课程05.简易教程①制作一个滚动弹幕.md b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/课程05.简易教程①制作一个滚动弹幕.md new file mode 100644 index 0000000..86db1b4 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/课程05.简易教程①制作一个滚动弹幕.md @@ -0,0 +1,41 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/5_1.e36f376b.jpg +hard: 进阶 +time: 25分钟 +--- + +# 简易教程①:制作一个滚动弹幕 + + + +#### 作者:境界 + + + +#### 设计UI部分: + +①在主节点下新建一个图片控件,命名为background,将图片资源类型改为原生,指向textures/ui/white_background.png。并打开原版九宫格,在“上"位置填入1px,使贴图可以被自由拉伸到与控件大小相符合。紧接着,将图片长度大小从固定大小改为百分比大小,设置为100%,将宽度大小设置为固定大小16px。 + +![](./images/5_1.jpg) + + + +②将background节点的锚点设置为居中靠上,向下偏移15px。 + +③在background节点下,新建一个文本控件,命名为text,锚点设置为居中,长度大小同样设置为100%的百分比大小,宽度大小设置为固定大小16px。将字颜色设置为黑色,字号设置为8,添加6的行间距让字靠中对齐,并将字的对齐方式设置为向右对齐。 + +![](./images/5_2.jpg) + + + +④将text节点偏移量设置为向左偏移100%的百分比大小,即-100%。这样字就会超出屏幕显示区域,同时顶在屏幕的左边。 + +⑤打开ui源文件,对background控件进行自定义,向里面加入alpha键对,值为透明度的值,1.0代表不透明,0.0代表全透明,这里设置为0.9。同时添加color键对,可以对图片进行颜色叠加,这里设置为"color": [0.3647, 0.3647, 0.3647],即灰色的RGB值92,92,92除以255得出的结果。这样可以让白色背景变成灰色,并带有透明度。会更加的好看。 + + + +#### 脚本代码部分: + +![](./images/5_3.jpg) + +重写UI类的Create方法和Update方法,当UI每一游戏刻更新时,都会调用Update方法,我们在里面更新text节点的位置,让它从左边往右边移动。而Create方法会在UI创建后调用,在这里我们对文本内容进行设置。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/课程06.简易教程②制作一个简易的生物血条.md b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/课程06.简易教程②制作一个简易的生物血条.md new file mode 100644 index 0000000..b45801a --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第16章:创建界面/课程06.简易教程②制作一个简易的生物血条.md @@ -0,0 +1,39 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/6_1.67d4a73b.jpg +hard: 进阶 +time: 30分钟 +--- + +# 简易教程② 制作一个简易的生物血条 + + + +#### 作者:境界 + + + +生物血条示例包下载:下载[示例包](https://g79.gdl.netease.com/guidedemo-case16.zip) 。 + +#### 设计UI部分: + +①在主节点下新建一个进度条控件,取名为health_bar。锚点设置为置中,并将长度设置为80像素,宽度设置为10像素。 + +②在主节点下新建一个文本控件,取名为health_text。锚点设置为置中,并将长度宽度设置为与health_bar相当,同时字号设置为8号,行间距设置为3。 + + + +#### 脚本代码部分: + +![](./images/6_1.jpg) + + + +重写UI类的Create方法和Update方法,当UI每一游戏刻更新时,都会调用Update方法,我们在里面使用一个小技巧,通过query函数表可知,query.health会返回生物的生命值,而query.max_health会返回生物的最大生命值。往常处理时,获取生物的血量需要通过服务端获取,而使用GetMolangValue接口可以在客户端获取生物的血量信息。我们在这里更新血量的数值和进度条的裁切程度。 + +![](./images/6_2.jpg) + +![](./images/6_3.jpg) + + + +而在客户端系统上,我们通过在UiInitFinish事件里,添加一个实例变量来确保从这之后开始,在AddEntityClientEvent事件里才能为其他实体生物绑定UI。在RemoveEntityClientEvent里移除UI。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/README.md b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/10_1.jpg b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/10_1.jpg new file mode 100644 index 0000000..2f8a626 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/10_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/10_2.jpg b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/10_2.jpg new file mode 100644 index 0000000..b29cd87 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/10_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/2_1.jpg b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/2_1.jpg new file mode 100644 index 0000000..32b415d Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/2_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/2_2.jpg b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/2_2.jpg new file mode 100644 index 0000000..08bc007 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/2_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/2_3.jpg b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/2_3.jpg new file mode 100644 index 0000000..7473fa8 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/2_3.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/2_4.jpg b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/2_4.jpg new file mode 100644 index 0000000..13fd6e9 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/2_4.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/3_1.jpg b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/3_1.jpg new file mode 100644 index 0000000..6cdc039 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/3_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/3_10.jpg b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/3_10.jpg new file mode 100644 index 0000000..0f77ae6 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/3_10.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/3_11.jpg b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/3_11.jpg new file mode 100644 index 0000000..ebc913d Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/3_11.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/3_2.jpg b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/3_2.jpg new file mode 100644 index 0000000..c13aa42 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/3_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/3_3.jpg b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/3_3.jpg new file mode 100644 index 0000000..0bd208d Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/3_3.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/3_4.jpg b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/3_4.jpg new file mode 100644 index 0000000..77d2a3e Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/3_4.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/3_5.jpg b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/3_5.jpg new file mode 100644 index 0000000..503f01f Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/3_5.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/3_6.jpg b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/3_6.jpg new file mode 100644 index 0000000..f2861a9 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/3_6.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/3_7.jpg b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/3_7.jpg new file mode 100644 index 0000000..cd9d749 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/3_7.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/3_8.jpg b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/3_8.jpg new file mode 100644 index 0000000..9bd1c36 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/3_8.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/3_9.jpg b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/3_9.jpg new file mode 100644 index 0000000..f9c9ab0 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/3_9.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/5_1.jpg b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/5_1.jpg new file mode 100644 index 0000000..44be895 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/5_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/5_2.jpg b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/5_2.jpg new file mode 100644 index 0000000..6c03de1 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/5_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/5_3.jpg b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/5_3.jpg new file mode 100644 index 0000000..f893ff5 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/5_3.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/5_4.jpg b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/5_4.jpg new file mode 100644 index 0000000..726c9a4 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/5_4.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/7_1.jpg b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/7_1.jpg new file mode 100644 index 0000000..0e807ba Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/7_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/7_2.jpg b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/7_2.jpg new file mode 100644 index 0000000..0118da0 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/7_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/7_3.jpg b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/7_3.jpg new file mode 100644 index 0000000..cffb95e Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/7_3.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/7_4.jpg b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/7_4.jpg new file mode 100644 index 0000000..9a97ed5 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/7_4.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/7_5.jpg b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/7_5.jpg new file mode 100644 index 0000000..4c9244f Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/7_5.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/8_1.jpg b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/8_1.jpg new file mode 100644 index 0000000..8ce02f0 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/8_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/9_1.jpg b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/9_1.jpg new file mode 100644 index 0000000..72c5028 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/9_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/9_2.jpg b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/9_2.jpg new file mode 100644 index 0000000..1e4aed2 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/9_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/9_3.jpg b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/9_3.jpg new file mode 100644 index 0000000..d40d7d5 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/images/9_3.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/课程01.原版粒子和特效粒子.md b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/课程01.原版粒子和特效粒子.md new file mode 100644 index 0000000..29dd3ed --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/课程01.原版粒子和特效粒子.md @@ -0,0 +1,21 @@ +--- +front: +hard: 进阶 +time: 10分钟 +--- + +# 原版粒子和特效粒子 + + + +#### 作者:境界 + + + +原版粒子指的是我的世界基岩版原生自带的粒子特效,亦或是由开发者通过基岩版粒子系统提供的自定义粒子方法制作的粒子。特效粒子则是由中国版官方维护的一套粒子系统,开发者可以通过官方Demo或是MODSDK文档研习,并用MCStudio的特效编辑器进行可视化制作。总的来说,两者制作粒子遵循的规律大致相同,只是服务的个体和细节功能稍有不同,以下表格来解释这些区别。 + +| | 原版粒子 | 特效粒子 | +| -------- | ------------------------------------------------------------ | ------------------------------------------------------------ | +| 功能区别 | 1)原版粒子可以带有碰撞检测。 粒子检测包括粒子与方块碰撞后执行事件(如蜜蜂方块的蜂蜜粒子,掉到地面后会播放蜂蜜掉落的音效);与方块碰撞后产生真实物理碰撞(如触碰到方块后停留或者反弹)。 2)原版粒子可以设置是否受到光照影响,当设置为真时,粒子颜色会因光照变暗(如MC世界中天色变晚)而变暗。 3)粒子没有层级概念,即支持粒子前后层级的效果,例如:层级越大,渲染越晚,粒子越靠前显示。 | 1)特效粒子没有碰撞检测。 2)特效粒子不受光照影响,始终维持原来的颜色。 3)粒子拥有层级概念,即支持粒子前后层级的效果,例如:层级越大,渲染越晚,粒子越靠前显示。 | +| 性能区别 | 1)原版粒子会在粒子消失后被游戏回收,动态调配资源。 2)原版粒子贴图和序列帧贴图在2048x2048大小以上会打印游戏日志做出警告。 | 1)特效粒子在粒子消失后会停留在游戏中,占用资源。 2)在官方文档的规范中,单张粒子贴图不超过32x32,序列帧贴图不超过512x512。 | +| 使用区别 | 1)原版粒子可以通过指令生成在世界上,亦或是绑定在原版模型上,但无法将它绑定在骨骼模型的骨骼上。 2)原版粒子没有官方维护的编辑器,但有国外民间开发者维护的特效编辑器snowstorm。 3)原版粒子在动态变化、效果程度上对开发者的水平要求高,如粒子渐变颜色、粒子缩放大小等都需要写Molang语法表达式。 | 1)特效粒子可以通过MODSDK生成在世界上,亦或是绑定在骨骼模型上,但无法将它绑定在原版模型的骨骼上。 2)特效粒子可由MCSTUDIO特效编辑器编辑制作,是由中国版官方开发并维护的。 3)特效粒子制作上手难度低,很容易就能做出好看的特效,在渐变颜色、缩放大小上有直接的输入面板可以控制。 | \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/课程02.粒子和粒子发射器的关系.md b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/课程02.粒子和粒子发射器的关系.md new file mode 100644 index 0000000..5cbf348 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/课程02.粒子和粒子发射器的关系.md @@ -0,0 +1,43 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/2_2.253bcfde.jpg +hard: 进阶 +time: 20分钟 +--- + +# 粒子和粒子发射器的关系 + + + +#### 作者:境界 + + + +在3D游戏世界中,许许多多模糊现象的视觉效果是由特效这一概念包装而成。经常用到这一概念的现象有火、爆炸、烟、水流、雪、尘等这些抽象视觉效果。粒子发射器是操控粒子在三维空间位移运动的典型实现,而其中各种动态变化的部分再由技术团队进行包装后提供粒子行为参数来操控。中国版目前的下界更新版本上,同时支持原版粒子自定义与MODSDK粒子自定义,虽然功能细节上可能略微带有差异,但制作粒子特效、序列帧特效的思路没有太多差异,学会其中一种后去迁移学习到另一种系统带来的学习成本会少很多,让我们来看看它们相同的地方吧。 + +| | 原版粒子 | 特效粒子 | +| -------- | ------------------------------------------------------------ | ------------------------------------------------------------ | +| 发射器 | 1)原版粒子发射器一共有5种形状,它们分别是:点、球形、方形、平面形、生物碰撞箱形。 发射器形状使粒子沿着形状的表面分散,并控制粒子的发射方向和方向。例如,粒子发射器设置为方形,则粒子会在一个方型空间分散开来,若设置为点形,则粒子会在一个点上聚集。 2)可以通过设置发射器的大小来缩放特效的大小。 | 1)特效粒子发射器一共有4种形状,分别是球形、半球形、圆柱形、方形。 发射器形状使粒子沿着形状的表面分散,并控制粒子的发射方向和方向。例如,粒子发射器设置为方形,则粒子会在一个方形空间分散开来,若设置为点形,则粒子会在一个点上聚集。 2)可以通过设置发射器的大小来缩放特效的大小。 | +| 变化 | 1)粒子可以随参数调整加速度和阻力、旋转角度、旋转速度等,在三维空间内进行位移、旋转。 2)粒子具有数量概念,例如:将粒子形状变成火,通过改变数量可以增加或减少火的数量。 3)粒子具有大小的概念,例如:将粒子形状变成火,一团火在世界中的显示大小同样可以调整。 | | +| 朝向方式 | 1)粒子朝向方式可以设置为面向相机、面向地面、面向方向,是指粒子在玩家视线中朝向何种方向的参数。 面向相机是指粒子始终朝向玩家相机视角,无论在任何角度看向这个粒子都能看到它完整的样子。【图例1】 面向地面是指粒子会在Y轴方向始终朝向玩家视角,即玩家视角与地面平行时,可以看到完整的粒子效果。若头朝下方看向粒子,则无法看到粒子的完全样貌。【图例2】 面向方向是指粒子朝向某个特定方向,如:地面冒出一滩绿色液体,开发者希望粒子紧贴着地面,则将朝向方式设置为朝向Z轴。【图例3】 | | +| 持续时间 | 1)粒子具有生命周期存留的时间概念,当持续时间结束时,粒子会消失。 2)粒子具有循环播放和只播放一次的选项,当设置为循环时,粒子在持续时间结束后,会在初始位置重新出现。而设置为播放一次时,则粒子消失后就不再会出现。 | | +| 渲染 | 1)原版粒子同样需要贴图纹理和材质,单张粒子贴图导入进snowstorm编辑器时,会自动匹配贴图大小。若是由多张带有连续性变化的粒子贴图拼接而成序列帧,则需要将UV模式改成动画模式,通过设置uv起点、uv大小、uv步进、帧率、最大渲染帧数等参数来播放序列帧。 2)原版粒子当前支持的材质为透明、半透明、不透明。 3)原版粒子可以着色。 | 1)特效粒子需要贴图纹理,不需要额外设置材质种类。 2)开发者需要提前确定特效粒子种类是使用单张贴图还是序列帧,序列帧需要先用TexturePacker工具进行打包后输出配置文件搭配使用。 3)特效粒子可以着色,形式可以选择透明度叠加或者颜色值叠加模式。 | + + + +#### 朝向相机【图例1】 + +![](./images/2_1.jpg) + + + +#### 朝向地面【图例2】 + +![](./images/2_2.jpg) + +![](./images/2_3.jpg) + + + +#### 朝向Z方向【图例3】 + +![](./images/2_4.jpg) \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/课程03.渲染粒子和着色.md b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/课程03.渲染粒子和着色.md new file mode 100644 index 0000000..2b426ae --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/课程03.渲染粒子和着色.md @@ -0,0 +1,101 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/3_1.ae73d778.jpg +hard: 进阶 +time: 25分钟 +--- + +# 渲染粒子和着色 + + + +#### 作者:境界 + + + +#### 原版粒子渲染教程 + +#### 设置贴图纹理 + +1)进入[https://jannisx11.github.io/snowstorm/](https://jannisx11.github.io/snowstorm/),下拉到texture(贴图纹理)。选择文件选择原版贴图粒子,它位于原版材质包/textures/particle的particles文件。由于原版粒子贴图是一张集合贴图,它将多种粒子集合在一张图片文件中,里面既有单张粒子纹理也有连续性多张粒子纹理的序列帧,每单张粒子纹理是8x8的尺寸。我们将贴图尺寸(texture size)设置为128x128,与硬盘中的贴图文件像素匹配。UV起点是以左上角的点为起点,我们将Y轴向下移动24格像素,x轴不变,接着将uv尺寸设置为8x8,这样粒子纹理就锁定在了火焰粒子上。 + +![](./images/3_1.jpg) + + + +2)使用序列帧纹理时,需要将uv mode(贴图模式)改成animated(动态式),则在uv尺寸下会新增uv step(uv步进)、fps(一秒内播放几帧)、max frame(最大序列帧数)、stretch to life time(序列帧数匹配粒子持续时间)、loop(循环播放序列帧)等参数。 + +以原版粒子贴图倒数第6列的爆炸序列帧为例,从左往右数一共有16张连续性的贴图纹理,因此最大序列帧数应设为16,fps设置为可以被16整除的数值,如2就是一秒播放2帧、16就是一秒内播放完16帧。uv步进设置为8x0,即每一帧结束时,uv向右增加8个像素点,开始播放下一帧,粒子会在帧数到尾时回到第一帧。 + +stretch to life time一旦打勾选取,则会让序列帧数匹配粒子生命周期且无视fps,如粒子生命周期时长为1秒,则序列帧会在1秒内播放16帧,如果生命周期为0.5秒,则会在0.5秒内播放完16帧,FPS等于1秒播放32帧。 + +loop参数勾选可以循环播放序列帧,直到发射器被游戏清理为止。 + +![](./images/3_2.jpg) + + + +#### 特效粒子教程 + +#### 设置贴图纹理 + +1)进入MCSTUDIO,可以在新建入口快速创建特效制作模板,创建好进入编辑器后,页面会自动划转到特效编辑器。 + +![](./images/3_3.jpg) + + + +2)在页面下方中央的资源入口列表里,选择effects(网易版特效)选项的+号键,可以选择创建粒子或是创建序列帧,取决于届时开发者制作自定义特效时所用到的贴图纹理类型。在教程里,我们创建一个粒子特效。 + +![](./images/3_4.jpg) + +![](./images/3_5.jpg) + + + +3)在资源管理入口点击一次特效json文件,将粒子贴图拖拉进名为贴图的入口,粒子带有默认的位移和生命周期,为了马上看到效果,可以在属性-预览设置里将右侧面板切换到预览设置中。在场景特效内点击“+”号,默认设置都不用改动,在特效选项选择之前创建好的粒子,开发者便能看到特效在世界中的变化了。 + + + +#### 原版粒子着色教程 + +#### 设置颜色 + +1)点击color&light(颜色和光照)面板,拖动下方第一格滑轨来调整颜色色相,拖动第二条滑轨设置颜色透明度,点选颜色调盘选取颜色。 + +![](./images/3_6.jpg) + + + +2)若有提前选好的颜色,可以直接在下方输入口输入颜色代码。 + +3)若希望粒子颜色受到光照影响,即会随着游戏内天亮天黑而增强&衰弱颜色。若不勾选,粒子颜色在黑夜或白天都会显示原来的颜色。 + +![](./images/3_7.jpg) + + + +4)若想要设置动态颜色,需要将color mode(颜色模式)设置为渐变(Gradient),在Interpolant插值窗口输入variable.particle_age,将range调整为1,可以让粒子在渐变颜色选区中播放完所有的颜色。 + +![](./images/3_8.jpg) + + + +#### 特效粒子渲染教程 + +#### 设置颜色 + +1) 点击粒子的属性面板,划到动态属性折叠窗口内,选择动态颜色。若只想着一种颜色,可将左右两点选择相同的颜色。 + +![](./images/3_9.jpg) + + + +2) 若想要多种颜色,可以选择插入关键帧选取点,在每个选取点上选择需要的颜色。 + +![](./images/3_10.jpg) + + + +3)选取点拉得越高,越不透明,拉得越低,透明度越低。 + +![](./images/3_11.jpg) \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/课程04.制造位移和旋转.md b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/课程04.制造位移和旋转.md new file mode 100644 index 0000000..007e083 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/课程04.制造位移和旋转.md @@ -0,0 +1,43 @@ +--- +front: +hard: 进阶 +time: 20分钟 +--- + +# 制造位移和旋转 + + + +#### 作者:境界 + + + +#### 原版粒子动态教程 + + + +#### 制造位移 + +1)在snowstorm中选择shape,通过offset(偏移)可以偏移粒子发射器的位置,自然粒子的起始点也会被偏移。 + +2)划到motion(移动)折叠面板下,当模式选择dynamic时,可以给予粒子一个speed(起始速度),或者给予一个加速度acceleration。 + +3)划到rotation(旋转)折叠面板下,当模式选择dynamic时,可以给予粒子一个start rotation(初始旋转角度),一个speed(旋转速度),一个acceleration(旋转加速度)。 + +3)在移动和旋转面板下都有一个air drag,可以形容为空气摩擦力,该值越大,给予的位移效果、旋转效果表现越差。 + + + +#### 特效粒子渲动态教程 + +#### 制造位移 + +1)点击粒子属性面板,在基础属性上有一栏特效方向(direction),若选择向内(Inwards),则粒子会向内移动,若选择向外(Outwards),则会由内向外移动。选择粒子初始方向的时候,可以自定义粒子的方向,方向是在之后给予粒子移动的速度时,飞出的方向。 + +2)初始速度是一开始的速度,恒定力就是之后的加速度,阻尼力即摩擦力。 + +3)粒子尺寸是指粒子在面向玩家摄像机前的大小。 + +4)初始旋转是一开始的旋转角度,旋转速度就是旋转的恒定加速度。 + +5)绝大多数基础属性都有最小和最大值,当最小值和最大值不一样时,最终作用在粒子的属性值会在最小值和最大值期间随机。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/课程04.渲染粒子和着色.md b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/课程04.渲染粒子和着色.md new file mode 100644 index 0000000..99f2d70 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/课程04.渲染粒子和着色.md @@ -0,0 +1,37 @@ +# 渲染粒子和着色 + + + +#### 作者:境界 + + + +#### 原版粒子动态教程 + + + +#### 制造位移 + +1)在snowstorm中选择shape,通过offset(偏移)可以偏移粒子发射器的位置,自然粒子的起始点也会被偏移。 + +2)划到motion(移动)折叠面板下,当模式选择dynamic时,可以给予粒子一个speed(起始速度),或者给予一个加速度acceleration。 + +3)划到rotation(旋转)折叠面板下,当模式选择dynamic时,可以给予粒子一个start rotation(初始旋转角度),一个speed(旋转速度),一个acceleration(旋转加速度)。 + +3)在移动和旋转面板下都有一个air drag,可以形容为空气摩擦力,该值越大,给予的位移效果、旋转效果表现越差。 + + + +#### 特效粒子渲动态教程 + +#### 制造位移 + +1)点击粒子属性面板,在基础属性上有一栏特效方向(direction),若选择向内(Inwards),则粒子会向内移动,若选择向外(Outwards),则会由内向外移动。选择粒子初始方向的时候,可以自定义粒子的方向,方向是在之后给予粒子移动的速度时,飞出的方向。 + +2)初始速度是一开始的速度,恒定力就是之后的加速度,阻尼力即摩擦力。 + +3)粒子尺寸是指粒子在面向玩家摄像机前的大小。 + +4)初始旋转是一开始的旋转角度,旋转速度就是旋转的恒定加速度。 + +5)绝大多数基础属性都有最小和最大值,当最小值和最大值不一样时,最终作用在粒子的属性值会在最小值和最大值期间随机。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/课程05.让粒子动起来.md b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/课程05.让粒子动起来.md new file mode 100644 index 0000000..4dc19d5 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/课程05.让粒子动起来.md @@ -0,0 +1,41 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/5_4.be84bb9e.jpg +hard: 进阶 +time: 15分钟 +--- + +# 让粒子动起来 + + + +#### 作者:境界 + + + +#### 雨水粒子教程 + + + +#### 制造位移 + +1) 选择方型粒子发射器,将方形发射器的形状调整为8,0,8,同时将粒子发射器高度便宜到10格。 + +![](./images/5_1.jpg) + + + +2) 制作一个只有1格像素点的贴图,并将它作为粒子贴图纹理,用颜色叠加叠加上蓝色。 + +![](./images/5_2.jpg) + + + +3) 叠加一个蓝色颜色后,粒子变为蓝色,并将粒子大小调整为宽比长大,拉出一条下落蓝色直线。将朝向设置为朝向地面,材质保留为带有透明通道的默认设定。 + +![](./images/5_3.jpg) + + + +4) 提高粒子的生命周期时长,给予加速度中填写0,-4,0,提供一个向y轴下方移动的加速度,这样粒子就实现好了。 + +![](./images/5_4.jpg) \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/课程06.伸缩粒子的持续时间.md b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/课程06.伸缩粒子的持续时间.md new file mode 100644 index 0000000..8bbe4c9 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/课程06.伸缩粒子的持续时间.md @@ -0,0 +1,19 @@ +--- +front: +hard: 进阶 +time: 10分钟 +--- + +# 伸缩粒子的持续时间 + + + +#### 作者:境界 + + + +#### 设置持续时间 + +1)Texture面板上方即是lifetime(粒子生命周期)面板,默认模式是Time,即设置Max Age(最大持续时间)后,它相当于特效编辑器的生存时间。每个由发射器发射的粒子会在达到生存时间最大值后消失。Snowstorm编辑器还有额外两个参数,Kill in blocks内可以加入原版方块的名称域,即当粒子进入某种方块后便会被游戏销毁而消失,only in blocks相反,只有粒子在这种方块内活动才会显示,否则会消失。使用这种参数的一个经典案例是水中浮泡,在之前的绿头水鸭的粒子效果绑定教程中,开发者可以发现水泡只会在水里产生,正是用到了这一功能。 + +2)emitter lifetime意为发射器生命周期,active time意为在这个时间内发射器会连续发射粒子,相当于特效编辑器的发射周期长度。sleep time意为发射器在active time结束后,发射器会休息多久,仅当将发射器生命周期的模式改为循环时有效,相当于特效编辑器的发射周期间隔。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/课程07.识粒子的朝向方式.md b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/课程07.识粒子的朝向方式.md new file mode 100644 index 0000000..5eadd3a --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/课程07.识粒子的朝向方式.md @@ -0,0 +1,59 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/7_1.13819fac.jpg +hard: 进阶 +time: 10分钟 +--- + +# 识粒子的朝向方式 + + + +#### 作者:境界 + + + +#### 面向相机 + +开发者如果希望粒子在玩家从任何角度过去都是最完整的样子,如原版世界中的烟雾粒子、爱心粒子、村民愤怒的粒子,在制作原版粒子或是特效粒子时,可以选择面向相机的朝向方式。 + +![](./images/7_1.jpg) + +![](./images/7_2.jpg) + + + +1)在snowstorm编辑器中,面向相机一共有Rotate xyz和lookat xyz两种朝向方式。 + +2)在MCSTUDIO的特效编辑器中,面向相机就是被称为面向相机的朝向方式,可以在朝向方式面板里找到。 + + + +#### 面向地面 + +开发者如果希望粒子朝向方式垂直于Y轴的,可以选择面向地面的朝向方式。在这个效果里,开发者可以看到低下头只能看到粒子的一部分,这是因为粒子的贴图是与水平地面垂直的。 + +![](./images/7_3.jpg) + + + +1)在snowstorm编辑器中,面向相机一共有Rotate y、lookat y两种朝向方式。 + +![](./images/7_4.jpg) + + + +2)在MCSTUDIO的特效编辑器中,面向地面的朝向方式可以在朝向方式面板里找到。 + + + +#### 面向方向 + +开发者如果希望粒子朝向方式面向某个方向,如希望粒子贴图纹理与地面紧紧贴合,或者希望粒子紧贴在一个广告牌上,就要用到面向方向的朝向方式。 + +1)在snowstorm编辑器中,面向方向的朝向方式有Direction x、Direction y、Direction Z三种。注意:使用这个模式需要将motion的mode改成Parametric,再设置Motion面板下的Direction,这样粒子才会在世界里正常显示。 + +![](./images/7_5.jpg) + + + +2)在MCSTUDIO的特效编辑器中,水平朝向方式相当于原版粒子的Direction Z。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/课程08.使用序列帧强化效果.md b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/课程08.使用序列帧强化效果.md new file mode 100644 index 0000000..a9f5be5 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/课程08.使用序列帧强化效果.md @@ -0,0 +1,37 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/8_1.7818512f.jpg +hard: 进阶 +time: 10分钟 +--- + +# 使用序列帧强化效果 + + + +#### 作者:境界 + + + +#### 配置序列帧文件 + +1)在制作原版粒子和特效粒子时,用带有连续性的动态贴图可以增强粒子的视觉效果。原版粒子的序列帧贴图不需要额外配置文件,只需用snowstorm编辑器设置好FPS、最大帧数、uv等参数即可,而特效粒子的序列帧,需要借用TexturePacker这个软件进行打包后输出一份配置文件才能被特效编辑器使用。 + +![](./images/8_1.jpg) + + + +2)再用texturepacker打包序列帧时,需要注意的配置如下: + +Allow rotation不要勾选; + +Trim mode选择为None; + +这样导出的大图中每一幅小图都一样大,当前特效编辑器支持的序列帧只支持大小相同的子图。 + +同时序列帧贴图有以下规范: + +贴图格式为 png; + +原版粒子单张贴图最大不超过2048x2048; + +特效粒子单张贴图大小不超过512x512; \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/课程09.理解粒子碰撞与事件.md b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/课程09.理解粒子碰撞与事件.md new file mode 100644 index 0000000..797c207 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/课程09.理解粒子碰撞与事件.md @@ -0,0 +1,102 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/9_1.045044b5.jpg +hard: 进阶 +time: 15分钟 +--- + +# 理解粒子碰撞与事件 + + + +#### 作者:境界 + + + +#### 产生碰撞 + +1)该功能只能作用于原版自定义粒子。 + +2)打开snowstorm编辑器,下拉到collision面板,在Enable窗口输入true,其中collision drag代表粒子与环境产生碰撞后,作用于粒子上的阻力。值越大,粒子碰撞环境后的阻力越大,粒子可能会减速到停止;值越小,粒子可能会顺着环境方块而贴着位移。Bounciness代表粒子于环境产生碰撞后的弹力,弹力越大,粒子与环境方块后的反弹力越大,弹力越小,粒子与环境方块后的反弹力越小,可能会出现多次弹跳的现象,取值范围在0.0~1.0之间。Collision Radius用于最小化粒子与环境方块的重叠问题,该值必须在0.0~0.5之间,不设置时,粒子贴图可能有部分区域会陷入方块内。Expire on Contact意为当粒子与环境产生碰撞时进行自我销毁。 + +![](./images/9_1.jpg) + + + +【与环境产生碰撞而自行销毁的方型发射器雨水粒子,可以看到粒子掉落至坐标轴上后不会继续下渗】 + + + +#### 触发事件 + +1)该功能只能作用于原版自定义粒子,并且当前尚未实装于snowstorm编辑器中,需要开发者进行手写配置。 + +![](./images/9_2.jpg) + + + +``` +{ + "format_version":"1.10.0", + "particle_effect":{ + "description":{ + "identifier":"...", + "basic_render_parameters":{ + "material":"particles_alpha", + "texture":"textures/particle/particles" + } + }, + "events":{ + "hit_ground":{ + "sound_effect":{ + "event_name":"..." + } + } + }, + "components":{ + "minecraft:particle_motion_collision":{ + "coefficient_of_restitution":0.1, + "collision_drag":"10.0", + "collision_radius":0.01, + "events":[ + { + "event":"hit_ground", + "min_speed":0.5 + } + ] + } + } + } +} +``` + + + +2)需要minecraft:particle_motion_collision指定事件名称,可以触发多个事件,min_speed意为触发事件的最短时间。 + +![](./images/9_3.jpg) + + + +``` +{ + "format_version":"1.10.0", + "particle_effect":{ + "description":{ + "identifier":"...", + "basic_render_parameters":{ + "material":"particles_alpha", + "texture":"textures/particle/particles" + } + }, + "events":{ + "hit_ground":{ + "sound_effect":{ + "event_name":"block.beehive.drip" + } + } + } + } +} +``` + +3)在particle_effect下定义events,播放音效,音效以sound_definitions.json里的音效资源路径名称为准。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/课程10.简易教学.md b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/课程10.简易教学.md new file mode 100644 index 0000000..3eb69b6 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第17章:原版粒子和特效粒子/课程10.简易教学.md @@ -0,0 +1,61 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/10_1.423727d2.jpg +hard: 进阶 +time: 20分钟 +--- + +# 简易教学 + + + +### 作者:境界 + + + +### 简易教学① :制作火焰蔓延粒子 + +1)首先打开snowstorm编辑器,定义一个粒子的名称域,格式与物品、方块、生物等大同小异,以命名空间:名称为格式。 + +2)Texture选择原版的火焰序列帧贴图,路径位于原版材质包的textures/flame_atlas.png。将UV MODE设置为animate,贴图大小设置为16x512,uv start设置为0x0,uv size设置为16x16,uv step设置为0x16,FPS设置为32,勾选stretch to lifetime。 + +3)打开Rate发射频率面板,相当于MCSTUDIO的特效编辑器的粒子发射频率,选择Steady模式,将发射率设置为10,最大值为20。 + +4)Emitter Lifetime内将循环模式设置为once(只播放一次),其余设定不变。 + +5)emitter shape设置不变。 + +6)将Apperance内的size(粒子大小)设置为合适大小,朝向方式设置为lookat xyz,材质设置为透明材质(alpha)。 + +7)Motion内将speed设置为1.0。 + + + +#### 效果图如下: + +![](./images/10_1.jpg) + + + +#### 简易教学② :制作岩浆滴落粒子 + +1)首先打开snowstorm编辑器,定义一个粒子的名称域,格式与物品、方块、生物等大同小异,以命名空间:名称为格式。 + +2)Texture选择原版的粒子集合贴图,路径位于原版材质包的textures/particle/particles.png。将UV MODE设置为static,贴图大小设置为128x128,uv start设置为8x56,uv size设置为8x8。 + +3)打开Rate发射频率面板,相当于MCSTUDIO的特效编辑器的粒子发射频率,选择Steady模式,将发射率设置为10,最大值为20。 + +4)Emitter Lifetime内将循环模式设置为once(只播放一次),其余设定不变。 + +5)emitter shape使用默认的点形,并将发射器偏移Y轴高度到4格位置上。 + +6)将Apperance内的size(粒子大小)设置为0.15x0.3,拉长粒子贴图,朝向方式设置为lookat xyz,材质设置为透明材质。 + +7)Motion内将acceleration的y轴加速度设置为-3,让粒子向下飞行。 + +8)点开collision面板,勾选expire on contact,使粒子在与地面接触后消失。 + + + +#### 效果图如下: + +![](./images/10_2.jpg) \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/README.md b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_1.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_1.jpg new file mode 100644 index 0000000..3f2df1c Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_10.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_10.jpg new file mode 100644 index 0000000..5625031 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_10.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_11.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_11.jpg new file mode 100644 index 0000000..60d8c13 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_11.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_12.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_12.jpg new file mode 100644 index 0000000..e0699e6 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_12.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_13.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_13.jpg new file mode 100644 index 0000000..ef77dea Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_13.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_14.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_14.jpg new file mode 100644 index 0000000..baa0634 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_14.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_15.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_15.jpg new file mode 100644 index 0000000..a53ecc1 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_15.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_16.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_16.jpg new file mode 100644 index 0000000..22ebba9 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_16.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_17.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_17.jpg new file mode 100644 index 0000000..49b23a3 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_17.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_18.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_18.jpg new file mode 100644 index 0000000..2a17edf Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_18.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_19.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_19.jpg new file mode 100644 index 0000000..643d387 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_19.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_2.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_2.jpg new file mode 100644 index 0000000..ad7002b Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_20.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_20.jpg new file mode 100644 index 0000000..0607dc7 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_20.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_21.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_21.jpg new file mode 100644 index 0000000..8f8c326 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_21.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_22.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_22.jpg new file mode 100644 index 0000000..ae25c5e Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_22.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_23.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_23.jpg new file mode 100644 index 0000000..91be633 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_23.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_24.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_24.jpg new file mode 100644 index 0000000..a9fc7fb Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_24.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_25.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_25.jpg new file mode 100644 index 0000000..17ffbbd Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_25.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_26.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_26.jpg new file mode 100644 index 0000000..dd2a979 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_26.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_27.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_27.jpg new file mode 100644 index 0000000..8db1e90 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_27.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_28.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_28.jpg new file mode 100644 index 0000000..3d8323d Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_28.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_29.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_29.jpg new file mode 100644 index 0000000..982c4fc Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_29.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_3.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_3.jpg new file mode 100644 index 0000000..24e1c8c Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_3.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_30.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_30.jpg new file mode 100644 index 0000000..a80130c Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_30.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_31.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_31.jpg new file mode 100644 index 0000000..2cb5c91 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_31.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_32.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_32.jpg new file mode 100644 index 0000000..0c9b471 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_32.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_33.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_33.jpg new file mode 100644 index 0000000..59cfe69 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_33.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_34.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_34.jpg new file mode 100644 index 0000000..f4e2a27 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_34.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_35.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_35.jpg new file mode 100644 index 0000000..6d70bc1 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_35.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_36.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_36.jpg new file mode 100644 index 0000000..019dad6 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_36.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_37.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_37.jpg new file mode 100644 index 0000000..686c2e4 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_37.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_38.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_38.jpg new file mode 100644 index 0000000..08ab866 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_38.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_39.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_39.jpg new file mode 100644 index 0000000..fda231b Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_39.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_4.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_4.jpg new file mode 100644 index 0000000..0bf3c55 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_4.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_40.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_40.jpg new file mode 100644 index 0000000..d0d4b2c Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_40.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_41.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_41.jpg new file mode 100644 index 0000000..4b8bd11 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_41.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_42.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_42.jpg new file mode 100644 index 0000000..7e91f73 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_42.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_43.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_43.jpg new file mode 100644 index 0000000..e33ffac Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_43.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_44.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_44.jpg new file mode 100644 index 0000000..a246e48 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_44.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_45.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_45.jpg new file mode 100644 index 0000000..864dc8d Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_45.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_46.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_46.jpg new file mode 100644 index 0000000..4b9db1f Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_46.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_47.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_47.jpg new file mode 100644 index 0000000..11f4487 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_47.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_48.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_48.jpg new file mode 100644 index 0000000..f556a5a Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_48.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_49.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_49.jpg new file mode 100644 index 0000000..7b27e5f Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_49.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_5.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_5.jpg new file mode 100644 index 0000000..6ad5eb0 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_5.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_50.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_50.jpg new file mode 100644 index 0000000..217c68d Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_50.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_51.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_51.jpg new file mode 100644 index 0000000..c68acfd Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_51.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_52.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_52.jpg new file mode 100644 index 0000000..85ca6e3 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_52.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_53.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_53.jpg new file mode 100644 index 0000000..e4cd3e7 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_53.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_54.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_54.jpg new file mode 100644 index 0000000..be376da Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_54.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_55.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_55.jpg new file mode 100644 index 0000000..e5f2423 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_55.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_6.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_6.jpg new file mode 100644 index 0000000..3232c36 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_6.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_7.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_7.jpg new file mode 100644 index 0000000..84c80a1 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_7.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_8.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_8.jpg new file mode 100644 index 0000000..50ffde1 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_8.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_9.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_9.jpg new file mode 100644 index 0000000..962afba Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/1_9.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_1.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_1.jpg new file mode 100644 index 0000000..55cfbc1 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_10.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_10.jpg new file mode 100644 index 0000000..112277a Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_10.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_11.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_11.jpg new file mode 100644 index 0000000..56c2987 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_11.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_12.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_12.jpg new file mode 100644 index 0000000..b7d3b1b Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_12.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_2.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_2.jpg new file mode 100644 index 0000000..5ab1c61 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_3.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_3.jpg new file mode 100644 index 0000000..abe5131 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_3.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_4.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_4.jpg new file mode 100644 index 0000000..8b75fc9 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_4.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_5.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_5.jpg new file mode 100644 index 0000000..fc6cb01 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_5.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_6.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_6.jpg new file mode 100644 index 0000000..b9548df Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_6.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_7.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_7.jpg new file mode 100644 index 0000000..04ae9b0 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_7.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_8.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_8.jpg new file mode 100644 index 0000000..6571591 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_8.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_9.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_9.jpg new file mode 100644 index 0000000..e6bba98 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/2_9.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_1.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_1.jpg new file mode 100644 index 0000000..6f7430e Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_1.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_10.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_10.jpg new file mode 100644 index 0000000..670acba Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_10.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_11.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_11.jpg new file mode 100644 index 0000000..9d58e70 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_11.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_12.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_12.jpg new file mode 100644 index 0000000..74876e7 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_12.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_13.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_13.jpg new file mode 100644 index 0000000..cc26338 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_13.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_14.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_14.jpg new file mode 100644 index 0000000..355d2d5 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_14.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_16.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_16.jpg new file mode 100644 index 0000000..2eaeef2 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_16.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_17.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_17.jpg new file mode 100644 index 0000000..dc627db Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_17.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_18.gif b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_18.gif new file mode 100644 index 0000000..6fb805f Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_18.gif differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_2.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_2.jpg new file mode 100644 index 0000000..89af1ec Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_2.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_3.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_3.jpg new file mode 100644 index 0000000..81bdfc3 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_3.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_4.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_4.jpg new file mode 100644 index 0000000..3b61a56 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_4.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_5.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_5.jpg new file mode 100644 index 0000000..eb21687 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_5.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_6.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_6.jpg new file mode 100644 index 0000000..25b190c Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_6.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_7.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_7.jpg new file mode 100644 index 0000000..af2f851 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_7.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_8.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_8.jpg new file mode 100644 index 0000000..1df076f Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_8.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_9.jpg b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_9.jpg new file mode 100644 index 0000000..832b322 Binary files /dev/null and b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/images/3_9.jpg differ diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/课程01.包体的结构和导入.md b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/课程01.包体的结构和导入.md new file mode 100644 index 0000000..a0cf2b5 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/课程01.包体的结构和导入.md @@ -0,0 +1,404 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/1_1.dc5d5f44.jpg +hard: 进阶 +time: 20分钟 +--- + +# 包体的结构和导入 + + + +#### 作者:境界 + + + +包体也被称为Package,它是由开发者将本地的JAVA版资源内容和基岩版资源内容进行压缩打包后的档案。在中国版开发者平台拿到上传的包体后,通过机器审核和人工审核后,包体最终会被推送至公共下载服务器。最后再由玩家下载后压缩包后,经客户端解压才能玩到组件资源。 + +在上传至平台前,开发者应确保在本地开发的内容达到了预期的运行效果。并遵照本章的打包方式进行压缩打包,降低机器审核的打回概率或审核时间。 + + + +### 认识JAVA包体结构 + +#### PC地图 + + + +![](./images/1_1.jpg) + + + +1) 截图截选了我的世界JAVA版1.12.2游戏客户端生成的世界存档的内容。test文件夹下存放着名为test的世界信息。 + +![](./images/1_2.jpg) + + + +2) 打开这个客户端可以看到test存档。 + +![](./images/1_3.jpg) + + + +3) 返回到目录的上一级,saves文件夹下则会存放着这个客户端可以启动的所有存档文件夹,其中就有前面提到的test存档。 + +![](./images/1_4.jpg) + + + +4) 打开MCSTUDIO,点击作品库后再点击Java版组件。在右上角点击本地导入,【作品分区】选择Java版作品。【作品分类】中选择地图。【适用MC版本】选择对应存档本版。若支持多个版本可以选择多个相应版本。 + +![](./images/1_5.jpg) + + + +5) 点击导入窗体内的+号按钮,直接选择test文件夹后点击选择文件夹。 + +![](./images/1_6.jpg) + + + +6) 最后点击导入按钮,即可由MCSTUDIO拷贝作品后导入进作品库内,进行后续的测试、发布、配置等操作。若开发者选择删除作品,也不会影响到最原先的存档文件夹。 + +![](./images/1_7.jpg) + + + +7) 开发者不通过MCSTUDIO而选择自行打包时,请将存档文件夹使用7Z压缩工具打包成一个后缀为7z的压缩文档。再通过开发者平台进行上传操作。 + + + +#### PC MOD + +![](./images/1_8.jpg) + + + +1) 上传平台的MOD文件必须以jar格式作为分发给玩家游玩的标准文件格式。 + +![](./images/1_9.jpg) + + + +2) 打开MCSTUDIO,点击作品库后再点击Java版组件。在右上角点击本地导入,【作品分区】选择Java版作品。【作品分类】中选择Mod。【适用MC版本】选择对应存档本版。若支持多个版本可以选择多个相应版本。 + +![](./images/1_10.jpg) + + + +3)点击导入窗体内的+号按钮,直接双击或选择jar文件后选打开。 + +![](./images/1_11.jpg) + + + +4) 最后点击导入按钮,即可由MCSTUDIO拷贝作品后导入进作品库内,进行后续的测试、发布、配置等操作。若开发者选择删除作品,也不会影响到最原先的jar文件。 + +![](./images/1_12.jpg) + +![](./images/1_13.jpg) + + + +5) 开发者不通过MCSTUDIO而选择自行打包时,请将jar文件放入一个文件夹内,将文件夹用7z压缩工具压缩成7z后缀格式的压缩包。再通过开发者平台上传组件资源源文档。 + + + +#### PC 材质 + +![](./images/1_14.jpg) + + + +1) 截图主要展示JAVA版材质的目录和文件结构。 + +![](./images/1_15.jpg) + + + +2) 直接将材质文件夹内的内容全部选取后右键保存成zip压缩文档。 + +![](./images/1_16.jpg) + + + +3) 打开MCSTUDIO,点击作品库后再点击Java版组件。在右上角点击本地导入,【作品分区】选择Java版作品。【作品分类】中选择材质。【适用MC版本】选择对应存档本版。若支持多个版本可以选择多个相应版本。 + +![](./images/1_17.jpg) + + + +4) 点击导入窗体内的+号按钮,直接双击或选择zip文档后选打开。 + +![](./images/1_18.jpg) + + + +5) 最后点击导入按钮,即可由MCSTUDIO拷贝作品后导入进作品库内,进行后续的测试、发布、配置等操作。若开发者选择删除作品,也不会影响到最原先的材质压缩档。 + +![](./images/1_19.jpg) + + + +6) 开发者不通过MCSTUDIO而选择自行打包时,请将材质压缩文档放入一个文件夹内,将文件夹用7z压缩工具压缩成7z后缀格式的压缩包。再通过开发者平台上传组件资源源文档。 + + + +#### PC 光影 + +![](./images/1_20.jpg) + + + +1) 截图主要展示JAVA版光影的内容目录和文件结构。 + +![](./images/1_21.jpg) + + + +2) 直接将文件夹内的所有文件全部选取后右键保存成zip压缩文档。 + +![](./images/1_22.jpg) + + + +3) 打开MCSTUDIO,点击作品库后再点击Java版组件。在右上角点击本地导入,【作品分区】选择Java版作品。【作品分类】中选择光影。【适用MC版本】选择对应存档本版。若支持多个版本可以选择多个相应版本。 + +![](./images/1_23.jpg) + + + +4) 点击导入窗体内的+号按钮,直接双击或选择zip文档后选打开。 + +![](./images/1_24.jpg) + + + +5) 最后点击导入按钮,即可由MCSTUDIO拷贝作品后导入进作品库内,进行后续的测试、发布、配置等操作。若开发者选择删除作品,也不会影响到最原先的光影压缩档。 + +6) 开发者不通过MCSTUDIO而选择自行打包时,请将光影压缩文档放入一个文件夹内,将文件夹用7z压缩工具压缩成7z后缀格式的压缩包。再通过开发者平台上传组件资源源文档。 + + + +#### PC 皮肤 + +![](./images/1_25.jpg) + + + +1) 截图主要展示PC皮肤资源支持的文件格式,为结尾png的图片文档。该格式的图片保存着一道透明通道。 + +![](./images/1_26.jpg) + + + +2) 打开MCSTUDIO,点击作品库后再点击Java版组件。在右上角点击本地导入,【作品分区】选择Java版作品。【作品分类】中选择皮肤。【适用MC版本】选择默认选择全版本即可。 + +![](./images/1_27.jpg) + + + +3) 点击导入窗体内的+号按钮,直接双击或选择png文档后选打开。 + +![](./images/1_28.jpg) + + + +4) 最后点击导入按钮,即可由MCSTUDIO拷贝作品后导入进作品库内,进行后续的测试、发布、配置等操作。若开发者选择删除作品,也不会影响到最原先的图片文档。 + +5) 开发者不通过MCSTUDIO而选择自行打包时,请直接通过开发者平台上传皮肤组件源图片文档。 + + + +#### PC 玩法 + +![](./images/1_29.jpg) + + + +1) 截图截选了我的世界JAVA版1.12.2游戏客户端生成的地图的内容。test文件夹下存放着名为test的世界信息。命令方块玩法、数据包等是用我的世界JAVA版地图作为承载。 + +![](./images/1_30.jpg) + + + +2) 打开这个客户端可以看到test存档。 + +![](./images/1_31.jpg) + + + +3) 返回到目录的上一级,saves文件夹下则会存放着这个客户端可以启动的所有存档文件夹,其中就有前面提到的test存档。 + +![](./images/1_32.jpg) + + + +4) 打开MCSTUDIO,点击作品库后再点击Java版组件。在右上角点击本地导入,【作品分区】选择Java版作品。【作品分类】中选择玩法。【适用MC版本】选择对应存档本版。若支持多个版本可以选择多个相应版本。 + +![](./images/1_33.jpg) + + + +5) 点击导入窗体内的+号按钮,直接选择test文件夹后点击选择文件夹。 + +![](./images/1_34.jpg) + + + +6) 最后点击导入按钮,即可由MCSTUDIO拷贝作品后导入进作品库内,进行后续的测试、发布、配置等操作。若开发者选择删除作品,也不会影响到最原先的存档文件夹。 + +![](./images/1_35.jpg) + + + +7) 开发者不通过MCSTUDIO而选择自行打包时,请将存档文件夹使用7Z压缩工具打包成一个后缀为7z的压缩文档。再通过开发者平台进行上传操作。 + + + +### 认识基岩版包体结构 + +#### PE地图 + +![](./images/1_36.jpg) + + + +1) 截图截选了我的世界基岩版1.16.12游戏客户端生成的世界存档的内容。英文数字组合名称的文件夹下存放着该存档的世界信息。 + +![](./images/1_38.jpg) + + + +2) 打开MCSTUDIO,点击作品库后再点击基岩版版组件,分类选择【地图】。若勾选【复制文件到默认文件夹】,MCSTUDIO会将地图完整拷贝一份至C:/MCStudioDownload/work/开发者ID/Cpp/Map下。若不勾选,则不会做出此操作。 + +![](./images/1_37.jpg) + + + +3) 勾选选项时,点击窗体内的+号。不勾选选项时,点击窗体内的选择按钮。双击存档文件夹或选择文件夹后点击【选择文件夹】按钮。 + +![](./images/1_39.jpg) + + + +4) 最后点击导入按钮,即可由MCSTUDIO拷贝作品后导入进作品库内,进行后续的测试、发布、配置等操作。若开发者选择删除作品并且作品勾选【复制文件到默认文件夹】,则会系统会将C:/MCStudioDownload/work/开发者ID/Cpp/Map下相应的拷贝文件夹删除。 + +![](./images/1_40.jpg) + + + +5) 开发者不通过MCSTUDIO而选择自行打包时,请将整个存档文件夹使用任意压缩工具打包成一个后缀为zip的压缩文档。再通过开发者平台进行上传操作。 + + + +#### PE 联机地图 + +![](./images/1_41.jpg) + + + +1) 截图截选了我的世界基岩版1.16.12游戏客户端生成的世界存档的内容。英文数字组合名称的文件夹下存放着该存档的世界信息。联机地图指代可以同时在本地、本地联机、联机大厅上使用的地图资源。 + +![](./images/1_42.jpg) + + + +![](./images/1_43.jpg) + + + +2) 若世界存档携带附加包玩法,请确保存档文件夹目录下存有world_behavior_packs.json和world_resource_packs.json文件。其中pack_id对应材质包或行为包manifest内,header下的uuid。version对应manifest内header下的version。若只携带材质包或行为包,可以在world_behavior_packs.json内或world_resource_packs.json将第一个方括号内的内容全部清除。若不携带附加包,则则不需要在存档文件夹下携带此类文件。 + +![](./images/1_44.jpg) + + + +3) 开发者可以在存档中放置一个server.properties,指定玩家的游戏模式。这样在联机大厅玩这个联机地图时,玩家每次进入都是server.properties文件中设置的游戏模式。更多基岩版多人联机地图的配置设定,请参考网址[https://zh.minecraft.wiki/w/%E6%9C%8D%E5%8A%A1%E7%AB%AF%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E6%A0%BC%E5%BC%8F](https://zh.minecraft.wiki/w/%E6%9C%8D%E5%8A%A1%E7%AB%AF%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E6%A0%BC%E5%BC%8F) + +4) 当前MCSTUDIO暂不支持导入联机大厅地图,请将整个存档文件夹使用任意压缩工具打包成一个后缀为zip的压缩文档。再通过开发者平台进行上传操作。 + + + +#### PE Addon + +![](./images/1_45.jpg) + + + +1) 截图截选了我的世界基岩版Addon的内容。Addon又被称作附加包,它由材质包和行为包组成。因此开发者在上架完整的附加包内容(包含附加包和行为包),需依照接下来的规范进行打包上传即可。请注意:上架至中国版平台的附加包中,行为包内必须携带entities文件夹,材质包内必须携带textures文件夹。 + +![](./images/1_46.jpg) + + + +![](./images/1_47.jpg) + + + +2) 打开MCSTUDIO,点击作品库后再点击基岩版版组件。若勾选【复制文件到默认文件夹】,请将行为包、材质包全部选取后右键使用压缩工具压缩成zip文档,MCSTUDIO会在导入zip文档后,将一份完整拷贝放至C:/MCStudioDownload/work/开发者ID/Cpp/AddOn下。若不勾选,则直接选择带有行为包或材质包的文件夹即可。最后点击导入即可,之后开发者可以在MCSTUDIO进行后续的测试、发布、配置等操作。 + +3) 若开发者不通过MCSTUDIO而选择自行打包时,请将行为包、材质包全部选取后右键使用压缩工具压缩成zip文档。再通过开发者平台进行上传操作。 + + + +#### PE 材质 + +![](./images/1_48.jpg) + + + +1) 附加包是行为包和材质包的统称,因此材质包被算入附加包的一种类型。因此开发者在上架完整的材质包内容前,需依照接下来的规范进行打包上传。请注意:上架至中国版平台的材质包内必须携带textures文件夹。 + +![](./images/1_49.jpg) + + + +![](./images/1_50.jpg) + + + +2) 打开MCSTUDIO,点击作品库后再点击基岩版版组件,在右上角点击本地导入,分区选择【基岩版作品】,分类选择【皮肤】。若勾选【复制文件到默认文件夹】,请将材质包文件夹右键使用压缩工具压缩成zip文档,MCSTUDIO会在导入zip文档后,将一份完整拷贝放至C:/MCStudioDownload/work/开发者ID/Cpp/Material下。若不勾选,则直接选择带有材质包的文件夹即可。最后点击导入即可,之后开发者可以在MCSTUDIO进行后续的测试、发布、配置等操作。 + +3) 若开发者不通过MCSTUDIO而选择自行打包时,请将材质包文件夹右键使用压缩工具压缩成zip文档。再通过开发者平台进行上传操作。 + + + +#### PE 光影 + +![](./images/1_51.jpg) + + + +1) 光影的内容都被保存在材质包文件夹中,因此在运行机制与打包机制上与材质包类似。因此开发者在上架完整的光影内容前,需依照接下来的规范进行打包上传。请注意:上架至中国版平台的材质包内必须携带textures文件夹。 + +![](./images/1_52.jpg) + + + +![](./images/1_53.jpg) + + + +2) 打开MCSTUDIO,点击作品库后再点击基岩版版组件,在右上角点击本地导入,分区选择【基岩版作品】,分类选择【光影】。若勾选【复制文件到默认文件夹】,请将光影材质包文件夹右键使用压缩工具压缩成zip文档,MCSTUDIO会在导入zip文档后,将一份完整拷贝放至C:/MCStudioDownload/work/开发者ID/Cpp/Light下。若不勾选,则直接选择带有光影的材质包文件夹即可。最后点击导入即可,之后开发者可以在MCSTUDIO进行后续的测试、发布、配置等操作。 + +3) 若开发者不通过MCSTUDIO而选择自行打包时,请将带有光影的材质包文件夹右键使用压缩工具压缩成zip文档。再通过开发者平台进行上传操作。 + + + +#### PE 皮肤 + +![](./images/1_54.jpg) + + + +1) 截图主要展示PE皮肤资源支持的文件格式,为结尾png的图片文档。该格式的图片保存着一道透明通道。 + +![](./images/1_55.jpg) + + + +2) 打开MCSTUDIO,点击作品库后再点击基岩版版组件,在右上角点击本地导入,分区选择【基岩版作品】,分类选择【皮肤】。直接点击+号选择png结尾的皮肤图片即可。 + +3) 若开发者不通过MCSTUDIO而选择自行打包时,请将带有皮肤文件直接通过开发者平台进行上传操作。 + diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/课程02.包体的导出.md b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/课程02.包体的导出.md new file mode 100644 index 0000000..f49bf44 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/课程02.包体的导出.md @@ -0,0 +1,97 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/2_1.9097d2e2.jpg +hard: 进阶 +time: 15分钟 +--- + +# 包体的导出 + + + +#### 作者:境界 + + + +在进行作品编辑、测试、上架等操作时,开发者可能需要将包体进行备份等操作。现在使用MCSTUDIO平台将包体导入后,可以导出包体到本地电脑上的任何一个地方。 + + + +### JAVA包体的导出 + +![](./images/2_1.jpg) + + + +![](./images/2_2.jpg) + + + +1) 打开MCSTUDIO,点击作品库后再点击Java版组件。鼠标放在单个组件窗体上后直到出现【更多】按钮。接着点击【更多】,再点击【导出】。 + +![](./images/2_3.jpg) + + + +2) 进入【文件另存为】界面,选择想要的路径导出即可。 + + + +### 基岩版包体的导出 + +#### 地图资源 + +![](./images/2_4.jpg) + + + +![](./images/2_5.jpg) + + + +1) 打开MCSTUDIO,点击作品库后再点击基岩版版组件,组件标签分类选择【游戏地图】。鼠标放在单个组件窗体上后直到出现【更多】按钮。接着点击【更多】,再点击【导出】。 + +![](./images/2_6.jpg) + + + +2) 进入【文件另存为】界面,选择想要的路径导出即可。 + + + +#### Addon资源 + +![](./images/2_7.jpg) + + + +![](./images/2_8.jpg) + + + +1) 打开MCSTUDIO,点击作品库后再点击基岩版版组件,组件标签分类选择【AddOn】。鼠标放在单个组件窗体上后直到出现【更多】按钮。接着点击【更多】,再点击【导出】。 + +![](./images/2_9.jpg) + + + +2) 进入【文件另存为】界面,选择想要的路径导出即可。 + + + +#### PE 光影/材质/皮肤 + +![](./images/2_10.jpg) + + + +![](./images/2_11.jpg) + + + +1) 打开MCSTUDIO,点击作品库后再点击基岩版版组件,组件标签分类选择【其他作品】。鼠标放在单个组件窗体上后直到出现【更多】按钮。接着点击【更多】,再点击【导出】。 + +![](./images/2_12.jpg) + + + +2) 进入【文件另存为】界面,选择想要的路径导出即可。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/课程03.在手机和电脑上测试你的作品.md b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/课程03.在手机和电脑上测试你的作品.md new file mode 100644 index 0000000..c2fb263 --- /dev/null +++ b/mconline/100-历史归档教程/10-addon教程/第18章:打包导出你的作品/课程03.在手机和电脑上测试你的作品.md @@ -0,0 +1,108 @@ +--- +front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/3_2.0bf61735.jpg +hard: 入门 +time: 10分钟 +selection: true +--- +# 在手机和电脑上测试你的作品 + + + +#### 作者:境界 + + + +在进行作品测试操作时,开发者可能需要使用手机环境对作品进行测试。现在使用MCSTUDIO一键发布资源并将资源上传到自测平台后,就可以使用自测手机开发客户端进行测试。 + + + +## MCSTUDIO下载我的世界测试版 + + + +1. 打开MCSTUDIO,点击右上角的【工具箱。选择第二个选项【手机测试器】。 + + ![](./images/3_1.jpg) + +2. MCSTUDIO会将当前页面自动跳转到【开发者平台内嵌页】的二维码链接上。开发者通过手机扫描二维码的功能就能下载到手机测试客户端。 + + ![](./images/3_2.jpg) + + + +## 使用MCSTUDIO和手机设备进行作品测试 + +通过本大章的第一节【包体的结构与导入】教程,开发者对于MCSTUDIO的作品导入功能应该具有初步的掌握。不仅如此,开发者还可以通过MCSTUDIO进行测试组件,上传组件资源至开发者平台进行审核上架。基岩版组件还支持自测功能,自测组件不需要经过人工审核,只需组件通过机器审核的打包流程后,开发者可以通过手机测试客户端进行下载测试资源。我们来看看该如何达到这些想要的目的。 + +![](./images/3_3.jpg) + +![](./images/3_4.jpg) + +1. 打开MCSTUDIO,点击【作品库】,再点击【基岩版组件】。将鼠标悬浮在资源窗体上直至【更多】按钮出现,点击【更多】按钮。再次点击【发布】按钮。 + + ![](./images/3_5.jpg) + +2. MCSTUDIO会将页面跳转至【开发者平台内嵌页】的【发布资源】窗口,并自动预备上传好资源,开发者只需填写基本信息、收费模式、宣传素材等即可。最后记得点击【保存】按钮。 + + ![](./images/3_6.jpg) + +3. 建立好的基岩版资源处于【待提交审核】状态。在这个状态下,开发者可以选择自测。自测的组件最后会上架至开发者自测手机客户端。**请注意:若开发者设置了付费价格,可以先将需要自测的组件收费改成免费形式,自测结束后统一等到送审前再更改回原来的价格。** + + ![](./images/3_7.jpg) + +4. 自测手机客户端登陆时需要创建一个新的账号,并且您只能在资源中心看到自己的提交自助测试的组件。 + + + +## 单人测试环境 + +![img](./images/3_8.jpg) + +![img](./images/3_9.jpg) + +1. JAVA版资源只能使用PC进行测试。在MCSTUDIO主界面上点击【作品库】,再点击【本地作品】分类。鼠标悬浮在资源窗体上方直至显示出【测试】按钮后,点击【测试】按钮即可。 + + ![img](./images/3_10.jpg) + + ![img](./images/3_11.jpg) + +2. 基岩版资源可以使用手机自测客户端或MODPC客户端进行测试。在MCSTUDIO主界面上点击【作品库】,再点击资源所在的标签分类。鼠标悬浮在资源窗体上方直至显示出【开发测试】按钮后,点击【开发测试】按钮即可。 + + ![img](./images/3_12.jpg) + +3. 电脑启动资源前,系统可能需要下载资源所依赖的JAVA版客户端或基岩版MODPC客户端,请耐心等待。 + + + +## 多人联机测试方案 + +目前MCSTUDIO支持**本地ModPC局域网联机**和**使用开发者子母账号在手机测试端联机**。 + +### 本地ModPC局域网联机 + +![img](./images/3_13.jpg) + +1. 启动MODPC客户端或点击相应的资源进行【开发测试】。 + + ![img](./images/3_14.jpg) + +2. 可以在局域网游戏内看到其他人创建的存档房间。点击进入即可联机测试组件。 + +### 使用开发者子母账号在手机测试端上联机 + +1. 在使用手机测试端与其他成员测试玩法前,需要您先将成员设置为开发者子账号。有关添加开发者子账号的相关信息,请单击本链接查询。 +2. 再确认加入成员子账号后,请您进入手机测试端。在主页下进入 **【联机大厅】** ,选择 **【本地联机】** ,再次选择 **【自定义联机】** 。待您创建完房间后,其他成员即可在 **【自定义联机】** 区域找到您的房间。 + +![3_18](./images/3_18.gif) + +## 查找你的问题 + +模组玩法开发上遇到Bug可在开发者官网查询相关问题的解决方法,请单击本链接查询。 + +## 开发者常见问题答疑 + +![img](./images/3_16.jpg) + +在上传组件或是结算收益时,遇到一些比较常见的问题,可以在《我的世界》开发者平台-【开发者常见问题答疑】中找到对应方法。若是无法找到相关问题的解决办法,可以打开[【开发者常见问题答疑】-【反馈其他问题】表单](https://mcdev.webapp.163.com/#/feedbackModal?target=browser),提交对应问题,官方将通过站内邮件与短信告知处理进度或结果。 + +![img](./images/3_17.jpg) \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/0-摘要.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/0-摘要.md new file mode 100644 index 0000000..74aab15 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/0-摘要.md @@ -0,0 +1,15 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 5分钟 +--- + +# 摘要 + +在本章的学习中,你将学到如何使用**我的世界开发工作台**(**MC Studio**)创建一个自定义合成**配方**(**Recipe**)的玩法组件。 + +- 在第一节(*新建第一个空白基岩版组件作品*)中,你将接触到何为我的世界开发工作台,并学习开发工作台的界面。通过对开发工作台的简单操作,你将成功创建你的第一个空白基岩版组件作品。这意味着你已经成功迈出了我的世界玩法创作的第一步! +- 在第二节(*使用配方配置自定义新的合成配方*)中,通过开发工作台中的简单可视化界面,你将学习如何通过**配置**(**Configuration**)功能新建一个自定义合成配方。通过一系列易于上手的操作,合成配方的自定义将不在话下。 +- 在最后一节(*保存并运行玩法*)中,你将学习到如何运行和自测一个玩法组件。这将有助于你在今后的日子里更加有力地编写和调试自己的模组! + +本章关键词:我的世界开发工作台 玩法组件 自定义配方 命名空间 配置 编辑器 自测 diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/1-新建第一个空白基岩版组件作品.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/1-新建第一个空白基岩版组件作品.md new file mode 100644 index 0000000..8166325 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/1-新建第一个空白基岩版组件作品.md @@ -0,0 +1,34 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 10分钟 +--- + +# 新建第一个空白基岩版组件作品 + +**我的世界开发工作台**(**MC Studio**)是一个集成了开发者启动器、地图编辑器、关卡编辑器、逻辑编辑器、特效编辑器、云端测试平台等功能的一体化开发工具。它极易上手的操作和简单易懂的可视化界面能够为我们的开发工作提供极大的便利。 + +接下来,我们通过我的世界开发工作台,为我的世界基岩版制作第一个玩法组件作品! + +## 下载我的世界开发工作台 + +我的世界开发工作台可以在我的世界开发者官网([https://mc.163.com/dev/](https://mc.163.com/dev/))下载。进入官网,点击“下载我的世界开发工作台”即可开始下载。下载完成后点击安装包开始安装并等待安装完成。 + +![我的世界开发者官网](./images/1.1_official_dev_website.png) + +安装完成后桌面即会出现“**我的世界开发者启动器**”的快捷方式。双击打开,在登陆界面中输入开发者账号密码进行登录,便可以查看到我的世界开发工作台的主界面了。 + +![我的世界开发工作台新闻页面](./images/1.1_mc_studio_main_screen.png) + +## 新建组件 + +在上图中我们可以看到,窗口左侧有一个竖排导航栏,这里是我的世界开发工作台的诸多功能的选项卡。我们点击“+新建”按钮,跳转到新建组件页面。 + +![我的世界开发工作台新建页面](./images/1.1_mc_studio_new_screen.png) + +此处的**组件**(**Component**)指代的便是可以作为一个作品而独立存在的模组和地图文件的集合。我们只需要点击“**空白地图**”或“**空白AddOn**”便可以新建一个基岩版空白组件。当然,等你熟悉了组件的结构之后,你也可以通过下方的各种**模板**来快速生成一些具备一定初始功能的组件。此处我们只希望建立一个空白的不包含地图的组件,即空白的**附加包**(**Add-on**)组件,所以我们点击上方的“空白AddOn”按钮。此时我们看到了一个弹出窗口: + +![新建空白附加包](./images/1.1_new_component_configurating.png) + +根据你的需要进行配置之后,点击“启动编辑”,即可打开我的世界开发工作台的**编辑器**(**Editor**)。此时,我们便已经成功新建了一个空白的附加包组件啦!之后,你便可以在这个空白的组件上大展拳脚,充分发挥你的想象力和技术力,开始你的我的世界开发之旅! + diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/2-使用配方配置自定义新的合成配方.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/2-使用配方配置自定义新的合成配方.md new file mode 100644 index 0000000..f01d91a --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/2-使用配方配置自定义新的合成配方.md @@ -0,0 +1,109 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 15分钟 +--- + +# 使用配方配置自定义新的合成配方 + +接下来我们使用关卡编辑器来添加一个自定义配方。不过在在此之前,我们首先需要熟悉一下编辑器的界面和编辑器的新旧之别。 + +## 新旧编辑器 + +长久以来,我的世界开发工作台都使用的编辑器都是现在被称作旧版编辑器的一款软件。在最新的版本中,我们引入了新版编辑器,该编辑器是旧版编辑器的一个重构,引入了旧版编辑器的大部分功能,同时优化了很多功能,使得模组的开发更加便捷与高效。 + +旧编编辑器的初始化页面: + +![旧编编辑器的初始化页面](./images/1.2_level_editor_init_screen.png) + +新编编辑器的初始化页面: + +![新编编辑器的初始化页面](./images/1.2_new_level_editor_init_screen.png) + +### 将组件作品升级为新版编辑器作品 + +在上一节的末尾,我们的操作将默认打开旧版编辑器。而为了使我们的作品开发更加有效,在接下来的教程中我们将使用新版编辑器进行讲解。所以我们需要把我们的作品升级为新版编辑器作品。我们首先切换到“**最近**”标签页,如图所示,这里有我们刚刚创建的组件作品。 + +![我的世界开发工作台最近页面](./images/1.2_mc_studio_recent_screen.png) + +点击“**编辑**”按钮,点击“**升级作品**”,并在弹出的对话框中点击“**确定**”。之后开发工作台会自动创建一个同名的新版作品,并打开该作品。之后,你便可以使用新版编辑器进行作品编辑了! + +![作品编辑对话框](./images/1.2_recent_screen_component_edit.png) + +![升级警告对话框](./images/1.2_upgrade_warning.png) + +在返回到“最近”标签页时,你可以看到新版作品的周围带有绿色边框,并标注有“新版”字样。 + +![新版作品](./images/1.2_mc_studio_recent_screen_with_new_editor.png) + +## 命名空间 + +接下来我们希望添加一个自定义配方。但是在添加配方之前,我们有一个不得不需要理解的概念,那就是**命名空间**(**Namespace**)。每个模组都具有且必须具有至少一个命名空间。命名空间就像一个身份证号一样,他保证了模组与模组之间就算有重名的项目出现,也可以互不干扰,互相可以分辨得开。 + +你也可以把命名空间比作文件夹。不同的文件夹中就算出现同名的文件,他们依旧可以共存。但是如果没有了文件夹,那么相同命名的文件之间就会出现冲突,出现要么只能保留一个,要么就要有一个文件妥协而改名的尴尬处境。因此,给自己的模组一个合适的命名空间是非常必要的。 + +### 更改命名空间 + +我的世界开发工作台的编辑器提供了一个快速更改命名空间的功能。打开新版编辑器,在编辑器顶部的导航栏点击:作品 -> 命名空间,即可打开更改命名空间的对话框。 + +![作品 -> 命名空间](./images/1.2_navigation_namespace.png) + +命名空间实质上是一个标记你所创作的内容所有权的标识符,所以在给你的命名空间起名字时,我们建议使用英语单词配合下划线的方式进行命名。在本教程中,为了行文统一,我们使用`tutorial_demo`作为命名空间。如果你正在跟随本教程进行实践操作,不必拘泥,请尽情地使用你自己想用的命名空间。只需记住一点,命名空间要尽可能的独特和唯一,只有这样才能把你的作品和其他人的作品更好地区分开来。 + +![输入命名空间](./images/1.2_modify_namespace.png) + +## 配置 + +在新版编辑器中,我们引入了**配置**(**Configuration**)功能。它默认在关卡编辑器窗口的左下角。 + +![配置子窗口](./images/1.2_configuration_subwindow.png) + +但此时配置是空的,因为我们还没有创建过任何配置。因此我们需要在“**资源管理**”窗口内通过“**新建**”功能来创建新的配置。 + +![资源管理 - 新建](./images/1.2_resource_management_new.png) + +一个配置是一系列有关某个功能的文件的集合。通过对配置的修改,可以实现相关文件的自动修改和自动匹配。这使得复杂的文件变得可视化和有序化。我们在“新建文件向导”对话框中选中“**配置**”选项卡。然后选择一个你想要创建的配置,即可通过向导完成一个配置的创建。 + +![配置选项卡](./images/1.2_new_file_wizard_config.png) + +### 创建新的合成配方 + +我们回归到本节的正题,通过配置功能创建一个自定义合成**配方**(**Recipe**)。我们只需要在上图窗口中选择“配方”配置,即可进入新建自定义配方的向导。我们可以看到,这里有两个功能,第一个是选择**数据模板**(**Data Template**)。数据模板是编辑器内置的已经有一些初始数据的配置,如果选择数据模板,即意味着你可以在一个已经设置了一些属性的配置的基础上继续进行操作。我们选择“**空**”,即创建一个完全空白的配方配置。第二个功能是给配方**命名**(**Naming**)。如同刚才所述的命名空间代表着模组的唯一标识,这里的名字代表着该配方的唯一标识。给它起一个好名字有助于之后再次看到它时能够迅速回忆起其内容,也有助于避免配方之间的冲突。谨记,命名只能使用英文、数字和下划线,且对大小写不敏感,因此`Aa`和`aa`本质上是相同的名字,所以建议所有的字母都采用小写。这里我们使用`recipe_demo`来代表这个演示用的配方。你可以根据自己的喜好与习惯对其赋予任意的命名。 + +![新建自定义配方](./images/1.2_recipe_config_wizard.png) + +我们可以看到,配置会为我们自动创建一个叫做`<命名空间>_<配方名>.json`的文件,这是该配置所对应的数据文件。命名空间的存在使其有效避免了与其他模组的同名配方的冲突。 + +在创建配方配置后,我们便可以在“配置”窗口和“属性”窗口下看到我们刚创建的配方了。如果你成功地看到了如下界面,那么恭喜你,你已经成功创建了一个空白的自定义配方!现在我们只需要把该配方稍加完善即可得到我们想要的结果! + +![完成配方配置的创建](./images/1.2_post-creation_of_recipe.png) + +### 给配方添加属性 + +目光转移到屏幕右侧,我们看到了配方的属性栏。在此我们可以更改我们刚才新创建的配方的属性。 + +![配方属性栏](./images/1.2_recipe_property.png) + +**配方类型**代表着配方的适用情况。 + +- **有序合成**是类工作台配方的一种。对于这种类型的配方,玩家必须摆出和配方的形状一模一样的物品组合时,才会合成出对应物品。在原版中只适用于工作台。 +- **无序合成**也是类工作台配方的一种,但是只要合成网格中对应的物品及其数目满足要求即可合成出对应的结果,无需形状如何。在原版中,除了工作台,这种配方还适用于制图台和切石机等方块。 +- **熔炉配方**代表着类熔炉配方,熔炉、高炉、营火、烟熏炉等都适用于此类配方。 + +**配方构造**中的网格代表配方的输入物品和形状,点击网格中的槽位,即可弹出一个可视化物品选择器。通过浏览和搜索,你可以选择任意的原版方块和在我的世界开发工作台中自定义的方块作为槽位物品。这对开发者非常有帮助。 + +![资源选择器](./images/1.2_resource_pickup_dialog.png) + +**配方结果**代表着配方的输出物品。你依旧可以通过点击其右侧文件夹形状的图标来进入物品选择器,从而选取对应的配方结果。**结果数量**即对配方进行一次合成产出的物品数目。 + +### 示例:可合成的命名牌 + +我们将配方属性调整至如下所示: + +![可合成的命名牌](./images/1.2_craftable_nametag.png) + +即可在游戏中获得一个可合成的命名牌。 + +![可合成的命名牌配方](./images/1.2_craftable_nametag_recipe.png) + +恭喜你!你已经熟练掌握了自定义合成配方的制作!但是,这还不代表着万无一失。你还没有保存和在游戏内进行测试。下一节我们将一起学习如何保存并自测玩法组件。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/3-保存并运行玩法.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/3-保存并运行玩法.md new file mode 100644 index 0000000..b305f8b --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/3-保存并运行玩法.md @@ -0,0 +1,63 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 15分钟 +--- + +# 保存并运行玩法 + +及时保存玩法是开发过程中非常重要的一环。只有养成及时保存的习惯才能避免自己的心血成果因各种不确定因素而丢失的情形发生。而在保存之后,我们也需要通过实机测试来确保自己的玩法真正有效地加入了游戏。 + +## 保存玩法 + +我们在编辑器的右上角,可以看到有几个全局按钮。我们只需点击“**保存**”按钮,便可以启动组件的保存程序。 + +![编辑器右上角](./images/1.3_editor_top-right_save.png) + +如果见到如下提示弹窗,说明你已经保存成功了。你可以安心地继续你的创作,或者稍微暂停离开椅子休息一下了。 + +![保存成功](./images/1.3_save_successfully.png) + +## 运行和自测 + +只在编辑器中进行操作,终归不是真实的游戏体验。在你的作品分发到千千万万玩家手中之前,确保玩法不会出现纰漏最好的办法就是打开游戏进行实测。我的世界开发工作台便提供了这一功能。 + +### 电脑开发版自测 + +在我的世界开发工作台中,最简单的运行和自测方式便是使用**我的世界基岩版电脑开发版**(即**Mod PC开发包**)进行自测。你有两种进入电脑开发版的方式。 + +#### 编辑器内直接进入 + +不要退出编辑器,目光定位到右上角。我们可以看到一个“**运行**”按钮。点击运行按钮,你的玩法组件将自动保存,同时系统开启我的世界基岩版电脑开发版,自动进入一个加载着你的玩法组件的存档进行测试。 + +![运行按钮](./images/1.3_editor_top-right_run.png) + +#### 从主界面进入 + +在我的世界开发工作台中找到“**最近**”或“**作品库**”标签页,找到你的作品。将鼠标移至“**开发测试**”按钮。 + +![开发测试按钮](./images/1.3_mc_studio_lib_screen_dev_test.png) + +点击开发测试按钮将会弹出一个开发测试对话框。进行一定配置后点击“**开始**”按钮,即可达到和从编辑器内进入相同的效果——成功进入我的世界基岩版电脑开发版进行测试。 + +![开发测试对话框](./images/1.3_dev_test_dialog.png) + +### 手机开发版自测 + +**我的世界基岩版手机开发版**自测需要我们先将组件发布至云端。之后我们便可以从手机开发版下载进行测试。我们将鼠标移动至地图或组件作品上,点击“**更多**”按钮。 + +![更多按钮](./images/1.3_mc_studio_lib_screen_more.png) + +之后点击“**发布**”按钮。根据发布资源的流程依次填写好相关内容,点击保存,再点击提交审核。当作品处于“审核中”的状态时,便可以在手机开发版中访问到该作品。 + +![发布按钮](./images/1.3_release.png) + +#### 下载和进入手机开发版启动器 + +在我的世界开发工作台上找到“**管理**”标签页。找到顶部“**开发者内容管理工具**”一栏中的“**测试版启动器下载**”。点击该按钮,在弹出的对话框中会出现两个二维码,分别是iOS版本和Android版本的手机开发版启动器下载链接。使用对应的手机扫码下载、安装,即可得到手机开发版启动器应用程序。 + +![管理标签页](./images/1.3_mc_studio_management_screen_test_launcher_download.png) + +![下载开发版的对话框](./images/1.3_test_launcher_download.png) + +在我的世界基岩版手机开发版中打开你发布的组件,即可进入地图测试啦! \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/README.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.1_mc_studio_main_screen.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.1_mc_studio_main_screen.png new file mode 100644 index 0000000..3513ad4 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.1_mc_studio_main_screen.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.1_mc_studio_new_screen.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.1_mc_studio_new_screen.png new file mode 100644 index 0000000..86e169d Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.1_mc_studio_new_screen.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.1_new_component_configurating.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.1_new_component_configurating.png new file mode 100644 index 0000000..6fe48d8 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.1_new_component_configurating.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.1_official_dev_website.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.1_official_dev_website.png new file mode 100644 index 0000000..6ffc59f Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.1_official_dev_website.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_configuration_subwindow.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_configuration_subwindow.png new file mode 100644 index 0000000..4486aa3 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_configuration_subwindow.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_craftable_nametag.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_craftable_nametag.png new file mode 100644 index 0000000..6ccf0f2 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_craftable_nametag.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_craftable_nametag_recipe.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_craftable_nametag_recipe.png new file mode 100644 index 0000000..5a5143e Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_craftable_nametag_recipe.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_level_editor_init_screen.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_level_editor_init_screen.png new file mode 100644 index 0000000..1fc688e Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_level_editor_init_screen.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_mc_studio_recent_screen.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_mc_studio_recent_screen.png new file mode 100644 index 0000000..bc0b0ab Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_mc_studio_recent_screen.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_mc_studio_recent_screen_with_new_editor.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_mc_studio_recent_screen_with_new_editor.png new file mode 100644 index 0000000..89888bb Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_mc_studio_recent_screen_with_new_editor.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_modify_namespace.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_modify_namespace.png new file mode 100644 index 0000000..ba61d46 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_modify_namespace.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_navigation_namespace.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_navigation_namespace.png new file mode 100644 index 0000000..df5a3d1 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_navigation_namespace.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_new_file_wizard_config.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_new_file_wizard_config.png new file mode 100644 index 0000000..830a6b6 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_new_file_wizard_config.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_new_level_editor_init_screen.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_new_level_editor_init_screen.png new file mode 100644 index 0000000..8ab00ca Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_new_level_editor_init_screen.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_post-creation_of_recipe.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_post-creation_of_recipe.png new file mode 100644 index 0000000..f27a729 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_post-creation_of_recipe.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_recent_screen_component_edit.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_recent_screen_component_edit.png new file mode 100644 index 0000000..1fa9ed6 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_recent_screen_component_edit.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_recipe_config_wizard.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_recipe_config_wizard.png new file mode 100644 index 0000000..5210efc Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_recipe_config_wizard.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_recipe_property.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_recipe_property.png new file mode 100644 index 0000000..f085116 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_recipe_property.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_resource_management_new.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_resource_management_new.png new file mode 100644 index 0000000..53cfa19 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_resource_management_new.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_resource_pickup_dialog.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_resource_pickup_dialog.png new file mode 100644 index 0000000..3b6931e Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_resource_pickup_dialog.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_upgrade_warning.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_upgrade_warning.png new file mode 100644 index 0000000..0a93c43 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.2_upgrade_warning.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.3_dev_test_dialog.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.3_dev_test_dialog.png new file mode 100644 index 0000000..4523b2b Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.3_dev_test_dialog.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.3_editor_top-right_run.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.3_editor_top-right_run.png new file mode 100644 index 0000000..a17f9e6 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.3_editor_top-right_run.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.3_editor_top-right_save.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.3_editor_top-right_save.png new file mode 100644 index 0000000..f2822fd Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.3_editor_top-right_save.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.3_mc_studio_lib_screen_dev_test.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.3_mc_studio_lib_screen_dev_test.png new file mode 100644 index 0000000..d3cde49 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.3_mc_studio_lib_screen_dev_test.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.3_mc_studio_lib_screen_more.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.3_mc_studio_lib_screen_more.png new file mode 100644 index 0000000..197fe16 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.3_mc_studio_lib_screen_more.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.3_mc_studio_management_screen_test_launcher_download.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.3_mc_studio_management_screen_test_launcher_download.png new file mode 100644 index 0000000..c387aed Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.3_mc_studio_management_screen_test_launcher_download.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.3_release.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.3_release.png new file mode 100644 index 0000000..34d177a Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.3_release.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.3_save_successfully.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.3_save_successfully.png new file mode 100644 index 0000000..8e438b7 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.3_save_successfully.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.3_test_launcher_download.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.3_test_launcher_download.png new file mode 100644 index 0000000..51b28f7 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/0-使用我的世界开发工作台制作第一个玩法/images/1.3_test_launcher_download.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/0-摘要.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/0-摘要.md new file mode 100644 index 0000000..a862e54 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/0-摘要.md @@ -0,0 +1,17 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 5分钟 +--- + +# 摘要 + +在本章中,你将认识我的世界中的各种基本概念。这将有助于你在后续开发过程中能够更好地理解我的世界这款游戏,从而写出优异的模组。 + +- 在第一节(*区块、世界和存档*)中,你将认识我的世界中一个**世界**(**World**)的基本组成,了解一个世界是由一个个的**区块**(**Chunk**)组成的,而区块又是一个个**方块**(**Block**)组成的。 +- 在第二节(*实体、生物和弹射物*)中,你讲学习到这个世界的**生物**(**Mob**)事实上都属于一种更广泛的类别——**实体**(**Entity**),以及了解到**弹射物**(**Projectile**)其实也是一种实体。 +- 在第三节(*方块、物品和物品实体*)中,你将认识到方块和**物品**(**Item**)的关系,了解到物品和**物品实体**(**Item Entity**)的不同。 +- 在第四节(*模型、纹理和动画*)中,你讲学习到两种不同的**模型**(**Model**),分别是原版模型的**几何**(**Geometry**)和中国版特有的骨骼模型。你还会认识到什么是**纹理**(**Texture**)和**动画**(**Animation**)。 +- 在最后一节(*游戏界面和特效粒子*)中,你将熟悉什么是**UI**、**粒子**(**Particle**)和**特效**(**Effect**),了解我的世界的特效粒子是如何运行的。 + +本章关键词:世界 存档 区块 子区块 方块 碰撞箱 亮度 材料 形状 液体 物品 方块物品 堆叠 耐久 武器 工具 盔甲 物品实体 实体 生物 玩家 弹射物 模型 纹理 动画 序列帧 UI 粒子 特效 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/1-区块、世界和存档.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/1-区块、世界和存档.md new file mode 100644 index 0000000..ed1b2c8 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/1-区块、世界和存档.md @@ -0,0 +1,29 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 10分钟 +--- + +# 区块、世界和存档 + +在本节中,我们将一起了解我的世界中一个世界的组成方式。 + +## 世界和存档 + +在我的世界中,**世界**(**World**)是玩家最主要的游玩场所。一个世界便是玩家进入游戏后所观察到一切事物的总和。丰富多彩的群系,林林总总的生物和各式各样的地形与组成地形的方块,都是这个世界的一部分。对于每一个世界,我们都有一个**存档**(**Level**)来储存它。存档是一个世界的文件记录,它通常是以一个文件夹的形式而存在的。在游戏选择世界的界面上,我们能看到我们创建过、游玩过的的各式各样的存档。这其中每一个条目都是代表着一个存档,同时也是一个可以供我们选择进入的世界 + +![游戏的世界选择界面](./images/2.1_world_screen.png) + +在我们进入世界后,我们最先接触到的便是**方块**(**Block**)了。由于世界约等于是无限大的,所以方块也可以认为是无限多的。一个方块便是在游戏中以1m³为单位的一个块状物体。有的方块可以充斥这个1m³的空间,我们称作完整方块;有的则更小一些,形状也多样一些,这些都是不完整方块;开发者还可以通过自定义方块定义出超过该空间大小的方块。方块组成了这个世界的基本形状,配合以生物群系和各种生物,使得这个世界更加多彩。 + +![方块举例](./images/2.1_block_example.png) + +## 区块 + +世界在横向上非常广大,为了使世界与世界之间不出现重复,游戏使用**种子**(**Seed**)来配合生成世界。每一个种子都将对世界生成器造成随机的影响,使得每一个世界的每一个相同的位置都会出现不同的结果。再加之以玩家后续在世界中还会进行各种建筑、开采和改造,我们便需要一种存储方式来将这种不同存储在存档中。**区块**(**Chunk**)应运而生。 + +区块是一个尺寸为16×256×16个方块的集合。由于在我的世界的坐标中,x坐标和z坐标代表长宽,y坐标代表高度。所以我们可以看到区块其实是一个竖长条的长方体。而一个存档,便是由一个个区块并排横向排列的一个二维地图。 + +![一个区块](./images/2.1_chunk.png) + +区块又由一个个的**子区块**(**Subchunk**)组成,每个子区块是一个16×16×16个方块的立方体。所以一个区块事实上是由16个子区块自下而上排列而成的。我的世界中很多游戏机制都是以区块或者子区块为单位的,更多的信息可以参考[Minecraft Wiki上的区块页面](https://zh.minecraft.wiki/w/%E5%8C%BA%E5%9D%97)。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/2-实体、生物和弹射物.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/2-实体、生物和弹射物.md new file mode 100644 index 0000000..bc1cf4d --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/2-实体、生物和弹射物.md @@ -0,0 +1,31 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 10分钟 +--- + +# 实体、生物和弹射物 + +本节中,我们将一起学习实体,了解实体的组成和分类,为以后自定义实体打下概念基础。 + +## 生物和实体 + +在一个存档中,除了方块之外,和玩家接触最密切的便是**生物**(**Mob**)了。在世界中,猪、牛、羊等有好生物,僵尸、骷髅、末影人等敌对生物,还有末影龙、凋灵等Boss,都是生物的一员。然而,生物并不是这个世界的非方块造物的全部。比如,我们经常使用盔甲架摆放物品、做出造型,有时还拿它加入红石电路,制作一些高级的机械。而我们刚刚提到的盔甲架,便不是生物。它属于一个更上层的概念,那便是**实体**(**Entity**)。 + +![盔甲架](./images/2.2_armor_stand.png) + +实体也称为**活动对象**(**Actor**),是一种拥有各种形状、各种行为的可交互的物体。与方块不同,实体拥有生命值,可以遭到攻击。当然,有时候有些实体也可以攻击他人,和玩家一样破坏一些方块。**玩家**(**Player**)本质上也是实体的一种。 + +![实体和方块](./images/2.2_entity_and_block.png) + +一般来说,实体分为生物和**非生物实体**(**Non-mob Entity**),但是这两种实体其实并没有非常严格的界定。一般人们认为,拥有AI的的实体都被称为生物。在我们自定义实体时,我们也可以给予该实体一些**AI意向**(**AI Goal**),使其富有生命活力。 + +![村民的AI意向](./images/2.2_villager_aigoals.png) + +## 弹射物 + +**弹射物**(**Projectile**)是一种典型的非生物实体,他是一类可以被玩家抛出或发射器发射出去的实体,往往在空中运行一定的轨迹后击中某个方块或实体,然后触发一定的效果。箭、雪球、鸡蛋、末影珍珠、末影之眼等都是弹射物的一种。 + +![抛出的末影珍珠](./images/2.2_thrown_ender_pearl.png) + +弹射物一般拥有和其他实体不同的运动计算方式,而且一般和其对应的物品共享同一个纹理贴图,是一种非常特殊的实体。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/3-方块、物品和物品实体.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/3-方块、物品和物品实体.md new file mode 100644 index 0000000..bb39813 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/3-方块、物品和物品实体.md @@ -0,0 +1,37 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 10分钟 +--- + +# 方块、物品和物品实体 + +在本节中,我们将一起进一步学习方块,学习物品和物品实体的区别。 + +## 方块 + +**方块**(**Block**,又称作**Tile**)是世界的基本组成单位,是玩家和这个世界交互的重要途径。方块可以被玩家拿在物品栏里,也可以被放置在世界中。当方块被放置在世界中时,大部分方块将在世界中占据一个块状的位置,阻止玩家和其他生物从此处通过,这被称作方块的**碰撞箱**(**Collision Box**)。并非所有的方块都有碰撞箱,如草、告示牌和火把都没有碰撞箱,玩家可以直接从其中穿过。 + +有些方块具有发光的能力,会使周围的亮度大为提升,如火把和荧石。而有些方块则会阻挡光的前进,减少或消除光照的等级,如会完全阻挡光线的石头和会部分阻挡光线的冰。不同光照属性的方块使世界中的**亮度**(**Lighting**)系统丰富多彩。 + +![火把和荧石的发光](./images/2.3_torch_and_glowstone.png) + +方块有着不同的**材料**(**Material**)和**形状**(**Shape**)。方块的材料决定了其固液性质、开采性质和在地图上的颜色表现,而方块的形状则决定了其碰撞箱的造型、视觉的形状和渲染效果。 + +水和岩浆等方块属于**液体**(**Liquid**),他们也是方块的一种。只不过,他们有着一种特殊的传播方式。水和岩浆的流动本质上属于在其周围特定的位置放置一个新的方块,只不过新方块的碰撞体积会表现得要么更小一些(向四周流动),要么是整个方块(向下流动)。 + +## 物品 + +**物品**(**Item**,又称作**Icon**)是出现在玩家或其他实体的手上或物品栏中的物体。物品往往具有一些和方块或实体的交互性。例如,拿着名为剪刀的物品对着绵羊按下使用键,就可以将绵羊身上的毛剪落。有一些物品的图标是一个方块,放置后也可以在世界上生成一个方块,这种物品便是**方块物品**(**Block Item**),方块被破坏后被玩家收集到物品栏中的便是方块物品。广义上讲,方块物品也是物品的一种,只不过他们都是和方块一一对应的,因此大部分人也不区分方块物品和方块的关系,将其混淆使用,统一称呼为方块。此时,剩下的非方块物品便被称呼为狭义的物品。 + +很多物品可被**堆叠**(**Stack**)。堆叠后的物品会在物品的右下角显示一个数字,代表其目前的堆叠数目。有些物品可以被使用,使用后往往会减少其堆叠数目。但是,有一类特殊的物品的使用并不会减少堆叠数目,相反,他们会损失一种被称作**耐久**(**Durability**)的属性,这种特殊的物品往往是一种**武器**(**Weapon**)、**工具**(**Tool**)或**盔甲**(**Armor**)。当然,一般来说,这种特殊物品的最大堆叠数目是1,不过他们却可以使用相当多的次数而不消失。 + +![使用了一半的盔甲](./images/2.3_half-used_armor.png) + +## 物品实体 + +**物品实体**(**Item Entity**)其实便是我们常说的**掉落物**(**Drop Item**)。破坏方块、击杀生物都有可能会产生掉落物,即物品实体。顾名思义,物品实体本质上是一种实体,但是其被渲染成了一个或一组物品的样子,并且可以自动与玩家交互——被靠近的玩家拾获。所以物品实体其实并不是物品,只不过他们存储了物品的数据,拥有物品的外观,并且可以在被玩家拾获变成玩家物品栏内的物品。 + +![一个物品实体](./images/2.3_drop_item.png) + +为了使大家对概念更加地明晰,我们可以考虑如下场景:世界中存在一个草方块,它是一个方块。我破坏了草方块,他掉落了一块泥土,这个泥土其实是一个物品实体。更精确地说,它是一个方块物品的物品实体。我拾起了泥土,它变成了我物品栏中真真正正的物品。不过,这个物品是一个方块物品。在我对着地面放置这个物品后,它再次变成了方块,变成了世界中某处的一个泥土方块。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/4-模型、纹理和动画.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/4-模型、纹理和动画.md new file mode 100644 index 0000000..de6ea0a --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/4-模型、纹理和动画.md @@ -0,0 +1,53 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 15分钟 +--- + +# 模型、纹理和动画 + +在本节中,我们将一起学习我的世界中的模型,了解模型的本质和分类。进一步了解纹理和动画对于模型的依赖性和重要性。 + +## 模型 + +在我的世界中,不论是方块、物品还是实体,他们本质上都是由一些具有体积的更基础的元素构成的,比如,绵羊是由头部的长方体、身体的长方体和四个代表着四肢的竖直长方体构成的。我们通常将这种具有一定体积的一个由多种基本元素构成的几何形状称为一个**模型**(**Model**)。 + +在我的世界中,每种生物由具备一个模型。我们可以轻松地在游戏中通过观察看出每种生物模型的形状。每种方块也有一个应用在它身上的模型,不过在游戏中,很多方块可能会共用一个模型。比如全体的1×1×1的完整方块,他们共用一个被称作`block`的模型,代表着标准正方体。 + +![不同的模型](./images/2.4_some_models.png) + +开发者也可以在自己的模组中自定义生物和方块的模型。我的世界中国版支持两种模型格式,一种是微软开发支持的**原版模型**格式,使用该格式编写的模型作品被称为**几何**(**Geometry**),另一种是网易开发支持的**骨骼模型**格式,使用该格式得到的作品被称作**骨架**(**Skeleton**)。在我的世界开发工作台中,我们可以通过外部文件导入这两种模型。根据这两种模型的制作工具和格式的不同,又分别称作**BlockBench模型**(`*.bbmodel`)和**FBX模型**(`*.fbx`)。 + +### 原版模型 + +**原版模型**本质上是一个按照国际版的[几何JSON模式(Geometry JSON Schema)](https://docs.microsoft.com/en-us/minecraft/creator/reference/content/schemasreference/schemas/minecraftschema_geometry_1.16.0)编写的JSON文件。开发者可以使用低多边形风格的编辑器Blockbench中可以通过可视化编辑的形式来创建原版模型几何,然后通过我的世界开发工作台导入到模组中。在下一章中我们将重点介绍该编辑器的使用方法。 + +原版模型可以直接挂接在实体上,稍加修改后也可以挂接到方块上,为我们自定义模型提供了很大的便利。通过挂接在**附着物**(**Attachable**,***挂件***)上后,还可以通过和物品配合实现3D物品模型的制作。但是,原版模型也有相当大的缺点。最重要的一点就是原版模型只支持低多边形风格,即我们俗称的方块像素风格。这导致我们最多实现至四边形网格,而无法实现曲线和曲面。同时,由于模型的局限性,我们也无法在原版模型的基础上实现动态的纹理。因此,通过网易开发组的努力,我们实现了骨骼模型的支持。 + +### 骨骼模型 + +**骨骼模型**本质上也是一个JSON文件,但是它是可以由FBX格式文件导入而生成的,因此具备几乎所有原FBX模型的性质。因此,骨骼模型支持各种高级的模型特性,比如可以创造曲线和曲面。同时,由于我的世界中国版的支持,骨骼模型还可以加载模型贴图序列帧动画、绑定各种高级特效,从而使模组玩法和外观更加丰富。 + +不管是原版模型还是骨骼模型,模型中的基本部件都被称作是一个**骨骼**(**Bone**)。每个骨骼有着自己特定的形状和位置,而骨骼与骨骼之间又相对独立,这有助于模型更好地完成预期的操作。 + +## 纹理 + +**纹理**(**Texture**,旧译**材质**)是指覆盖在模型上的一层贴图文件,也是我们在游戏中最能直观感受到的一个事物。不管是方块、物品还是实体,他们都是以特定的纹理展示到玩家的镜头前的。比如,在工作台方块上,我们看到工作台顶部有一块桌布,桌布上有一个3×3网格,而侧面则摆放着锯子、齿轮等工具。这些外观贴图其实就是纹理,而工作台本身就是在一个1×1×1的立方体模型的6个面上分别贴上了特定的纹理而得到的方块。实体亦然,只不过由于实体的模型较为复杂,我们看到的纹理贴图也稍微有些复杂。 + +![工作台的六个面](./images/2.4_workbench_texture.png) + +![史蒂夫的纹理](./images/2.4_steve_texture.png) + +## 动画 + +一个模型如果仅仅是一个模型,那么它便只能是静态的,好似一个假人。为了让模型更加逼真,实现各种运动或姿态的需要,**动画**(**Animation**)是必不可缺的一部分。一个动画就是一个和模型中的各个骨骼相绑定,然后通过**动画控制器**(**Animation Controller**)来使各个骨骼进行相对运动的过程。玩家的奔跑,生物的攻击,鱼儿的游泳,乃至潜影贝盖子旋转打开,这些都是动画。有了动画,我们的游戏内容才会更加生动形象。 + +潜影贝的动画行为,其中盖子旋转打开时,盖子所对应的骨骼进行了旋转变换和平移变换: + +![潜影贝动画](./images/2.4_shulker_shooting.gif) + +动画并不是实体独有的,只要游戏引擎支持,所有的模型都可以配以动画。比如,通过中国版的自定义方块实体功能,便可以为方块模型添加动画,以达到和实体模型动画相同的效果。 + +## 序列帧动画 + +在我的世界中一提到动画,人们往往想起的是“模型的动画”,也就是我们刚才介绍的动画。但是,还有一种“动画”存在于游戏中,它便是**序列帧动画**(**Frame Animation**)。序列帧动画是一种“纹理的动画”,又称**逐帧动画**,其原理是在“连续的关键帧”中分解动画动作,在时间轴的每帧上逐帧绘制不同的内容,使其连续播放而成动画。所以,这种动画非常类似于我们小时候玩的翻页书,在微软的开发文档中又称作**翻书动画**(**Flipbook Animation**)。大家口中经常说的动态纹理或动态贴图,便是指的序列帧动画。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/5-游戏界面和特效粒子.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/5-游戏界面和特效粒子.md new file mode 100644 index 0000000..b8e92a8 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/5-游戏界面和特效粒子.md @@ -0,0 +1,35 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 15分钟 +--- + +# 游戏界面和特效粒子 + +在本节中,我们将一起认识游戏界面,了解什么是特效和粒子。 + +## 游戏界面 + +每当我们打开我的世界游戏时,我们都能看到一个界面展现在我们面前,上面有着按钮与文字。当我们通过按钮与界面交互时,我们可能会看到弹窗或新的界面。我们进入游戏的世界后,除了眼前的立体的方块世界之外,我们也能看到屏幕下方的物品栏,如果是手机游玩,还能看到屏幕上的方向按键、跳跃键和顶部的暂停键等按钮。这些所有的按钮、文字、窗口等元素都属于游戏界面的一部分,我们统称为**UI**(**用户界面**,**User Interface**,又译作**用户接口**)。顾名思义,用户界面就是用户与机器之间交互的接口。在计算机显示屏上,用户与机器交互的接口都是图形化的,因此**GUI**(**图形用户界面**,**Graphical User Interface**)一词在游戏中与UI一词并无分别。 + +![游戏内界面](./images/2.5_in-game_UI.png) + +我的世界的UI是一套工业化的JSON控制系统,通过控件来添加屏幕元素,同时通过绑定来事先逻辑功能。我的世界工作台提供了一套简便的可视化UI编辑器,省去了繁杂的JSON数据编写的过程,为我们提供了极大地方便,我们将在下一章中介绍该功能。 + +## 粒子与特效 + +**粒子**(**Particle**)是除了UI之外另一种非常重要的可视化效果,同时也是一个玩家经常遇到但是又容易忽视的效果。但是,细心的玩家依旧能够注意到,在我的世界中处处都有着粒子的存在。粒子是一个颗粒状的始终面向玩家的平面面片贴图,根据种类不同有着不同的存在时间和运动状态。破坏方块的时候出现的碎屑、玩家从高空掉在地上是激起的扬尘、火把和熔炉燃烧时产生的火焰、一脚踏进水里溅起的水花,这些本质上都是粒子效果。基岩版内置的粒子效果在玩家客户端中并没有办法通过命令调出,这种粒子称为**旧版粒子**(**Legacy Particle**),以上我们举的例子都是旧版粒子。为了让玩家和开发者在游戏中更好地使用粒子,国际版引入了一种“新版”的粒子功能,并直接在游戏中称之为**粒子**。 + +![粒子](./images/2.5_particles.png) + +### 粒子 + +国际版的粒子可以通过一个JSON文件来定义。按照国际版的[粒子JSON的文件格式](https://docs.microsoft.com/en-us/minecraft/creator/reference/content/particlesreference/)便可以定义出一个粒子。这种粒子本质上是一个**粒子发射器**(**Particle Emitter**),是一种可以在世界中移动的能够根据预先设置好的规则源源不断地发射出单个粒子的虚拟物体。最终展现给玩家的效果只有其发射出的粒子,玩家本身是看不到粒子发射器的。因此,粒子发射器可以实现比单纯召唤出一个粒子更多的功能,这也给了开发者们进行创作时更多的可能。 + +### 特效 + +中国版的**特效**(**Effect**)是中国版开发组在旧版粒子系统的基础上独立于国际版的粒子而创做出的新的粒子系统。通过与骨骼模型的配合,开发者甚至可以快速地将其挂接到骨骼模型上,实现模型和粒子效果的紧密结合。 + +中国版的特效分为**粒子特效(Particle Effect)**和**序列帧特效(Frame Effect)**。粒子特效本质上也是一个粒子发射器,通过不断发射多个不同大小规模的单个粒子,来达到一种特定的效果。序列帧特效也是在游戏中展现一个平面面片贴图,但是其允许纹理贴图具备动态效果,也就是在贴图上播放序列帧动画,使其成为“动态的粒子效果”。因此,序列帧特效往往更加具备开发价值,也能使模组的模型或内容展现得更加丰富。 + +到此为止,我们在本章中对于我的世界概念的学习就先告一段落。如果有些内容并没有看懂,也并不用担心。因为,我们后面还会讲到这些内容的制作。届时,我们将能够通过各种操作和代码进一步加深这些概念的理解! \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/README.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.1_block_example.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.1_block_example.png new file mode 100644 index 0000000..6a850e9 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.1_block_example.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.1_chunk.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.1_chunk.png new file mode 100644 index 0000000..f036df2 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.1_chunk.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.1_world_screen.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.1_world_screen.png new file mode 100644 index 0000000..06e4e05 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.1_world_screen.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.2_armor_stand.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.2_armor_stand.png new file mode 100644 index 0000000..7b15370 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.2_armor_stand.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.2_entity_and_block.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.2_entity_and_block.png new file mode 100644 index 0000000..2049b24 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.2_entity_and_block.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.2_thrown_ender_pearl.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.2_thrown_ender_pearl.png new file mode 100644 index 0000000..2f1b32d Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.2_thrown_ender_pearl.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.2_villager_aigoals.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.2_villager_aigoals.png new file mode 100644 index 0000000..518e8b4 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.2_villager_aigoals.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.3_drop_item.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.3_drop_item.png new file mode 100644 index 0000000..d020c0b Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.3_drop_item.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.3_half-used_armor.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.3_half-used_armor.png new file mode 100644 index 0000000..e4e2f8d Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.3_half-used_armor.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.3_torch_and_glowstone.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.3_torch_and_glowstone.png new file mode 100644 index 0000000..1d4644e Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.3_torch_and_glowstone.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.4_shulker_shooting.gif b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.4_shulker_shooting.gif new file mode 100644 index 0000000..b05c6a9 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.4_shulker_shooting.gif differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.4_some_models.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.4_some_models.png new file mode 100644 index 0000000..ea51943 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.4_some_models.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.4_steve_texture.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.4_steve_texture.png new file mode 100644 index 0000000..b8e0a9b Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.4_steve_texture.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.4_workbench_texture.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.4_workbench_texture.png new file mode 100644 index 0000000..0540132 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.4_workbench_texture.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.5_in-game_UI.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.5_in-game_UI.png new file mode 100644 index 0000000..4cfc3db Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.5_in-game_UI.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.5_particles.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.5_particles.png new file mode 100644 index 0000000..77289ab Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/1-认识我的世界/images/2.5_particles.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/0-摘要.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/0-摘要.md new file mode 100644 index 0000000..820582f --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/0-摘要.md @@ -0,0 +1,19 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 5分钟 +--- + +# 摘要 + +在本章中,我们将一起初步学习**模组SDK**(**Mod SDK**)的编写。一起了解模组SDK是如何使用系统、组件、事件相配合从而在脚本中完成各种逻辑操作的。 + +- 在第一节(*了解模组SDK框架的使用方法*)中,我们将一起了解模组SDK框架的构成和用法,一起学习模组SDK的**系统**(**System**)。 +- 在第二节(*了解事件驱动*)中,我们讲学习模组SDK的**事件**(**Event**),了解什么是事件驱动编程。 +- 在第三节(*在快捷入口新建服务端系统文件*)中,我们将学习如何通过编辑器快速新建一个系统文件。 +- 在第四节(*监听事件并创建组件逻辑*)中,我们将学习如何监听事件和创建**组件**(**Component**)并执行组件的逻辑。 +- 在第五节(*打印信息并运调试运行*)中,我们将学习如何打印消息和调试脚本。 +- 在第六节(*从编辑器里导出组件*)中,我们将一起导出我们的脚本作品。 +- 在第七节(*挑战:自定义传送点*)中,我们将进行一个挑战,自定义一个传送点。 + +本章关键词:脚本 模组SDK 模组API 系统 事件 响应 监听 通知 广播 引擎组件 引擎组件工厂 滴答 更新 脚本刻 打印 日志 传送点 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/1-了解模组SDK框架的使用方法.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/1-了解模组SDK框架的使用方法.md new file mode 100644 index 0000000..95e161d --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/1-了解模组SDK框架的使用方法.md @@ -0,0 +1,110 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 进阶 +time: 20分钟 +--- + +# 了解模组SDK框架的使用方法 + +我们前面一起学习了数据驱动JSON文件的写法、功能。通过学习,我们可以看到数据驱动文件具有非常重要的作用,它们可以定义游戏玩法,通过基于游戏中的实体各种各样的组件,为我们的游戏赋予各种玩法的基础属性。但是,数据驱动也有明显的缺点,其中最大的缺点就是无法定义复杂的逻辑。即使某些数据驱动定义可以定义一些事件,这些事件也只能在某些特定的限制下执行相应的逻辑,比如只能去执行特定的有限的功能,调用预先设定好的属性,而且不能将各个属性通过灵活的逻辑连接起来。最终只能得到一个线性的事件响应网。这种事件系统被我们认为是一种伪事件系统。 + +为了实现真正的事件系统,我们需要更灵活的编写方法和标准。所以,我们引入了**脚本**(**Script**)系统。中国版的脚本系统使用Python语言来编写,通过脚本使能的组件灵活地响应事件,将回调函数绑定到游戏运行时的对应位点上,赋予游戏更灵活的逻辑。中国版的这套脚本系统提供的Python脚本接口叫做**模组API**(**Mod API**),而中国版为带有以模组API为标准的脚本的附加包提供的开发包称为**模组SDK**(**Mod SDK**)。 + +## 配置开发环境 + +在开始进行模组SDK开发之前,我们需要配置Python环境。否则,我们将无法正常进行编写和调试。注意,中国版的模组SDK使用的是Python2环境。Python2和Python3之间具有较大的代码差异,我们无需使用Python3的相关语法。 + +### 安装Python2 + +我们进入[Python官网](https://www.python.org/downloads/)下载最新的Python2版本。由于Python2已停止维护,我们搜索最新版本2.7并在下面的往期版本中下载。 + +![](./images/11.1_download_python.png) + +**安装时请务必选择安装环境变量**,这样可以方便我们快速在命令行中输入Python的程序可执行文件名。 + +### 安装IDE + +我们推荐PyCharm作为Python的集成开发环境(IDE)。我们在[JetBrains官网](https://www.jetbrains.com/pycharm/download/)上下载PyCharm并安装。如果你没有PyCharm许可证,则请下载社区版(Community)。如果你欲购买许可证或具备学生身份,可以申请许可证并下载专业版(Professional)。 + +![](./images/11.1_install_pycharm.png) + +### 安装补全库 + +为了使我们的代码具备自动补全和定义查询功能,我们需要安装模组SDK的补全库。我们可以使用编辑器来安装补全库。 + +![](./images/11.1_install_lib.png) + +打开编辑器,在菜单栏的“工具”下拉菜单中选择你要安装补全库的版本。你可以根据面向的版本和规划来选择安装稳定版补全库或测试版补全库。 + +![](./images/11.1_cmd_line.png) + +开始安装后将提示一个命令行。如果之前安装过旧版本补全库,将先卸载旧版本再安装新版本。 + +![](./images/11.1_install_successfully.png) + +安装成功后将弹出一个提示窗口,提示你安装完成。当然,你也可以像提示窗口中所说的那样,通过命令提示符手动安装补全库。 + +### 查看示例包 + +至此,我们做好了所有准备工作。我们打开文档配套的 示例包 ,找到TutorialMod演示模组。我们可以在附加包中找到`tutorialScripts`文件夹。这里是便是该模组的一个脚本Python模块文件夹。如果你对Python较为熟悉,可以注意到`__init__.py`,这代表着这是一个Python模块。事实上,一个包中可以有多个脚本文件夹。但每个文件夹都必须是一个模块,也就是具备一个`__init__.py`文件。 + +![](./images/11.1_open_demo.png) + +## 什么是系统 + +我们打开`modMain.py`文件,这个文件是Python脚本的入口文件。基岩脚本的引擎将寻找入口文件作为一个Python模块的初始执行文件。目前我们无法自定义入口文件的文件名,因此我们需要保证入口文件的文件名为`modMain.py`。我们一起来查看这个入口文件。 + +```python +# -*- coding: utf-8 -*- +# 上面这行是让这个文件按utf-8进行编码,这样就可以在注释中写中文了 + +# 这行是import到MOD的绑定类Mod,用于绑定类和函数 +from mod.common.mod import Mod +# 这行import到的是引擎服务端的API模块 +import mod.server.extraServerApi as serverApi +# 这行import到的是引擎客户端的API模块 +import mod.client.extraClientApi as clientApi + +# 用Mod.Binding来绑定MOD的类,引擎从而能够识别这个类是MOD的入口类 +@Mod.Binding(name = "TutorialMod", version = "0.0.1") +class TutorialMod(object): + + # 类的初始化函数 + def __init__(self): + print "===== init tutorial mod =====" + + # InitServer绑定的函数作为服务端脚本初始化的入口函数,通常是用来注册服务端系统system和组件component + @Mod.InitServer() + def TutorialServerInit(self): + print "===== init tutorial server =====" + # 函数可以将System注册到服务端引擎中,实例的创建和销毁交给引擎处理。第一个参数是MOD名称,第二个是System名称,第三个是自定义MOD System类的路径 + # 取名名称尽量个性化,不能与其他人的MOD冲突,可以使用英文、拼音、下划线这三种。 + serverApi.RegisterSystem("TutorialMod", "TutorialServerSystem", "tutorialScripts.tutorialServerSystem.TutorialServerSystem") + + # DestroyServer绑定的函数作为服务端脚本退出的时候执行的析构函数,通常用来反注册一些内容,可为空 + @Mod.DestroyServer() + def TutorialServerDestroy(self): + print "===== destroy tutorial server =====" + + # InitClient绑定的函数作为客户端脚本初始化的入口函数,通常用来注册客户端系统system和组件component + @Mod.InitClient() + def TutorialClientInit(self): + print "===== init hugo fps client =====" + # 函数可以将System注册到客户端引擎中,实例的创建和销毁交给引擎处理。第一个参数是MOD名称,第二个是System名称,第三个是自定义MOD System类的路径 + # 取名名称尽量个性化,不能与其他人的MOD冲突,可以使用英文、拼音、下划线这三种。 + clientApi.RegisterSystem("TutorialMod", "TutorialClientSystem", "tutorialScripts.tutorialClientSystem.TutorialClientSystem") + + # DestroyClient绑定的函数作为客户端脚本退出的时候执行的析构函数,通常用来反注册一些内容,可为空 + @Mod.DestroyClient() + def TutorialClientDestroy(self): + print "===== destroy hugo fps client =====" + +``` + +在入口文件中,我们看到了我们的Python模组作为一个类而定义,而类中除了`__init__`方法之外定义了四个基本方法。分别为`TutorialServerInit`、`TutorialServerDestroy`、`TutorialClientInit`和`TutorialClientDestroy`。这四个方法的作用便是用于注册和销毁**系统**(**System**)。 + +我们的模组是分为服务端和客户端分别执行的,这是因为我的世界游戏是分为服务端和客户端两部分互联互通运行的。试想,如果我们不分服务端和客户端,执行一个操作时会出现什么样的结果。在一个服务器中,一个玩家触发了一定的逻辑,逻辑的结果是在世界(0, 64, 0)处放置一个特定的方块。如果我们的服务端和客户端时分开执行的,那么所有的玩家的客户端都将开始执行这一逻辑。但是我们先前知道,玩家在同一时刻只可能在某一个维度的世界中。所以客户端是分辨不出这个放置方块的指示是应该位于哪一维度的。虽然我们只希望在主世界中放置方块,但是处于下界和末地的客户端也将“模拟地”在这两个维度的对应位置放置一个方块。这个方块事实上在服务端中的下界和末地中并未放置,因此将出现客户端和服务端不同步的严重问题。所以,我们的脚本也需要和游戏的基本逻辑一样,分别在服务端和客户端同时设置一个系统,分别执行不同的逻辑,同时通过一定的功能互相通讯,保持同步。 + +那么,系统是什么呢?在模组API中,我们在每个端都可以设置一个或多个系统。系统将负责宏观工作,例如定义和反定义事件、注册和反注册事件的监听、广播或通知一个事件,进行各种更新操作。而通过系统进行事件的广播和监听,执行逻辑的过程便称为**事件驱动**。 + +事实上,所有的系统都是一个继承自系统基类的类。这个系统的基类为`mod.common.system.baseSystem`。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/2-了解事件驱动.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/2-了解事件驱动.md new file mode 100644 index 0000000..a506d61 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/2-了解事件驱动.md @@ -0,0 +1,112 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 进阶 +time: 25分钟 +--- + +# 了解事件驱动 + +接下来,我们以TutorialMod演示模组的服务端系统的定义部分来了解什么是**事件驱动**(**Event-driven**)系统。我们一起来查看演示模组中的`tutorialServerSystem.py`文件。 + +```python +# -*- coding: utf-8 -*- + +# 获取引擎服务端API的模块 +import mod.server.extraServerApi as serverApi +# 获取引擎服务端System的基类,System都要继承于ServerSystem来调用相关函数 +ServerSystem = serverApi.GetServerSystemCls() +# 获取组件工厂,用来创建组件 +compFactory = serverApi.GetEngineCompFactory() + +# 在modMain中注册的Server System类 +class TutorialServerSystem(ServerSystem): + + # ServerSystem的初始化函数 + def __init__(self, namespace, systemName): + # 首先调用父类的初始化函数 + super(TutorialServerSystem, self).__init__(namespace, systemName) + print "===== TutorialServerSystem init =====" + # 初始时调用监听函数监听事件 + self.ListenEvent() + + # 监听函数,用于定义和监听函数。函数名称除了强调的其他都是自取的,这个函数也是。 + def ListenEvent(self): + # 在自定义的ServerSystem中监听引擎的事件ServerChatEvent,回调函数为OnServerChat + self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), "ServerChatEvent", self, self.OnServerChat) + # 监听引擎的事件ServerBlockUseEvent, 回调函数为 OnServerBlockUseEvent + self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), "ServerBlockUseEvent", self, self.OnServerBlockUseEvent) + + # 反监听函数,用于反监听事件,在代码中有创建注册就对应了销毁反注册是一个好的编程习惯,不要依赖引擎来做这些事。 + def UnListenEvent(self): + self.UnListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), "ServerChatEvent", self, self.OnServerChat) + self.UnListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), "ServerBlockUseEvent", self, self.OnServerBlockUseEvent) + + # 监听ServerBlockUseEvent的回调函数 + def OnServerBlockUseEvent(self, args): + # 这里的sdkteam_test:block1替换成你自己的自定义方块的命名空间与方块名 + if args["blockName"] == "sdkteam_test:block1": + # 调用给予物品的接口,与OnServerChat中相似 + playerId = args["playerId"] + comp = compFactory.CreateItem(playerId) + # 这里填钻石剑的物品名 + comp.SpawnItemToPlayerInv({"itemName": "minecraft:diamond_sword", "count": 1, 'auxValue': 0}, args["playerId"]) + + # 监听ServerChatEvent的回调函数 + def OnServerChat(self, args): + print "==== OnServerChat ==== ", args + # 生成掉落物品 + # 当我们输入的信息等于右边这个值时,创建相应的物品 + # 创建Component,用来完成特定的功能,这里是为了创建Item物品 + playerId = args["playerId"] + comp = compFactory.CreateItem(playerId) + if args["message"] == "钻石剑": + # 调用SpawnItemToPlayerInv接口生成物品到玩家背包,参数参考《MODSDK文档》 + comp.SpawnItemToPlayerInv({"itemName":"minecraft:diamond_sword", "count":1, 'auxValue': 0}, playerId) + elif args["message"] == "钻石镐": + comp.SpawnItemToPlayerInv({"itemName":"minecraft:diamond_pickaxe", "count":1, 'auxValue': 0}, playerId) + elif args["message"] == "钻石头盔": + comp.SpawnItemToPlayerInv({"itemName":"minecraft:diamond_helmet", "count":1, 'auxValue': 0}, playerId) + elif args["message"] == "钻石胸甲": + comp.SpawnItemToPlayerInv({"itemName":"minecraft:diamond_chestplate", "count":1, 'auxValue': 0}, playerId) + elif args["message"] == "钻石护腿": + comp.SpawnItemToPlayerInv({"itemName":"minecraft:diamond_leggings", "count":1, 'auxValue': 0}, playerId) + elif args["message"] == "钻石靴子": + comp.SpawnItemToPlayerInv({"itemName":"minecraft:diamond_boots", "count":1, 'auxValue': 0}, playerId) + else: + print "==== Sorry man ====" + + # 函数名为Destroy才会被调用,在这个System被引擎回收的时候会调这个函数来销毁一些内容 + def Destroy(self): + print "===== TutorialServerSystem Destroy =====" + # 调用上面的反监听函数来销毁 + self.UnListenEvent() + +``` + +## 事件驱动 + +我们可以看到,为了定义我们的系统的类,我们从**额外API**(**Extra API**,服务端为`mod.server.extraServerApi`)下的`GetServerSystemCls`方法得到了继承自系统基类`mod.common.system.baseSystem`的服务端系统类`mod.server.system.serverSystem`的引用。我们的`TutorialServerSystem`类的定义将继承自该服务端系统类。 + +接着,在`TutorialServerSystem`类的`__init__`方法中,我们可以看到在调用完毕超类的初始化方法后紧接着便调用了自定义方法`ListenEvent`。这是用来注册各种事件监听的方法。 + +在`ListenEvent`方法中,我们通过调用系统(`self`)的`ListenForEvent`方法来注册一个事件监听。前两个参数分别是该事件监听要监听的系统的命名空间和系统的名称。如果我们要监听引擎默认给出的模组API原生事件,我们需要使用额外API中的`GetEngineNamespace`和`GetEngineSystemName`方法获取**引擎系统**(**Engine System**)的命名空间和名称。引擎系统也是一个系统,是基岩引擎中模组API原生的系统。如果要监听自己的或者其他模组的自定义系统广播的事件,则需要正确填写对应的命名空间和名称,使两个参数和`modMain.py`中注册系统时提供的命名空间和系统名相一致。 + +**事件**(**Event**)是由基岩引擎发出或用户自定义的代码发出的,在游戏发生某种特定的逻辑时触发的一种标识。对一个事件进行**监听**(**Listen**)指的是将一条回调函数传入一个事件的响应代码中,在该事件被触发时同时执行该回调函数。这条回调函数往往被称为事件的**响应**(**Response**)。而在事件触发时将这一消息发送给指定的客户端或服务端的行为,根据发送的对象和接收端的数目不同分别被称作**广播**(**Broadcast**)或**通知**(**Notify**)。 + +`ListenEvent`方法的第三个参数便是事件的标识符,告诉引擎我将“哪个”事件的监听设置到了该系统上。第四个参数是该监听绑定到的系统的实例。因为我们的事件监听响应(即回调函数)是在该系统的示例中定义的,所以我们填写该系统自身(`self`)。在第五个参数中写入该监听的响应函数。这个函数会在事件触发时异步触发。 + +这一系列操作便是一个简单的事件驱动逻辑。通过注册一个事件的监听将一个回调函数作为相应绑定到事件上。当事件被触发并广播或通知到该端时便异步执行该回调函数,从而运行相关的逻辑。 + +## 组件接口的使用 + +我们从额外API下的`GetEngineCompFactory`方法可以获得服务端**引擎组件工厂**(**Engine Component Factory**)的类`mod.server.component.engineCompFactoryServer`的引用。引擎组件工厂是用来创建**引擎组件**(**Engine Component**)的类。引擎组件与常规的ECS系统中的组件不同,他们不仅是用来存储数据的,更是用来执行逻辑的。每个引擎组件下都具有各种各样的方法,每种方法都可以用来执行一个不同的逻辑或完成一个不同的操作。比如引擎组件`game`(服务端为`mod.server.component.gameCompServer`的实例)下的`KillEntity`方法便可以杀死一个指定ID的实体。只需使用如下实例代码便可做到杀死一个实体: + +```python +import mod.server.extraServerApi as serverApi +comp = serverApi.GetEngineCompFactory().CreateGame(levelId) +comp.KillEntity(entityId) +``` + +上面的`comp`变量便是`mod.server.component.gameCompServer`引擎组件的一个实例。 + +通过引擎组件可以做到各种逻辑,配合事件驱动系统便可以”精准打击“到游戏中各种逻辑发生点,并插入自己的逻辑。这样的运作模式便是模组API如何修改游戏的基本方法! \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/3-在快捷入口新建服务端系统文件.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/3-在快捷入口新建服务端系统文件.md new file mode 100644 index 0000000..fa39fee --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/3-在快捷入口新建服务端系统文件.md @@ -0,0 +1,101 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 15分钟 +--- + +# 在快捷入口新建服务端系统文件 + +本节中,我们一起学习如何在新版编辑器中创建脚本文件。 + +## 使用资源管理器快捷新建脚本系统 + +我们打开编辑器,点击“**资源管理**”窗格的“**新建**”按钮。 + +![](./images/11.3_create_main.png) + +在“**代码**”选项卡中找到**ModMain**。这个选项将可以帮助我们快速创建一个模组API文件夹,其中包括一个代表该文件夹是一个模块的`__init__.py`和一个入口文件`modMain.py`。 + +![](./images/11.3_create_main_2.png) + +我们在这里给文件命名,实际上就是在给模组命名。我们模组类的装饰器`@Mod.Binding(name = "ModName", version = "0.0.1")`中的`ModName`处便代表我们的模组名。这里的名字将自动填充至模组入口文件中模组类的装饰器中。同时,脚本文件夹名和模组的类名也将会使用我们这里的模组名,所以请谨慎填写一个不会和其他开发者的模组中的模组名重名的名称,否则将有可能造成加载冲突。 + +接下来我们就可以分别给我们的模组创建服务端文件和客户端文件了!我们以服务端文件为例。 + +![](./images/11.3_create_server.png) + +还是在“**代码**”选项卡中,我们选择**ServerSystem**。这个选项可以用于创建服务端系统文件`xxxxServerSystem.py`。 + +![](./images/11.3_create_server_2.png) + +我们在第一个下拉菜单中选择我们刚才创建的脚本文件夹目录。如果一个附加包中存在多个脚本文件夹,我们就需要进行在下拉菜单中选择正确的我们想要创建的脚本目录。文件名便是将会自动使用到服务端系统的类名上的名称。 + +![](./images/11.3_created.png) + +我们可以看到,服务端系统文件也创建了。同时,编辑器还创建了一个`Part`文件夹,这是新版编辑器自动创建的文件。如果我们的脚本不是用于零件脚本,那么这个文件夹可以忽略。 + +我们来查看自动创建的`modMain.py`: + +```python +# -*- coding: utf-8 -*- + +from common.mod import Mod + + +@Mod.Binding(name="DemoTutorialMod", version="0.0.1") +class DemoTutorialMod(object): + + def __init__(self): + pass + + @Mod.InitServer() + def DemoTutorialModServerInit(self): + pass + + @Mod.DestroyServer() + def DemoTutorialModServerDestroy(self): + pass + + @Mod.InitClient() + def DemoTutorialModClientInit(self): + pass + + @Mod.DestroyClient() + def DemoTutorialModClientDestroy(self): + pass + +``` + +和`DemoTutorialServerSystem.py`: + +```python +# -*- coding: utf-8 -*- + +import server.extraServerApi as serverApi +ServerSystem = serverApi.GetServerSystemCls() + + +class DemoTutorialServerSystem(ServerSystem): + def __init__(self, namespace, systemName): + ServerSystem.__init__(self, namespace, systemName) + + # ScriptTickServerEvent的回调函数,会在引擎tick的时候调用,1秒30帧(被调用30次) + def OnTickServer(self): + """ + Driven by event, One tick way + """ + pass + + # 这个Update函数是基类的方法,同样会在引擎tick的时候被调用,1秒30帧(被调用30次) + def Update(self): + """ + Driven by system manager, Two tick way + """ + pass + + def Destroy(self): + pass + +``` + +我们可以看到,自动创建时并不会在模组类中注册我们的系统,所以我们的系统注册代码需要手动输入。同时,自动创建给我们的服务端系统类写入了两个函数,分别是脚本刻的**滴答**(**Tick**)函数和系统类的**更新**(**Update**)函数,这两个函数分别通过事件响应回调和重载超类方法的形式实现每秒运行30次的代码。这样运行一次的时间我们称为一个**脚本刻**(**Script Tick**)。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/4-监听事件并创建组件逻辑.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/4-监听事件并创建组件逻辑.md new file mode 100644 index 0000000..a38a949 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/4-监听事件并创建组件逻辑.md @@ -0,0 +1,122 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 进阶 +time: 20分钟 +--- + +# 监听事件并创建组件逻辑 + +现在我们已经创建好了一个服务端系统,我们来尝试向这个系统中添加逻辑。 + +在添加逻辑之前,我们需要手动将系统在主模组文件中注册。我们仿照之前看到的示例,将额外API的`RegisterSystem`函数写在`@Mod.InitServer()`装饰器下的方法中。 + +```python +@Mod.InitServer() +def DemoTutorialModServerInit(self): + serverApi.RegisterSystem("DemoTutorialMod", "Server", "Script_DemoTutorialMod.DemoTutorialServerSystem.DemoTutorialServerSystem") + +``` + +## 监听实体受伤事件 + +我们回到服务端系统文件`DemoTutorialServerSystem.py`中。我们一起尝试做一个简单的事件监听,比如,监听实体(活动对象)收到伤害的事件。 + +通过查阅API文档,我们得到了控制实体受到伤害的事件`ActorHurtServerEvent`,其字面意思为“活动对象受伤服务端事件”。我们通过该系统本身的`ListenForEvent`方法来注册这个事件监听。我们在`__init__`方法的末尾加入我们的监听注册函数,同时在该类中定义一个新的方法,比如名为`OnActorHurtServer`,将该方法作为回调函数绑定到事件上。 + +```python +def __init__(self, namespace, systemName): + ServerSystem.__init__(self, namespace, systemName) + self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), "ActorHurtServerEvent", self, self.OnActorHurtServer) + +``` + +这样,我们便完成了实体受伤的监听。当实体受到伤害时,`DemoTutorialServerSystem`实例的`OnActorHurtServer`方法将会运行。 + +## 在事件内触发击退逻辑 + +我们希望不仅仅是监听该事件,更要在事件发生时执行一些逻辑,比如更改受击实体的击退属性。 + +我们通过查阅API文档得知,`action`组件(`mod.server.component.actionCompServer`)具备更改击退逻辑的方法`SetMobKnockback`。所以我们使用引擎组件工厂创建一个`action`引擎组件,然后调用它的设置击退的方法做到一些逻辑,比如我们想增加击退的威力。我们在`OnActorHurtServer`中写入如下内容。 + +```python + def OnActorHurtServer(self, args): + comp = serverApi.GetEngineCompFactory().CreateAction(args["entityId"]) + comp.SetMobKnockback(0.1, 0.1, 10.0, 1.0, 1.0) + +``` + +![](./images/11.4_set_knockback.png) + +这样,我们便成功更改了击退的威力。我们将完整的修改过的代码展示在此处。首先是`modMain.py`: + +```python +# -*- coding: utf-8 -*- + +from mod.common.mod import Mod +import mod.server.extraServerApi as serverApi +import mod.client.extraClientApi as clientApi + +@Mod.Binding(name="DemoTutorialMod", version="0.0.1") +class DemoTutorialMod(object): + + def __init__(self): + + @Mod.InitServer() + def DemoTutorialModServerInit(self): + serverApi.RegisterSystem("DemoTutorialMod", "Server", "Script_DemoTutorialMod.DemoTutorialServerSystem.DemoTutorialServerSystem") + + @Mod.DestroyServer() + def DemoTutorialModServerDestroy(self): + pass + + @Mod.InitClient() + def DemoTutorialModClientInit(self): + pass + + @Mod.DestroyClient() + def DemoTutorialModClientDestroy(self): + pass + +``` + +然后是`DemoTutorialServerSystem.py`: + +```python +# -*- coding: utf-8 -*- + +import mod.server.extraServerApi as serverApi +ServerSystem = serverApi.GetServerSystemCls() + + +class DemoTutorialServerSystem(ServerSystem): + def __init__(self, namespace, systemName): + ServerSystem.__init__(self, namespace, systemName) + self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), "ActorHurtServerEvent", self, self.OnActorHurtServer) + + def OnActorHurtServer(self, args): + comp = serverApi.GetEngineCompFactory().CreateAction(args["entityId"]) + comp.SetMobKnockback(0.1, 0.1, 10.0, 1.0, 1.0) + + # ScriptTickServerEvent的回调函数,会在引擎tick的时候调用,1秒30帧(被调用30次) + def OnTickServer(self): + """ + Driven by event, One tick way + """ + pass + + # 这个Update函数是基类的方法,同样会在引擎tick的时候被调用,1秒30帧(被调用30次) + def Update(self): + """ + Driven by system manager, Two tick way + """ + pass + + def Destroy(self): + self.UnListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), "ActorHurtServerEvent", self, self.OnActorHurtServer) + +``` + +![](./images/11.4_in-game.gif) + +进入游戏测试,便可以发现`SetMobKnockback`更改击退属性“诚不我欺”。 + diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/5-打印信息并运调试运行.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/5-打印信息并运调试运行.md new file mode 100644 index 0000000..8aca1a9 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/5-打印信息并运调试运行.md @@ -0,0 +1,58 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 10分钟 +--- + +# 打印信息并运调试运行 + +懂得如何将信息打印到控制台上是一个合格的开发者必须具备的本领。在本节中我们将学习如何打印信息并调试运行。 + +## 学会打印日志并查看控制台 + +每次打开电脑开发版,我的世界开发工作台便会创建一个“**脚本测试日志**”窗口,这边是我们的脚本调试控制台。 + +在模组中,我们可以在任何位置凭借自己的意愿将任何模组中的上下文信息打印到控制台上。我们有两种打印方式。 + +### `print` + +我们可以使用原生的`print`函数来打印信息。比如,这边是一个`print`函数的打印示例,它将在初始化模组服务端注册之前打印开始初始化的消息。 + +```python +@Mod.InitServer() +def DemoTutorialModServerInit(self): + print "===== init tutorial server =====" + serverApi.RegisterSystem("DemoTutorialMod", "Server", "Script_DemoTutorialMod.DemoTutorialServerSystem.DemoTutorialServerSystem") + +``` + +### `mod_log` + +我们还可以通过`mod_log`模块来打印信息。下面的示例中通过导入并执行`logger.info`打印了一个信息。 + +```python +from mod_log import logger +logger.info("print log: %s", "OK") +``` + +### 两者的不同 + +我们以下面代码为示例可以看到两种打印方式在控制台表现的不同。 + +```python +def OnActorHurtServer(self, args): + print "==== 使用print打印的日志 ==== " + logger.info("使用logger打印的日志") + comp = serverApi.GetEngineCompFactory().CreateAction(args["entityId"]) + comp.SetMobKnockback(0.1, 0.1, 10.0, 1.0, 1.0) +``` + +![](./images/11.5_print.png) + +## 使用热更新快速修复问题 + +我们的Python代码在游戏中支持热更新调试。在电脑开发版的游戏运行中时,我们修改Python代码将导致文件在开发版中的重载。自动热更新主要用于修改函数内实现,比如我可以修改击退威力,使其增加为20,那么我在游戏中就可以在下一次攻击时延长击退的距离。但是热更新对全局变量、新增类或文件无效。因为这些都是在Python代码运行一开始便初始化的内容,热更新文件不会使其再次初始化,所以可能会导致无效。 + +![](./images/11.5_overload.png) + +我们将击退为例从10更改为20,可以在控制台中看到热更新的信息。灵活掌握代码的热更新,将有效大幅度减少开启关闭游戏的次数,使调试更加方便。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/6-从编辑器里导出组件.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/6-从编辑器里导出组件.md new file mode 100644 index 0000000..bd304be --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/6-从编辑器里导出组件.md @@ -0,0 +1,25 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 10分钟 +--- + +# 从编辑器里导出组件 + +导出组件也是开发者应该学会的开发中必不可少的一环。我们有多种组件导出方法。 + +## 导出为资源包 + +打开编辑器,点击“资源管理”窗格中的“导出”可以将组件导出为资源包,文件格式为`.mep`。 + +![](./images/11.6_export.png) + +通过这种方式导出的资源包可以在另一个附加包组件中通过同一位置的“导入”功能合并导入。 + +## 整体导出 + +在我的世界开发工作台中找到作品。右键或点击“更多”按钮,在展开的菜单中点击“导出”或“导出(含编辑信息)”。 + +![](./images/11.6_export_2.png) + +这种方式可以使整个包导出为一个`.zip`文件。通过这种方式导出的文件可以在我的世界开发工作台中通过“本地导入”功能重新导入为一个组件。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/7-挑战:自定义传送点.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/7-挑战:自定义传送点.md new file mode 100644 index 0000000..9ff602b --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/7-挑战:自定义传送点.md @@ -0,0 +1,362 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 进阶 +time: 30分钟 +--- + +# 挑战:自定义传送点 + +在本节中,我们一起来制作一个传送点模组。我们知道,在我的世界中,地形可以近似认为是无限广阔的,所以,我们在探索的过程中可能经常迷失方向,找不到回到据点的路。因此,我们可以自定义一个传送点模组。通过保存一个传送点来做到在迷路时快速回到传送点所指定的位置。 + +下面,我们一起使用模组SDK来制作一个传送点模组。 + +## 创建项目 + +我们在我的世界开发工作台中创建一个“传送点模组”的组件,然后使用编辑器“资源管理”窗格中“创建”按钮来快速创建一个Python模组模块,将其命名为`TeleportPointMod`。 + +![](./images/11.7_tp_point_server_create.png) + +由于我们知道,只有服务端才能获取和控制每个玩家的位置,所以我们只需创建一个服务端系统,将其命名为`TeleportPointServerSystem`。对于每个玩家来说,客户端一般只负责屏幕的渲染和本地的一些计算,是无法直接改变玩家处于整个世界中的位置的,所以我们无需使服务端与客户端交互。因此我们无需创建客户端系统。 + +![](./images/11.7_tp_point_server_created.png) + +创建结束后,我们可以在“资源管理”窗格中看到如图的文件排布。我们将整个行为包文件夹在Python的IDE中打开,以便之后的代码编写。此时的Python文件内容如下。 + +`modMain.py`: + +```python +# -*- coding: utf-8 -*- + +from common.mod import Mod + + +@Mod.Binding(name="TeleportPointMod", version="0.0.1") +class TeleportPointMod(object): + + def __init__(self): + pass + + @Mod.InitServer() + def TeleportPointModServerInit(self): + pass + + @Mod.DestroyServer() + def TeleportPointModServerDestroy(self): + pass + + @Mod.InitClient() + def TeleportPointModClientInit(self): + pass + + @Mod.DestroyClient() + def TeleportPointModClientDestroy(self): + pass + +``` + +`TeleportPointServerSystem.py` + +```python +# -*- coding: utf-8 -*- + +import server.extraServerApi as serverApi +ServerSystem = serverApi.GetServerSystemCls() + + +class TeleportPointServerSystem(ServerSystem): + def __init__(self, namespace, systemName): + ServerSystem.__init__(self, namespace, systemName) + + # ScriptTickServerEvent的回调函数,会在引擎tick的时候调用,1秒30帧(被调用30次) + def OnTickServer(self): + """ + Driven by event, One tick way + """ + pass + + # 这个Update函数是基类的方法,同样会在引擎tick的时候被调用,1秒30帧(被调用30次) + def Update(self): + """ + Driven by system manager, Two tick way + """ + pass + + def Destroy(self): + pass + +``` + +## 注册服务端系统 + +我们知道,单纯创建了服务端系统文件是无法在主模组文件中同时将其注册的。我们手动将我们的服务端系统注册。我们在`TeleportPointModServerInit`函数中输入注册系统的方法: + +```python + @Mod.InitServer() + def TeleportPointModServerInit(self): + serverApi.RegisterSystem('TeleportPointMod', 'TeleportPointServerSystem', 'Script_TeleportPointMod.TeleportPointServerSystem.TeleportPointServerSystem') + +``` + +其中`Script_TeleportPointMod.TeleportPointServerSystem.TeleportPointServerSystem`对应着`Script_TeleportPointMod`文件夹中`TeleportPointServerSystem.py`文件的`TeleportPointServerSystem`类。这样,我们便成功注册了系统。 + +## 制作传送点模组的主体 + +传送点模组的主体部分应该都位于服务单系统中,我们只需要考虑实现的逻辑即可。 + +我们可以允许玩家使用多种方式来触发传送。最简单的一种便是使用聊天栏触发。我们不妨就采取这种方式来制作模组。我们可以设置一些关键词,用于传送点的设置、传送和移除,比如`tppoint set`、`tppoint apply`、`tppoint remove`。而每个玩家为其使用一个变量来保存其设置的传送点坐标的数据。为了方便保存,我们可以使用一个字典来做到这一点。字典中的键为玩家的ID,而值为玩家保存的传送点坐标。当玩家执行传送时,我们主需要检索该玩家的ID,获取字典保存的值,然后将玩家的坐标设定为这个值即可。 + +我们依次来实现这个逻辑。 + +### 监听玩家的聊天栏消息 + +我们通过查阅文档可知,服务端的`ServerChatEvent`事件可以做到响应玩家发送聊天栏信息,于是我们为其设置事件监听。 + +```python +def __init__(self, namespace, systemName): + ServerSystem.__init__(self, namespace, systemName) + self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), "ServerChatEvent", self, self.on_chat) + +``` + +同时加入一个`on_chat`方法,用于充当回调函数。 + +```python +def on_chat(self, event): + pass +``` + +### 记录传说点 + +下面我们按照我们刚才的设想开始写入基本逻辑。我们读取事件数据中的`playerId`和`message`。它们分别是发送消息的玩家ID和所发送的消息。我们使用字符串的`startswith`方法来判定消息是否以`tppoint set`开头。如果消息以`tppoint set`开头,那么就通过玩家ID创建一个`pos`引擎组件。`pos`引擎组件具备`GetFootPos`方法、`GetPos`方法和`SetPos`方法。我们可以使用`GetFootPos`方法来获取玩家脚步坐标并存入一个字典,我们不妨起名为`player_tp_cache`。`player_tp_cache`字典变量由于是需要在整个类中实现的,所以我们将其放在类的初始化方法里。 + +```python +class TeleportPointServerSystem(ServerSystem): + def __init__(self, namespace, systemName): + ServerSystem.__init__(self, namespace, systemName) + self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), "ServerChatEvent", self, self.on_chat) + # 用来暂时缓存玩家的传送点信息,在每次重新开启一次游戏后会被重置 + self.player_tp_cache = {} + + def on_chat(self, event): + # 获取玩家ID + player_id = event['playerId'] + # 获取聊天消息 + message = event['message'] + if message.startswith('tppoint set'): + pos_comp = serverApi.GetEngineCompFactory().CreatePos(player_id) + teleport_pos = pos_comp.GetFootPos() + # 保存玩家的当前坐标至字典缓存中 + self.player_tp_cache[player_id] = teleport_pos + print '已设置玩家传送点' + +``` + +### 删除传送点 + +依据同样的原理,我们使用字典的`pop`方法来移除一个玩家的键值对。我们加入删除传送点的逻辑。 + +```python +def on_chat(self, event): + # 获取玩家ID + player_id = event['playerId'] + # 获取聊天消息 + message = event['message'] + if message.startswith('tppoint set'): + pos_comp = serverApi.GetEngineCompFactory().CreatePos(player_id) + teleport_pos = pos_comp.GetFootPos() + # 保存玩家的当前坐标至字典缓存中 + self.player_tp_cache[player_id] = teleport_pos + print '已设置玩家传送点' + if message.startswith('tppoint remove'): + # 判断该玩家是否存有数据 + if player_id in self.player_tp_cache: + # 弹出字典中的玩家数据 + self.player_tp_cache.pop(player_id) + print '已移除玩家传送点' + else: + print '该玩家没有设置传送点' + +``` + +### 传送至传送点 + +最后,我们使用`pos`引擎组件的`SetPos`方法来实现传送逻辑。 + +```python +def on_chat(self, event): + # 获取玩家ID + player_id = event['playerId'] + # 获取聊天消息 + message = event['message'] + if message.startswith('tppoint set'): + pos_comp = serverApi.GetEngineCompFactory().CreatePos(player_id) + teleport_pos = pos_comp.GetFootPos() + # 保存玩家的当前坐标至字典缓存中 + self.player_tp_cache[player_id] = teleport_pos + print '已设置玩家传送点' + if message.startswith('tppoint remove'): + # 判断该玩家是否存有数据 + if player_id in self.player_tp_cache: + # 弹出字典中的玩家数据 + self.player_tp_cache.pop(player_id) + print '已移除玩家传送点' + else: + print '该玩家没有设置传送点' + if message.startswith('tppoint apply'): + pos_comp = serverApi.GetEngineCompFactory().CreatePos(player_id) + # 判断该玩家是否存有数据 + if player_id in self.player_tp_cache: + teleport_pos = self.player_tp_cache[player_id] + # 传送玩家 + pos_comp.SetPos(teleport_pos) + print '已应用玩家传送点' + else: + print '该玩家没有设置传送点' + +``` + +这样,我们便完成了传送逻辑的编写。额外地,我们还可以通过`msg`组件来为玩家显示消息提示,展示设置、传送或移除成功的提示。我们可以修改如下: + +```python +def on_chat(self, event): + # 获取玩家ID + player_id = event['playerId'] + # 获取聊天消息 + message = event['message'] + if message.startswith('tppoint set'): + pos_comp = serverApi.GetEngineCompFactory().CreatePos(player_id) + teleport_pos = pos_comp.GetFootPos() + # 保存玩家的当前坐标至字典缓存中 + self.player_tp_cache[player_id] = teleport_pos + msg_comp = serverApi.GetEngineCompFactory().CreateMsg(player_id) + msg_comp.NotifyOneMessage(player_id, "已设置传送点") + print '已设置玩家传送点' + if message.startswith('tppoint remove'): + # 判断该玩家是否存有数据 + if player_id in self.player_tp_cache: + # 弹出字典中的玩家数据 + self.player_tp_cache.pop(player_id) + msg_comp = serverApi.GetEngineCompFactory().CreateMsg(player_id) + msg_comp.NotifyOneMessage(player_id, "已移除传送点") + print '已移除玩家传送点' + else: + msg_comp = serverApi.GetEngineCompFactory().CreateMsg(player_id) + msg_comp.NotifyOneMessage(player_id, "请先设置传送点") + print '该玩家没有设置传送点' + if message.startswith('tppoint apply'): + pos_comp = serverApi.GetEngineCompFactory().CreatePos(player_id) + # 判断该玩家是否存有数据 + if player_id in self.player_tp_cache: + teleport_pos = self.player_tp_cache[player_id] + # 传送玩家 + pos_comp.SetPos(teleport_pos) + msg_comp = serverApi.GetEngineCompFactory().CreateMsg(player_id) + msg_comp.NotifyOneMessage(player_id, "已传送至传送点") + print '已应用玩家传送点' + else: + msg_comp = serverApi.GetEngineCompFactory().CreateMsg(player_id) + msg_comp.NotifyOneMessage(player_id, "请先设置传送点") + print '该玩家没有设置传送点' + +``` + +我们可以进入游戏查看传送和消息提示情况。 + +![](./images/11.7_in-game.png) + +我们可以看到,传送和消息都和我们所设置的一样正常运作! + +我们最后放出完整的代码供大家参考,`modMain.py`如下: + +```python +# -*- coding: utf-8 -*- + +from mod.common.mod import Mod +import mod.server.extraServerApi as serverApi + + +@Mod.Binding(name="TeleportPointMod", version="0.0.1") +class TeleportPointMod(object): + + def __init__(self): + pass + + @Mod.InitServer() + def TeleportPointModServerInit(self): + serverApi.RegisterSystem('TeleportPointMod', 'TeleportPointServerSystem', 'Script_TeleportPointMod.TeleportPointServerSystem.TeleportPointServerSystem') + + @Mod.DestroyServer() + def TeleportPointModServerDestroy(self): + pass + + @Mod.InitClient() + def TeleportPointModClientInit(self): + pass + + @Mod.DestroyClient() + def TeleportPointModClientDestroy(self): + pass + +``` + +`TeleportPointServerSystem.py`如下: + +```python +# -*- coding: utf-8 -*- + +import mod.server.extraServerApi as serverApi +from mod.server.system.serverSystem import ServerSystem +ServerSystem = serverApi.GetServerSystemCls() + + +class TeleportPointServerSystem(ServerSystem): + def __init__(self, namespace, systemName): + ServerSystem.__init__(self, namespace, systemName) + self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), "ServerChatEvent", self, self.on_chat) + # 用来暂时缓存玩家的传送点信息,在每次重新开启一次游戏后会被重置 + self.player_tp_cache = {} + + def on_chat(self, event): + # 获取玩家ID + player_id = event['playerId'] + # 获取聊天消息 + message = event['message'] + if message.startswith('tppoint set'): + pos_comp = serverApi.GetEngineCompFactory().CreatePos(player_id) + teleport_pos = pos_comp.GetFootPos() + # 保存玩家的当前坐标至字典缓存中 + self.player_tp_cache[player_id] = teleport_pos + msg_comp = serverApi.GetEngineCompFactory().CreateMsg(player_id) + msg_comp.NotifyOneMessage(player_id, "已设置传送点") + print '已设置玩家传送点' + if message.startswith('tppoint remove'): + # 判断该玩家是否存有数据 + if player_id in self.player_tp_cache: + # 弹出字典中的玩家数据 + self.player_tp_cache.pop(player_id) + msg_comp = serverApi.GetEngineCompFactory().CreateMsg(player_id) + msg_comp.NotifyOneMessage(player_id, "已移除传送点") + print '已移除玩家传送点' + else: + msg_comp = serverApi.GetEngineCompFactory().CreateMsg(player_id) + msg_comp.NotifyOneMessage(player_id, "请先设置传送点") + print '该玩家没有设置传送点' + if message.startswith('tppoint apply'): + pos_comp = serverApi.GetEngineCompFactory().CreatePos(player_id) + # 判断该玩家是否存有数据 + if player_id in self.player_tp_cache: + teleport_pos = self.player_tp_cache[player_id] + # 传送玩家 + pos_comp.SetPos(teleport_pos) + msg_comp = serverApi.GetEngineCompFactory().CreateMsg(player_id) + msg_comp.NotifyOneMessage(player_id, "已传送至传送点") + print '已应用玩家传送点' + else: + msg_comp = serverApi.GetEngineCompFactory().CreateMsg(player_id) + msg_comp.NotifyOneMessage(player_id, "请先设置传送点") + print '该玩家没有设置传送点' + + def Destroy(self): + pass + +``` diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/README.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.1_cmd_line.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.1_cmd_line.png new file mode 100644 index 0000000..d8dd07e Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.1_cmd_line.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.1_download_python.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.1_download_python.png new file mode 100644 index 0000000..bce9366 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.1_download_python.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.1_install_lib.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.1_install_lib.png new file mode 100644 index 0000000..b46fb37 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.1_install_lib.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.1_install_pycharm.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.1_install_pycharm.png new file mode 100644 index 0000000..ac13862 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.1_install_pycharm.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.1_install_successfully.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.1_install_successfully.png new file mode 100644 index 0000000..11c5b3c Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.1_install_successfully.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.1_open_demo.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.1_open_demo.png new file mode 100644 index 0000000..fc86b6e Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.1_open_demo.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.3_create_main.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.3_create_main.png new file mode 100644 index 0000000..fbe7172 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.3_create_main.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.3_create_main_2.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.3_create_main_2.png new file mode 100644 index 0000000..8670097 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.3_create_main_2.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.3_create_server.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.3_create_server.png new file mode 100644 index 0000000..a7ed666 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.3_create_server.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.3_create_server_2.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.3_create_server_2.png new file mode 100644 index 0000000..f8e3e2d Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.3_create_server_2.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.3_created.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.3_created.png new file mode 100644 index 0000000..ebe1710 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.3_created.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.4_in-game.gif b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.4_in-game.gif new file mode 100644 index 0000000..192fcd4 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.4_in-game.gif differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.4_set_knockback.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.4_set_knockback.png new file mode 100644 index 0000000..8a31d51 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.4_set_knockback.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.5_overload.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.5_overload.png new file mode 100644 index 0000000..f11195b Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.5_overload.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.5_print.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.5_print.png new file mode 100644 index 0000000..565ef27 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.5_print.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.6_export.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.6_export.png new file mode 100644 index 0000000..265030f Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.6_export.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.6_export_2.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.6_export_2.png new file mode 100644 index 0000000..3112f85 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.6_export_2.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.7_in-game.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.7_in-game.png new file mode 100644 index 0000000..68d02f2 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.7_in-game.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.7_tp_point_server_create.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.7_tp_point_server_create.png new file mode 100644 index 0000000..1aefce7 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.7_tp_point_server_create.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.7_tp_point_server_created.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.7_tp_point_server_created.png new file mode 100644 index 0000000..b710c54 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/10-模组SDK初步/images/11.7_tp_point_server_created.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/0-摘要.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/0-摘要.md new file mode 100644 index 0000000..89569de --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/0-摘要.md @@ -0,0 +1,20 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 5分钟 +--- + +# 摘要 + +在本章中,我们将一起更详细地学习**Molang**,了解Molang在实体中的应用,并一起在实体中实践Molang。 + +- 在第一节(*认识Molang*)中,我们将一起认识Molang,了解Molang语言的词法、结构和基本用法。 +- 在第二节(*Molang在自定义实体中的常用场景*)中,我们将一起来学习Molang在实体中的应用,了解Molang的应用场景。 +- 在第三节(*使用配置功能创建基础实体*)中,我们将使用编辑器创建一个基础的松鼠实体,作为Molang学习的演示实体。 +- 在第四节(*自定义松鼠实体资源*)中,我们将一起使用Molang自定义松鼠的资源。 +- 在第五节(*自定义松鼠实体行为*)中,我们将一起定义松鼠的行为。 +- 在第六节(*将实体动画与行为结合*)中,我们将把实体的动画和行为相结合,制作一个松鼠的攻击动画。 +- 在第七节(*联动生物事件与行为*)中,我们将把实体的事件和行为相结合,制作一个逃跑机制。 +- 在最后一节(*挑战:制作一辆卡丁车*)中,我们将进行一个挑战,制作一辆卡丁车。 + +本章关键词:Molang 表达式 子表达式 简单表达式 复杂表达式 查询函数 数学函数 实体变量 临时变量 上下文变量 松鼠 变体 简谐振动 宿存 导航 移动 跳跃 AI意向 属性 特性 内置事件 触发器 过滤器 目标 卡丁车 偏航角 钳制 定位器 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/1-认识Molang.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/1-认识Molang.md new file mode 100644 index 0000000..4772070 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/1-认识Molang.md @@ -0,0 +1,109 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 20分钟 +--- + +# 认识Molang + +**Molang**是一种基于表达式的类脚本语言,旨在使用简单的类脚本语言在较低层次的系统中不脱离数据驱动实现复杂的行为。当然,在脚本中我们依旧可以使用Molang,这有助于我们轻松获取一些内部成员或旗标的值,也可以实现和数据驱动的更复杂联动。Molang常用于实体的资源控制或世界生成器相关的操作中,支持单一的简单表达式和多行的复杂表达式的运算。 + +## 基本概念 + +Molang在数据驱动的JSON文件中经常作为一个字段的值而出现,这往往是一个字符串。字符串中的表达式便是Molang表达式。比如: + +```json +"some_field": "math.sin(query.anim_time * 1.23)" +``` + +其中`math.sin(query.anim_time * 1.23)`便是一个Molang表达式。根据国际版最新的概念标准,一个Molang**表达式**(**Expression**)指一个被引号包裹住的全部Molang语句的总和。如果一个表达式中存在多个语句,那么每个语句都被称为一个**子表达式**(**Sub-expression**)。比如: + +```cpp +temp.moo = math.sin(query.anim_time * 1.23); +temp.baa = math.cos(query.life_time + 2.0); +return temp.moo * temp.moo + temp.baa; +``` + +其中有三个语句,两个赋值语句和一个返回语句。这三条语句的每条语句都被称为一个Molang子表达式。在最新的标准中,我们不用“脚本”一词来称呼Molang。 + +通过上面的例子,我们可以看出,Molang表达式存在两种形态,一种是只包含一个语句的表达式,并且结尾不存在`;`作为“结束标识“,这种表达式称为**简单表达式**(**Simple Expression**)。另一种是包含多个子表达式(即多个语句)的表达式,每个子表达式的结尾必须存在一个`;`作为“结束标识“,这种表达式称为**复杂表达式**(**Complex Expression**)。两种表达式均可作为Molang参数写在JSON字段的值中。复杂表达式在作为字符串值时,所有的语句可以写在同一行中,也可以换行且保留缩进,类似于: + +```json +{ + "some_field": " + temp.moo = math.sin(query.anim_time * 1.23); + temp.baa = math.cos(query.life_time + 2.0); + return temp.moo * temp.moo + temp.baa; + ", + "some_other_field": "something" + // ... +} +``` + +我的世界可以解析这种换行的Molang字段,也仅Molang字段可以如此这般地解析。如果某个字段只接受普通的字符串,则引擎无法像上面的例子一样换行且保留缩进地解析。 + +每个Molang表达式都必须返回一个值。简单表达式将返回该语句本身计算的值。如果该语句是一个布尔校验,则返回布尔校验的结果值,`true`等价于`1.0`,`false`等价于`0.0`。如果该语句没有产生值,将返回`0.0`。 + +如果是复杂表达式,则需要使用`return`的子表达式来返回值。如果不存在这种返回语句,则不论中间计算多么“激烈”,最后都将只返回一个`0.0`。 + +## 运算符和关键字 + +Molang中有多种运算符和关键字,下面列出了全部的运算符和关键字。值得注意的是,除了字符串内的内容外,其他地方Molang中的字母都是大小写不敏感的,也就是说,同一个字母的大写和小写没有任何分别。 + +| 运算符、关键字或字面量 | 描述 | +| --------------------------------- | ------------------------------------------------------------ | +| `1.23` | 一个数值常量值。 | +| `! && || < <= >= > == !=` | 逻辑运算符,分别是非、与、或、小于、小于或等于、大于或等于、大于、等于、不等于运算符。 | +| `* / + -` | 基本数学运算符,分别是乘、除、加、减运算符。 | +| `(` `)` | 用于控制表达式中的项求值用的圆括号,也用于带参查询函数的传参。 | +| `[` `]` | 用于访问数组的方括号。 | +| `{` `}` | 用于控制执行作用域的花括号。 | +| `??` | 空合并运算符,用于处理丢失的变量和过时的活动对象引用。 | +| ` ? ` | 二元条件运算符。 | +| ` ? : ` | 三元条件运算符。 | +| `->` | 箭头运算符,用于访问来自另一个不同实体的数据。 | +| `geometry.geometry_name` | 一个在实体定义文件中命名的几何的短名称引用。 | +| `material.material_name` | 一个在实体定义文件中命名的材质的短名称引用。 | +| `texture.texture_name` | 一个在实体定义文件中命名的纹理的短名称引用。 | +| `math.function_name` | 用于访问各种数学函数。 | +| `query.function_name` | 用于访问一个实体的属性。 | +| `variable.variable_name` | 读写一个活动对象的存储器。 | +| `temp.variable_name` | 读写暂时存储器。 | +| `context.variable_name` | 访问在某些场景下游戏提供的只读存储。 | +| `this` | 该表达式(在指定上下文的情况下)最终写入值的当前值。 | +| `return` | 为复杂表达式设计,这将计算紧跟着该关键词的语句并停止整个表达式的执行,返回计算出的值。 | +| `loop` | 用于重复执行一个或多个命令。 | +| `for_each` | 用于遍历迭代一个实体数组。 | +| `break` | 用于提前退出`loop`或`for_each`的作用域。 | +| `continue` | 用于跳过`loop`或`for_each`迭代的语句集的其余部分,并移动到下一次迭代。 | + +上述运算符或关键字的详细用法可以参考[bedrock.dev上托管的Molang文档](https://bedrock.dev/zh/b/Molang)。 + +## 查询函数 + +在上面列出的关键字中,存在一种比较特殊的变量类型,被称为**查询函数**(**Query Function**),它的语法是`query.function_name`,其中`function_name`为某个查询函数名。顾名思义,查询函数是用于查询一个属性的值的函数。包括**全局参数**(**Global Parameter**)、**实体成员**(**Entity Member**)、**实体旗标**(**Entity Flag**)在内的各种各样的值都可以被查询函数所查询。 + +查询函数分为**无参查询函数**和**带参查询函数**。无参查询函数就是不具有参数表的查询函数。对于这种查询函数,直接写出其函数名即可获得对应属性的返回。比如本节最开头例子中的`query.anim_time`,便是用来查询一个全局参数`anim_time`的无参查询函数。 + +带参查询函数指的是比如先传入一些参数作为基础来查询特定的值的查询函数。这样的查询函数末尾需要紧跟一对圆括号(`(` `)`),然后在圆括号内写入参数表。比如下面的例子中的`query.get_nearby_entities`函数,他的第一个参数接受一个数字值,第二个参数接受一个字符串。 + +```cpp +v.x = 0; +for_each(v.pig, query.get_nearby_entities(4, 'minecraft:pig'), { + v.x = v.x + v.pig->query.get_relative_block_state(0, 1, 0, 'flammable'); +}); +``` + +具体的查询函数列表依旧可以在[bedrock.dev上托管的Molang文档](https://bedrock.dev/zh/b/Molang)中找到。在版本列表中选择与当前我的世界中国版相吻合的版本即可查看相对应版本的查询函数列表。 + +## 自定义变量 + +Molang中具备各种**变量**(**Variable**)。变量可以用来存储一个值,以供后续访问。Molang的变量存在三种类型,分别是实体变量、临时变量和上下文变量。 + +**实体变量**(**Entity Variable**)是一种存储在实体上的变量,这里的实体指的是广义的实体,指的是ECS框架中的实体,包括了方块、物品、粒子、世界生成器等。实体变量的生存周期与实体相一致,当实体在内存中被销毁时(比如实体在世界中消失),它的实体变量也将随之销毁而变得无法访问。这种变量我们使用`variable.variable_name`语法定义。 + +**临时变量**(**Temp Variable**)是一种存储在暂存器中的变量,这种变量在他们所定义的作用域中是有效的,在作用域的运算结束后将被销毁。不过,由于一些缺陷,目前的临时变量生命周期依然是全局的,所以在给临时变量命名时格外小心。这种变量使用`temp.variable_name`语法定义。 + +**上下文变量**(**Context Variable**)是一种只读变量,只能由硬编码的游戏引擎定义。这种变量是依赖于游戏运行状态的上下文而存在的。比如在铁砧上下文中,存在`context.other`代表铁砧的第二个输入槽位。如果脱离了铁砧的环境,将不存在该变量。这种变量使用`context.variable_name`语法定义。 + +开发者可以灵活地自定义实体变量和临时变量,在合适的实机改写或访问各种变量的值,从而使开发事倍功半。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/2-Molang在自定义实体中的常用场景.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/2-Molang在自定义实体中的常用场景.md new file mode 100644 index 0000000..807582e --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/2-Molang在自定义实体中的常用场景.md @@ -0,0 +1,96 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 10分钟 +--- + +# Molang在自定义实体中的常用场景 + +Molang在附加包各处都有应用,但是如果要说应用次数最多的场景,那当自定义实体莫属。本节中我们一起来了解Molang在自定义实体中的应用。 + +## 控制实体渲染 + +在第八章中,我们便了解了实体的渲染控制器。在实体的渲染控制器中,我们可以通过Molang来控制实体的几何、材质和纹理的选用。一般来说,我们存在两种比较常见的控制方法。第一种是使用数组,第二种是使用三元条件运算符`?:`。 + +```json +"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" +] +``` + +在上面的例子中,我们可以看到几何和纹理都是使用了数组,通过`query.get_animation_frame`查询的值作为数组下标索来得到对应的几何和纹理。而材质则是使用了三元条件运算符,通过对变量`variable.is_enchanted`的值进行布尔校验来决定是使用附魔的材质和默认的材质。 + +## 参与动画表现力 + +我们之前便了解到,动画中也可以使用Molang表达式来控制一个骨骼的某个通道的值。比如我们之前水鸭的移动动画。 + +```json +"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] + } + } +} +``` + +我们可以看到动画中使用了$\mathrm{cos}(query.anim\_time \times38.17)\times80.0$和$\mathrm{cos}(query.anim\_time\times38.17)\times-80.0$分别控制了`leg0`和`leg1`在$yOz$平面的旋转角度。`query.anim_time`是当前动画的播放时间,默认单位为秒(s),不过我们这里使用了`anim_time_update`字段更改了动画时间流逝速度,因此变为了使用移动速度来控制动画流速。且需要注意的是,`math.cos`等三角数学函数只接受角度制的值作为输入,并输出对应的三角函数值。 + +## 行为与动画结合 + +我们可以通过在动画中调用一些Molang变量使得动画和行为相结合。当一个实体的行为包组件中定义了攻击的AI意向时,我们便可以在其动画中访问到一个正确的`variable.attack_time`变量,这个变量对于非玩家实体代表着其攻击时间。当该实体不攻击时,则返回`0.0`。对于玩家来说则会返回一个攻击动画完成的百分比,取值为`0.0`至`1.0`。 + +我们以僵尸的徒手攻击动画为例: + +```json +"animation.zombie.attack_bare_hand" : { + "loop" : true, + "bones" : { + "leftarm" : { + "rotation" : [ "-90.0 - ((math.sin(variable.attack_time * 180.0) * 57.3) * 1.2 - (math.sin((1.0 - (1.0 - variable.attack_time) * (1.0 - variable.attack_time)) * 180.0) * 57.3) * 0.4) - (math.sin(query.life_time * 76.776372) * 2.865) - this", "5.73 - ((math.sin(variable.attack_time * 180.0) * 57.3) * 0.6) - this", "math.cos(query.life_time * 103.13244) * -2.865 - 2.865 - this" ] + }, + "rightarm" : { + "rotation" : [ "90.0 * (variable.is_brandishing_spear - 1.0) - ((math.sin(variable.attack_time * 180.0) * 57.3) * 1.2 - (math.sin((1.0 - (1.0 - variable.attack_time) * (1.0 - variable.attack_time)) * 180.0) * 57.3) * 0.4) + (math.sin(query.life_time * 76.776372) * 2.865) - this", "(math.sin(variable.attack_time * 180.0) * 57.3) * 0.6 - 5.73 - this", "math.cos(query.life_time * 103.13244) * 2.865 + 2.865 - this" ] + } + } +}, +``` + +可以看到`variable.attack_time`作为一个类似于查询函数作用的变量传入了旋转通道的各个分量中。 + +## 行为包实体组件和事件 + +实体的行为包定义文件中常常会定义一些组件和事件。Molang可以用来控制组件中相关变量的计算和事件的触发。比如,几乎所有的实体都会定义一个经验奖励组件,用于计算掉落的经验值。 + +```json +"minecraft:experience_reward": { + "on_bred": "Math.Random(1,7)", + "on_death": "query.last_hit_by_player?Math.Random(1,3):0" +} +``` + +## 联动模组SDK + +在模组SDK中可以使用`queryVariable`引擎组件来注册一个自定义查询函数。比如,以下代码便可以在客户端中注册一个`query.mod.is_custom_material`查询。 + +```python +import mod.client.extraClientApi as clientApi +compFactory = clientApi.GetEngineCompFactory() +# 注册一个自定义材质切换的查询函数 +comp = compFactory.CreateQueryVariable(clientApi.GetLevelId()) +result = comp.Register('query.mod.is_custom_material', 0.0) +# 更改该查询函数的值 +comp.Set('query.mod.is_custom_material', 1.0) +``` + +之后我们便可以在动画控制器中用`query.mod.is_custom_material`来控制实体的材质,而`query.mod.is_custom_material`的值则可以由Python脚本来控制,从而做到脚本使能资源控制。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/3-使用配置功能创建基础实体.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/3-使用配置功能创建基础实体.md new file mode 100644 index 0000000..c4c0a46 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/3-使用配置功能创建基础实体.md @@ -0,0 +1,107 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 10分钟 +--- + +# 使用配置功能创建基础实体 + +在接下来的几个小节中,我们将一起制作一个松鼠的演示实体。我们将一起通过松鼠实体的制作来学习Molang的各种高级功能。 + +## 使用配置创建松鼠实体 + +我们打开编辑器,创建一个新的AddOn组件,将其命名为“松鼠演示实体”。然后打开编辑器,将命名空间更改为`tutorial_demo`。预备工作完成后,我们开始使用配置功能新建实体。 + +![](./images/12.3_config_entity.png) + +![](./images/12.3_squirrel_entity.png) + +我们为松鼠进行命名,然后点击“创建”,便可以成功创建松鼠实体。创建结束后,我们可以打开松鼠的行为包和资源包定义文件来查看效果。 + +资源包定义文件: + +```json +{ + "format_version": "1.8.0", + "minecraft:client_entity": { + "description": { + "identifier": "tutorial_demo:squirrel" + } + } +} +``` + +行为包定义文件: + +```json +{ + "format_version": "1.12.0", + "minecraft:entity": { + "description": { + "identifier": "tutorial_demo:squirrel", + "is_experimental": false, + "is_spawnable": true, + "is_summonable": true + }, + "component_groups": { + + }, + "components": { + "minecraft:persistent": { + + } + }, + "events": { + + } + } +} +``` + +我们可以看到,由于我们使用的是空白数据模板,所以这两个文件内容较为“干瘪”。 + +我们对资源包定义文件稍加补充,以便我们之后添加资源: + +```json +{ + "format_version": "1.8.0", + "minecraft:client_entity": { + "description": { + "identifier": "tutorial_demo:squirrel",/* + "materials": { + + }, + "textures": { + + }, + "geometry": { + + }, + "animations": { + + }, + "animation_controllers": [ + + ], + "render_controllers": [ + + ], + "spawn_egg": { + + }*/ + } + } +} +``` + +由于编辑器自动创建的是`1.8.0`的格式版本,所以动画和动画控制器是分开定义的。我们也为其创建`animation_controllers`数组,以便定义动画控制器。与`1.10.0`的格式版本不同,`1.8.0`的格式版本无法进行条件控制的动画播放,即没有`scripts/animate`字段。不过这不要紧,因为在`1.8.0`格式版本下,虽然动画本身不能直接通过实体的资源包定义文件播放,但是所有的动画控制器都是自动开始播放,我们只需要通过动画控制器来控制动画即可。 + +我们为松鼠添加一个默认的材质。因为我们希望松鼠的各方便表现得和兔子差不多,自然也希望松鼠像兔子一样渲染,所以我们为松鼠添加原版兔子默认的材质。 + +```json +"materials": { + "default": "rabbit" +} +``` + +这样,我们便创建了一个新的尚未添加模型和行为的实体,我们将在下面两节中集中完成这些工作。 diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/4-自定义松鼠实体资源.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/4-自定义松鼠实体资源.md new file mode 100644 index 0000000..a83b2ff --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/4-自定义松鼠实体资源.md @@ -0,0 +1,674 @@ +--- +front: https://nie.res.netease.com/r/pic/20220408/710ecfce-57e5-4f5d-88b9-0eda8c2d1c45.gif +hard: 高级 +time: 40分钟 +selection: true +--- + +# 自定义松鼠实体资源 + +在本节中,我们将一起完成松鼠的资源部分。有关[松鼠模型](https://g79.gdl.netease.com/addonguide-12.zip)的资源可以点击链接下载。 + +## 挂接几何体和贴图纹理资源 + +类似以第八章中的步骤,我们在Blockbench中创建松鼠模型的几何和纹理。 + +![](./images/12.4_squirrel_geometry.png) + +在这里,我们创建了一个松鼠的几何,同时为这个几何创建了两套纹理,一套红色纹理,一套灰色纹理。我们希望在世界中生成的有些松鼠是红色松鼠,有些松鼠是灰色松鼠,因此两套纹理便可以满足我们的需求。 + +我们将松鼠的几何`squirrel.geo.json`导出到资源包的`models\entity`文件夹下,将纹理`red.png`和`gray.png`分别导出到资源包的`textures\entity\squirrel`文件夹下。我们展示一下松鼠导出的几何JSON文件: + +```json +{ + "format_version": "1.12.0", + "minecraft:geometry": [ + { + "description": { + "identifier": "geometry.squirrel", + "texture_width": 64, + "texture_height": 64, + "visible_bounds_width": 4, + "visible_bounds_height": 2, + "visible_bounds_offset": [0, 1, 0] + }, + "bones": [ + { + "name": "root", + "pivot": [0, 7, 3], + "rotation": [-20, 0, 0], + "mirror": true + }, + { + "name": "body", + "parent": "root", + "pivot": [0, 5.83072, 3.3825], + "cubes": [ + {"origin": [-3, 8.83072, 1.3825], "size": [6, 1, 7], "uv": [22, 0]}, + {"origin": [-3, 3.83072, -1.6175], "size": [6, 5, 10], "uv": [0, 0]} + ] + }, + { + "name": "head", + "parent": "body", + "pivot": [0, 6.83072, -0.6175], + "rotation": [20, 0, 0], + "mirror": true, + "cubes": [ + {"origin": [-2.5, 6.83072, -5.6175], "size": [5, 4, 5], "uv": [26, 26], "mirror": false}, + {"origin": [-2.5, 10.83072, -4.6175], "size": [5, 1, 4], "uv": [40, 21], "mirror": false}, + {"origin": [-1, 8.33072, -6.1175], "size": [2, 1, 1], "uv": [0, 19], "mirror": false} + ] + }, + { + "name": "bone", + "parent": "head", + "pivot": [0, 11.83072, -1.6175], + "rotation": [25, 0, 0], + "cubes": [ + {"origin": [-2.5, 11.83072, -2.6175], "size": [1, 2, 2], "uv": [0, 15]}, + {"origin": [1.5, 11.83072, -2.6175], "size": [1, 2, 2], "uv": [0, 15], "mirror": true} + ] + }, + { + "name": "frontLegLeft", + "parent": "body", + "pivot": [-3, 5.83072, 0.3825], + "mirror": true, + "cubes": [ + {"origin": [-4, -1.16928, -0.6175], "size": [2, 7, 2], "uv": [0, 44], "mirror": false} + ] + }, + { + "name": "frontLegRight", + "parent": "body", + "pivot": [3, 5.83072, 0.3825], + "mirror": true, + "cubes": [ + {"origin": [2, -1.16928, -0.6175], "size": [2, 7, 2], "uv": [0, 44]} + ] + }, + { + "name": "tail", + "parent": "body", + "pivot": [0, 5.83072, 7.3825], + "mirror": true, + "cubes": [ + {"origin": [-1.5, 4.33072, 7.3825], "size": [3, 14, 3], "uv": [32, 8], "mirror": false}, + {"origin": [-3.5, 7.33072, 10.3825], "size": [7, 12, 6], "uv": [0, 15], "mirror": false} + ] + }, + { + "name": "haunchRight", + "parent": "body", + "pivot": [3, 7.33072, 7.0825], + "rotation": [-20, 0, 0], + "mirror": true, + "cubes": [ + {"origin": [-4, 3.92839, 4.80079], "size": [2, 5, 6], "uv": [0, 33], "mirror": false} + ] + }, + { + "name": "rearFootRight", + "parent": "haunchRight", + "pivot": [0, 3.92839, 9.80079], + "rotation": [40, 0, 0], + "mirror": true, + "cubes": [ + {"origin": [-4, 2.42839, 4.10079], "size": [2, 2, 7], "inflate": 0.05, "uv": [16, 35], "mirror": false} + ] + }, + { + "name": "haunchLeft", + "parent": "body", + "pivot": [3, 7.33072, 7.0825], + "rotation": [-20, 0, 0], + "mirror": true, + "cubes": [ + {"origin": [2, 3.92839, 4.80079], "size": [2, 5, 6], "uv": [0, 33]} + ] + }, + { + "name": "rearFootLeft", + "parent": "haunchLeft", + "pivot": [0, 3.92839, 9.80079], + "rotation": [40, 0, 0], + "mirror": true, + "cubes": [ + {"origin": [2, 2.42839, 4.10079], "size": [2, 2, 7], "inflate": 0.05, "uv": [16, 35]} + ] + } + ] + } + ] +} +``` + +然后我们就可以开始在资源包定义中挂接几何和纹理了。 + +```json +{ + "format_version": "1.8.0", + "minecraft:client_entity": { + "description": { + "identifier": "tutorial_demo:squirrel", + "materials": { + "default": "rabbit" + }, + "textures": { + "red": "textures/entity/squirrel/red", + "gray": "textures/entity/squirrel/gray" + }, + "geometry": { + "default": "geometry.squirrel" + },/* + "animations": { + + }, + "animation_controllers": [ + + ], + "render_controllers": [ + + ], + "spawn_egg": { + + }*/ + } + } +} +``` + +这样,我们的纹理和几何便挂接完毕了,接下来我们需要使用渲染控制器来将材质、纹理和几何应用到实体上,同时为红色和灰色纹理制作单独的变体渲染。 + +## 使用渲染控制器制作变体 + +我们在资源包的`render_controllers`文件夹中创建`squirrel.render_controllers.json`文件。然后在先其中写入我们的纹理数组、几何和材质。 + +```json +{ + "format_version": "1.8.0", + "render_controllers": { + "controller.render.squirrel": { + "arrays": { + "textures": { + "Array.skins": [ + "Texture.red", + "Texture.brown" + ] + } + }, + "geometry": "Geometry.default", + "materials": [ { "*": "Material.default" } ], + "textures": [ + // texture list + ] + } + } +} +``` + +最重要的便是如何使用变体制作纹理。事实上,原版的兔子便提供了一种非常好的变体方案。那便是在行为包定义中使用`minecraft:variant`组件。`minecraft:variant`组件只接受一个整数值,这个值可以作为实体的“变体ID”,从而定义实体的一个**变体**(**Variant**)。而实体的变体ID又可以通过Molang查询函数`query.variant`在客户端得到。所以我们可以使用该ID来引用纹理数组。我们假设我们已经在行为包中定义了红色的变体ID是0,灰色的变体ID是1,这是和我们的纹理数组索引相一致的定义。这样我们就可以使用`Array.skins[query.variant]`这样的Molang表达式来做到实时根据变体来变换纹理。补全的控制器如下: + +```json +{ + "format_version": "1.8.0", + "render_controllers": { + "controller.render.squirrel": { + "arrays": { + "textures": { + "Array.skins": [ + "Texture.red", + "Texture.gray" + ] + } + }, + "geometry": "Geometry.default", + "materials": [ { "*": "Material.default" } ], + "textures": [ + "Array.skins[query.variant]" + ] + } + } +} +``` + +我们将该控制器定义到实体资源包定义文件中,得到如下定义文件: + +```json +{ + "format_version": "1.8.0", + "minecraft:client_entity": { + "description": { + "identifier": "tutorial_demo:squirrel", + "materials": { + "default": "rabbit" + }, + "textures": { + "red": "textures/entity/squirrel/red", + "gray": "textures/entity/squirrel/gray" + }, + "geometry": { + "default": "geometry.squirrel" + },/* + "animations": { + + }, + "animation_controllers": [ + + ],*/ + "render_controllers": [ + "controller.render.squirrel" + ],/* + "spawn_egg": { + + }*/ + } + } +} +``` + +## 自定义实体蛋贴图 + +接下来我们自定义实体的刷怪蛋贴图。我们可以仿照之前水鸭的定义,通过两个颜色**基色**(**Base Color**)和**覆盖色**(**Overlay Color**)来来定义刷怪蛋的颜色。颜色使用的是16进制颜色码。开发者可以使用网上很多公开的颜色预览软件来预览颜色并使用。我们也可以使用Blockbench的Minecraft Entity Wizard插件来快速预览,这是一种取巧的办法。 + +![](./images/12.4_squirrel_egg.png) + +我们将刷怪蛋颜色添加到客户端定义文件中: + +```json +{ + "format_version": "1.8.0", + "minecraft:client_entity": { + "description": { + "identifier": "tutorial_demo:squirrel", + "materials": { + "default": "rabbit" + }, + "textures": { + "red": "textures/entity/squirrel/red", + "gray": "textures/entity/squirrel/gray" + }, + "geometry": { + "default": "geometry.squirrel" + },/* + "animations": { + + }, + "animation_controllers": [ + + ],*/ + "render_controllers": [ + "controller.render.squirrel" + ], + "spawn_egg": { + "base_color":"#1778D2", + "overlay_color":"#1778D2" + } + } + } +} +``` + +![](./images/12.4_squirrel_static_in-game.png) + +至此,我们初步制作了一个“不会动”的松鼠。我们可以先进入游戏自测来查看这个松鼠的渲染情况了。接下来,我们为松鼠制作动画。由于我们还没有为松鼠定义行为,因此我们的动画目前还不能在游戏中预览,因此我们打算采用Blockbench可视化地制作动画。 + +## 利用Molang制作闲置动画 + +![](./images/12.4_squirrel_blockbench_animation_mode.png) + +我们再次打开我们之前制作的松鼠模型的Blockbench项目文件。点击右上角的“**动画模式**”进入动画制作模式。 + +![](./images/12.4_squirrel_blockbench_animation_mode_full_screen.png) + +我们可以看到,整个界面的各个窗格发生了改变。左侧原来是纹理贴图的窗格变为了“**动画**”窗格,这里存放着我们为该实体添加的动画。动画窗格的下方是“**关键帧**”窗格和“**变量占位符**”窗格。关键帧窗格将显示当前通道当前关键帧的各个轴向的值。而变量占位符则是用于定义一些只有在游戏内才能获取的变量或查询的“占位”值。因为这些变量或查询是需要在游戏中有上下文的,而在Blockbench中是没有值的。为了预览效果,我们需要在这里给他们定义值。最下方是动画的**时间轴**,我们可以通过时间轴进行调整时间、预览动画等操作。 + +![](./images/12.4_squirrel_create.png) + +我们在动画窗格中右键,点击“**添加动画**”。 + +![](./images/12.4_squirrel_idle_create.png) + +然后将名称改为`animation.squirrel.idle`,循环改为“循环播放”。因为我们希望我们的`idle`动画在松鼠的闲置AI意向触发时持续播放,所以我们使用循环播放。这等价于在对应的JSON文件中为其添加`"loop": true`。这样我们就添加了一个新动画。 + +![](./images/12.4_squirrel_save.png) + +![](./images/12.4_squirrel_save_as.png) + +为了防止我们的制作成果丢失,我们先及时将其保存为JSON文件。点击文件上的保存按钮,并定位到我们的资源包中的`animations`文件夹中,然后在另存为对话框中点击“**保存(S)**”。 + +![](./images/12.4_squirrel_idle_start.png) + +下面,我们开始制作待机动画。我们希望松鼠在待机时整体身子轻微前后移动。所以我们在右侧先选中`body`骨骼组。 + +![](./images/12.4_squirrel_body_selected.png) + +我们可以看到,此时预览窗中`body`的轮廓已经显示出来了,同时下方时间轴中加入了`body`的三个通道。由于我们希望其仅仅是前后摇动,所以我们目前只关注“**旋转**”通道。 + +![](./images/12.4_squirrel_add_key_frame_rotation.png) + +我们点击旋转通道右侧的“**+**”按钮,这个按钮可以在当前时间轴的位置上添加一个关键帧。由于我们的时间轴目前停留在`0.0`的初始位置,此时如果只在此处加入一个关键帧就相当于之前我们讲过的不使用关键帧的情形。其实这种情形就是等价于只在`0.0`处有一个关键帧。我们在这个关键帧中加入Molang表达式,同时由于这是循环动画,这样就能保证Molang表达式每帧进行运算并将每帧算出的不同的值应用到实体上,而实体就会根据Molang运算的结果进行移动。 + +![](./images/12.4_squirrel_key_frame.png) + +加入关键帧后,我们可以在时间轴上发现一个菱形标记。这意味着一个关键帧。同时,我们可以在左侧“关键帧”窗格中看到当前的值。此时,松鼠上的坐标轴变为球形坐标轴。我们可以看到,我们希望的”前后摇摆“动画可以由球形坐标轴中红色代表的面进行旋转而得到。而我们又可以在“关键帧”窗格中定位到红色,这是X轴。所以我们在X轴上进行Molang撰写。事实上,X轴是模型空间的东西方向,所以X轴的垂面便是我们在预览窗中看到的红色的面。 + +那么,我们到底如何才能通过Molang实现身体随着时间的流逝前后摇摆呢?这需要通过Molang的查询函数`query.anim_time`来做到。`query.anim_time`是一个全局参数,代表着一个动画自开始播放以来过去的时间,单位为秒(s)。那么,我们来回忆,以时间为自变量,什么函数能够做到“来回摆动”呢?我们可能很快能想到单摆、弹簧、简谐运动。没错,我们只需要像简谐运动那样使用一个`cos`或`sin`函数来控制,就可以做到随着时间骨骼在一定方向上来回的摆动。 + +对于“旋转”通道来说,就是摆动的角度随着时间做简谐振动;对于“位置”通道来说,就是骨骼在某个轴向上的位移随着时间做简谐振动。简谐振动公式为: +$$ +x=A\mathrm{cos}(\omega t+\varphi)+x_0 +$$ +其中,$A$为**振幅**,即摆动或振动的幅度,其值越大,骨骼就会“越摆”或者“越振”。$\omega$是振动的**角速度**或**角频率**,代表摆动或振动的快慢。$\varphi$代表振动的初相位,即在振动的一个循环中位于哪个初始位置。$x_0$代表初位移,一般来说,我们使用余弦函数时,不指定初相位的情况下,$t$为0时就会出现最后的计算值为1或者为其振幅$A$的情况,此时代表着动画从振动的一个端点开始,而非原点开始。如果我们希望动画从静止状态开始,即从原点开始,那么就必须令$x_0=-A$,比如,使用$x=A\mathrm{cos}(\omega t)-A$这种表达式。 + +在我的世界中,`query.anim_time`便代表时间$t$,我们使用`math.cos`来做到余弦函数的计算。比如,我们在Blockbench中使用这种表达: + +![](./images/12.4_squirrel_molang.png) + +这代表着$x=5\mathrm{cos}(450t)$。当然,其中的角速度450是我们摸索出来的比较合适的速度。你可以通过时间轴上的播放按钮来播放预览,然后根据自己的感觉再修改这里的值。 + +![](./images/12.4_squirrel_play.png) + +![](./images/12.4_molang_playing.gif) + +依据这个原理,我们为所有骨骼添加`0.0`的关键帧并在X轴向上添加简谐运动的动画。注意,为了保持所有的骨骼运动频率一致,我们需要所有骨骼全部使用相同的角速度,否则运动着运动着就会出现“四肢不协调”的丑态。 + +![](./images/12.4_idle_anim.gif) + +我们同时将其完成后的JSON展示如下: + +```json +{ + "format_version": "1.8.0", + "animations": { + "animation.squirrel.idle": { + "loop": true, + "bones": { + "tail": { + "rotation": ["math.sin(query.anim_time * 450) * 15", 0, 0] + }, + "head": { + "rotation": ["math.sin(query.anim_time * 450) * 15", 0, 0] + }, + "frontLegLeft": { + "rotation": ["math.cos(query.anim_time * 450) * 65 - 65", 0, 0] + }, + "frontLegRight": { + "rotation": ["math.cos(query.anim_time * 450) * 45 - 45", 0, 0] + }, + "body": { + "rotation": ["math.cos(query.anim_time * 450) * 5", 0, 0] + } + } + } + } +} +``` + +事实上,在编辑动画的过程中,我们可以在时间轴中点击左上角的“切换图像编辑器”以打开函数图像视图。这个视图会渲染我们的Molang表达式所运算的函数图像,这将更有助于我们理解动画的过程。 + +![](./images/12.4_squirrel_graph.png) + +而且,如果开发者有充足的数学功底,我们还可以使用多个简谐运动叠加成一个更复杂的运动,这被称为傅里叶级数的叠加。通过傅里叶叠加的运动将更加真实,不过,也将涉及到更多的数学技巧,所以这不属于我们今天的讨论范围。 + +## 利用Molang制作移动动画 + +移动动画和闲置动画原理是一致的,不过,除了各个骨骼的旋转之外,这里统控所有骨骼的`body`骨骼组需要进行一个平移。也就是说,我们需要修改`body`的位置通道。 + +![](./images/12.4_squirrel_move.png) + +由于我们是要前后移动,而X轴代表东西反向,也就是松鼠的左右,所以我们的X轴不懂,而其余两个轴以相同的频率前后或上下振动。 + +![](./images/12.4_move_body.gif) + +在整体平移的过程中我们再为其他各个骨骼添加同角频率旋转效果。因此,我们得到了一个移动的动画,我们命名为`move`并放置到同一个JSON文件中。 + +![](./images/12.4_move_all.gif) + +我们查看JSON文件中该动画的部: + +```json +{ + "format_version": "1.8.0", + "animations": { + "animation.squirrel.idle": { + // ... + }, + "animation.squirrel.move": { + "loop": true, + "bones": { + "body": { + "position": [0, "math.cos(query.anim_time * 500) * -1.4 + 1.4", "math.cos(query.anim_time * 500) * 3 - 3"] + }, + "head": { + "rotation": ["math.sin(query.anim_time * 500) * 10", 0, 0] + }, + "haunchLeft": { + "rotation": ["math.cos(query.anim_time * 500) * -30 + 30", 0, 0] + }, + "rearFootLeft": { + "rotation": ["math.cos(query.anim_time * 500) * -15 + 15", 0, 0] + }, + "frontLegLeft": { + "rotation": ["math.cos(query.anim_time * 500) * 20 - 20", 0, 0] + }, + "frontLegRight": { + "rotation": ["math.cos(query.anim_time * 500) * 35 - 35", 0, 0] + }, + "tail": { + "rotation": ["math.sin(query.anim_time * 500) * 20", 0, 0] + }, + "haunchRight": { + "rotation": ["math.cos(query.anim_time * 500) * -35 + 35", 0, 0] + }, + "rearFootRight": { + "rotation": ["math.cos(query.anim_time * 500) * -17.5 + 17.5", 0, 0] + } + } + } + } +} +``` + +## 利用Molang制作头部旋转动画 + +我们知道,原版的很多实体在玩家靠近时都会看向玩家,这是由于实体具备看向玩家的AI意向同时具备一个`look_at_target`的动画配合完成的。那么,我们如何制作看向玩家的动画呢?这需要用到`query.target_x_rotation`和`query.target_y_rotation`。这两个Molang变量代表实体看向玩家旋转的角度,因此,我们只需要将实体的`head`骨骼的旋转通道设定为这两个查询函数即可。 + +![](./images/12.4_squirrel_look_at_player.png) + +```json +{ + "format_version": "1.8.0", + "animations": { + "animation.squirrel.idle": { + // ... + }, + "animation.squirrel.move": { + // ... + }, + "animation.squirrel.look_at_target": { + "loop": true, + "bones": { + "head": { + "rotation": ["query.target_x_rotation", "query.target_y_rotation", 0] + } + } + } + } +} +``` + +## 将动画挂接到实体上 + +由于`1.8.0`的实体客户端格式版本无法做到直接条件控制播放动画,我们使用动画控制器来控制动画的播放。我们在资源包的`animation_controllers`文件夹下新建一个`squirrel.animation_controllers.json`文件,用于控制实体的动画播放。 + +在编辑动画控制器之前,我们先定义动画的短名称到实体客户端定义文件中。 + +```json +{ + "format_version": "1.8.0", + "minecraft:client_entity": { + "description": { + "identifier": "tutorial_demo:squirrel", + "materials": { + "default": "rabbit" + }, + "textures": { + "red": "textures/entity/squirrel/red", + "gray": "textures/entity/squirrel/gray" + }, + "geometry": { + "default": "geometry.squirrel" + }, + "animations": { + "move": "animation.squirrel.move", + "idle": "animation.squirrel.idle", + "look_at_target": "animation.squirrel.look_at_target" + },/* + "animation_controllers": [ + + ],*/ + "render_controllers": [ + "controller.render.squirrel" + ], + "spawn_egg": { + "base_color":"#1778D2", + "overlay_color":"#1778D2" + } + } + } +} +``` + +然后我们便可以在动画控制器中写入动画的播放条件了,比如,我们按照如下格式来书写: + +```json +{ + "format_version" : "1.10.0", + "animation_controllers" : { + "controller.animation.squirrel.general" : { + "initial_state" : "default", + "states" : { + "default" : { + "variables": { + "move_speed": { + "input": "query.modified_move_speed", + "remap_curve": { + "0.01": 0.0, + "0.1": 1.0 + } + }, + "is_idling": { + "input": "query.modified_move_speed", + "remap_curve": { + "0.0": 1.0, + "0.01": 0.0 + } + } + }, + "animations" : [ + { + "idle" : "variable.is_idling && query.is_on_ground" + }, + "look_at_target", + { + "move" : "!query.is_on_ground || variable.move_speed" + } + ] + } + } + } + } +} +``` + +其中`variables`字段用于定义后面可能会用到的变量。这里我们使用了`remap_curve`字段,即**曲线重映射**(**Curve Remap**)功能。根据`query.modified_move_speed`的值,当其在0.01到0.1之间变动时`variable.move_speed`将线性重映射到0.0至1.0之间,低于0.01则重映射到0.0,大于0.1将重映射到1.0;同时,当其在0.0到0.01之间变动时,`variable.is_idling`将线性重映射到1.0至0.0之间,当其大于0.01时将始终重映射到0.0。 + +`"idle" : "variable.is_idling && query.is_on_ground"`和`"move" : "!query.is_on_ground || variable.move_speed"`的意思便是冒号后面两个Molang表达式的值分别传入`idle`和`move`动画,作为`query.anim_time`的流逝速度取值来源。本来`query.anim_time`的取值来源是计算机的时间计时器流逝速度,但是一旦更改为我们修改过的速度,比如`!query.is_on_ground || variable.move_speed`,由于`||`的短路性,这便相当于`query.anim_time`变成了实体在生成后走过的实际距离,同时只有在实体离地时`query.anim_time`才会流逝。这是更加合理的。 + +我们将动画控制器也挂接在实体上: + +```json +{ + "format_version": "1.8.0", + "minecraft:client_entity": { + "description": { + "identifier": "tutorial_demo:squirrel", + "materials": { + "default": "rabbit" + }, + "textures": { + "red": "textures/entity/squirrel/red", + "gray": "textures/entity/squirrel/gray" + }, + "geometry": { + "default": "geometry.squirrel" + }, + "animations": { + "move": "animation.squirrel.move", + "idle": "animation.squirrel.idle", + "look_at_target": "animation.squirrel.look_at_target" + }, + "animation_controllers": [ + { "general": "controller.animation.squirrel.general" }, + { "move": "controller.animation.squirrel.move" } + ], + "render_controllers": [ + "controller.render.squirrel" + ], + "spawn_egg": { + "base_color":"#1778D2", + "overlay_color":"#1778D2" + } + } + } +} +``` + +至此,我们完成了基本的资源控制,最后,为了后续讲述方便,我们再为实体添加一个功能。 + +## 为松鼠打开装备渲染 + +我们让松鼠具备穿戴**附着物**(**Attachable**,***挂件***)的功能,这样我们便可以在行为组件中使用装备组件为其添加装备,比如一个头盔。注意,各种装备的挂接骨骼名称都是硬编码的,比如头盔只能挂接在`head`骨骼上。如果你的实体几何中没有`head`骨骼,那么挂接了头盔的附着物也无法正常显示。 + +我们使用`enable_attachables`启用附着物: + +```json +{ + "format_version": "1.8.0", + "minecraft:client_entity": { + "description": { + "identifier": "tutorial_demo:squirrel", + "materials": { + "default": "rabbit" + }, + "textures": { + "red": "textures/entity/squirrel/red", + "gray": "textures/entity/squirrel/gray" + }, + "geometry": { + "default": "geometry.squirrel" + }, + "animations": { + "move": "animation.squirrel.move", + "idle": "animation.squirrel.idle", + "look_at_target": "animation.common.look_at_target" + }, + "animation_controllers": [ + { "general": "controller.animation.squirrel.general" } + ], + "render_controllers": [ + "controller.render.squirrel" + ], + "spawn_egg": { + "base_color":"#1778D2", + "overlay_color":"#1778D2" + }, + "enable_attachables": true + } + } +} +``` + +接下来,我们便需要将关注点从资源包转移到行为包了。毕竟,我们的松鼠目前还是没有任何行为,如同一个“呆鼠”,我们需要使用行为包为其赋予生命活力! diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/5-自定义松鼠实体行为.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/5-自定义松鼠实体行为.md new file mode 100644 index 0000000..3bd8123 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/5-自定义松鼠实体行为.md @@ -0,0 +1,669 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 30分钟 +--- + +# 自定义松鼠实体行为 + +下面,我们一起来制作松鼠的行为。我们打开松鼠的行为包定义文件,依次向其中加入一些必要的行为组件。 + +```json +{ + "format_version": "1.12.0", + "minecraft:entity": { + "description": { + "identifier": "tutorial_demo:squirrel", + "is_experimental": false, + "is_spawnable": true, + "is_summonable": true + }, + "component_groups": { + + }, + "components": { + "minecraft:persistent": { + + } + }, + "events": { + + } + } +} +``` + +我们可以看到,已经有一个组件随着编辑器创建而加入了,即`minecraft:persistent`。`minecraft:persistent`组件用于控制生物是否是**宿存的**(**Persistent**,***持久化保存***)。宿存的生物不会因距离过远而被引擎销毁。事实上,我们是不需要这个组件的。因为这不是类似于画之类的必须宿存的实体,因而其存在只会徒增引擎负担,所以我们将其删去。 + +## 添加组件 + +下面我们开始添加一系列组件。在这里,我们仅仅为了演示,所以并不打算介绍所有的组件,也不欲纠结于一个生物的组件搭配是否非常完美。 + +### 添加常规组件 + +```json +{ + "format_version": "1.12.0", + "minecraft:entity": { + "description": { + "identifier": "tutorial_demo:squirrel", + "is_experimental": false, + "is_spawnable": true, + "is_summonable": true + }, + "component_groups": { + + }, + "components": { + "minecraft:hurt_on_condition": { + "damage_conditions": [ + { + "filters": { + "test": "in_lava", + "subject": "self", + "operator": "==", + "value": true + }, + "cause": "lava", + "damage_per_tick": 4 + } + ] + }, + "minecraft:pushable": { + "is_pushable": true, + "is_pushable_by_piston": true + }, + "minecraft:experience_reward": { + "on_death": "query.last_hit_by_player ? Math.Random(0,1) : 0" + }, + "minecraft:breathable": { + "total_supply": 15, + "suffocate_time": 0 + }, + "minecraft:physics": {} + }, + "events": { + + } + } +} +``` + +我们添加一些大部分生物都应该具有的组件。`minecraft:hurt_on_condition`是依照条件收到伤害,这里我们设置为在熔岩中会受到伤害。我们知道在熔岩中我们会同时受到两种伤害,一个是火焰灼伤,一个是熔岩灼伤,这里便控制着熔岩的灼伤。`minecraft:pushable`代表着我们的实体是否会被推动。我们把被实体推动和被活塞推动全部设置为`true`。`minecraft:experience_reward`代表击杀时掉落的经验奖励,Molang表达式用于计算掉落的经验值。`minecraft:breathable`为实体在水中的可呼吸性。`minecraft:physics`代表该实体受到物理引擎的影响。 + +### 添加导航组件 + +```json +{ + "format_version": "1.12.0", + "minecraft:entity": { + "description": { + "identifier": "tutorial_demo:squirrel", + "is_experimental": false, + "is_spawnable": true, + "is_summonable": true + }, + "component_groups": { + + }, + "components": { + // ... + "minecraft:breathable": { + "total_supply": 15, + "suffocate_time": 0 + }, + "minecraft:physics": {}, + "minecraft:navigation.walk": { + "can_path_over_water": true, + "avoid_water": true + } + }, + "events": { + + } + } +} +``` + +我们加入一个`navigation.`前缀的组件,这类组件被称为导航组件。每个实体都必须有一个导航组件才能正常运行其寻路算法。不同的导航组件将执行不同的寻路逻辑。实体不可以拥有多个导航组件,即每个实体只能拥有一种导航组件。这里的`minecraft:navigation.walk`组件意味着实体使用“步行”的寻路算法。 + +### 添加移动组件 + +```json +{ + "format_version": "1.12.0", + "minecraft:entity": { + "description": { + "identifier": "tutorial_demo:squirrel", + "is_experimental": false, + "is_spawnable": true, + "is_summonable": true + }, + "component_groups": { + + }, + "components": { + // ... + "minecraft:physics": {}, + "minecraft:navigation.walk": { + "can_path_over_water": true, + "avoid_water": true + }, + "minecraft:movement.skip": {} + }, + "events": { + + } + } +} +``` + +我们接着添加一个`movement.`前缀的组件,这类组件称为移动组件。移动组件控制着实体“如何进行移动”。这里的移动并不是指如何寻路,而是如何“沿着寻路算法给出的路径到达目的地”。说白了,就是实体“走路”的方式,只不过这里的“走路”不一定真的指代行走。当然,如果实体缺失了该类型的组件,它将不具备移动的能力。这里我们期待松鼠采用兔子一样边走边跳跃的方式来“走路”,即“跳着走”。所以我们使用了`minecraft:movement.skip`。注意,史莱姆和岩浆怪采用了另一种移动方式组件,被称为`minecraft:movement.jump`,这代表只用跳跃来走路,和兔子的“蹿行”的跳跃方式是不同的。 + +### 添加跳跃组件 + +```json +{ + "format_version": "1.12.0", + "minecraft:entity": { + "description": { + "identifier": "tutorial_demo:squirrel", + "is_experimental": false, + "is_spawnable": true, + "is_summonable": true + }, + "component_groups": { + + }, + "components": { + // ... + "minecraft:navigation.walk": { + "can_path_over_water": true, + "avoid_water": true + }, + "minecraft:movement.skip": {}, + "minecraft:jump.static": {} + }, + "events": { + + } + } +} +``` + +还有一种组件是以`jump.`为前缀的组件,我们称为跳跃组件。跳跃组件即控制实体跳跃的方式。注意,这种组件仅仅是控制实体跳跃的方式,并不控制实体何时跳跃,也不控制实体是否在跳跃。但是,如果缺失该组件,实体将不具备跳跃的能力。目前只有两种跳跃组件,一种是静态跳跃`minecraft:jump.static`,代表每次跃起都保持完全一致,另一种是动态跳跃`minecraft:jump.dynamic`,代表每次跃起可能会根据实体的速度修饰符进行改变,比如跃起的高度将随速度发生变化。 + +### 添加AI意向组件 + +```json +{ + "format_version": "1.12.0", + "minecraft:entity": { + "description": { + "identifier": "tutorial_demo:squirrel", + "is_experimental": false, + "is_spawnable": true, + "is_summonable": true + }, + "component_groups": { + + }, + "components": { + // ... + "minecraft:movement.skip": {}, + "minecraft:jump.static": {}, + "minecraft:behavior.float": { + "priority": 0 + }, + "minecraft:behavior.hurt_by_target": { + "priority": 1 + }, + "minecraft:behavior.random_stroll": { + "priority": 6, + "speed_multiplier": 0.8, + "xz_dist": 2, + "y_dist": 1 + }, + "minecraft:behavior.random_look_around": { + "priority": 9 + }, + "minecraft:behavior.look_at_player": { + "priority": 11 + } + }, + "events": { + + } + } +} +``` + +接下来,我们添加实体的**AI意向**(**AI Goal**)。实体的AI意向全都以`behavior.`为前缀,代表实体对某种类型的动作的“钟意”程度。每种AI意向都有一个**优先级**(**Priority**),优先级是一个整数。当意向之间发生冲突时,优先级的值越低的AI意向越可能优先执行,即**优先级的值越低代表着优先级越高**。我们为这个实体加入了一个优先级最高的浮动意向`minecraft:behavior.float`,这代表只要实体在水中,一定会优先在水面上浮动。然后是一个由伤害锁定目标的意向`minecraft:behavior.hurt_by_target`,这意味着只要有实体对松鼠造成伤害,松鼠就会将其锁定为自己的目标。然后添加了一个优先级并不那么高的随意漫步的意向`minecraft:behavior.random_stroll`。接着添加了一个`minecraft:behavior.random_look_around`用于随机向四周看。最后添加了一个优先级最低的`minecraft:behavior.look_at_player`,用于看向玩家。 + +### 添加属性组件 + +**属性**(**Property**)组件是一种特殊的组件,一般用于控制一个实体实例的属性,往往和实体在内存中存在的结构体中的一些属性相对应,代表着一个实体具备的“能力”或“性质”。 + +```json +{ + "format_version": "1.12.0", + "minecraft:entity": { + "description": { + "identifier": "tutorial_demo:squirrel", + "is_experimental": false, + "is_spawnable": true, + "is_summonable": true + }, + "component_groups": { + + }, + "components": { + // ... + "minecraft:behavior.random_look_around": { + "priority": 9 + }, + "minecraft:behavior.look_at_player": { + "priority": 11 + }, + "minecraft:type_family": { + "family":["squirrel", "mob"] + }, + "minecraft:can_climb": {}, + "minecraft:collision_box": { + "width": 0.7, + "height": 0.7 + } + }, + "events": { + + } + } +} +``` + +属性组件一般没有特殊的前缀,所以不易与常规组件区分。我们添加了三个属性组件。`minecraft:type_family`代表该实体的类型的族的属性,常常和命令相配合。`minecraft:can_climb`代表该实体具备攀爬属性,遇到梯子时该实体能够登梯而上。`minecraft:collision_box`定义了该实体的碰撞箱属性,用于支持该实体的碰撞。 + +### 添加特性组件 + +**特性**(**Attribute**)组件是另一种特殊的组件,往往用于指定实体所具备的特定性质的或进行特定操作时该操作的“数值”,比如攻击数值、生命值、具备的护甲值等。特性是和实体NBT中的`Attributes`列表相绑定的,定义的特性组件最终都会作用到这个实体的`Attributes`列表中定义的特性中。 + +```json +{ + "format_version": "1.12.0", + "minecraft:entity": { + "description": { + "identifier": "tutorial_demo:squirrel", + "is_experimental": false, + "is_spawnable": true, + "is_summonable": true + }, + "component_groups": { + + }, + "components": { + // ... + "minecraft:can_climb": {}, + "minecraft:collision_box": { + "width": 0.7, + "height": 0.7 + }, + "minecraft:movement": { + "value": 0.3 + }, + "minecraft:health": { + "value": 30, + "max": 30 + } + }, + "events": { + + } + } +} +``` + +特性组件一般也没有特殊的前缀。这里我们添加了两个特性组件`minecraft:movement`和`minecraft:health`,分别用于定义实体的基础移动速度的值、基础和最大的生命值。 + +至此,我们便完成了松鼠的组件定义,我们可以进入游戏查看我们的行为效果。 + +![](./images/12.5_behavior_in-game.gif) + +### 使用组件组和事件来制作变体 + +到此为止,我们已经为松鼠添加了组件。事实上,这时候的松鼠已经可以正常表现了。但是,我们依然记得我们曾经为松鼠绘制了灰色的纹理贴图。我们现在使用组件组和事件来控制灰色纹理变体。 + +#### 添加变体组件组 + +```json +{ + "format_version": "1.12.0", + "minecraft:entity": { + "description": { + "identifier": "tutorial_demo:squirrel", + "is_experimental": false, + "is_spawnable": true, + "is_summonable": true + }, + "component_groups": { + "tutorial_demo:color_red": { + "minecraft:variant": { + "value": 0 + } + }, + "tutorial_demo:color_gray": { + "minecraft:variant": { + "value": 1 + } + } + }, + "components": { + // ... + }, + "events": { + + } + } +} +``` + +我们使用`minecraft:variant`组件来添加变体组件组。`minecraft:variant`组件是一个专门用于控制变体的组件。在游戏中,它定义的值可以同步到客户端,因此,我们可以在客户端用`query.variant`获取到它的值。 + +我们定义两个组件组,一个` tutorial_demo:color_red`,变体ID为0;另一个` tutorial_demo:color_gray`,变体ID为1。不管哪个组件组,当它被加入到全局组件中时都会带入一个`minecraft:variant`组件,用于确定`query.variant`获取到的值。 + +#### 定义变体生成事件 + +我们希望定义内置事件`minecraft:entity_spawned`。这个事件会在松鼠自然生成或者刷怪蛋生成时被引擎自主触发。因此我们在这个事件中分别定义生成两种变体的几率。 + +```json +{ + "format_version": "1.12.0", + "minecraft:entity": { + "description": { + "identifier": "tutorial_demo:squirrel", + "is_experimental": false, + "is_spawnable": true, + "is_summonable": true + }, + "component_groups": { + "minecraft:color_red": { + "minecraft:variant": { + "value": 0 + } + }, + "minecraft:color_gray": { + "minecraft:variant": { + "value": 1 + } + } + }, + "components": { + // ... + }, + "events": { + "minecraft:entity_spawned": { + "sequence": [ + { + "randomize": [ + { + "weight": 1, + "add": { + "component_groups": [ + " tutorial_demo:color_red" + ] + } + }, + { + "weight": 1, + "add": { + "component_groups": [ + " tutorial_demo:color_gray" + ] + } + } + ] + } + ] + } + } + } +} +``` + +我们将两种松鼠的权重全部设为1,这样可以做到两种松鼠1:1生成。 + +![](./images/12.5_one_to_one.png) + +## 增加松鼠的自然生成 + +我们自定义了新生物之后,就需要为其写入除了刷怪蛋生成之外新的自然生成机制。否则,玩家在生存模式下边无法体验到实体的生成。自然生成机制需要使用我们的生成规则定义文件来制作。我们在行为包中新建一个`spawn_rules`文件夹并在其中新建一个`squirrel.json`文件,作为我们的生成规则定义文件。 + +```json +{ + "format_version": "1.8.0", + "minecraft:spawn_rules": { + "description": { + "identifier": "tutorial_demo:squirrel", + "population_control": "animal" + }, + "conditions": [ + { + "minecraft:spawns_on_surface": {}, + "minecraft:brightness_filter": { + "min": 0, + "max": 15, + "adjust_for_weather": true + }, + "minecraft:weight": { + "default": 12 + }, + "minecraft:herd": { + "min_size": 8, + "max_size": 12 + }, + + "minecraft:biome_filter": [ + {"test": "has_biome_tag", "operator":"==", "value": "giant"} + ] + } + ] + } +} +``` + +我们希望其生成于带有`giant`标签的生物群系的地表,并且成兽群生成,以`animal`的方式控制种群数目,并根据天气调整生成比率。我们便可以编写上述内容,然后将其写入到我们的生成规则定义文件中。 + +## 添加装备组件 + +还记得我们之前为我们的松鼠开启了附着物么?我们现在为松鼠添加一个头盔装备。为此,我们需要在行为包中使用`minecraft:equipment`组件。`minecraft:equipment`组件接受一个战利品表,为此,我们需要制作一个只有头盔的战利品表。我们不妨制作一个只有锁链头盔的战利品表。 + +在行为包中找到`loot_tables`文件夹并在其中新建一个`entities`文件夹,然后在其中创建`squirrel_equipment.json`文件,作为我们战利品表。我们向其中写入如下内容以代表只有一个锁链头盔。 + +```json +{ + "pools": [ + { + "rolls": 1, + "entries": [ + { + "type": "item", + "name": "minecraft:chainmail_helmet", + "weight": 1 + } + ] + } + ] +} +``` + +然后,我们在实体行为包定义中加入`minecraft:equipment`。`minecraft:equipment`是一个属性组件,我们将其和`minecraft:type_family`、`minecraft:can_climb`与`minecraft:collision_box`放置在一起,方便我们之后调试。 + +```json +{ + "format_version": "1.12.0", + "minecraft:entity": { + "description": { + "identifier": "tutorial_demo:squirrel", + "is_experimental": false, + "is_spawnable": true, + "is_summonable": true + }, + "component_groups": { + // ... + }, + "components": { + // ... + "minecraft:behavior.look_at_player": { + "priority": 11 + }, + "minecraft:equipment": { + "table": "loot_tables/entities/squirrel_equipment.json" + }, + "minecraft:type_family": { + "family":["squirrel", "mob"] + }, + "minecraft:can_climb": {}, + "minecraft:collision_box": { + "width": 0.7, + "height": 0.7 + }, + "minecraft:movement": { + "value": 0.3 + } + // ... + }, + "events": { + // ... + } + } +} +``` + +这些都完成以后,我们便可以进入游戏自测了。 + +![](./images/12.5_armor_in-game.png) + +可以看到,我们的松鼠现在都带有一个头盔了!当然,头盔有点“不合身”,不过这不要紧,这只是一个演示。在实际操作中,我们可以通过自定义附着物的方式自定义一个头盔。在第九章的挑战中,我们就制作过自定义盔甲。参考那个步骤,稍微调整模型的大小,便可以依据松鼠的“头型”打造出量身定制的头盔了。作为练习,这一要点就交给开发者们自行操作了。 + +最后,我们将截止到目前我们已经在行为包定义文件中编写的内容做一个展示,方便开发者们宏观地感受一个带有相对完整的组件、组件组和事件的实体: + +```json +{ + "format_version": "1.12.0", + "minecraft:entity": { + "description": { + "identifier": "tutorial_demo:squirrel", + "is_experimental": false, + "is_spawnable": true, + "is_summonable": true + }, + "component_groups": { + "minecraft:color_red": { + "minecraft:variant": { + "value": 0 + } + }, + "minecraft:color_gray": { + "minecraft:variant": { + "value": 1 + } + } + }, + "components": { + "minecraft:hurt_on_condition": { + "damage_conditions": [ + { + "filters": { + "test": "in_lava", + "subject": "self", + "operator": "==", + "value": true + }, + "cause": "lava", + "damage_per_tick": 4 + } + ] + }, + "minecraft:pushable": { + "is_pushable": true, + "is_pushable_by_piston": true + }, + "minecraft:experience_reward": { + "on_death": "query.last_hit_by_player ? Math.Random(0,1) : 0" + }, + "minecraft:breathable": { + "total_supply": 15, + "suffocate_time": 0 + }, + "minecraft:physics": {}, + "minecraft:navigation.walk": { + "can_path_over_water": true, + "avoid_water": true + }, + "minecraft:movement.skip": {}, + "minecraft:jump.static": {}, + "minecraft:behavior.float": { + "priority": 0 + }, + "minecraft:behavior.hurt_by_target": { + "priority": 1 + }, + "minecraft:behavior.random_stroll": { + "priority": 6, + "speed_multiplier": 0.8, + "xz_dist": 2, + "y_dist": 1 + }, + "minecraft:behavior.random_look_around": { + "priority": 9 + }, + "minecraft:behavior.look_at_player": { + "priority": 11 + }, + "minecraft:equipment": { + "table": "loot_tables/entities/squirrel_equipment.json" + }, + "minecraft:type_family": { + "family":["squirrel", "mob"] + }, + "minecraft:can_climb": {}, + "minecraft:collision_box": { + "width": 0.7, + "height": 0.7 + }, + "minecraft:movement": { + "value": 0.3 + }, + "minecraft:health": { + "value": 30, + "max": 30 + } + }, + "events": { + "minecraft:entity_spawned": { + "sequence": [ + { + "randomize": [ + { + "weight": 1, + "add": { + "component_groups": [ + "minecraft:color_red" + ] + } + }, + { + "weight": 1, + "add": { + "component_groups": [ + "minecraft:color_gray" + ] + } + } + ] + } + ] + } + } + } +} +``` \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/6-将实体动画与行为结合.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/6-将实体动画与行为结合.md new file mode 100644 index 0000000..3cb5167 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/6-将实体动画与行为结合.md @@ -0,0 +1,299 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 30分钟 +--- + +# 将实体动画与行为结合 + +在实体的行为包组件中,有些组件可以做到和实体的渲染帧相结合。本节我们以实体的攻击组件为例,探索如何将实体动画与行为相结合。 + +## 利用关键帧动画制作攻击动画 + +与之前几个动画使用Molang不同,我们现在使用关键帧制作一个攻击动画。我们在Blockbench中切换到动画模式,新添加一个名为`attack`的动画。 + +![](./images/12.6_key_frame_1.png) + +我们像之前一样,先为整个body制作一个向前平移的动画,然后再在各个骨骼上制作旋转动画。我们可以使用“+”按钮多添加几个关键帧。 + +![](./images/12.6_key_frame_2.png) + +与使用Molang不同,这里我们直接指定该关键帧处的值。我们可以定位到该关键帧后,拉动预览窗中的坐标轴来实时改变某个轴向上的值。在拉动坐标轴的时候,左侧关键帧窗格里的值会随之改变。 + +![](./images/12.6_key_frame_3.png) + +![](./images/12.6_body.gif) + +最后我们添加一个关键帧用于骨骼的位置归位,这是为了使该攻击和下一次攻击相衔接。接下来我们依照相同的原理制作其他骨骼的旋转。 + +![](./images/12.6_all.gif) + +我们可以看到,经过一段时间的制作,我们的松鼠具备了基本的攻击动画。我们将该动画保存,以备之后制作行为时联动使用。下面是保存之后的动画JSON文件: + +```json +{ + "format_version": "1.8.0", + "animations": { + "animation.squirrel.idle": { + // ... + }, + "animation.squirrel.move": { + // ... + }, + "animation.squirrel.look_at_target": { + // ... + }, + "animation.squirrel.attack": { + "animation_length": 0.54167, + "bones": { + "body": { + "position": { + "0.0": [0, 0, 0], + "0.2917": [0, 0, -8], + "0.4167": [0, 0, 0] + } + }, + "head": { + "rotation": { + "0.0": [0, 0, 0], + "0.25": [40, 0, 0], + "0.4167": [0, 0, 0] + } + }, + "haunchLeft": { + "rotation": { + "0.0": [0, 0, 0], + "0.25": [20, 0, 0], + "0.4167": [0, 0, 0] + } + }, + "rearFootLeft": { + "rotation": { + "0.0": [0, 0, 0], + "0.25": [17.5, 0, 0], + "0.4167": [0, 0, 0] + } + }, + "frontLegLeft": { + "rotation": { + "0.0833": [0, 0, 0], + "0.3333": [-32.5, 0, 0], + "0.5417": [0, 0, 0] + } + }, + "frontLegRight": { + "rotation": { + "0.0": [0, 0, 0], + "0.25": [-32.5, 0, 0], + "0.4167": [0, 0, 0] + } + }, + "tail": { + "rotation": { + "0.0": [0, 0, 0], + "0.25": [35, 0, 0], + "0.4167": [0, 0, 0] + } + }, + "haunchRight": { + "rotation": { + "0.0833": [0, 0, 0], + "0.3333": [17.5, 0, 0], + "0.5": [0, 0, 0] + } + }, + "rearFootRight": { + "rotation": { + "0.0833": [0, 0, 0], + "0.3333": [17.5, 0, 0], + "0.5": [0, 0, 0] + } + } + } + } + } +} +``` + +## 结合Molang与行为将攻击动画实装 + +我们希望松鼠的攻击能够在动画播放到特定的时间时再打出,所以我们需要使用一种“延迟”攻击的组件。事实上,我们确实有这种组件。我们可以在劫掠兽的行为定义中看到一个`minecraft:behavior.delayed_attack`的组件,这个组件便控制着劫掠兽的延迟攻击,使劫掠兽的攻击能够在开始攻击动画一定时间后再打出。原版的劫掠兽延迟攻击组件如下: + +```json +"minecraft:behavior.delayed_attack": { + "priority": 4, + "reach_multiplier": 1.5, + "attack_duration": 0.75, + "hit_delay_pct": 0.5, + "track_target": true, + "sound_event": "attack.strong" +} +``` + +其中`hit_delay_pct`字段便是攻击造成伤害时动画已播放的时间。根据我们刚才制作的动画,我们将其修改为`0.25`,也就是我们的头部动画达到最大旋转角的关键帧所在的时间点。然后,我们将其加入到我们的松鼠行为包定义中。 + +```json +{ + "format_version": "1.12.0", + "minecraft:entity": { + "description": { + "identifier": "tutorial_demo:squirrel", + "is_experimental": false, + "is_spawnable": true, + "is_summonable": true + }, + "component_groups": { + // ... + }, + "components": { + // ... + "minecraft:behavior.float": { + "priority": 0 + }, + "minecraft:behavior.hurt_by_target": { + "priority": 1 + }, + "minecraft:behavior.delayed_attack": { + "priority": 4, + "reach_multiplier": 1.5, + "attack_duration": 0.75, + "hit_delay_pct": 0.25, + "track_target": true, + "sound_event": "attack.strong" + }, + "minecraft:behavior.random_stroll": { + "priority": 6, + "speed_multiplier": 0.8, + "xz_dist": 2, + "y_dist": 1 + }, + "minecraft:behavior.random_look_around": { + "priority": 9 + }, + "minecraft:behavior.look_at_player": { + "priority": 11 + } + // ... + }, + "events": { + // ... + } + } +} +``` + +这个`minecraft:behavior.delayed_attack`AI意向可以使松鼠延迟攻击自己的**目标**(**Target**)。而我们知道,`minecraft:behavior.hurt_by_target`AI意向可以将攻击松鼠的实体设置为松鼠的目标。这样,我们的松鼠便具备了受到攻击后对攻击自己的实体进行延迟攻击的能力。但是,此时我们的松鼠仅仅是“会攻击”了,但还“攻击不动”。这是为什么呢?因为我们还没有为松鼠设定攻击力,所以此时松鼠的攻击就算打出也只能算“攻击了个寂寞”。我们通过查阅文档找到设置攻击力的组件`minecraft:attack`。它是一个特性组件,所以为了方便维护,我们将其加入到特性那部分的组件中。为了方便调试,我们将其设置为1,以免我们两下便被打死,造成测试的不便。 + +```json +{ + "format_version": "1.12.0", + "minecraft:entity": { + "description": { + "identifier": "tutorial_demo:squirrel", + "is_experimental": false, + "is_spawnable": true, + "is_summonable": true + }, + "component_groups": { + // ... + }, + "components": { + // ... + "minecraft:attack": { + "damage": 1.0 + }, + "minecraft:movement": { + "value": 0.3 + }, + "minecraft:health": { + "value": 30, + "max": 30 + } + }, + "events": { + // ... + } + } +} +``` + +现在,我们已经为松鼠设置好了攻击的能力,接下来,只需要将攻击动画正确绑定到实体资源包定义文件中,便可以使松鼠在攻击时正确播放动画了。此时,我们便需要使用Molang查询函数`query.is_delayed_attacking`。该查询函数在`minecraft:behavior.delayed_attack`AI意向处于激活状态时返回1.0,处于未激活状态时返回0.0。因此,我们可以用这个查询函数控制动画的播放。由于我们使用的实体资源定义是旧版的`1.8.0`,所以没有办法直接条件控制动画播放。不过不用担心,我们可以使用一个动画控制器: + +```json +{ + "format_version" : "1.10.0", + "animation_controllers" : { + "controller.animation.squirrel.general" : { + // ... + }, + "controller.animation.squirrel.attack" : { + "initial_state" : "default", + "states" : { + "attacking" : { + "animations" : [ "attack" ], + "transitions" : [ + { + "default" : "query.is_delayed_attacking == 0" + } + ] + }, + "default" : { + "transitions" : [ + { + "attacking" : "query.is_delayed_attacking == 1" + } + ] + } + } + } + } +} + +``` + +我们将动画和动画控制器绑定在实体资源包定义上: + +```json +{ + "format_version": "1.8.0", + "minecraft:client_entity": { + "description": { + "identifier": "tutorial_demo:squirrel", + "materials": { + "default": "rabbit" + }, + "textures": { + "red": "textures/entity/squirrel/red", + "gray": "textures/entity/squirrel/gray" + }, + "geometry": { + "default": "geometry.squirrel" + }, + "animations": { + "walk": "animation.squirrel.move", + "general": "animation.squirrel.idle", + "look_at_target": "animation.squirrel.look_at_target", + "attack": "animation.squirrel.attack" + }, + "animation_controllers": [ + { "general": "controller.animation.squirrel.general" }, + { "attack": "controller.animation.squirrel.attack" } + ], + "render_controllers": [ + "controller.render.squirrel" + ], + "spawn_egg": { + "base_color":"#1778D2", + "overlay_color":"#1778D2" + }, + "enable_attachables": true + } + } +} +``` + +这样,我们便可以进入游戏自测查看效果了。我们可以通过`/aigoals`命令打开“AI意向显示”以清晰地查看AI意向的激活情况。 + +![](./images/12.6_in-game.gif) + +我们可以看到,松鼠的攻击动画正常播放了,而且我们确实是在动画播放接近一半时被攻击掉血。这说明我们的自定义攻击动画和攻击行为成功结合了。 diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/7-联动生物事件与行为.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/7-联动生物事件与行为.md new file mode 100644 index 0000000..49cb607 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/7-联动生物事件与行为.md @@ -0,0 +1,524 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 25分钟 +--- + +# 联动生物事件与行为 + +最后,我们来探索生物的触发器组件。在本节中,我们欲通过**触发器**(**Trigger**)来做到一个松鼠的“避难”机制。 + +## 事件和触发器 + +通过之前的章节,我们已经了解到**事件**是实体定义文件中一种非常重要的机制。我们可以通过定义不同的事件来触发不同的效果、添加或移除组件组。而事件的触发机制也是多种多样的。不过,除了内置事件的硬编码触发以外,最多的触发手段应该当属**触发器**组件了。 + +触发器组件也是一种实体行为包组件。不同于其他类型的组件,触发器组件主要用于触发一个事件。换句话说,触发器组件就是为了事件而生的。触发器的原理与模组SDK中的事件监听类似,都是通过监听一定的系统事件,然后在特定的时机执行指定的内容。只不过,在实体定义文件这种数据驱动文件中,事件响应执行的内容往往是有限的。在这里,触发器只能用于触发另一个定义在实体行为包定义文件中的事件。 + +实体的触发器组件也有很多类型。我们这里想制作一个松鼠受伤后就会逃跑的机制,于是我们需要用到与受伤有关的触发器。在掠夺者的组件定义中,我们便可以发现类似的触发器。 + +```json +"minecraft:on_hurt": { + "event": "minecraft:ranged_mode", + "target": "self" +}, +"minecraft:on_hurt_by_player": { + "event": "minecraft:ranged_mode", + "target": "self" +}, +"minecraft:on_target_escape": { + "event": "minecraft:calm", + "target": "self" +} +``` + +这里,`minecraft:on_hurt`是实体受到伤害时触发,`minecraft:on_hurt_by_player`是实体受到玩家伤害时触发,而`minecraft:on_target_escape`是该实体的目标消失后(即不再将某个实体视为目标后)触发。 + +我们可以使用`minecraft:on_hurt`和`minecraft:on_target_escape`来设计一个逃跑机制。 + +## 为松鼠设计逃跑机制 + +我们知道,要想使松鼠逃跑,我们应该需要使用一种可以用来规避生物的AI意向。通过对原版机制的了解,我们知道苦力怕会规避“猫科”生物。我们利用这一点到苦力怕的行为文件中寻找相关AI意向。不出所料,我们找到了规避特定生物的AI意向`minecraft:behavior.avoid_mob_type`,这是他在苦力怕文件中的表达: + +```json +"minecraft:behavior.avoid_mob_type": { + "priority": 3, + "entity_types": [ + { + "filters": { + "any_of": [ + { "test" : "is_family", "subject" : "other", "value" : "ocelot"}, + { "test" : "is_family", "subject" : "other", "value" : "cat"} + ] + }, + "max_dist": 6, + "walk_speed_multiplier": 1, + "sprint_speed_multiplier": 1.2 + } + ] +} +``` + +我们便可以使用这个AI意向来作为松鼠规避对它造成伤害的生物的意向。为了改造成我们需要的这种功能,我们需要先了解这个AI意向中使用的一种我的世界机制——**过滤器**(**Filter**)。 + +上面这段代码中的`filters`字段便是用于指定过滤器的字段。我们可以看到原版的苦力怕指定了两个过滤器,并且使用`any_of`逻辑,意为只要两者出现其一满足,便可以触发该AI意向。这里原版使用了`is_family`过滤器。该过滤器是一个字符串测试,这里原版分别测试了对立实体是否为豹猫或猫,一旦满足便执行规避的AI意向。 + +我们期望我们的实体规避对其造成伤害的实体。而我们知道我们已经有了`minecraft:behavior.hurt_by_target`AI意向,任意对其造成伤害的实体都会被其标记为目标。因此我们只需要`has_target`过滤器,该过滤器是一个布尔测试。我们只需要测试对立实体是否为我们实体的目标即可。 + +我们改造后的组件如下: + +```json +"minecraft:behavior.avoid_mob_type": { + "priority": 3, + "entity_types": [ + { + "filters": { + "test": "is_target", + "subject": "other", + "value": true + }, + "max_dist": 6, + "walk_speed_multiplier": 1, + "sprint_speed_multiplier": 1.2 + } + ] +} +``` + +这样,只要对立实体是其目标,我们的松鼠便会自动规避至与其距离6米远之外。 + +当然,这里我们意图通过触发器来做到这一点。虽然由于过滤器的存在,再使用触发器便没有必要了,但是我们依旧设计了一个使用触发器触发的松鼠。设计完毕的实体定义文件如下: + +```json +{ + "format_version": "1.12.0", + "minecraft:entity": { + "description": { + "identifier": "tutorial_demo:squirrel", + "is_experimental": false, + "is_spawnable": true, + "is_summonable": true + }, + "component_groups": { + "tutorial_demo:color_red": { + "minecraft:variant": { + "value": 0 + } + }, + "tutorial_demo:color_gray": { + "minecraft:variant": { + "value": 1 + } + }, + "tutorial_demo:escaping": { + "minecraft:behavior.avoid_mob_type": { + "priority": 3, + "entity_types": [ + { + "filters": { + "test": "is_target", + "subject": "other", + "value": true + }, + "max_dist": 6, + "walk_speed_multiplier": 1, + "sprint_speed_multiplier": 1.2 + } + ] + } + } + }, + "components": { + // ... + "minecraft:behavior.hurt_by_target": { + "priority": 1 + }, + "minecraft:behavior.delayed_attack": { + "priority": 4, + "reach_multiplier": 1.5, + "attack_duration": 0.75, + "hit_delay_pct": 0.25, + "track_target": true, + "sound_event": "attack.strong" + }, + "minecraft:behavior.random_stroll": { + "priority": 6, + "xz_dist": 2, + "y_dist": 1, + "speed_multiplier": 0.8 + }, + "minecraft:behavior.random_look_around": { + "priority": 9 + }, + "minecraft:behavior.look_at_player": { + "priority": 11 + }, + // ... + "minecraft:movement": { + "value": 0.3 + }, + "minecraft:health": { + "value": 30, + "max": 30 + }, + "minecraft:on_hurt": { + "event": "tutorial_demo:escape_started", + "target": "self" + }, + "minecraft:on_hurt_by_player": { + "event": "tutorial_demo:escape_started", + "target": "self" + }, + "minecraft:on_target_escape": { + "event": "tutorial_demo:escape_ended", + "target": "self" + } + }, + "events": { + "minecraft:entity_spawned": { + // ... + }, + "tutorial_demo:escape_started": { + "add": { + "component_groups": [ + "tutorial_demo:escaping" + ] + } + }, + "tutorial_demo:escape_ended": { + "remove": { + "component_groups": [ + "tutorial_demo:escaping" + ] + } + } + } + } +} +``` + +这里我们添加了一个`tutorial_demo:escaping`组件组,用于`minecraft:behavior.avoid_mob_type`AI意向的动态增减。然后我们通过`tutorial_demo:escape_started`和`tutorial_demo:escape_ended`事件来进行组件组的增减。最后,我们通过`minecraft:on_hurt`、`minecraft:on_hurt_by_player`和`minecraft:on_target_escape`来实现两个事件的触发。这样,一个动态增删功能(这里是增删规避生物的AI意向)的实现就完成了。 + +当然,如果我们的“脑补”能力比较强,我们可以发现这样的设计有个明显的缺点。那就是由于`minecraft:behavior.delayed_attack`的存在,虽然其优先级为4,低于优先级为3的`minecraft:behavior.avoid_mob_type`,但是`minecraft:behavior.avoid_mob_type`只会让实体规避至6米以外。当实体行走至6米以外时,`minecraft:behavior.avoid_mob_type`便会暂时被停用,此时`minecraft:behavior.delayed_attack`便会由于高优先级的AI意向被停用从而被激活,实体就会重新接近之前攻击它的目标。而接近到一定距离时,又会因为太近而重新激活`minecraft:behavior.avoid_mob_type`,同时`minecraft:behavior.delayed_attack`再次被停用。实体便会处于一会规避,一会接近的循环状态。我们也可以在电脑开发版中通过ImGui来直观地看到这一点。 + +![](./images/12.7_in-game_1.gif) + +我们需要对此进行优化。 + +### 优化数据 + +我们意图通过改变`minecraft:behavior.delayed_attack`的存在性来解决这个问题。我们期望两种变体的松鼠的行为不一致,红色松鼠比较“勇”,只要有人打它就反击,而灰色松鼠比较“逊”,有人打它就逃跑。那么,我们如何实现这一机制呢?事实上,`minecraft:on_hurt`等触发器也是可以使用过滤器来进行条件触发的。我们可以以如下这种方式来写入一个过滤器: + +```json +"minecraft:on_hurt": { + "event": "tutorial_demo:escape_started", + "filters":{ + "test": "is_variant", + "value": 1 + }, + "target": "self" +}, +"minecraft:on_hurt_by_player": { + "event": "tutorial_demo:escape_started", + "filters":{ + "test": "is_variant", + "value": 1 + }, + "target": "self" +}, +"minecraft:on_target_escape": { + "event": "tutorial_demo:escape_ended", + "target": "self" +} +``` + +其中`is_variant`过滤器是用于检测变体的值的过滤器。灰色松鼠的变体值为1,因此,我们只有检测到变体值为1时我们才触发添加对应AI意向的事件。而红色松鼠则是直接在生成时也通过组件组的形式添加延迟攻击组件,这样,灰色松鼠就不会附带有这个组件。我们将`minecraft:behavior.delayed_attack`组件移动到红色松鼠的变体组件组中,如下: + +```json +{ + "format_version": "1.12.0", + "minecraft:entity": { + "description": { + "identifier": "tutorial_demo:squirrel", + "is_experimental": false, + "is_spawnable": true, + "is_summonable": true + }, + "component_groups": { + "tutorial_demo:color_red": { + "minecraft:behavior.delayed_attack": { + "priority": 4, + "reach_multiplier": 1.5, + "attack_duration": 0.75, + "hit_delay_pct": 0.25, + "track_target": true, + "sound_event": "attack.strong" + }, + "minecraft:variant": { + "value": 0 + } + }, + "tutorial_demo:color_gray": { + "minecraft:variant": { + "value": 1 + } + }, + "tutorial_demo:escaping": { + "minecraft:behavior.avoid_mob_type": { + "priority": 3, + "entity_types": [ + { + "filters": { + "test": "is_target", + "subject": "other", + "value": true + }, + "max_dist": 6, + "walk_speed_multiplier": 1, + "sprint_speed_multiplier": 1.2 + } + ] + } + } + }, + "components": { + // ... + "minecraft:behavior.hurt_by_target": { + "priority": 1 + }, + "minecraft:behavior.random_stroll": { + "priority": 6, + "xz_dist": 2, + "y_dist": 1, + "speed_multiplier": 0.8 + }, + "minecraft:behavior.random_look_around": { + "priority": 9 + }, + "minecraft:behavior.look_at_player": { + "priority": 11 + }, + // ... + "minecraft:on_hurt": { + "event": "tutorial_demo:escape_started", + "filters":{ + "test": "is_variant", + "value": 1 + }, + "target": "self" + }, + "minecraft:on_hurt_by_player": { + "event": "tutorial_demo:escape_started", + "filters":{ + "test": "is_variant", + "value": 1 + }, + "target": "self" + }, + "minecraft:on_target_escape": { + "event": "tutorial_demo:escape_ended", + "target": "self" + } + }, + "events": { + "minecraft:entity_spawned": { + // ... + }, + "tutorial_demo:escape_started": { + // ... + }, + "tutorial_demo:escape_ended": { + // ... + } + } + } +} +``` + +这样,我们便实现了红灰两种松鼠的行为特异化,我们进入游戏来检测我们的内容。我们可以通过电脑开发版的ImGui直观地看到组件组的增删和AI意向的激活与停用。 + +![](./images/12.7_in-game_2.gif) + +这样,我们便修复了我们的异常。下面,我们放出我们目前为止行为包定义文件中全部的内容,供各位开发者们参考: + +```json +{ + "format_version": "1.12.0", + "minecraft:entity": { + "description": { + "identifier": "tutorial_demo:squirrel", + "is_experimental": false, + "is_spawnable": true, + "is_summonable": true + }, + "component_groups": { + "tutorial_demo:color_red": { + "minecraft:behavior.delayed_attack": { + "priority": 4, + "reach_multiplier": 1.5, + "attack_duration": 0.75, + "hit_delay_pct": 0.25, + "track_target": true, + "sound_event": "attack.strong" + }, + "minecraft:variant": { + "value": 0 + } + }, + "tutorial_demo:color_gray": { + "minecraft:variant": { + "value": 1 + } + }, + "tutorial_demo:escaping": { + "minecraft:behavior.avoid_mob_type": { + "priority": 3, + "entity_types": [ + { + "filters": { + "test": "is_target", + "subject": "other", + "value": true + }, + "max_dist": 6, + "walk_speed_multiplier": 1, + "sprint_speed_multiplier": 1.2 + } + ] + } + } + }, + "components": { + "minecraft:hurt_on_condition": { + "damage_conditions": [{ + "filters": { + "test": "in_lava", + "subject": "self", + "operator": "==", + "value": true + }, + "cause": "lava", + "damage_per_tick": 4 + }] + }, + "minecraft:pushable": { + "is_pushable": true, + "is_pushable_by_piston": true + }, + "minecraft:experience_reward": { + "on_death": "query.last_hit_by_player ? Math.Random(0,1) : 0" + }, + "minecraft:breathable": { + "total_supply": 15, + "suffocate_time": 0 + }, + "minecraft:physics": {}, + "minecraft:navigation.walk": { + "can_path_over_water": true, + "avoid_water": true + }, + "minecraft:movement.skip": {}, + "minecraft:jump.static": {}, + "minecraft:behavior.float": { + "priority": 0 + }, + "minecraft:behavior.hurt_by_target": { + "priority": 1 + }, + "minecraft:behavior.random_stroll": { + "priority": 6, + "xz_dist": 2, + "y_dist": 1, + "speed_multiplier": 0.8 + }, + "minecraft:behavior.random_look_around": { + "priority": 9 + }, + "minecraft:behavior.look_at_player": { + "priority": 11 + }, + "minecraft:equipment": { + "table": "loot_tables/entities/squirrel_equipment.json" + }, + "minecraft:type_family": { + "family":["squirrel", "mob"] + }, + "minecraft:can_climb": {}, + "minecraft:collision_box": { + "width": 0.7, + "height": 0.7 + }, + "minecraft:attack": { + "damage": 1.0 + }, + "minecraft:movement": { + "value": 0.3 + }, + "minecraft:health": { + "value": 30, + "max": 30 + }, + "minecraft:on_hurt": { + "event": "tutorial_demo:escape_started", + "filters":{ + "test": "is_variant", + "value": 1 + }, + "target": "self" + }, + "minecraft:on_hurt_by_player": { + "event": "tutorial_demo:escape_started", + "filters":{ + "test": "is_variant", + "value": 1 + }, + "target": "self" + }, + "minecraft:on_target_escape": { + "event": "tutorial_demo:escape_ended", + "target": "self" + } + }, + "events": { + "minecraft:entity_spawned": { + "sequence": [ + { + "randomize": [ + { + "weight": 1, + "add": { + "component_groups": [ + "tutorial_demo:color_red" + ] + } + }, + { + "weight": 1, + "add": { + "component_groups": [ + "tutorial_demo:color_gray" + ] + } + } + ] + } + ] + }, + "tutorial_demo:escape_started": { + "add": { + "component_groups": [ + "tutorial_demo:escaping" + ] + } + }, + "tutorial_demo:escape_ended": { + "remove": { + "component_groups": [ + "tutorial_demo:escaping" + ] + } + } + } + } +} +``` + diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/8-挑战:制作一辆卡丁车.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/8-挑战:制作一辆卡丁车.md new file mode 100644 index 0000000..7f4dd14 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/8-挑战:制作一辆卡丁车.md @@ -0,0 +1,493 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 30分钟 +--- + +# 挑战:制作一辆卡丁车 + +在本节中,我们一起来制作一个卡丁车。这次的挑战主要是针对Molang的复习和更多复杂应用的学习,所以卡丁车本身模型的精细便置为次要目标,同时我们将精力全部放在Molang相关的表达式的编写过程上。 + +## 准备模型 + +![](./images/12.8_kart_model.png) + +我们粗略地绘制一个卡丁车的模型,重点突出了卡丁车的四个轮胎骨骼,同时将所有的骨骼全部放在一个根组里。这样能够方便我们的后续操作。 + +![](./images/12.8_kart_texture_create.png) + +![](./images/12.8_kart_texture_created.png) + +我们通过新建纹理贴图的“填充”功能为其快速上色。之后,我们便可以关注卡丁车的动画了。这也是我们本节挑战的重中之重。 + +## 设计移动、拐弯和起伏动画 + +我们将关注点移动到车轮上。通过对现实的观察,我们可以得知,当一个卡丁车移动时,轮胎会旋转,而车身则相对不动。当一个卡丁车拐弯时,前轮的旋转角度将会相对于车身更加地大。当卡丁车在空中飞起或下降时,车身会整体后仰或前倾。我们根据实际卡丁车的这些效果来制作我们游戏中卡丁车的动画。 + +### 移动动画 + +对于移动动画来说,我们只需要关注四个车轮的旋转即可。我们只需要车轮随着前进的距离进行旋转。 + +![](./images/12.8_kart_animation.png) + +通过查阅文档,我们可以得知`query.modified_distance_moved`查询函数可以用于返回一个实体从进入世界到当前时间移动的总距离。我们可以使用这个查询来进行车轮向前移动的动画。我们更改$yOz$面的转角,即X轴向所代表的字段,比如,将其设置为`50 * query.modified_distance_moved`。我们将四个轮胎设置为同样的值,这样可以保证四个轮子转速一致。 + +### 拐弯动画 + +在拐弯时,我们需要改变前轮的转角。我们继续关注前轮的旋转通道。 + +![](./images/12.8_kart_animation_1.png) + +![](./images/12.8_kart_animation_2.png) + +我们通过查阅文档得知,当实体在左右移动时,实体会具备一个**偏航角**(**Yaw**)速度,这个速度可以通过`query.yaw_speed`查询函数得到。我们可以利用这一点来做到车轮的左右偏转。我们将Y轴向的值改为例如`math.clamp(query.yaw_speed * 35, -35, 35)`,这样可以在使其旋转的同时不要转得过于离谱。`math.clamp`可以用于**钳制**(**Clamp**)一个值到指定的区间中。 + +### 起伏动画 + +接下来我们制作起伏动画。我们对于一个卡丁车在下落时一般都具有这种直观:随着卡丁车在空中滑行时落得越来越快,车头会越来越靠下。因此,我们对卡丁车的`root`骨骼,也就是控制着整体的骨骼的旋转通道来进行操作,使其随着竖直速度的变化而旋转。 + +![](./images/12.8_kart_animation_3.png) + +![](./images/12.8_kart_animation_4.png) + +通过查阅文档我们可以得知,实体的竖直速度可以用`query.vertical_speed`查询函数来获得。因此,我们可以在整体骨骼的X轴向上使用类似于`math.clamp(query.vertical_speed * -7, -35, 35)`的表达式来做到控制起伏。不过值得注意的是,`query.vertical_speed`的方向与世界坐标的Y轴方向一致。也就是说,当卡丁车下落时,竖直速度的方向是向下的。为了使我们的骨骼向前转,我们需要对其乘一个负数。 + +这样,我们就完成了卡丁车基本动画的制作。接下来,我们并不着急导出动画。我们在这个基础上为其添加粒子效果。 + +## 添加尾气和胎痕粒子 + +我们准备好尾气粒子和胎痕粒子,假设他们的短名称分别为`gas`和`mark`。为了把他们挂接到实体上,我们需要为他们设置**定位器**(**Locator**)。定位器是模型中一个单点元素,代表模型中一个固定的位置。有了定位器,我们就可以将其他的一些小部件例如粒子挂接到模型的特定位置上。 + +![](./images/12.8_locator.png) + +我们回到“编辑”模式,在右侧的“大纲”窗格上选中我们想加入定位器的骨骼,右键点击“**添加定位器**”。 + +![](./images/12.8_locator_add.png) + +像其他元素一样,我们可以通过拖动预览窗中的坐标轴来改变定位器的位置。这里我们将其放在车轮的底部,代表胎痕粒子的起始位置。 + +![](./images/12.8_locator_added.png) + +很快,我们为四个车轮和尾气管的位置添加了定位器。现在,我们便可以为其添加粒子动画了。回到“动画”模式,我们将着眼于下方的时间轴窗格。 + +![](./images/12.8_effect.png) + +点击“动画效果”按钮,我们可以在时间轴中看到一个除了骨骼之外的额外的动画主体,那便是包含了粒子、声音和命令的效果主体。我们点击“粒子”通道右侧的“+”按钮,为其添加一个`0.0`的关键帧。 + +### 尾气粒子 + +我们点击关键帧,可以在左侧的“关键帧”窗格中看到除了“脚本”之外,多出了“效果”和“定位器”两个属性。 + +![](./images/12.8_locator_select.png) + +其中,定位器可以通过下拉菜单选择的方式来选取。 + +![](./images/12.8_gas.png) + +我们将为其粒子的短名称挂接到`pipe`定位器上,这样只差尾气粒子在实体定义文件中的短名称定义,就完成尾气粒子的添加了。 + +### 胎痕粒子 + +我们点击“关键帧”窗格右侧的“+”按钮,在同一个关键帧处添加多个效果。 + +![](./images/12.8_particle_added_all.png) + +通过重复上述过程,即可将四个轮胎的胎痕粒子也挂接完毕。现在,我们可以到处几何、纹理和动画文件了,接下来,我们将这些资源挂接到实体客户端定义文件上。 + +## 挂接卡丁车实体资源 + +我们在我的世界开发工作台中创建一个AddOn组件,然后通过配置创建一个`tutorial_demo:kart`实体。我们将我们在Blockbench项目中的几何、纹理和动画全部导出到我们的AddOn组件的文件夹中。 + +我们将实体客户端定义文件补全: + +```json +{ + "format_version": "1.8.0", + "minecraft:client_entity": { + "description": { + "identifier": "tutorial_demo:kart", + "materials": { + "default": "entity_alphatest" + }, + "textures": { + "default": "textures/entity/kart/kart" + }, + "geometry": { + "default": "geometry.kart" + }, + "animations": { + "move": "animation.kart.move" + }, + "animation_controllers": [ + { "move": "controller.animation.kart.move" } + ], + "render_controllers": [ + "controller.render.kart" + ], + "particle_effects": { + "mark": "tutorial_demo:mark", + "gas": "tutorial_demo:gas" + } + } + } +} +``` + +除此之外,我们需要单独制作一个动画控制器用于播放`move`动画: + +```json +{ + "format_version" : "1.10.0", + "animation_controllers" : { + "controller.animation.kart.move" : { + "initial_state" : "default", + "states" : { + "default" : { + "animations" : [ + "move" + ] + } + } + } + } +} +``` + +还需制作一个基础的渲染控制器用于使实体正常渲染: + +```json +{ + "format_version": "1.8.0", + "render_controllers": { + "controller.render.kart": { + "geometry": "Geometry.default", + "materials": [ { "*": "Material.default" } ], + "textures": [ + "Texture.default" + ] + } + } +} +``` + +除此之外,我们也需要检查一下导出的资源文件,避免挂接失败。首先是几何文件: + +```json +{ + "format_version": "1.12.0", + "minecraft:geometry": [ + { + "description": { + "identifier": "geometry.kart", + "texture_width": 128, + "texture_height": 128, + "visible_bounds_width": 3, + "visible_bounds_height": 2.5, + "visible_bounds_offset": [0, 0.75, 0] + }, + "bones": [ + { + "name": "root", + "pivot": [0, 1.5, -3.5], + "cubes": [ + {"origin": [-7, 1, -4], "size": [14, 1, 1], "uv": [0, 24]}, + {"origin": [-7, 1, 7], "size": [14, 1, 1], "uv": [0, 21]}, + {"origin": [-7, 3.5, -7.5], "size": [14, 1, 19], "uv": [0, 0]}, + {"origin": [-0.5, 4.5, -3.5], "size": [1, 6, 1], "uv": [0, 27]}, + {"origin": [-3.5, 10.5, -5.5], "size": [7, 1, 5], "pivot": [0, 10.5, -3.5], "rotation": [-40, 0, 0], "uv": [26, 22]}, + {"origin": [-0.5, 2, -4], "size": [1, 1.5, 1], "uv": [11, 0]}, + {"origin": [-1, 2, 7], "size": [1, 1.5, 1], "uv": [6, 0]} + ], + "locators": { + "pipe": [-2, 4, 11] + } + }, + { + "name": "left_wheel", + "parent": "root", + "pivot": [7.5, 1.5, -3.5], + "cubes": [ + {"origin": [7, 0, -5], "size": [1, 3, 3], "uv": [9, 11]} + ], + "locators": { + "left_front": [7.5, 0, -3.5] + } + }, + { + "name": "right_wheel", + "parent": "root", + "pivot": [-8, 1.5, -3.5], + "cubes": [ + {"origin": [-8, 0, -5], "size": [1, 3, 3], "uv": [0, 8]} + ], + "locators": { + "right_front": [-7.5, 0, -3.5] + } + }, + { + "name": "left_back_wheel", + "parent": "root", + "pivot": [7.5, 1.5, 7.5], + "cubes": [ + {"origin": [7, 0, 6], "size": [1, 3, 3], "uv": [6, 4]} + ], + "locators": { + "left_back": [7.5, 0, 7.5] + } + }, + { + "name": "right_back_wheel", + "parent": "root", + "pivot": [-7.5, 1.5, 7.5], + "cubes": [ + {"origin": [-8, 0, 6], "size": [1, 3, 3], "uv": [0, 0]} + ], + "locators": { + "right_back": [-7.5, 0, 7.5] + } + } + ] + } + ] +} +``` + +然后是动画文件: + +```json +{ + "format_version": "1.8.0", + "animations": { + "animation.kart.move": { + "loop": true, + "bones": { + "left_wheel": { + "rotation": ["50 * query.modified_distance_moved", "math.clamp(query.yaw_speed * 35, -35, 35)", 0] + }, + "right_wheel": { + "rotation": ["50 * query.modified_distance_moved", "math.clamp(query.yaw_speed * 35, -35, 35)", 0] + }, + "root": { + "rotation": ["math.clamp(query.vertical_speed * -7, -35, 35)", 0, 0] + }, + "left_back_wheel": { + "rotation": ["50 * query.modified_distance_moved", 0, 0] + }, + "right_back_wheel": { + "rotation": ["50 * query.modified_distance_moved", 0, 0] + } + }, + "particle_effects": { + "0.0": [ + { + "effect": "gas", + "locator": "pipe" + }, + { + "effect": "mark", + "locator": "left_front" + }, + { + "effect": "mark", + "locator": "right_front" + }, + { + "effect": "mark", + "locator": "left_back" + }, + { + "effect": "mark", + "locator": "right_back" + } + ] + } + } + } +} +``` + +最后是两个粒子: + +```json +{ + "format_version": "1.10.0", + "particle_effect": { + "description": { + "identifier": "tutorial_demo:mark", + "basic_render_parameters": { + "material": "particles_alpha", + "texture": "textures/particle/particles" + } + }, + "curves": { + "variable.psize": { + "type": "catmull_rom", + "input": "variable.particle_age", + "horizontal_range": "variable.particle_lifetime", + "nodes": [1, 1, 0.83, 0, 0] + } + }, + "components": { + "minecraft:emitter_initialization": { + "creation_expression": "variable.radius = 2.5;" + }, + "minecraft:emitter_rate_instant": { + "num_particles": 1 + }, + "minecraft:emitter_lifetime_looping": { + "active_time": 2 + }, + "minecraft:emitter_shape_point": {}, + "minecraft:particle_lifetime_expression": { + "max_lifetime": 1.5 + }, + "minecraft:particle_appearance_billboard": { + "size": ["0.12 * variable.psize", "0.12 * variable.psize"], + "facing_camera_mode": "emitter_transform_xz", + "uv": { + "texture_width": 128, + "texture_height": 128, + "uv": [56, 0], + "uv_size": [8, 8] + } + }, + "minecraft:particle_appearance_tinting": { + "color": [0.01176, 0.01176, 0.01176, 0.9098] + } + } + } +} +``` + +```json +{ + "format_version": "1.10.0", + "particle_effect": { + "description": { + "identifier": "tutorial_demo:gas", + "basic_render_parameters": { + "material": "particles_alpha", + "texture": "textures/particle/particles" + } + }, + "curves": { + "variable.psize": { + "type": "catmull_rom", + "input": "variable.particle_age", + "horizontal_range": "variable.particle_lifetime", + "nodes": [1, 1, 0.83, 0, 0] + } + }, + "components": { + "minecraft:emitter_initialization": { + "creation_expression": "variable.radius = 2.5;" + }, + "minecraft:emitter_rate_instant": { + "num_particles": 1 + }, + "minecraft:emitter_lifetime_looping": { + "active_time": 2 + }, + "minecraft:emitter_shape_point": {}, + "minecraft:particle_lifetime_expression": { + "max_lifetime": 1.5 + }, + "minecraft:particle_appearance_billboard": { + "size": ["0.12 * variable.psize", "0.12 * variable.psize"], + "facing_camera_mode": "lookat_xyz", + "uv": { + "texture_width": 128, + "texture_height": 128, + "uv": [56, 0], + "uv_size": [8, 8] + } + }, + "minecraft:particle_appearance_tinting": { + "color": [0.01176, 0.01176, 0.01176, 0.9098] + } + } + } +} +``` + +这样,就可以保证我们的卡丁车的渲染不会出现任何问题了。接下来,我们为卡丁车添加实体行为组件。 + +## 制作卡丁车实体行为 + +我们接下来为卡丁车添加行为。比较重要的行为便是可以使其受到物理引擎作用的`minecraft:physics`,使其可被骑乘的`minecraft:rideable`。添加行为如下: + +```json +{ + "format_version": "1.12.0", + "minecraft:entity": { + "description": { + "identifier": "tutorial_demo:kart", + "is_experimental": false, + "is_spawnable": false, + "is_summonable": true + }, + "component_groups": { + + }, + "components": { + "minecraft:persistent": {}, + "minecraft:physics": {}, + "minecraft:rideable": { + "seat_count": 1, + "crouching_skip_interact": true, + "interact_text": "action.interact.ride.kart", + "family_types": [ + "player" + ], + "seats": [ + { + "position": [ + 0, + 0.2, + -0.3 + ], + "min_rider_count": 1, + "max_rider_count": 1 + } + ] + }, + "minecraft:pushable": { + "is_pushable": false, + "is_pushable_by_piston": true + }, + "minecraft:movement.basic": {}, + "minecraft:jump.static": {}, + "minecraft:input_ground_controlled": {}, + "minecraft:collision_box": { + "width": 1, + "height": 1 + }, + "minecraft:type_family": { + "family": [ + "kart", + "inanimate" + ] + }, + "minecraft:health": { + "value": 50 + }, + "minecraft:knockback_resistance": { + "value": 1, + "max": 1 + }, + "minecraft:movement": { + "value": 0.5 + } + }, + "events": { + + } + } +} +``` + +![](./images/12.8_in-game_1.gif) + +![](./images/12.8_in-game_2.gif) + +我们可以看到,卡丁车的各个部分的行为和动画皆按照我们预想的那样进行,这说明我们的成功添加了一个卡丁车实体! \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/README.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.3_config_entity.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.3_config_entity.png new file mode 100644 index 0000000..c34b0ee Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.3_config_entity.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.3_squirrel_entity.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.3_squirrel_entity.png new file mode 100644 index 0000000..957e833 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.3_squirrel_entity.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_idle_anim.gif b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_idle_anim.gif new file mode 100644 index 0000000..3c587a7 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_idle_anim.gif differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_molang_playing.gif b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_molang_playing.gif new file mode 100644 index 0000000..6083a9a Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_molang_playing.gif differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_move_all.gif b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_move_all.gif new file mode 100644 index 0000000..259899f Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_move_all.gif differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_move_body.gif b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_move_body.gif new file mode 100644 index 0000000..4011885 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_move_body.gif differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_add_key_frame_rotation.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_add_key_frame_rotation.png new file mode 100644 index 0000000..3a2f4e3 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_add_key_frame_rotation.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_blockbench_animation_mode.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_blockbench_animation_mode.png new file mode 100644 index 0000000..0d08277 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_blockbench_animation_mode.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_blockbench_animation_mode_full_screen.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_blockbench_animation_mode_full_screen.png new file mode 100644 index 0000000..c9b401e Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_blockbench_animation_mode_full_screen.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_body_selected.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_body_selected.png new file mode 100644 index 0000000..7eed748 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_body_selected.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_create.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_create.png new file mode 100644 index 0000000..b9c8522 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_create.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_egg.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_egg.png new file mode 100644 index 0000000..dd1123d Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_egg.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_geometry.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_geometry.png new file mode 100644 index 0000000..ae73446 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_geometry.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_graph.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_graph.png new file mode 100644 index 0000000..54dac91 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_graph.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_idle_create.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_idle_create.png new file mode 100644 index 0000000..51ff37d Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_idle_create.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_idle_start.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_idle_start.png new file mode 100644 index 0000000..2b45373 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_idle_start.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_key_frame.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_key_frame.png new file mode 100644 index 0000000..7fab274 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_key_frame.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_look_at_player.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_look_at_player.png new file mode 100644 index 0000000..355ac0d Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_look_at_player.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_molang.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_molang.png new file mode 100644 index 0000000..6dd8d33 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_molang.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_move.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_move.png new file mode 100644 index 0000000..77d57ae Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_move.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_play.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_play.png new file mode 100644 index 0000000..2ef176b Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_play.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_save.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_save.png new file mode 100644 index 0000000..4d05016 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_save.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_save_as.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_save_as.png new file mode 100644 index 0000000..8bcb251 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_save_as.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_static_in-game.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_static_in-game.png new file mode 100644 index 0000000..d8abd8f Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.4_squirrel_static_in-game.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.5_armor_in-game.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.5_armor_in-game.png new file mode 100644 index 0000000..8ebb0e5 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.5_armor_in-game.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.5_behavior_in-game.gif b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.5_behavior_in-game.gif new file mode 100644 index 0000000..ebe7682 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.5_behavior_in-game.gif differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.5_one_to_one.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.5_one_to_one.png new file mode 100644 index 0000000..4f0ff6e Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.5_one_to_one.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.6_all.gif b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.6_all.gif new file mode 100644 index 0000000..11d1421 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.6_all.gif differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.6_body.gif b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.6_body.gif new file mode 100644 index 0000000..63018cf Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.6_body.gif differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.6_in-game.gif b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.6_in-game.gif new file mode 100644 index 0000000..c3e231d Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.6_in-game.gif differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.6_key_frame_1.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.6_key_frame_1.png new file mode 100644 index 0000000..6eab5dd Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.6_key_frame_1.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.6_key_frame_2.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.6_key_frame_2.png new file mode 100644 index 0000000..766bdbd Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.6_key_frame_2.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.6_key_frame_3.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.6_key_frame_3.png new file mode 100644 index 0000000..7def2c0 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.6_key_frame_3.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.7_in-game_1.gif b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.7_in-game_1.gif new file mode 100644 index 0000000..3e93755 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.7_in-game_1.gif differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.7_in-game_2.gif b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.7_in-game_2.gif new file mode 100644 index 0000000..112cc92 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.7_in-game_2.gif differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_effect.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_effect.png new file mode 100644 index 0000000..25c5f39 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_effect.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_gas.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_gas.png new file mode 100644 index 0000000..3f338e5 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_gas.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_in-game_1.gif b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_in-game_1.gif new file mode 100644 index 0000000..8cf7bf4 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_in-game_1.gif differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_in-game_2.gif b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_in-game_2.gif new file mode 100644 index 0000000..1490c6a Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_in-game_2.gif differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_kart_animation.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_kart_animation.png new file mode 100644 index 0000000..7ee3faa Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_kart_animation.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_kart_animation_1.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_kart_animation_1.png new file mode 100644 index 0000000..896973c Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_kart_animation_1.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_kart_animation_2.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_kart_animation_2.png new file mode 100644 index 0000000..2551617 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_kart_animation_2.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_kart_animation_3.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_kart_animation_3.png new file mode 100644 index 0000000..97a53fb Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_kart_animation_3.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_kart_animation_4.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_kart_animation_4.png new file mode 100644 index 0000000..73943d3 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_kart_animation_4.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_kart_model.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_kart_model.png new file mode 100644 index 0000000..96955d1 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_kart_model.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_kart_texture_create.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_kart_texture_create.png new file mode 100644 index 0000000..ba1be60 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_kart_texture_create.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_kart_texture_created.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_kart_texture_created.png new file mode 100644 index 0000000..089588e Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_kart_texture_created.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_locator.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_locator.png new file mode 100644 index 0000000..8edd06b Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_locator.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_locator_add.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_locator_add.png new file mode 100644 index 0000000..56d536b Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_locator_add.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_locator_added.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_locator_added.png new file mode 100644 index 0000000..6a61eed Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_locator_added.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_locator_select.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_locator_select.png new file mode 100644 index 0000000..b664b6c Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_locator_select.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_particle_added_all.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_particle_added_all.png new file mode 100644 index 0000000..1ed3ad4 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/11-精通自定义复杂的实体/images/12.8_particle_added_all.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/0-摘要.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/0-摘要.md new file mode 100644 index 0000000..9e508b7 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/0-摘要.md @@ -0,0 +1,16 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 5分钟 +--- + +# 摘要 + +在本章中,我们将一起学习**方块实体**(**Block Entity**,又称**Tile Entity**)的制作,了解这种强大且实用的功能,使我们的自定义方块具有更加丰富的特性。 + +- 在第一节(*了解方块实体*)中,我们将一起了解什么是方块实体,了解方块实体存在的必要性和使用方法。 +- 在第二节(*了解方块实体外观*)中,我们将接触方块实体的外观,了解方块实体是如何拥有自定义外观的。 +- 在第三节(*制作一个加速火把*)中,我们将一起制作一个演示用的加速火把,利用模组SDK为方块实体添加功能。 +- 在最后一节(*挑战:制作一个自定义箱子(无法储藏版)*)中,我们将一起进行一个挑战,制作一个不具备储物界面的仅具备外观的箱子。 + +本章关键词:方块实体 方块状态 附加值 置换 滴答 额外数据 加速火把 箱子 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/1-了解方块实体.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/1-了解方块实体.md new file mode 100644 index 0000000..394bb3e --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/1-了解方块实体.md @@ -0,0 +1,50 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 20分钟 +--- + +# 了解方块实体 + +在第十章中,我们曾一起了解了自定义方块,通过各种示例学会了如何自定义一个基本方块。其中,我们学会了如何给予一个方块自定义模型,也学会了如何使用各种组件控制方块的行为。但是,通过这种方式定义的方块还有着种种不足。这是因为普通的方块存在一些固有缺陷。 + +众所周知,普通的方块或者说是常规的方块都是以如下这些机制在世界中运作的。首先,方块具备各种**方块状态**(**Block State**),而每种方块状态拥有一个或多个允许的值,世界中每一个该类型的方块都是所有方块状态各赋予一个允许值中的值产生的。换句话说,世界中每种方块的实例都是其方块状态的排列组合。在旧版中,我们知道方块具有一种被称为特殊值、数据值、损坏值或**附加值**(**Aux Value**)的属性,这其实是和现在的方块状态系统等价的。每种附加值都对应一种方块状态的组合。各方块状态的一种组合称为方块的一个**置换**(或**排列**,**Permutation**)。这种由方块状态组成方块置换的模式只能存储布尔值或整数等基本值的组合,却无法将字符串等更高级的值存储在一个方块中,因此具备存储上的局限性。比如,如果我想将一个物品标识符存储在一个方块中,这只靠常规方块的定义是做不到的。 + +第二种机制是方块滴答机制。这里的**滴答**(**Tick**)可以大致理解为更新,但实际上指的是一种可以随着游戏的系统主滴答循环的有序推进而进行的一种更新。常规的方块都无法保证自己的每游戏刻(即每次系统主滴答循环)都能滴答。液体方块的两个方块中动态的那一个是例外,但是即使是例外,也不能长期地保证每刻的更新性,那是因为动态液体一旦停止流动,它将转换为静态液体。常规的方块只有在自己的**毗邻**(**Neighbor**)发生变化或者发生**随机滴答**(**Random Tick**,又称**随机刻**)事件时才能进行一次滴答或将自身加入**挂起滴答队列**(**Pending Tick Queue**)中,用社区的说法为进入了**计划刻**。而计划刻内的方块会在每次系统主滴答循环顺序中的指定时刻位置处集中进行滴答,这是一种性能缓冲机制。总而言之,常规方块几乎不可以“自主地”进行滴答或更新,也无法侦测到除了自身毗邻之外的其他方块或实体发生的变化。这是常规方块的一个重大缺陷。 + +第三种缺陷就是无法像实体那样使模型播放动画。常规定义的方块的模型几乎都是静态的,定义之后便不会再进行变动,至多存在随着方块状态的改变稍微改变一下模型的朝向或部分骨骼的显示等,但无法完成复杂的动画。为了使模型具备动画,我们必须另寻高法。 + +所以,和附着物与物品的关系类似,我们拥有了一种可以将客户端实体作为一种“资源控制器”的容器挂接到方块上的功能,这种挂接到方块上的仅仅具备客户端实体定义的实体便被称为**方块实体**(**Block Entity**,又称**Tile Entity**)。方块实体可以帮助我们存储大规模数据、自主地进行滴答和播放模型动画。原版游戏中的箱子、告示牌、熔炉、信标、附魔台、床、钟、活塞臂等都具备方块实体。 + +## 定义方块实体 + +了解了这么多理论机制,我们如何实际定义一个方块实体呢?我们可以在方块的服务端定义文件中通过`netease:block_entity`组件定义方块实体。我们在编辑器中新建一个方块,并在服务端组件中添加“**方块实体**”组件。 + +![](./images/13.1_block_entity_component.png) + +我们可以看到其对应的JSON组件字段 + +```json +"netease:block_entity": { + "tick": true, + "movable": false +} +``` + +`tick`字段决定了方块实体是否会每刻向脚本发送一个`ServerBlockEntityTickEvent`事件,通过配合脚本可以做到每刻执行滴答逻辑。`movable`字段决定了该方块是否可以被粘性活塞拉回。 + +一旦定义好了`netease:block_entity`组件,一个方块便算是拥有了一个基础的方块实体。接下来,我们就需要配合模组SDK来添加方块实体的逻辑了。 + +## 连接模组SDK + +我们可以在模组SDK中通过一系列接口操纵方块实体。 + +### 引擎组件 + +我们可以通过在服务端创建`blockEntityData`组件来修改方块实体的**额外数据**(**Extra Data**)。额外数据是中国版自定义方块实体的一个存储器,存储着开发者可以自定义的各种数据。我们可以通过该组件的 `GetBlockEntityData` 方法得到额外数据的引用,然后直接读取额外数据或在这个引用上修改额外数据的值。 + +### 事件 + + `ServerPlaceBlockEntityEvent` 事件可以用于在方块实体被放置时触发。 `ChunkGeneratedServerEvent` 事件可以在区块结束生成是触发,数据中包含一个方块实体列表。 `ServerBlockEntityTickEvent` 事件可以在开启了每刻滴答的方块实体进行滴答时触发。这三个事件都位于服务端。 + +在文档中还有更多的方块实体相关的模组SDK接口等待我们发现和使用。有效利用各种接口将使我们的方块实体拥有各种丰富的逻辑与功能! diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/2-了解方块实体外观.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/2-了解方块实体外观.md new file mode 100644 index 0000000..8c8bcc9 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/2-了解方块实体外观.md @@ -0,0 +1,60 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 15分钟 +--- + +# 了解方块实体外观 + +在行为包定义了方块实体的组件之后,我们还可以在客户端定义方块实体所使用的客户端实体。 + +## 定义实体及其外观 + +我们像第八章中讲的实体那样制作一个客户端实体定义文件,包括实体的各种资源,然后将其准备在资源包中。为了让实体挂接在方块实体上,我们需要修改资源包根目录下的`blocks.json`文件。 + +```json +{ + "format_version": [ 1, 1, 0 ], + ///... + "customblocks:custom_block_entity": { + "sound": "grass", + // 可与netease_model字段一起使用,定义方块的方块模型 + "netease_model": "customblocks:customblocks_model_decoration", + // --- 实体模型配置 --- + // 这个方块实体将会使用到实体模型 + "client_entity": { + // 对应custom_block_entity.entity.json里面的identifier + "identifier": "customblocks:custom_block_entity", + // 这个方块实体的手持模型或掉落时模型是否使用该实体模型。 + "hand_model_use_client_entity": true, + // 这个方块实体的物品icon贴图 + "block_icon": "test_block_icon", + // 方块实体被破坏时使用的贴图 + "destoryed_textures":"destroy_entity" + }, + }, + ///... +} +``` + +这是一个示例的方块实体的实体挂接定义。我们可以看到,`client_entity`字段将负责将实体挂接到方块上。 + +- `identifier`:字符串,实体的标识符,需要与实体的客户端定义中标识符相一致。 +- `hand_model_use_client_entity`:可选,布尔值,玩家的手持模型和该方块的掉落物模型是否适用实体的模型,若为`false`,则仅仅在方块被放置在世界中时使用实体的模型,其余情况(手持和掉落物)使用`netease_model`定义的模型或原版的方块形状。 +- `block_icon`:可选,字符串,方块在物品栏中作为物品时的图标纹理的短名称。事实上,由于游戏机制,物品的图标并不能渲染一个实体,因而对于方块来说只能渲染一个平面图标、按照微软方块形状渲染或按照自定义的方块模型渲染。如果这里没有定义,则会自动渲染`netease_model`定义的方块模型或原版方块形状作为图标。短名称在资源包的纹理图集定义文件`textures/terrain_texture.json`中定义。 +- `destoryed_textures`:可选,字符串,方块破坏粒子的纹理短名称。方块被破坏时会产生旧版粒子(介绍见第二章)中的`terrain`粒子,而`terrain`粒子会从方块的地形图集(定义见第十章)中指定UV处选取纹理作为其贴图。这里可以通过更改地形图集中的短名称来变相地指定产生的`terrain`粒子的纹理UV。若未定义,则自动依次检测`block_icon`字段中定义的纹理、`netease_model`字段中定义的模型中定义的纹理、`textures`字段中定义的纹理的存在性并使用最先检测到的那个。该字段的短名称也在资源包的纹理图集定义文件`textures/terrain_texture.json`中定义。 + +至此,我们便定义好了方块实体对应的实体,而方块实体的包括模型在内的各种资源将交由该实体来进行控制,这样,我们便可以将我们的方块玩出更多的“花样”。 + +## 连接模组SDK + +方块实体的服务端实体定义完成后,我们又多出了很多接口来对接这个实体。与上一节中的接口不同的是,针对于方块实体的实体部分的接口全都位于客户端。这也很容易理解,毕竟这是在代表客户端的资源包中定义的实体。 + +| 接口 | 所属端 | 用处 | +| ------------------------------------------------------------ | -------------------------------------------------------- | ---------------------------------------------- | +| `SetBlockEntityModelPosOffset` | 客户端 | 设置自定义方块实体的实体模型的位置偏移。 | +| `SetBlockEntityModelRotation` | 客户端 | 设置自定义方块实体的实体模型在各个轴上的旋转。 | +| `SetBlockEntityModelScale` | 客户端 | 设置自定义方块实体的实体模型的尺度。 | +| `SetEnableBlockEntityAnimations` | 客户端 | 设置是否开启自定义方块实体的动画。 | +| `SetBlockEntityMolangValue` | 客户端 | 设置自定义方块实体的Molang变量的值。 | +| `GetBlockEntityMolangValue` | 客户端 | 获取自定义方块实体的Molang变量的值。 | diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/3-制作一个加速火把.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/3-制作一个加速火把.md new file mode 100644 index 0000000..58259c6 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/3-制作一个加速火把.md @@ -0,0 +1,226 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 进阶 +time: 35分钟 +--- + +# 制作一个加速火把 + +本节中,我们将通过制作一个加速火把的过程来学习方块实体方块的制作。 + +## 使用Blockbench准备火把模型 + +我们的加速火把不需要骨骼动画或资源控制,只需要一个静态的火把模型即可。所以我们在Blockbench中绘制一个火把模型。 + +![](./images/13.3_torch.png) + +## 使用编辑器导入模型并配置火把方块 + +![](./images/13.3_torch_import.png) + +我们在编辑器中使用导入方块模型的功能导入我们的火把模型。 + +![](./images/13.3_torch_model_set.png) + +然后新建一个火把方块,在“基础属性”中配置模型,这等价于我们在`blocks.json`中配置`netease_model`字段。 + +![](./images/13.3_torch_component_set.png) + +然后为我们的火把加上“方块实体”的组件。与此同时,我们为了使火把正常渲染,将其调整为可寻路、透明材质和非固体,同时设置好其碰撞箱。 + +我们也可以在JSON文件中进行编辑。方块的服务端定义如下: + +```json +{ + "format_version": "1.16.0", + "minecraft:block": { + "description": { + "identifier": "design:custom_torch", + "register_to_creative_menu": true + }, + "components": { + "minecraft:block_light_absorption": 0, + "minecraft:block_light_emission": 1.0, + "minecraft:destroy_time": 1.0, + "netease:render_layer": { + "value": "alpha" + }, + "netease:aabb": { + "collision": { + "min": [0.4375, 0.0, 0.4375], + "max": [0.5625, 0.6875, 0.5625] + }, + "clip": { + "min": [0.4375, 0.0, 0.4375], + "max": [0.5625, 0.6875, 0.5625] + } + }, + "netease:solid": { + "value": false + }, + "netease:pathable": { + "value": true + }, + "netease:block_entity": { + "tick": true, + "movable": false + } + } + } +} +``` + +客户端定义如下: + +```json +{ + "format_version": [ 1, 1, 0 ], + "design:custom_torch": { + "netease_model": "design:custom_torch", + "sound": "wood" + } +} +``` + +同时我们也可以查看已经导入的火把模型文件: + +```json +{ + "format_version": "1.13.0", + "netease:block_geometry": { + "description": { + "identifier": "design:custom_torch", + "textures": ["torch_on"], + "use_ao": false + }, + "bones": [{ + "name": "root", + "pivot": [0, 0, 0], + "rotation": [0, 0, 0], + "cubes": [{ + "origin": [-9, 0, 7], + "pivot": [-8, 0, 8], + "rotation": [0, 0, 0], + "size": [2, 11, 2], + "uv": { + "down": { + "texture": 0, + "uv": [7, 14], + "uv_size": [2, 2] + }, + "east": { + "texture": 0, + "uv": [7, 6], + "uv_size": [2, 10] + }, + "north": { + "texture": 0, + "uv": [7, 6], + "uv_size": [2, 10] + }, + "south": { + "texture": 0, + "uv": [7, 6], + "uv_size": [2, 10] + }, + "up": { + "texture": 0, + "uv": [9, 8], + "uv_size": [-2, -2] + }, + "west": { + "texture": 0, + "uv": [7, 6], + "uv_size": [2, 10] + } + } + }] + }] + } +} +``` + +## 监听事件并催熟作物 + +接下来我们制作脚本部分。我们可以通过`ServerBlockEntityTickEvent`事件来监听滴答。我们通过编辑器在行为包中创建一个主模组目录和一个服务端控制中心,并在主模组文件中注册服务端系统: + +```python +# -*- coding: UTF-8 -*- +from mod.common.mod import Mod +import mod.server.extraServerApi as serverApi + + +@Mod.Binding(name="CustomTorch", version="0.1") +class CustomTorch(object): + + def __init__(self): + pass + + @Mod.InitClient() + def initClient(self): + pass + + @Mod.InitServer() + def initServer(self): + serverApi.RegisterSystem("CustomTorch", "CustomTorchServer", "CustomTorchScripts.ServerMgr.Main") + + @Mod.DestroyClient() + def destroyClient(self): + pass + + @Mod.DestroyServer() + def destroyServer(self): + pass + +``` + +然后我们在`ServerMgr.py`文件中写入服务端系统: + +```python +# -*- coding: UTF-8 -*- +from mod.server.system.serverSystem import ServerSystem +import mod.server.extraServerApi as serverApi + + +class Main(ServerSystem): + + def __init__(self, namespace, system_name): + ServerSystem.__init__(self, namespace, system_name) + namespace, system = serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName() + self.ListenForEvent(namespace, system, "ServerBlockEntityTickEvent", self, self.on_torch_grow) # 监听ServerBlockEntityTickEvent事件 + + def on_torch_grow(self, event): + x = event['posX'] # 获取坐标X + y = event['posY'] # 获取坐标Y + z = event['posZ'] # 获取坐标Z + dim_id = event['dimension'] # 获取维度ID + block_comp = serverApi.GetEngineCompFactory().CreateBlockInfo(serverApi.GetLevelId()) + block_state_comp = serverApi.GetEngineCompFactory().CreateBlockState(serverApi.GetLevelId()) + block_entity_comp = serverApi.GetEngineCompFactory().CreateBlockEntityData(serverApi.GetLevelId()) + block_entity_data = block_entity_comp.GetBlockEntityData(dim_id, (x, y, z)) # 获取方块数据 + tick = block_entity_data['tick'] # 获取方块滴答次数 + if not tick: + tick = 0 # 如果额外数据中没有tick属性便创建tick的值 + tick += 1 # 加速火把方块会在每一刻自增一次滴答次数 + block_entity_data['tick'] = tick # 应用回tick属性 + if tick % 20 != 0: # 1秒=20刻,当刷新次数与20求模为0时,即过了一秒钟 + return + # 运用列表推导式,获得[(x - 1, y, z - 1), (x, y, z - 1), (x + 1, y, z - 1), (x + 1, y, z + 1), (x, y, z), + # (x - 1, y, z + 1), (x, y, z + 1), (x - 1, y, z), (x + 1, y, z)]的坐标列表 + grow_poses = [(x + x_offset, y, z + z_offset) for x_offset in xrange(-1, 2) for z_offset in xrange(-1, 2)] + grow_poses.remove((x, y, z)) # 由于坐标(x, y, z)是加速火把,所以移除这个坐标 + for grow_pos in grow_poses: + block = block_comp.GetBlockNew(grow_pos, dim_id) + if block and block['name'] == 'minecraft:wheat': # 如果是小麦 + block_state = block_state_comp.GetBlockStates(grow_pos, dim_id) + if block_state['growth'] < 7: # 且小麦还未成熟,即成长值小于7 + block_state['growth'] += 1 # 增加一级成长值 + block_state_comp.SetBlockStates(grow_pos, block_state, dim_id) + +``` + +这样,我们便完成了可以为小麦加速的加速火把的制作,每秒将为小麦加速一个成长阶段。我们进入游戏查看效果。 + +![](./images/13.3_in-game.gif) + +可以看到,我们的加速火把如期加速了周围的小麦! \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/4-挑战:制作一个自定义箱子(无法储藏版).md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/4-挑战:制作一个自定义箱子(无法储藏版).md new file mode 100644 index 0000000..9e5f241 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/4-挑战:制作一个自定义箱子(无法储藏版).md @@ -0,0 +1,1423 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 进阶 +time: 50分钟 +--- + +# 挑战:制作一个自定义箱子(无法储藏版) + +在本节中,我们一起来完成一个挑战,使用方块实体和模组API配合来完成一个自定义箱子。不过,由于我们还没有讲解UI的制作,我们当前阶段先不制作箱子的存储功能。可以点击链接:[箱子Demo](https://g79.gdl.netease.com/addonguide-13.zip)下载到完整包体。 + +## 准备客户端实体及其资源 + +我们希望我们的箱子使用一个客户端实体作为其模型和动画的载体。我们手动在资源包的`entity`文件夹中创建一个客户端实体定义文件。 + +```json +{ + "format_version": "1.10.0", + "minecraft:client_entity": { + "description": { + "identifier": "tutorial_demo:custom_chest" + } + } +} +``` + +### 准备资源 + +接下来我们依次准备模型、动画、控制器等资源。 + +#### 准备模型 + +![](./images/13.4_chest_model.png) + +我们使用Blockbench按照正常实体的做法制作一个箱子模型。注意,我们希望箱子是可以打开的,所以箱子的上半部分和下半部分需要分成两个骨骼来做。模型的纹理我们直接使用原版纹理。开发者们如果有需求可以任意更改纹理。 + +```json +{ + "format_version": "1.12.0", + "minecraft:geometry": [ + { + "description": { + "identifier": "geometry.custom_chest", + "texture_width": 32, + "texture_height": 32, + "visible_bounds_width": 3, + "visible_bounds_height": 3.5, + "visible_bounds_offset": [0, 1.25, 0] + }, + "bones": [ + { + "name": "root", + "pivot": [0, 0, 0], + "cubes": [ + { + "origin": [-7, 0, -7], + "size": [14, 10, 14], + "uv": { + "north": {"uv": [0, 21], "uv_size": [16, 11]}, + "east": {"uv": [0, 21], "uv_size": [16, 11]}, + "south": {"uv": [0, 21], "uv_size": [16, 11]}, + "west": {"uv": [0, 21], "uv_size": [16, 11]}, + "up": {"uv": [32, 32], "uv_size": [-16, -16]}, + "down": {"uv": [32, 16], "uv_size": [-16, -16]} + } + }, + { + "origin": [-1, 8, -8], + "size": [2, 2, 1], + "uv": { + "north": {"uv": [7, 5], "uv_size": [2, 2]}, + "east": {"uv": [7, 5], "uv_size": [1, 2]}, + "south": {"uv": [0, 0], "uv_size": [2, 4]}, + "west": {"uv": [7, 5], "uv_size": [1, 2]}, + "up": {"uv": [9, 6], "uv_size": [-2, -1]}, + "down": {"uv": [9, 7], "uv_size": [-2, -1]} + } + } + ] + }, + { + "name": "up", + "parent": "root", + "pivot": [0, 10, 7], + "cubes": [ + { + "origin": [-7, 10, -7], + "size": [14, 4, 14], + "uv": { + "north": {"uv": [0, 0], "uv_size": [16, 5]}, + "east": {"uv": [0, 16], "uv_size": [16, 5]}, + "south": {"uv": [0, 16], "uv_size": [16, 5]}, + "west": {"uv": [0, 15], "uv_size": [16, 6]}, + "up": {"uv": [32, 16], "uv_size": [-16, -16]}, + "down": {"uv": [32, 32], "uv_size": [-16, -16]} + } + }, + { + "origin": [-1, 10, -8], + "size": [2, 2, 1], + "uv": { + "north": {"uv": [7, 3], "uv_size": [2, 2]}, + "east": {"uv": [7, 3], "uv_size": [1, 2]}, + "south": {"uv": [0, 0], "uv_size": [2, 4]}, + "west": {"uv": [7, 3], "uv_size": [1, 2]}, + "up": {"uv": [9, 4], "uv_size": [-2, -1]}, + "down": {"uv": [9, 7], "uv_size": [-2, -1]} + } + } + ] + } + ] + } + ] +} +``` + +![](./images/13.4_large_chest_model.png) + +同时,为了准备大箱子的模型,我们再准备一个大箱子的一半样貌的模型。 + +```json +{ + "format_version": "1.12.0", + "minecraft:geometry": [ + { + "description": { + "identifier": "geometry.custom_chest_pair", + "texture_width": 32, + "texture_height": 32, + "visible_bounds_width": 3, + "visible_bounds_height": 3.5, + "visible_bounds_offset": [0, 1.25, 0] + }, + "bones": [ + { + "name": "root", + "pivot": [0, 0, 0], + "cubes": [ + { + "origin": [-7, 0, -7], + "size": [15, 10, 14], + "uv": { + "north": {"uv": [0, 21], "uv_size": [15, 11]}, + "east": {"uv": [0, 21], "uv_size": [16, 11]}, + "south": {"uv": [1, 21], "uv_size": [15, 11]}, + "west": {"uv": [16, 17], "uv_size": [16, 15]}, + "up": {"uv": [32, 32], "uv_size": [-15, -16]}, + "down": {"uv": [32, 16], "uv_size": [-16, -16]} + } + }, + { + "origin": [7, 8, -8], + "size": [1, 2, 1], + "uv": { + "north": {"uv": [7, 5], "uv_size": [1, 2]}, + "east": {"uv": [7, 5], "uv_size": [1, 2]}, + "south": {"uv": [0, 0], "uv_size": [2, 4]}, + "west": {"uv": [7, 5], "uv_size": [1, 2]}, + "up": {"uv": [9, 6], "uv_size": [-2, -1]}, + "down": {"uv": [9, 7], "uv_size": [-2, -1]} + } + } + ] + }, + { + "name": "up", + "parent": "root", + "pivot": [0, 10, 7], + "rotation": [-50, 0, 0], + "cubes": [ + { + "origin": [-7, 10, -7], + "size": [15, 4, 14], + "uv": { + "north": {"uv": [0, 16], "uv_size": [15, 5]}, + "east": {"uv": [0, 16], "uv_size": [16, 5]}, + "south": {"uv": [1, 16], "uv_size": [15, 5]}, + "west": {"uv": [16, 15], "uv_size": [16, 6]}, + "up": {"uv": [32, 16], "uv_size": [-15, -16]}, + "down": {"uv": [32, 32], "uv_size": [-15, -16]} + } + }, + { + "origin": [7, 10, -8], + "size": [1, 2, 1], + "uv": { + "north": {"uv": [7, 3], "uv_size": [1, 2]}, + "east": {"uv": [7, 3], "uv_size": [1, 2]}, + "south": {"uv": [0, 0], "uv_size": [2, 4]}, + "west": {"uv": [7, 3], "uv_size": [1, 2]}, + "up": {"uv": [9, 4], "uv_size": [-2, -1]}, + "down": {"uv": [9, 7], "uv_size": [-2, -1]} + } + } + ] + } + ] + } + ] +} +``` + +#### 准备动画 + +![](./images/13.4_large_chest_invert_1.png) + +![](./images/13.4_large_chest_invert_-1.png) + +我们希望大箱子的一半的模型具备朝左和朝右两种姿态,这样我们才能将其拼在一起。因此我们制作`invert`动画,其中`variable.mod_invert`在实体定义文件中定义。 + +![](./images/13.4_chest_open.png) + +![](./images/13.4_chest_close.png) + +我们希望箱子具备打开和关闭动画,因此制作`open`和`close`动画。 + +![](./images/13.4_chest_offset.png) + +我们知道方块模型必须进行一个(-8, 0, 8)的位移才能正常渲染,因此我们制作`fix_scale`动画。 + +![](./images/13.4_chest_rotate.png) + +我们的箱子可以面向任何一面,我们为此制作一个`rotation`动画,其中`variable.mod_rotation`在实体定义文件中定义。虽然我们也可以在方块的行为包组件中使用`netease:face_directional`来完成多面向,但是为了后续制作方便,我们依旧可以采取直接在模型实体中加入一个面向的动画,同时使其受到实体定义中自定义的一个Molang变量的控制的功能。 + +```json +{ + "format_version": "1.8.0", + "animations": { + "animation.custom_chest.invert": { + "loop": true, + "bones": { + "root": { + "scale": ["variable.mod_invert == 0.0 ? 1.0 : variable.mod_invert", 1.0, 1.0] + } + } + }, + "animation.custom_chest.open": { + "loop": "hold_on_last_frame", + "animation_length": 0.25, + "bones": { + "up": { + "rotation": { + "0.0": [0, 0, 0], + "0.25": [-90, 0, 0] + } + } + } + }, + "animation.custom_chest.close": { + "loop": "hold_on_last_frame", + "animation_length": 0.25, + "bones": { + "up": { + "rotation": { + "0.0": [-90, 0, 0], + "0.25": [0, 0, 0] + } + } + } + }, + "animation.custom_chest.fix_scale": { + "loop": true, + "bones": { + "root": { + "position": [-8, 0, 8] + } + } + }, + "animation.custom_chest.rotation": { + "loop": true, + "bones": { + "root": { + "rotation": [0, "variable.mod_rotation * 90", 0] + } + } + } + } +} +``` + +#### 准备动画控制器 + +我们手动新建一个动画控制器文件,用于控制箱子的开关。 + +```json +{ + "format_version": "1.10.0", + "animation_controllers": { + "controller.animation.custom_chest.general": { + "initial_state": "default", + "states": { + "default": { + "transitions": [ + { + "open": "variable.mod_states" + } + ] + }, + "open": { + "animations": [ + "open" + ], + "transitions": [ + { + "close": "!variable.mod_states" + } + ] + }, + "close": { + "animations": [ + "close" + ], + "transitions": [ + { + "default": "query.any_animation_finished" + } + ] + } + } + } + } +} +``` + +其中`variable.mod_states`用于控制箱子的开闭,我们可以在实体定义文件中定义它,并在模组SDK中控制它。 + +#### 准备渲染控制器 + +我们主要是希望渲染控制器用于控制实体应该使用普通箱子模型还是大箱子模型,因此我们着眼于控制器中的`geometry`字段。 + +```json +{ + "format_version": "1.8.0", + "render_controllers": { + "controller.render.chest_pair": { + "geometry": "variable.mod_invert == 0.0 ? Geometry.default : Geometry.pair", + "materials": [{"*": "Material.default"}], + "textures": ["Texture.default"] + } + } +} +``` + +其中`variable.mod_invert`和我们动画中的是同一个变量,由实体定义文件定义并由模组SDK控制。我们希望`variable.mod_invert`等于0时为正常箱子,等于1或-1时为正的或反的大箱子。 + +### 挂接资源 + +我们接下来将资源全部挂接到实体定义文件上。 + +```json +{ + "format_version": "1.10.0", + "minecraft:client_entity": { + "description": { + "identifier": "tutorial_demo:custom_chest", + "materials": { + "default": "entity_alphatest" + }, + "geometry": { + "default": "geometry.custom_chest", + "pair": "geometry.custom_chest_pair" + }, + "textures": { + "default": "textures/entity/custom_chest/custom_chest" + }, + "animations": { + "open": "animation.custom_chest.open", + "close": "animation.custom_chest.close", + "fix_scale": "animation.custom_chest.fix_scale", + "rotation": "animation.custom_chest.rotation", + "pair_invert": "animation.custom_chest.invert", + "controller.general": "controller.animation.custom_chest.general" + }, + "scripts": { + "initialize": [ + "variable.mod_states = 0.0;", + "variable.mod_invert = 0.0;", + "variable.mod_rotation = 0.0;" + ], + "animate": [ + "fix_scale", + "pair_invert", + "rotation", + "controller.general" + ] + }, + "render_controllers": [ + "controller.render.chest_pair" + ] + } + } +} +``` + +同时我们在`scripts/initialize`中定义变量,这代表实体初始化时将会初始化这些变量。注意,这里的表达式是一个赋值表达式,所以带有等号,而带有等号的表达式会被认为是一个复杂表达式,所以必须以`;`结尾。这样,我们便准备好了一个箱子实体。我们接下来只需要将该箱子实体挂接到方块上,并使用模组SDK来控制上述三个变量的值即可实现箱子各种动画的变动。 + +## 创建方块 + +我们在我的世界开发工作台中新建一个AddOn组件,不妨命名为“自定义箱子模组”。我们通过配置新建一个方块。我们先来关注其资源包定义文件,我们为其挂接我们上面刚刚做好的实体。我们打开资源包根目录的`blocks.json`文件。 + +```json +{ + "format_version": [1, 1, 0], + "tutorial_demo:custom_chest": { + "sound": "stone", + "client_entity": { + "identifier": "tutorial_demo:custom_chest", + "hand_model_use_client_entity": true + } + } +} +``` + +我们将实体客户端文件挂接在方块客户端文件的`client_entity`字段中。这样我们的方块就拥有了一个方块实体模型。当然,为了使方块真正拥有方块实体,我们还需要在方块行为包定义中使用对应的组件来激活方块实体。 + +```json +{ + "format_version": "1.10.0", + "minecraft:block": { + "description": { + "identifier": "tutorial_demo:custom_chest", + "register_to_creative_menu": true + }, + "components": { + "netease:listen_block_remove": { + "value": true + }, + "netease:solid": { + "value": false + }, + "netease:block_entity": { + "tick": false, + "movable": false + }, + "netease:aabb": { + "collision": { + "min": [ + 0.0625, + 0, + 0.0625 + ], + "max": [ + 0.9375, + 0.875, + 0.9375 + ] + }, + "clip": { + "min": [ + 0.0625, + 0, + 0.0625 + ], + "max": [ + 0.9375, + 0.875, + 0.9375 + ] + } + }, + "minecraft:block_light_absorption": 0, + "minecraft:destroy_time": 0.1 + } + } +} +``` + +其中`netease:block_entity`用于开启方块实体。`netease:listen_block_remove`用于在模组SDK中启用该方块的移除监听。试想,我们当破坏一个大箱子的其中一边时,我们应该需要及时更改另一边的模型使其回归到普通箱子的样貌。因此,我们需要在模组SDK中启用方块移除监听。 + +目前,我们已经定义了方块的资源文件和行为文件,但是,这样的方块在世界中并没有什么功能,既不能随着玩家放置的方向不同而转向,又不能自动检测周围的方块来判断合并成大箱子,也不能打开和关闭。这些功能都需要模组SDK的配合来实现。接下来,我们便通过模组SDK来制作这些功能。 + +## 编写模组SDK脚本 + +我们手动创建Python脚本文件,我们创建一个`BlockEntityScripts`文件夹。在其中除了`__init__.py`和`modMain.py`之外分别创建`ServerSystem.py`和`ClientSystem.py`文件,并分别在其中创建`Main`类。 + +我们在`modMain.py`中注册两个系统供之后使用: + +```python +# -*- coding: UTF-8 -*- +from mod.common.mod import Mod +import mod.server.extraServerApi as serverApi +import mod.client.extraClientApi as clientApi + + +@Mod.Binding(name="LostWorld", version="0.2") +class TileEntityChest(object): + + def __init__(self): + pass + + @Mod.InitClient() + def initClient(self): + clientApi.RegisterSystem('tutorial_demo', 'BlockEntityClient', 'BlockEntityScripts.ClientSystem.Main') + + @Mod.InitServer() + def initServer(self): + serverApi.RegisterSystem('tutorial_demo', 'BlockEntityServer', 'BlockEntityScripts.ServerSystem.Main') + + @Mod.DestroyClient() + def destroyClient(self): + pass + + @Mod.DestroyServer() + def destroyServer(self): + pass + +``` + +### 制作箱子转向功能 + +我们知道,原版的箱子会在放置时根据玩家的朝向而改变放置的面向。对于我们的箱子,我们试图在大脑中模拟这一过程的实现方式: + +> 箱子的朝向信息必须是存储在服务端的,因为如果只存储在客户端,那么其他的客户端将获取不到相关信息,从而造成玩家之间视图不同步的现象。因此,我们可以将箱子的朝向信息存储在箱子在服务端的方块实体数据中。 +> +> 当玩家试图放置方块时,我们可以检测此时的上下文信息,只有当玩家是朝着一个非箱子的上表面放置时我们才允许箱子的放置。在玩家刚刚放置完成箱子的这一时刻,我们针对玩家目前的朝向计算出箱子应该具有的朝向,然后将箱子用于储存朝向的那个方块实体数据改变为正确的朝向。这一切都应该在服务端进行,因为这些运算都涉及到方块本身的状态,而方块本身的状态是存储在服务端的,客户端只是实时同步这些数据并渲染。说到这里,我们便知道了最后一步:通知客户端,请求客户端修改变量`variable.mod_rotation`的值,用于调整箱子的渲染情况。 + +我们在服务端系统中写入代码如下: + +```python +# -*- coding: UTF-8 -*- +from mod.server.system.serverSystem import ServerSystem +from mod.common.minecraftEnum import Facing +import mod.server.extraServerApi as serverApi + + +class Main(ServerSystem): + + def __init__(self, namespace, system_name): + ServerSystem.__init__(self, namespace, system_name) + namespace = serverApi.GetEngineNamespace() + system_name = serverApi.GetEngineSystemName() + # 监听引擎系统的ServerEntityTryPlaceBlockEvent事件,玩家尝试放置时就会触发,用于阻止一些我们不需要的放置情形,绑定on_try_placed回调 + self.ListenForEvent(namespace, system_name, 'ServerEntityTryPlaceBlockEvent', self, self.on_try_placed) + # 监听引擎系统的EntityPlaceBlockAfterServerEvent事件,方块放置后立马触发,用于在方块被放置后迅速更新方块的各种状态,绑定on_placed回调 + self.ListenForEvent(namespace, system_name, 'EntityPlaceBlockAfterServerEvent', self, self.on_placed) + + def on_try_placed(self, event): + # 获取XYZ坐标 + x = event['x'] + y = event['y'] + z = event['z'] + # 获取玩家欲放置的方块ID + block_name = event['fullName'] + # 获取维度ID + dimension_id = event['dimensionId'] + # 获取玩家所指的方块此时的面 + face = event['face'] + # 如果玩家指着上表面而且手里的方块就是我们的箱子 + if face == Facing.Up and block_name == 'tutorial_demo:custom_chest': + # 我们就打算放置它,但是这种情况下我们要排除一个例外,那便是我们不希望将箱子放在箱子上。我们使用blockInfo引擎组件获取往下一格的坐标处的方块 + block_data = serverApi.GetEngineCompFactory().CreateBlockInfo(serverApi.GetLevelId()).GetBlockNew((x, y - 1, z), dimension_id) + # 如果这个方块也是箱子 + if block_data['name'] == block_name: + # 取消放置 + event['cancel'] = True + + def on_placed(self, event): + # 获取维度ID + dimension_id = event['dimensionId'] + # 获取XYZ坐标 + x = event['x'] + y = event['y'] + z = event['z'] + # 获取已经放置了的这个方块的ID + block_name = event['fullName'] + # 获取要防止方块的实体ID,也就是我们的玩家ID + player_id = event['entityId'] + # 如果已经放置的是我们的自定义箱子 + if block_name == 'tutorial_demo:custom_chest': + # 通过rot引擎组件获取玩家的视角,为下面根据玩家的偏航角数值设置箱子的朝向做准备 + player_rot = serverApi.GetEngineCompFactory().CreateRot(player_id).GetRot() + # 通过blockEntityData引擎组件获取已经放置的这个箱子的方块实体数据 + block_data_comp = serverApi.GetEngineCompFactory().CreateBlockEntityData(serverApi.GetLevelId()) + block_data = block_data_comp.GetBlockEntityData(dimension_id, (x, y, z)) + # 如果获取到方块实体数据了 + if block_data: + # 我们用一个自定义函数get_block_facing来获取箱子应该朝向哪儿,设置到方块实体数据中。自定义函数在脚本最下面定义 + block_data['rotation'] = self.get_block_facing(player_rot) + # 此时默认箱子是小箱子,设置到方块实体数据中 + block_data['states'] = 0 + # 此时默认箱子没有反向,设置到方块实体数据中 + block_data['invert'] = 0 + # 我们在最后希望告知客户端箱子的各种状态的数据,以便客户端更新方块实体的Molang变量,完成渲染工作。我们这里准备一个字典用于储存这些需要张贴的数据 + post_data = {} + # 通过extraData引擎组件获取整个存档的额外数据,我们想把目前世界中存在的方块实体都存到整个存档的额外数据中,方便下次打开存档时能够复原当前箱子的各种状态 + level_data_comp = serverApi.GetEngineCompFactory().CreateExtraData(serverApi.GetLevelId()) + data = level_data_comp.GetExtraData(block_name) + # 如果当前不存在以该方块ID为键名的数据,那就创建一个新的 + if not data: + data = {} + # 向最终要张贴的数据的字典存储方块的各种状态 + post_data['{0},{1},{2}'.format(x, y, z)] = {'rotation': block_data['rotation'], 'invert': block_data['invert']} + # 向最终要设置到世界的额外数据中的字典存储方块的各种状态 + data['{0},{1},{2}'.format(x, y, z)] = {'rotation': block_data['rotation'], 'invert': block_data['invert']} + # 设置世界的额外数据 + level_data_comp.SetExtraData(block_name, data) + # 将张贴的数据广播到各个客户端,以事件名InitChestRotation + self.BroadcastToAllClient('InitChestRotation', post_data) + + def get_block_facing(self, rot): + # 这是我们依据玩家的偏航角返回箱子的朝向的函数。我们知道,玩家的偏航角是垂直于y轴的姿态角,在zOx平面上转动,因此z轴正方向也就是南面为0°,向x轴正方向旋转也就是逆时针旋转为正向。而我们的箱子默认面北,正好是共轭的方向,此时variable.mod_rotation应该为0。因此我们使用如下的判断即可完成玩家朝向到方块朝向的转换 + if 135.0 < rot[1] <= 180.0: + return 2.0 + elif 45.0 < rot[1] <= 135.0: + return 1.0 + elif -45.0 < rot[1] <= 45.0: + return 0.0 + elif -135.0 < rot[1] <= -45.0: + return 3.0 + elif -180.0 < rot[1] <= -135.0: + return 2.0 + else: + return 0.0 + +``` + +我们在客户端中写入代码如下: + +```python +# -*- coding: UTF-8 -*- +from mod.client.system.clientSystem import ClientSystem +import mod.client.extraClientApi as clientApi +import time + + +class Main(ClientSystem): + + def __init__(self, namespace, system_name): + ClientSystem.__init__(self, namespace, system_name) + # 我们监听我们本模组服务端的事件InitChestRotation,绑定chest_rotation作为回调 + self.ListenForEvent('tutorial_demo', 'BlockEntityServer', 'InitChestRotation', self, self.chest_rotation) + # 一个队列,下面会用到 + self.rotation_queue = [] + + def chest_rotation(self, event): + # 将event重新构造成易读的形式 + new_event = {tuple(map(int, k.split(','))): v for k, v in event.items()} + # 准备blockInfo引擎组件 + block_comp = clientApi.GetEngineCompFactory().CreateBlockInfo(clientApi.GetLevelId()) + # 自定义一个函数。之所以自定义这个函数,是因为事件触发时,可能会出现此时方块在客户端渲染完成,但无法设置方块的Molang的情况。因此我们试图将更新这个方块的Molang这一响应直接放到Update函数里,每脚本刻更新一次,直至至少更新2次。我们通过一个队列将这个函数传到Update里,也就是上面定义的rotation_queue,然后等到至少更新两次后再把这个函数弹出队列 + def rotate_chest(): + index = 0 + count = len(new_event.items()) + # 对当前事件响应传入的全部方块 + for pos, data in new_event.items(): + # 获取其ID和数据值 + block_data = block_comp.GetBlock(pos) + # 如果ID就是我们的箱子 + if block_data[0] == 'tutorial_demo:custom_chest': + # 设置各个Molang变量的值 + block_comp.SetBlockEntityMolangValue(pos, "variable.mod_rotation", data['rotation']) + block_comp.SetBlockEntityMolangValue(pos, "variable.mod_invert", float(data['invert']) if data['invert'] != 0 else 0.0) + index += 1 + if index == count: + return True + else: + return False + # 如果传入的数据里面是有方块的 + if new_event: + # 把我们的更新函数放到队列里 + self.rotation_queue.append([rotate_chest, 0]) + + def Update(self): + # 记录即将完成设置朝向任务的函数所处在列表的下标值 + _die = [] + # 对队列里每一个函数 + for index, value in enumerate(self.rotation_queue): + # 将其反复执行,直至满足两次 + if value[0](): + value[1] += 1 + if value[1] == 2: + _die.append(index) + # 将完成任务的函数所在的元素设置为None + for i in _die: + self.rotation_queue[i] = None + # 过滤掉用None占位的列表元素 + if self.rotation_queue: + self.rotation_queue = filter(None, self.rotation_queue) +``` + +这样就完成了箱子的转向功能。 + +![](./images/13.4_rotate.gif) + +### 制作箱子连接功能 + +箱子的连接功能即两个小箱子如果朝向相同且并排放置时,会自动合成一个大箱子的功能。我们应该注意到两点。第一点是第二个箱子放置时触发的合并逻辑,第二点是第二个箱子被破坏时触发的第一个箱子的复原逻辑。 + +> 这些逻辑依旧是需要先在服务端中运行,然后告知客户端来更新Molang变量的值。我们只需要根据箱子是东西放置的还是南北放置的来判断箱子的左右两侧有无同朝向的箱子即可。如果有,那就将自己和对方的`variable.mod_invert`分别设为1和-1,然后将这一消息告知客户端使其在视觉上真正地连起来。 + +我们在服务端系统中补充代码如下: + +```python +# -*- coding: UTF-8 -*- +from mod.server.system.serverSystem import ServerSystem +from mod.common.minecraftEnum import Facing +import mod.server.extraServerApi as serverApi + + +class Main(ServerSystem): + + def __init__(self, namespace, system_name): + ServerSystem.__init__(self, namespace, system_name) + namespace = serverApi.GetEngineNamespace() + system_name = serverApi.GetEngineSystemName() + self.ListenForEvent(namespace, system_name, 'ServerEntityTryPlaceBlockEvent', self, self.on_try_placed) + self.ListenForEvent(namespace, system_name, 'EntityPlaceBlockAfterServerEvent', self, self.on_placed) + # 监听引擎系统的BlockRemoveServerEvent事件,方块被移除时触发,用于大箱子被破坏了一个方块时将剩余的那个箱子复原,绑定block_removed回调 + self.ListenForEvent(namespace, system_name, 'BlockRemoveServerEvent', self, self.block_removed) + + def on_try_placed(self, event): + x = event['x'] + y = event['y'] + z = event['z'] + dimension_id = event['dimensionId'] + face = event['face'] + if face == Facing.Up and block_name == 'tutorial_demo:custom_chest': + block_data = serverApi.GetEngineCompFactory().CreateBlockInfo(serverApi.GetLevelId()).GetBlockNew((x, y - 1, z), dimension_id) + if block_data['name'] == block_name: + event['cancel'] = True + + def on_placed(self, event): + # 获取维度ID + dimension_id = event['dimensionId'] + # 获取XYZ坐标 + x = event['x'] + y = event['y'] + z = event['z'] + # 获取已经放置了的这个方块的ID + block_name = event['fullName'] + # 获取要防止方块的实体ID,也就是我们的玩家ID + player_id = event['entityId'] + # 如果已经放置的是我们的自定义箱子 + if block_name == 'tutorial_demo:custom_chest': + player_rot = serverApi.GetEngineCompFactory().CreateRot(player_id).GetRot() + # 通过blockEntityData引擎组件获取已经放置的这个箱子的方块实体数据 + block_data_comp = serverApi.GetEngineCompFactory().CreateBlockEntityData(serverApi.GetLevelId()) + block_data = block_data_comp.GetBlockEntityData(dimension_id, (x, y, z)) + # 如果获取到方块实体数据了 + if block_data: + block_data['rotation'] = self.get_block_facing(player_rot) + block_data['states'] = 0 + block_data['invert'] = 0 + # 准备blockInfo引擎组件 + block_info_comp = serverApi.GetEngineCompFactory().CreateBlockInfo(serverApi.GetLevelId()) + # 创建需要张贴的数据 + post_data = {} + # 如果箱子是东西放置的,即面向南北的 + if block_data['rotation'] % 2 == 0.0: + # 在-1和1中取 + for i in range(-1, 2, 2): + # 通过blockInfo引擎组件获取x坐标±1的方块,即东西两侧方块的信息 + block_info_data = block_info_comp.GetBlockNew( + (x + i, y, z), + dimension_id + ) + # 如果也是箱子 + if block_info_data['name'] == 'tutorial_demo:custom_chest': + # 通过blockEntityData引擎组件获取其方块实体数据 + connect_block_data = block_data_comp.GetBlockEntityData(dimension_id, (x + i, y, z)) + # 如果invert是0,也就是同样是小箱子 + if connect_block_data['invert'] == 0 and connect_block_data['rotation'] == block_data['rotation']: + # 给他整成大箱子的一半,同时自己也变成另一半 + block_data['invert'] = i * int(block_data['rotation'] - 1) + connect_block_data['invert'] = -i * int(block_data['rotation'] - 1) + # 把这个箱子也加入需要张贴的数据中,以供等一会传入客户端更新Molang变量 + post_data['{0},{1},{2}'.format(x + i, y, z)] = {'rotation': connect_block_data['rotation'], 'invert': connect_block_data['invert']} + break + # 如果箱子是南北放置的,即面向东西的,同理 + if block_data['rotation'] % 2 == 1.0: + for i in range(-1, 2, 2): + block_info_data = block_info_comp.GetBlockNew( + (x, y, z + i), + dimension_id + ) + if block_info_data['name'] == 'tutorial_demo:custom_chest': + connect_block_data = block_data_comp.GetBlockEntityData(dimension_id, (x, y, z + i)) + if connect_block_data['invert'] == 0 and connect_block_data['rotation'] == block_data['rotation']: + block_data['invert'] = i * int(block_data['rotation'] - 2) + connect_block_data['invert'] = -i * int(block_data['rotation'] - 2) + post_data['{0},{1},{2}'.format(x, y, z + i)] = {'rotation': connect_block_data['rotation'], 'invert': connect_block_data['invert']} + break + level_data_comp = serverApi.GetEngineCompFactory().CreateExtraData(serverApi.GetLevelId()) + data = level_data_comp.GetExtraData(block_name) + if not data: + data = {} + post_data['{0},{1},{2}'.format(x, y, z)] = {'rotation': block_data['rotation'], 'invert': block_data['invert']} + data['{0},{1},{2}'.format(x, y, z)] = {'rotation': block_data['rotation'], 'invert': block_data['invert']} + # 用需要张贴的数据中的值更新需要存储搭配世界的额外数据中的值 + data.update(post_data) + level_data_comp.SetExtraData(block_name, data) + self.BroadcastToAllClient('InitChestRotation', post_data) + + def block_removed(self, event): + # 获取被移除的方块ID + block_name = event['fullName'] + # 获取XYZ坐标 + x = event['x'] + y = event['y'] + z = event['z'] + # 获取维度ID + dimension_id = event['dimension'] + # 如果移除的是我们的箱子 + if block_name == 'tutorial_demo:custom_chest': + # 通过blockEntityData引擎组件获取其方块实体数据 + block_data_comp = serverApi.GetEngineCompFactory().CreateBlockEntityData(serverApi.GetLevelId()) + block_entity_data = block_data_comp.GetBlockEntityData(0, (x, y, z)) + # 通过extraData引擎组件获取存档的额外数据中我们箱子的键值对存储的值,放在data里 + level_data_comp = serverApi.GetEngineCompFactory().CreateExtraData(serverApi.GetLevelId()) + data = level_data_comp.GetExtraData(block_name) + # 定义一个空的需要在最后给客户端广播事件时张贴的数据 + post_data = {} + # 如果该方块原本是大箱子 + if block_entity_data['invert'] != 0: + # 准备blockInfo引擎组件 + block_info_comp = serverApi.GetEngineCompFactory().CreateBlockInfo(serverApi.GetLevelId()) + # 如果箱子原本是东西放置的,即面向南北的 + if block_entity_data['rotation'] % 2 == 0.0: + # 检测左右两边是哪一边有方块连着 + for i in range(-1, 2, 2): + block_info_data = block_info_comp.GetBlockNew( + (x + i, y, z), + dimension_id + ) + if block_info_data['name'] == 'tutorial_demo:custom_chest': + # 获取他的方块实体数据 + connect_block_data = block_data_comp.GetBlockEntityData(dimension_id, (x + i, y, z)) + # 如果就是和他连着的那个方块 + if connect_block_data['invert'] != 0 and connect_block_data['rotation'] == block_entity_data['rotation']: + # 改回小箱子 + connect_block_data['invert'] = 0 + # 更新世界的额外数据中的值 + data['{0},{1},{2}'.format(x + i, y, z)] = {'rotation': connect_block_data['rotation'], 'invert': connect_block_data['invert']} + # 加入到张贴的数据中 + post_data['{0},{1},{2}'.format(x + i, y, z)] = {'rotation': connect_block_data['rotation'], 'invert': connect_block_data['invert']} + # 同理 + if block_entity_data['rotation'] % 2 == 1.0: + for i in range(-1, 2, 2): + block_info_data = block_info_comp.GetBlockNew( + (x, y, z + i), + dimension_id + ) + if block_info_data['name'] == 'tutorial_demo:custom_chest': + connect_block_data = block_data_comp.GetBlockEntityData(dimension_id, (x, y, z + i)) + if connect_block_data['invert'] != 0 and connect_block_data['rotation'] == block_entity_data['rotation']: + connect_block_data['invert'] = 0 + data['{0},{1},{2}'.format(x, y, z + i)] = {'rotation': connect_block_data['rotation'], 'invert': connect_block_data['invert']} + post_data['{0},{1},{2}'.format(x, y, z + i)] = {'rotation': connect_block_data['rotation'], 'invert': connect_block_data['invert']} + # 把自己在世界中额外数据的值也更新了,但是无需将自己也加入张贴的数据了,因为自己已经被破坏了 + data = data.pop('{0},{1},{2}'.format(x, y, z), data) + level_data_comp.SetExtraData(block_name, data) + # 告诉客户端更新Molang变量 + self.BroadcastToAllClient('InitChestRotation', post_data) + + def get_block_facing(self, rot): + if 135.0 < rot[1] <= 180.0: + return 2.0 + elif 45.0 < rot[1] <= 135.0: + return 1.0 + elif -45.0 < rot[1] <= 45.0: + return 0.0 + elif -135.0 < rot[1] <= -45.0: + return 3.0 + elif -180.0 < rot[1] <= -135.0: + return 2.0 + else: + return 0.0 + +``` + +客户端无需做任何更改即可。这样,我们便完成了连接功能的制作。 + +![](./images/13.4_merge.gif) + +### 制作箱子开闭功能 + +最后,我们来看看如何制作箱子开闭功能。我们依旧先进行“颅内”设想。 + +> 我们首先希望玩家操作箱子不要太过频繁,不然动画的播放会不连贯。因此我们应该在玩家对箱子使用“使用键”时设置一个时间阈值,这个逻辑应该在客户端执行。紧接着客户端告知服务端玩家请求打开箱子,服务端用来检测到底能不能真的能打开。服务端检测上方是否为空气,我们认为只有上方是空气才允许箱子打开。如果能打开,在服务端更新箱子的方块实体数据。如果是大箱子,还要顺便更新与之相连的另外半个箱子的数据。在这之后,再告知客户端哪些数据更新了,客户端便可以更新Molang变量,使渲染正确进行。同时如果我们有UI,客户端还要负责打开UI,不过目前我们先不考虑这一功能。 + +我们在客户端中补充如下内容: + +```python +# -*- coding: UTF-8 -*- +from mod.client.system.clientSystem import ClientSystem +import mod.client.extraClientApi as clientApi +import time + + +class Main(ClientSystem): + + def __init__(self, namespace, system_name): + ClientSystem.__init__(self, namespace, system_name) + # 监听引擎系统的ClientBlockUseEvent事件,玩家与方块交互时触发,即客户端玩家打开箱子时,绑定block_used回调 + self.ListenForEvent(namespace, system_name, 'ClientBlockUseEvent', self, self.block_used) + # 我们监听我们本模组服务端的事件OpenChestFinished,作为最后更新Molang的事件监听,绑定chest_opened作为回调 + self.ListenForEvent('tutorial_demo', 'BlockEntityServer', 'OpenChestFinished', self, self.chest_opened) + self.ListenForEvent('tutorial_demo', 'BlockEntityServer', 'InitChestRotation', self, self.chest_rotation) + # 定义一个变量作为箱子交互的冷却时间 + self.block_interact_cooldown = {} + self.rotation_queue = [] + + def block_used(self, event): + # 获取玩家ID + player_id = event['playerId'] + # 获取被交互的方块 + block_name = event['blockName'] + # 获取XYZ坐标 + x = event['x'] + y = event['y'] + z = event['z'] + # 如果被交互的使我们的箱子 + if block_name == 'tutorial_demo:custom_chest': + # 玩家不在冷却中 + if player_id not in self.block_interact_cooldown: + # 给玩家加个冷却 + self.block_interact_cooldown[player_id] = time.time() + # 否则如果玩家与上一次记录的时间相差过短 + elif time.time() - self.block_interact_cooldown[player_id] < 0.15: + # 就放弃本次交互 + return + # 否则,即已经与上次交互记录的时间拉得足够长时 + else: + # 更新冷却里记录的时间戳 + self.block_interact_cooldown[player_id] = time.time() + # 使用game引擎组件获取当前维度ID + game_comp = clientApi.GetEngineCompFactory().CreateGame(clientApi.GetLevelId()) + dimension_id = game_comp.GetCurrentDimension() + # 告知服务端在这个维度里有一个玩家要与一个坐标处的方块交互 + self.NotifyToServer('TryOpenChest', {'dimensionId': dimension_id, 'pos': [x, y, z]}) + + def chest_opened(self, event): + # 获取事件数据中传给该响应的张贴的数据,此时已经交互完成,需要更新Molang变量的值 + data = event['data'] + # 准备blockInfo引擎组件 + block_comp = clientApi.GetEngineCompFactory().CreateBlockInfo(clientApi.GetLevelId()) + # 循环data中的方块 + for block_data in data: + # 获取当前循环方块的坐标 + block_pos = tuple(block_data['pos']) + # 通过blockInfo引擎组件设置该方块的Molang变量 + block_comp.SetBlockEntityMolangValue(block_pos, "variable.mod_states", float(block_data['states'])) + + def chest_rotation(self, event): + new_event = {tuple(map(int, k.split(','))): v for k, v in event.items()} + block_comp = clientApi.GetEngineCompFactory().CreateBlockInfo(clientApi.GetLevelId()) + def rotate_chest(): + index = 0 + count = len(new_event.items()) + for pos, data in new_event.items(): + block_data = block_comp.GetBlock(pos) + if block_data[0] == 'tutorial_demo:custom_chest': + block_comp.SetBlockEntityMolangValue(pos, "variable.mod_rotation", data['rotation']) + block_comp.SetBlockEntityMolangValue(pos, "variable.mod_invert", float(data['invert']) if data['invert'] != 0 else 0.0) + index += 1 + if index == count: + return True + else: + return False + if new_event: + self.rotation_queue.append([rotate_chest, 0]) + + def Update(self): + _die = [] + for index, value in enumerate(self.rotation_queue): + if value[0](): + value[1] += 1 + if value[1] == 2: + _die.append(index) + for i in _die: + self.rotation_queue[i] = None + if self.rotation_queue: + self.rotation_queue = filter(None, self.rotation_queue) +``` + +然后我们补充服务端内容: + +```python +# -*- coding: UTF-8 -*- +from mod.server.system.serverSystem import ServerSystem +from mod.common.minecraftEnum import Facing +import mod.server.extraServerApi as serverApi + + +class Main(ServerSystem): + + def __init__(self, namespace, system_name): + ServerSystem.__init__(self, namespace, system_name) + namespace = serverApi.GetEngineNamespace() + system_name = serverApi.GetEngineSystemName() + self.ListenForEvent(namespace, system_name, 'ServerEntityTryPlaceBlockEvent', self, self.on_try_placed) + self.ListenForEvent(namespace, system_name, 'EntityPlaceBlockAfterServerEvent', self, self.on_placed) + self.ListenForEvent(namespace, system_name, 'BlockRemoveServerEvent', self, self.block_removed) + # 监听本模组客户端的TryOpenChest事件,绑定try_open_chest回调 + self.ListenForEvent('tutorial_demo', 'BlockEntityClient', 'TryOpenChest', self, self.try_open_chest) + + def on_try_placed(self, event): + x = event['x'] + y = event['y'] + z = event['z'] + dimension_id = event['dimensionId'] + face = event['face'] + if face == Facing.Up and block_name == 'tutorial_demo:custom_chest': + block_data = serverApi.GetEngineCompFactory().CreateBlockInfo(serverApi.GetLevelId()).GetBlockNew((x, y - 1, z), dimension_id) + if block_data['name'] == block_name: + event['cancel'] = True + + def on_placed(self, event): + dimension_id = event['dimensionId'] + x = event['x'] + y = event['y'] + z = event['z'] + block_name = event['fullName'] + player_id = event['entityId'] + if block_name == 'tutorial_demo:custom_chest': + player_rot = serverApi.GetEngineCompFactory().CreateRot(player_id).GetRot() + block_data_comp = serverApi.GetEngineCompFactory().CreateBlockEntityData(serverApi.GetLevelId()) + block_data = block_data_comp.GetBlockEntityData(dimension_id, (x, y, z)) + if block_data: + block_data['rotation'] = self.get_block_facing(player_rot) + block_data['states'] = 0 + block_data['invert'] = 0 + block_info_comp = serverApi.GetEngineCompFactory().CreateBlockInfo(serverApi.GetLevelId()) + post_data = {} + if block_data['rotation'] % 2 == 0.0: + for i in range(-1, 2, 2): + block_info_data = block_info_comp.GetBlockNew( + (x + i, y, z), + dimension_id + ) + if block_info_data['name'] == 'tutorial_demo:custom_chest': + connect_block_data = block_data_comp.GetBlockEntityData(dimension_id, (x + i, y, z)) + if connect_block_data['invert'] == 0 and connect_block_data['rotation'] == block_data['rotation']: + block_data['invert'] = i * int(block_data['rotation'] - 1) + connect_block_data['invert'] = -i * int(block_data['rotation'] - 1) + post_data['{0},{1},{2}'.format(x + i, y, z)] = {'rotation': connect_block_data['rotation'], 'invert': connect_block_data['invert']} + break + if block_data['rotation'] % 2 == 1.0: + for i in range(-1, 2, 2): + block_info_data = block_info_comp.GetBlockNew( + (x, y, z + i), + dimension_id + ) + if block_info_data['name'] == 'tutorial_demo:custom_chest': + connect_block_data = block_data_comp.GetBlockEntityData(dimension_id, (x, y, z + i)) + if connect_block_data['invert'] == 0 and connect_block_data['rotation'] == block_data['rotation']: + block_data['invert'] = i * int(block_data['rotation'] - 2) + connect_block_data['invert'] = -i * int(block_data['rotation'] - 2) + post_data['{0},{1},{2}'.format(x, y, z + i)] = {'rotation': connect_block_data['rotation'], 'invert': connect_block_data['invert']} + break + level_data_comp = serverApi.GetEngineCompFactory().CreateExtraData(serverApi.GetLevelId()) + data = level_data_comp.GetExtraData(block_name) + if not data: + data = {} + post_data['{0},{1},{2}'.format(x, y, z)] = {'rotation': block_data['rotation'], 'invert': block_data['invert']} + data['{0},{1},{2}'.format(x, y, z)] = {'rotation': block_data['rotation'], 'invert': block_data['invert']} + data.update(post_data) + level_data_comp.SetExtraData(block_name, data) + self.BroadcastToAllClient('InitChestRotation', post_data) + + def block_removed(self, event): + block_name = event['fullName'] + x = event['x'] + y = event['y'] + z = event['z'] + dimension_id = event['dimension'] + if block_name == 'tutorial_demo:custom_chest': + block_data_comp = serverApi.GetEngineCompFactory().CreateBlockEntityData(serverApi.GetLevelId()) + block_entity_data = block_data_comp.GetBlockEntityData(0, (x, y, z)) + level_data_comp = serverApi.GetEngineCompFactory().CreateExtraData(serverApi.GetLevelId()) + data = level_data_comp.GetExtraData(block_name) + post_data = {} + if block_entity_data['invert'] != 0: + block_info_comp = serverApi.GetEngineCompFactory().CreateBlockInfo(serverApi.GetLevelId()) + if block_entity_data['rotation'] % 2 == 0.0: + for i in range(-1, 2, 2): + block_info_data = block_info_comp.GetBlockNew( + (x + i, y, z), + dimension_id + ) + if block_info_data['name'] == 'tutorial_demo:custom_chest': + connect_block_data = block_data_comp.GetBlockEntityData(dimension_id, (x + i, y, z)) + if connect_block_data['invert'] != 0 and connect_block_data['rotation'] == block_entity_data['rotation']: + connect_block_data['invert'] = 0 + data['{0},{1},{2}'.format(x + i, y, z)] = {'rotation': connect_block_data['rotation'], 'invert': connect_block_data['invert']} + post_data['{0},{1},{2}'.format(x + i, y, z)] = {'rotation': connect_block_data['rotation'], 'invert': connect_block_data['invert']} + if block_entity_data['rotation'] % 2 == 1.0: + for i in range(-1, 2, 2): + block_info_data = block_info_comp.GetBlockNew( + (x, y, z + i), + dimension_id + ) + if block_info_data['name'] == 'tutorial_demo:custom_chest': + connect_block_data = block_data_comp.GetBlockEntityData(dimension_id, (x, y, z + i)) + if connect_block_data['invert'] != 0 and connect_block_data['rotation'] == block_entity_data['rotation']: + connect_block_data['invert'] = 0 + data['{0},{1},{2}'.format(x, y, z + i)] = {'rotation': connect_block_data['rotation'], 'invert': connect_block_data['invert']} + post_data['{0},{1},{2}'.format(x, y, z + i)] = {'rotation': connect_block_data['rotation'], 'invert': connect_block_data['invert']} + data = data.pop('{0},{1},{2}'.format(x, y, z), data) + level_data_comp.SetExtraData(block_name, data) + self.BroadcastToAllClient('InitChestRotation', post_data) + + def try_open_chest(self, event): + # 获取数据传来的方块坐标为一个元组 + pos = tuple(event['pos']) + # 获取数据传来的维度ID + dimension_id = event['dimensionId'] + # 准备blockEntityData引擎组件 + block_data_comp = serverApi.GetEngineCompFactory().CreateBlockEntityData(serverApi.GetLevelId()) + # 准备blockInfo引擎组件 + block_info_comp = serverApi.GetEngineCompFactory().CreateBlockInfo(serverApi.GetLevelId()) + # 通过blockInfo引擎组件获取该方块上方方块的数据 + up_pos = (pos[0], pos[1] + 1, pos[2]) + up_block_data = block_info_comp.GetBlockNew(up_pos, dimension_id) + # 如果为空气 + if up_block_data['name'] != 'minecraft:air': + # 就不打开了 + return + # 通过blockEntityData引擎组件获取本位置方块的方块实体数据 + block_data = block_data_comp.GetBlockEntityData(dimension_id, pos) + # 创建一个需要张贴的数据 + post_data = [] + # 如果是关着的更新为开着的,反之亦反。事实上,我们应该设定逻辑为只进行打开操作,关闭操作由关闭UI时触发,但此时我们还没有设计UI,为了其保持完整我们补充关闭的逻辑 + if not block_data['states']: + block_data['states'] = 1 + else: + block_data['states'] = 0 + # 向张贴的数据中加入变更的数据信息 + post_data.append({'pos': list(pos), 'dimensionId': dimension_id, 'states': block_data['states']}) + # 如果是带箱子,照顾一下与他连着的方块,并一并加入张贴的数据中 + if block_data['invert'] != 0: + connect_pos = list(pos) + if block_data['rotation'] % 2 == 0.0: + connect_pos[0] += block_data['invert'] * int(block_data['rotation'] - 1) + if block_data['rotation'] % 2 == 1.0: + connect_pos[2] += block_data['invert'] * int(block_data['rotation'] - 2) + block_data_comp.GetBlockEntityData(dimension_id, tuple(connect_pos))['states'] = block_data['states'] + post_data.append({'pos': connect_pos, 'dimensionId': dimension_id, 'states': block_data['states']}) + # 向所有客户端广播箱子打开完成的事件,告知它们更新Molang变量 + self.BroadcastToAllClient('OpenChestFinished', {'data': post_data}) + + def get_block_facing(self, rot): + if 135.0 < rot[1] <= 180.0: + return 2.0 + elif 45.0 < rot[1] <= 135.0: + return 1.0 + elif -45.0 < rot[1] <= 45.0: + return 0.0 + elif -135.0 < rot[1] <= -45.0: + return 3.0 + elif -180.0 < rot[1] <= -135.0: + return 2.0 + else: + return 0.0 + +``` + +这样,我们就完成了箱子开闭功能的制作。 + +![](./images/13.4_open.gif) + +最后,我们还需要考虑到存在自定义箱子的世界加载时箱子的数据更新和动画显示问题。我们这里放出加入了这一功能后的完整代码,有兴趣的开发者可以模仿学习。 + +服务端脚本`ServerSystem.py`: + +```python +# -*- coding: UTF-8 -*- +from mod.server.system.serverSystem import ServerSystem +from mod.common.minecraftEnum import Facing +import mod.server.extraServerApi as serverApi + + +class Main(ServerSystem): + + def __init__(self, namespace, system_name): + ServerSystem.__init__(self, namespace, system_name) + namespace = serverApi.GetEngineNamespace() + system_name = serverApi.GetEngineSystemName() + self.ListenForEvent(namespace, system_name, 'ServerEntityTryPlaceBlockEvent', self, self.on_try_placed) + self.ListenForEvent(namespace, system_name, 'EntityPlaceBlockAfterServerEvent', self, self.on_placed) + self.ListenForEvent(namespace, system_name, 'BlockRemoveServerEvent', self, self.block_removed) + self.ListenForEvent('tutorial_demo', 'BlockEntityClient', 'TryOpenChest', self, self.try_open_chest) + self.ListenForEvent('tutorial_demo', 'BlockEntityClient', 'GetChestInit', self, self.init_chest_rotation) + + def on_try_placed(self, event): + x = event['x'] + y = event['y'] + z = event['z'] + block_name = event['fullName'] + dimension_id = event['dimensionId'] + face = event['face'] + if face == Facing.Up and block_name == 'tutorial_demo:custom_chest': + block_data = serverApi.GetEngineCompFactory().CreateBlockInfo(serverApi.GetLevelId()).GetBlockNew((x, y - 1, z), dimension_id) + if block_data['name'] == block_name: + event['cancel'] = True + + def on_placed(self, event): + dimension_id = event['dimensionId'] + x = event['x'] + y = event['y'] + z = event['z'] + block_name = event['fullName'] + player_id = event['entityId'] + if block_name == 'tutorial_demo:custom_chest': + player_rot = serverApi.GetEngineCompFactory().CreateRot(player_id).GetRot() + block_data_comp = serverApi.GetEngineCompFactory().CreateBlockEntityData(serverApi.GetLevelId()) + block_data = block_data_comp.GetBlockEntityData(dimension_id, (x, y, z)) + if block_data: + block_data['rotation'] = self.get_block_facing(player_rot) + block_data['states'] = 0 + block_data['invert'] = 0 + block_info_comp = serverApi.GetEngineCompFactory().CreateBlockInfo(serverApi.GetLevelId()) + post_data = {} + if block_data['rotation'] % 2 == 0.0: + for i in range(-1, 2, 2): + block_info_data = block_info_comp.GetBlockNew( + (x + i, y, z), + dimension_id + ) + if block_info_data['name'] == 'tutorial_demo:custom_chest': + connect_block_data = block_data_comp.GetBlockEntityData(dimension_id, (x + i, y, z)) + if connect_block_data['invert'] == 0 and connect_block_data['rotation'] == block_data['rotation']: + block_data['invert'] = i * int(block_data['rotation'] - 1) + connect_block_data['invert'] = -i * int(block_data['rotation'] - 1) + post_data['{0},{1},{2}'.format(x + i, y, z)] = {'rotation': connect_block_data['rotation'], 'invert': connect_block_data['invert']} + break + if block_data['rotation'] % 2 == 1.0: + for i in range(-1, 2, 2): + block_info_data = block_info_comp.GetBlockNew( + (x, y, z + i), + dimension_id + ) + if block_info_data['name'] == 'tutorial_demo:custom_chest': + connect_block_data = block_data_comp.GetBlockEntityData(dimension_id, (x, y, z + i)) + if connect_block_data['invert'] == 0 and connect_block_data['rotation'] == block_data['rotation']: + block_data['invert'] = i * int(block_data['rotation'] - 2) + connect_block_data['invert'] = -i * int(block_data['rotation'] - 2) + post_data['{0},{1},{2}'.format(x, y, z + i)] = {'rotation': connect_block_data['rotation'], 'invert': connect_block_data['invert']} + break + level_data_comp = serverApi.GetEngineCompFactory().CreateExtraData(serverApi.GetLevelId()) + data = level_data_comp.GetExtraData(block_name) + if not data: + data = {} + post_data['{0},{1},{2}'.format(x, y, z)] = {'rotation': block_data['rotation'], 'invert': block_data['invert']} + data['{0},{1},{2}'.format(x, y, z)] = {'rotation': block_data['rotation'], 'invert': block_data['invert']} + data.update(post_data) + level_data_comp.SetExtraData(block_name, data) + self.BroadcastToAllClient('InitChestRotation', post_data) + + def block_removed(self, event): + block_name = event['fullName'] + x = event['x'] + y = event['y'] + z = event['z'] + dimension_id = event['dimension'] + if block_name == 'tutorial_demo:custom_chest': + block_data_comp = serverApi.GetEngineCompFactory().CreateBlockEntityData(serverApi.GetLevelId()) + block_entity_data = block_data_comp.GetBlockEntityData(0, (x, y, z)) + level_data_comp = serverApi.GetEngineCompFactory().CreateExtraData(serverApi.GetLevelId()) + data = level_data_comp.GetExtraData(block_name) + post_data = {} + if block_entity_data['invert'] != 0: + block_info_comp = serverApi.GetEngineCompFactory().CreateBlockInfo(serverApi.GetLevelId()) + if block_entity_data['rotation'] % 2 == 0.0: + for i in range(-1, 2, 2): + block_info_data = block_info_comp.GetBlockNew( + (x + i, y, z), + dimension_id + ) + if block_info_data['name'] == 'tutorial_demo:custom_chest': + connect_block_data = block_data_comp.GetBlockEntityData(dimension_id, (x + i, y, z)) + if connect_block_data['invert'] != 0 and connect_block_data['rotation'] == block_entity_data['rotation']: + connect_block_data['invert'] = 0 + data['{0},{1},{2}'.format(x + i, y, z)] = {'rotation': connect_block_data['rotation'], 'invert': connect_block_data['invert']} + post_data['{0},{1},{2}'.format(x + i, y, z)] = {'rotation': connect_block_data['rotation'], 'invert': connect_block_data['invert']} + if block_entity_data['rotation'] % 2 == 1.0: + for i in range(-1, 2, 2): + block_info_data = block_info_comp.GetBlockNew( + (x, y, z + i), + dimension_id + ) + if block_info_data['name'] == 'tutorial_demo:custom_chest': + connect_block_data = block_data_comp.GetBlockEntityData(dimension_id, (x, y, z + i)) + if connect_block_data['invert'] != 0 and connect_block_data['rotation'] == block_entity_data['rotation']: + connect_block_data['invert'] = 0 + data['{0},{1},{2}'.format(x, y, z + i)] = {'rotation': connect_block_data['rotation'], 'invert': connect_block_data['invert']} + post_data['{0},{1},{2}'.format(x, y, z + i)] = {'rotation': connect_block_data['rotation'], 'invert': connect_block_data['invert']} + data = data.pop('{0},{1},{2}'.format(x, y, z), data) + level_data_comp.SetExtraData(block_name, data) + self.BroadcastToAllClient('InitChestRotation', post_data) + + def init_chest_rotation(self, event): + player_id = event['playerId'] + level_data_comp = serverApi.GetEngineCompFactory().CreateExtraData(serverApi.GetLevelId()) + data = level_data_comp.GetExtraData('tutorial_demo:custom_chest') + if data: + self.NotifyToClient(player_id, 'InitChestRotation', data) + + def try_open_chest(self, event): + pos = tuple(event['pos']) + dimension_id = event['dimensionId'] + block_data_comp = serverApi.GetEngineCompFactory().CreateBlockEntityData(serverApi.GetLevelId()) + block_info_comp = serverApi.GetEngineCompFactory().CreateBlockInfo(serverApi.GetLevelId()) + up_pos = (pos[0], pos[1] + 1, pos[2]) + up_block_data = block_info_comp.GetBlockNew(up_pos, dimension_id) + if up_block_data['name'] != 'minecraft:air': + return + block_data = block_data_comp.GetBlockEntityData(dimension_id, pos) + post_data = [] + if not block_data['states']: + block_data['states'] = 1 + else: + block_data['states'] = 0 + post_data.append({'pos': list(pos), 'dimensionId': dimension_id, 'states': block_data['states']}) + if block_data['invert'] != 0: + connect_pos = list(pos) + if block_data['rotation'] % 2 == 0.0: + connect_pos[0] += block_data['invert'] * int(block_data['rotation'] - 1) + if block_data['rotation'] % 2 == 1.0: + connect_pos[2] += block_data['invert'] * int(block_data['rotation'] - 2) + block_data_comp.GetBlockEntityData(dimension_id, tuple(connect_pos))['states'] = block_data['states'] + post_data.append({'pos': connect_pos, 'dimensionId': dimension_id, 'states': block_data['states']}) + self.BroadcastToAllClient('OpenChestFinished', {'data': post_data}) + + def get_block_facing(self, rot): + if 135.0 < rot[1] <= 180.0: + return 2.0 + elif 45.0 < rot[1] <= 135.0: + return 1.0 + elif -45.0 < rot[1] <= 45.0: + return 0.0 + elif -135.0 < rot[1] <= -45.0: + return 3.0 + elif -180.0 < rot[1] <= -135.0: + return 2.0 + else: + return 0.0 + +``` + +客户端脚本`ClientSystem.py`: + +```python +# -*- coding: UTF-8 -*- +from mod.client.system.clientSystem import ClientSystem +import mod.client.extraClientApi as clientApi +import time + + +class Main(ClientSystem): + + def __init__(self, namespace, system_name): + ClientSystem.__init__(self, namespace, system_name) + namespace = clientApi.GetEngineNamespace() + system_name = clientApi.GetEngineSystemName() + self.ListenForEvent(namespace, system_name, 'ClientBlockUseEvent', self, self.block_used) + self.ListenForEvent(namespace, system_name, 'ChunkLoadedClientEvent', self, self.chunk_first_loaded) + self.ListenForEvent(namespace, system_name, 'UiInitFinished', self, self.chunk_first_loaded) + self.ListenForEvent('tutorial_demo', 'BlockEntityServer', 'OpenChestFinished', self, self.chest_opened) + self.ListenForEvent('tutorial_demo', 'BlockEntityServer', 'InitChestRotation', self, self.chest_rotation) + self.block_interact_cooldown = {} + self.rotation_queue = [] + + def block_used(self, event): + player_id = event['playerId'] + block_name = event['blockName'] + x = event['x'] + y = event['y'] + z = event['z'] + if block_name == 'tutorial_demo:custom_chest': + if player_id not in self.block_interact_cooldown: + self.block_interact_cooldown[player_id] = time.time() + elif time.time() - self.block_interact_cooldown[player_id] < 0.15: + return + else: + self.block_interact_cooldown[player_id] = time.time() + game_comp = clientApi.GetEngineCompFactory().CreateGame(clientApi.GetLevelId()) + dimension_id = game_comp.GetCurrentDimension() + self.NotifyToServer('TryOpenChest', {'dimensionId': dimension_id, 'pos': [x, y, z]}) + + def chest_opened(self, event): + data = event['data'] + block_comp = clientApi.GetEngineCompFactory().CreateBlockInfo(clientApi.GetLevelId()) + for block_data in data: + block_pos = tuple(block_data['pos']) + block_comp.SetBlockEntityMolangValue(block_pos, "variable.mod_states", float(block_data['states'])) + + def chunk_first_loaded(self, event): + self.NotifyToServer('GetChestInit', {'playerId': clientApi.GetLocalPlayerId()}) + + def chest_rotation(self, event): + print event + new_event = {tuple(map(int, k.split(','))): v for k, v in event.items()} + block_comp = clientApi.GetEngineCompFactory().CreateBlockInfo(clientApi.GetLevelId()) + + def rotate_chest(): + index = 0 + count = len(new_event.items()) + for pos, data in new_event.items(): + block_data = block_comp.GetBlock(pos) + if block_data[0] == 'tutorial_demo:custom_chest': + block_comp.SetBlockEntityMolangValue(pos, "variable.mod_rotation", data['rotation']) + block_comp.SetBlockEntityMolangValue(pos, "variable.mod_invert", float(data['invert']) if data['invert'] != 0 else 0.0) + index += 1 + if index == count: + return True + else: + return False + if new_event: + self.rotation_queue.append([rotate_chest, 0]) + + def Update(self): + _die = [] + for index, value in enumerate(self.rotation_queue): + if value[0](): + value[1] += 1 + if value[1] == 2: + _die.append(index) + for i in _die: + self.rotation_queue[i] = None + if self.rotation_queue: + self.rotation_queue = filter(None, self.rotation_queue) +``` diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/README.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.1_block_entity_component.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.1_block_entity_component.png new file mode 100644 index 0000000..44afd6f Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.1_block_entity_component.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.3_in-game.gif b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.3_in-game.gif new file mode 100644 index 0000000..83e1461 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.3_in-game.gif differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.3_torch.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.3_torch.png new file mode 100644 index 0000000..440c4ea Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.3_torch.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.3_torch_component_set.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.3_torch_component_set.png new file mode 100644 index 0000000..f8a528d Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.3_torch_component_set.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.3_torch_import.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.3_torch_import.png new file mode 100644 index 0000000..bec3fab Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.3_torch_import.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.3_torch_model_set.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.3_torch_model_set.png new file mode 100644 index 0000000..63822dd Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.3_torch_model_set.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.4_chest_close.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.4_chest_close.png new file mode 100644 index 0000000..52ad125 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.4_chest_close.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.4_chest_model.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.4_chest_model.png new file mode 100644 index 0000000..19e9b07 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.4_chest_model.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.4_chest_offset.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.4_chest_offset.png new file mode 100644 index 0000000..007790c Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.4_chest_offset.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.4_chest_open.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.4_chest_open.png new file mode 100644 index 0000000..b1baa71 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.4_chest_open.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.4_chest_rotate.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.4_chest_rotate.png new file mode 100644 index 0000000..e61e440 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.4_chest_rotate.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.4_large_chest_invert_-1.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.4_large_chest_invert_-1.png new file mode 100644 index 0000000..7323964 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.4_large_chest_invert_-1.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.4_large_chest_invert_1.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.4_large_chest_invert_1.png new file mode 100644 index 0000000..8d37439 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.4_large_chest_invert_1.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.4_large_chest_model.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.4_large_chest_model.png new file mode 100644 index 0000000..8cc3274 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.4_large_chest_model.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.4_merge.gif b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.4_merge.gif new file mode 100644 index 0000000..4046970 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.4_merge.gif differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.4_open.gif b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.4_open.gif new file mode 100644 index 0000000..03e2bce Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.4_open.gif differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.4_rotate.gif b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.4_rotate.gif new file mode 100644 index 0000000..6774fa2 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/12-开始自定义方块实体/images/13.4_rotate.gif differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/0-摘要.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/0-摘要.md new file mode 100644 index 0000000..17951be --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/0-摘要.md @@ -0,0 +1,15 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 5分钟 +--- + +# 摘要 + +在本章中,我们将一起来学习**JSON UI**的制作,以及JSON UI和模组API的配合方法。 + +- 在第一节(*了解和创建JSON UI*)中,我们将一起学习什么是JSON UI,以及如何创建一个JSON UI的**屏幕**(**Screen**)。 +- 在第二节(*为自定义箱子绘制界面*)中,我们将一起使用编辑器的界面编辑器为我们之前的无法储藏版自定义箱子绘制一个界面,并使用模组SDK实现其在游戏内的显示。 +- 在第三节(*挑战:设计箱子锁*)中,我们将一起实现一个挑战,为箱子制作一个功能完备的箱子锁。 + +关键词:JOSN UI 界面 屏幕 场景栈 控件 继承 路由 路径 变量 全局变量 绑定 绑定名 绑定器 属性袋 按钮映射 动画 箱子锁 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/1-了解和创建JSON UI.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/1-了解和创建JSON UI.md new file mode 100644 index 0000000..2450fd7 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/1-了解和创建JSON UI.md @@ -0,0 +1,1549 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 进阶 +time: 40分钟 +--- + +# 了解和创建JSON UI + +在模组制作过程中,我们经常遇到需要为某一个功能绘制UI的需求。了解如何创建和修改UI能够使我们创作出更加精彩的模组。在当前阶段的我的世界中,大部分的UI依旧使用了一种通过JSON文件数据驱动并在源代码中给予相应的绑定和逻辑的UI形式,我们常称之为**JSON UI**。在本节中,我们将一起了解和学习如何创新和编辑JSON UI。 + +## 界面和屏幕 + +在了解JSON UI的创建之前,我们需要先了解什么是**界面**(**Interface**)和**屏幕**(**Screen**)。界面又译**接口**,一般指代两个个体之间交互的“临界”或“通道”。比如,我现在有一本书,那么文字便是这本书和我的界面;我在吃一个苹果,那么味蕾便是苹果和我感受甜味的神经的界面;我有一套模组SDK,那么模组API便是游戏引擎和我的模组的代码的接口(界面)。同样的,在电子设备上,设备的实体屏幕往往充当了电子设备与我们的界面,这种界面一般称为**图形用户界面**(**Graphical User Interface**),常简称为**用户界面**(**User Interface**),进而简称为**UI**。 + +然而,除了设备的实体屏幕,在我的世界中,我们还有一个虚拟的**屏幕**(**Screen**,***画布***)概念。在同一个设备上,界面只有一个,那就是实体屏,而屏幕则有多个。每个屏幕都是一个充斥了整个界面的图像,各个屏幕依次叠加在界面上,从而形成了我们看到的画面。这一点我们可以通过电脑开发版的调试屏幕来辅助理解。 + +![](./images/14.1_title_screen.png) + +按`F3`打开调试屏幕,我们可以在界面的右侧看到我们当前UI上的屏幕信息。调试屏幕上分别有四种代表UI的信息,分别是**主场景栈**(**Main Scene Stack**)、**主路由器历史**(**Main Router History**)、**客户端场景栈**(**Client Scene Stack**)、**客户端路由器历史**(**Client Router History**)。通过刚才的说明,细心的开发者或许已经发现了,我的世界UI中的屏幕是通过一种**栈**(**Stack**)的模式来控制的。也就是说,各个屏幕垂直叠加在界面上,最上面的屏幕是最后叠上去的,也是当前显示在界面上最上方的。就比如上图中的开始屏幕(`start_screen`)。我们将注意力放在主场景栈上,可以看到,开始屏幕叠在栈的最上方,再往下是`toast_screen`,再向下是我们的调试屏幕(`debug_screen`),最下方是负责充当背景的立方体贴图背景屏幕(`cubemap_backgroud_screen`)。 + +![](./images/14.1_hud_screen.png) + +我们再进入一个世界,开始进行游戏,我们再来看右侧的信息。此时,“**(使用中)**”(“**(in use)**”)字样从主场景栈转移到了客户端场景栈。这是因为客户端场景栈主要显示世界中游玩时的屏幕状况,而主场景栈则显示以游戏标题菜单为起点的,即非世界中游玩时的屏幕状况。此时,我们可以看到我们的界面上只有两个屏幕,分别是位于上方的HUD屏幕(`hud_screen`)和位于下方的游戏内游玩屏幕(`in_game_play_screen`)。 + +![](./images/14.1_inv_screen.png) + +当我们打开物品栏后,我们可以看到物品栏屏幕(`inventory_screen`)被**压入**(**Push**)了场景栈,同时我们看到了物品栏屏幕确实叠在了界面的最上方。当我们关闭物品栏时,物品栏屏幕作为栈最上方的的屏幕被**弹出**(**Pop**)场景栈。 + +同时,与场景栈一同变化的路由器历史信息能够告诉我们当前的屏幕是否是JSON UI定义的屏幕。我的世界UI的各个屏幕的调用路径被称为**路由**(**Route**),比如上图中物品栏屏幕的路由便为`/__bedrock__/inventory_screen`。所有由基岩引擎定义和控制的屏幕,即JSON UI的屏幕的路由都位于`/__bedrock__`下。我们可以通过这一点确定我们的屏幕来自于JSON UI。之后,我们将通过JSON UI创建自己的屏幕,这些屏幕的栈信息也可以通过调试屏幕的右方信息来进行调试。 + +## 通过界面编辑器创建界面 + +![](./images/14.1_interface_editor.png) + +我的世界开发工作台的编辑器中内置了一套可以方便开发者快速可视化地创建和编辑界面的工具,这边是我们的**界面编辑器**。我们可以在屏幕顶部的切换页签中选择“界面编辑器”来切换到该编辑器。界面编辑器的结构的也非常简单,除了中央预览窗之外,仅有四个功能窗格。 + +- **界面文件列表**:默认位于左下角,是当前该附加包内所有的界面工程文件的列表。开发者通过编辑器创建界面时,编辑器会自动在行为包的`ui`文件夹内生成一个`.mcgui`格式的界面工程文件,同时生成一个最终会被应用到游戏中的`.json`格式的数据文件。这里便是显示`ui`文件夹内所有的界面工程文件。 +- **控件结构**:默认位于左上角,显示当前“界面文件列表”中选中的界面的控件的树状结构,玩家可以通过点击或拖动来调整界面的结构。 +- **资源管理**:默认位于右上角,是当前所有导入到编辑器中的UI纹理贴图资源列表。支持通过拖动的方式应用到对应UI控件上。 +- **属性**:当前选中的控件的属性。 + +要想创建一个新的界面,我们只需要在“界面文件列表”中点击“**+**”按钮。 + +![](./images/14.1_create_ui.png) + +![](./images/14.1_create_ui_input.png) + +之后我们输入我们想创建的UI的名字,比如这里我们起名为`ui_demo_screen`。注意,这里的UI名比如使用字母、数字和下划线组合的形式。其他形式的名称将可能造成读取错误,请勿使用。 + +![](./images/14.1_interface_editor_created.png) + +此时,我们便看到我们的JSON UI文件已被创建,该文件中自动为我们创建一个屏幕(*画布*)控件`main`。接下来,我们可以在这个`main`屏幕上添加其他的控件,制作我们想要的功能。当我们需要在游戏内显示该屏幕时,我们便可以通过模组SDK将`main`压入我们的屏幕场景栈。这样我们的自定义屏幕便可以出现在游戏中了。 + +值得注意的是,我们可以在调试屏幕中看到,场景栈中或者路由器中的屏幕都是直接使用了该屏幕的名称,而不会出现文件名或其他标识符(比如我们之前创建的UI名`ui_demo_screen`),所以我们此处的屏幕名`main`将可能与其他人的屏幕发生冲突,也不利于我们识别和调试。 + +![](./images/14.1_change_screen_name.png) + +我们在右下角的属性窗格中将屏幕名也改成`ui_demo_screen`。这样,我们的屏幕在压入场景栈后将以`ui_demo_screen`的名称显示。 + +现在,我们来查看具体的JSON UI文件内容,以了解我们的UI文件结构。 + +![](./images/14.1_open_folder.png) + +在“界面文件列表”窗格中右键我们的UI,点击“**打开文件目录**”。 + +![](./images/14.1_ui_folder.png) + +我们可以看到,弹出了一个打开了我们附加包中资源包的`ui`文件夹的资源管理器窗口。窗口中便可以看到我们`ui_demo_screen`UI的工程文件`ui_demo_screen.mcgui`和最终将在游戏内起作用的JSON UI数据驱动文件`ui_demo_screen.json`,以及定义了包括`ui_demo_screen.json`的所有的JSON UI的UI定义文件`_ui_defs.json`。事实上,我们在编辑器中编辑时UI,直接发生改动的便是我们的工程文件`ui_demo_screen.mcgui`,随之,`ui_demo_screen.json`将根据工程文件自动生成。因此,在我们还需要在编辑器中编辑之前,我们并不推荐直接修改`.json`文件。虽然编辑器可以在检测到`.json`文件发生变动后将变动重新应用到`.mcgui`文件中并重新生成`.json`文件,但是由于目前我们的界面编辑器尚未支持全部的JSON UI内容,所以在这一修改过程中,我们的`.json`文件最终的显示结果可能会发生我们不希望产生的变化。我们推荐开发者优先使用界面编辑器绘制全部所需的界面元素,然后再集中地二次手动修改`.json`文件,删除不必要的冗余部分,并添加我们希望额外添加的内容。在最终的手动修改UI步骤之后,将`.mcgui`文件备份后删除,以免编辑器重载该文件后造成`.json`文件紊乱。 + +现在,我们打开`ui_demo_screen.json`文件来学习JSON UI文件结构: + +```json +{ + "namespace" : "ui_demo_screen", + "ui_demo_screen" : { + "type" : "screen", + "absorbs_input" : true, + "always_accepts_input" : false, + "force_render_below" : false, + "is_showing_menu" : true, + "render_game_behind" : true, + "render_only_when_topmost" : true, + "should_steal_mouse" : false + } +} +``` + +这是目前我们的`ui_demo_screen.json`文件全部的内容,可以看到,最开头我们有一个`namespace`字段,这是我们该文件所存储的所有控件的**命名空间**(**Namespace**)。由编辑器生成的JSON UI将产生相同的文件名和命名空间,事实上,文件名和命名空间可以不同,比如,原版的`hud_screen.json`的命名空间便是`hud`。但是,我们依旧推荐使命名空间和文件名相一致,这有利于我们识别JSON UI文件和增强兼容性。 + +接下来,便是我们该文件中存储的所有的**控件**(**Control**)。每个JSON UI文件中都存储着一系列各种各样的控件,比如我们刚刚添加并改名过的屏幕控件`ui_demo_screen`。每个控件在JSON中的结构都是一个对象,对象的键名是该控件的名称,对象的值是一个对象,对象中指定了该控件的一系列**属性**(**Property**)。 + +`type`属性代表一个控件的类型。控件分别有以下类型:**按钮**(`button`)、**自定义**(`custom`)、**下拉菜单**(`dropdown`)、**编辑框**(`edit_box`,*文本输入框*)、**工厂**(`factory`)、**网格**(`grid`)、**图像**(`image`,*图片*)、**输入面板**(`input_panel`,*点击面板*)、**标签**(`label`,*文本*)、**面板**(`panel`)、**屏幕**(`screen`,*画布*)、**滚动条框**(`scrollbar_box`)、**滚动跟踪**(`scroll_track`)、**滚动视图**(`scroll_view`,*滚动列表*)、**选轮**(`selection_wheel`)、**滑块**(`slider`)、**滑块框**(`slider_box`)、**栈面板**(`stack_panel`,*布局面板*)、**开关**(`toggle`)。其中,上述斜体字是界面编辑器中显示的该控件类型的易记名称,而我们的`ui_demo_screen`屏幕便是使用了`screen`类型的控件。所有的屏幕都是`screen`类型的。事实上,一个JSON UI文件中并不一定只定义一个屏幕控件,但是我们可以希望有一个用于展示在画面上的主屏幕,比如此处`ui_demo_screen`便是我们希望展示的屏幕。 + +在中国版中,我们还可以额外定义以下这些类型的控件:**组合框**(`combox`,*下拉框*)、**布局**(`layout`)、**摇杆**(`joystick`)、**栈网格**(`stack_grid`)、**富文本**(`rich_text`)、**多行**(`mul_lines`)、**16-9布局**(`sixteen_nine_layout`)、**动画进度条**(`anim_porecess_bar`)。 + +在JSON UI文件中,每个控件的完整名称都由命名空间与控件名中间加点(`.`)组合而成的,`ui_demo_screen`的完整引用名称为`ui_demo_screen.ui_demo_screen`。如果别处想引用该控件的名称,就需要提供这种完整名称的引用。 + +## 在界面编辑器中添加更多控件 + +![](./images/14.1_add_panel.png) + +接下来,我们在编辑器中再为我们的UI添加控件,以添加一个面板为例。很多种控件下都可以挂接其他的控件作为子控件,屏幕控件便是如此。我们想要向我们的`ui_demo_screen`下添加一个面板控件,我们有两种添加控件的方式。我们可以右击该屏幕控件,然后选择“**添加对象**->**面板**”,也可以选中我们的屏幕控件,然后点击上方功能区的“**面板**”按钮。两种方式都可以成功为我们的`ui_demo_screen`控件添加一个面板子控件,我们将该面板控件命名为`ui_demo_panel`。 + +![](./images/14.1_panel_added.png) + +可以看到,当我们在左侧“控件结构”窗格中选中该面板后,图中出现了一个红色方框,同时预览窗的边缘出现了一个蓝色方框。红色框便代表当前选中的控件,即我们此处的面板控件,而蓝色框代表我们当前选中控件的父控件,即理应充斥整个界面屏幕控件。屏幕控件的尺寸是无法修改的,不过面板的尺寸可以修改。所以我们通常推荐开发者在屏幕中先添加一个面板,然后再在面板上进行编辑。这有助于我们控制屏幕上元素的位置和尺寸。 + +![](./images/14.1_property.png) + +在右侧的属性窗格中,我们可以看到显示了一个“**通用**”属性菜单。这里是所有控件都具备的通用属性。我们可以在此修改该控件的**锚点**(**Anchor**)、**偏移**(**Offset**)、**尺寸**(**Size**)、**裁剪**(**Clip**)、**透明度**(**Alpha**)等属性。这些修改也都将同步到JSON文件中。 + +![](./images/14.1_image_added.png) + +我们可以看到,面板其实就是一种玩家不可见的透明板。比着葫芦画瓢,我们为这个面板再添加一个图像子控件,命名为`bg_image`,用于充当该面板的背景。 + +![](./images/14.1_property_image.png) + +我们可以看到,图像控件相对于面板控件多出了一个“图片”属性菜单,我们可以在这里修改图像控件所显示的图像,比如,我们可以将图像修改成原生面板背景图像。 + +![](./images/14.1_native_image.png) + +我们此时再看该UI的JSON文件: + +```json +{ + "namespace" : "ui_demo_screen", + "ui_demo_screen" : { + "type" : "screen", + "absorbs_input" : true, + "always_accepts_input" : false, + "force_render_below" : false, + "is_showing_menu" : true, + "render_game_behind" : true, + "render_only_when_topmost" : true, + "should_steal_mouse" : false, + "controls" : [ + { + "ui_demo_panel@ui_demo_screen.ui_demo_panel" : {} + } + ] + }, + "ui_demo_panel" : { + "type" : "panel", + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "enabled" : true, + "layer" : 1, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ 0, 0 ], + "priority" : 0, + "propagate_alpha" : false, + "size" : [ 100, 100 ], + "visible" : true, + "controls" : [ + { + "bg_image@ui_demo_screen.bg_image" : {} + } + ] + }, + "bg_image" : { + "type" : "image", + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "clip_direction" : "left", + "clip_offset" : [ 0, 0 ], + "clip_ratio" : 0.0, + "clips_children" : false, + "enabled" : true, + "fill" : false, + "grayscale" : false, + "is_new_nine_slice" : false, + "keep_ratio" : true, + "layer" : 1, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "nine_slice_buttom" : 0, + "nine_slice_left" : 0, + "nine_slice_right" : 0, + "nine_slice_top" : 0, + "nineslice_size" : [ 0, 0, 0, 0 ], + "offset" : [ 0, 0 ], + "priority" : 0, + "propagate_alpha" : false, + "size" : [ 100, 100 ], + "texture" : "textures/ui/dialog_background_opaque", + "uv" : [ 0, 0 ], + "uv_size" : [ 0, 0 ], + "visible" : true + } +} +``` + +我们可以明显看到,我们的JSON文件中多出了两个控件的定义,这便分别是我们的`ui_demo_panel`和`bg_image`,他们带有命名空间的完整名称分别为`ui_demo_screen.ui_demo_panel`和`ui_demo_screen.bg_image`,类型也分别由各自的`type`属性决定,分别是代表面板的`panel`和图像的`image`。 + +![](./images/14.1_inherit.png) + +另一个显眼的部分便是我们的屏幕`ui_demo_screen`中多出了一个`controls`属性,而且里面好似引用了我们的`ui_demo_panel`面板。事实上,这确实是代表着在我们的屏幕的节点上挂接了一个面板,不过,需要注意的是,这里的写法被称为**继承**(**Inherit**)。继承得到的控件是一个和源控件各种属性都相同的新控件,在继承之后我们也可以重写部分属性,以覆盖原先控件的属性。这相当于在原先控件的基础上再进行修改。不过,重写继承得到的控件的属性并不会影响源控件,所以这非常有助于我们快速创建多个相似的控件。继承的常规写法是`control_name@source_control_name`。`source_control_name`是要继承自的源控件,而`control_name`是通过继承产生的新控件的名称。虽然`control_name`从语法上将可以省略不写,但是我们并不推荐这样做,因为这样会使我们之后在模组SDK中编写逻辑时无法操作。 + +这里,我们在屏幕`ui_demo_screen`下挂接一个继承自`ui_demo_screen.ui_demo_panel`的新控件,并将命名为和原先的控件名一样的`ui_demo_panel`。所以,我们在这里写作了`ui_demo_panel@ui_demo_screen.ui_demo_panel`。同时,也正因如此,我们可以注意得到这个面板并不是我们在JSON文件的根节点上定义的`ui_demo_panel`面板,而是将`ui_demo_panel`原封不动复制了一份之后再重新命名的新面板。不过,我们的新面板并没有修改源控件的什么属性,所以我们将其后面的对象留空,写作`"ui_demo_panel@ui_demo_screen.ui_demo_panel" : {}`。 + +同理,面板`ui_demo_panel`控件下也挂接了一个继承自`ui_demo_screen.bg_image`且命名为同名的`bg_image`的图像控件。 + +继承之后的得到的新控件虽然命名和源控件相同,但是其不再是直接属于`ui_demo_screen`命名空间的根节点,所以我们可以用另一种表示方法:控件的**路径**(**Path**)来表示这种挂接在一个控件下的控件。比如,`ui_demo_screen`下挂接的`ui_demo_panel`控件,我们可以使用`ui_demo_screen/ui_demo_panel`来表示,而我们知道。`ui_demo_panel`控件本身自己还会挂接一个`bg_image`控件,该控件在`ui_demo_panel`控件被继承的时候也会同样被继承。所以,`ui_demo_screen`下挂接的`ui_demo_panel`控件下挂接的`bg_image`控件的路径便是`ui_demo_screen/ui_demo_panel/bg_image`。有时,我们希望路径是相对于一个屏幕书写的,此时,我们便不再将屏幕本身写入路径,对于上述示例,我们写作`/ui_demo_panel/bg_image`。 + +重新回到继承,事实上,我们并不一定非得使用继承的方法来书写控件的挂接关系。我们可以直接将控件本身书写在一个控件的子控件的位置上,比如下方的修改示例: + +```json +{ + "namespace" : "ui_demo_screen", + "ui_demo_screen" : { + "type" : "screen", + "absorbs_input" : true, + "always_accepts_input" : false, + "force_render_below" : false, + "is_showing_menu" : true, + "render_game_behind" : true, + "render_only_when_topmost" : true, + "should_steal_mouse" : false, + "controls" : [ + { + "ui_demo_panel" : { + "type" : "panel", + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "enabled" : true, + "layer" : 1, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ 0, 0 ], + "priority" : 0, + "propagate_alpha" : false, + "size" : [ 100, 100 ], + "visible" : true, + "controls" : [ + { + "bg_image" : { + "type" : "image", + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "clip_direction" : "left", + "clip_offset" : [ 0, 0 ], + "clip_ratio" : 0.0, + "clips_children" : false, + "enabled" : true, + "fill" : false, + "grayscale" : false, + "is_new_nine_slice" : false, + "keep_ratio" : true, + "layer" : 1, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "nine_slice_buttom" : 0, + "nine_slice_left" : 0, + "nine_slice_right" : 0, + "nine_slice_top" : 0, + "nineslice_size" : [ 0, 0, 0, 0 ], + "offset" : [ 0, 0 ], + "priority" : 0, + "propagate_alpha" : false, + "size" : [ 100, 100 ], + "texture" : "textures/ui/dialog_background_opaque", + "uv" : [ 0, 0 ], + "uv_size" : [ 0, 0 ], + "visible" : true + } + } + ] + } + } + ] + } +} +``` + +此时,我们便不再需要继承。不过,这也有两个缺点,其一便是当一个控件被多次使用时,我们无法做到通过简便的修改来实现多处更改, 更没有办法使用我们后续会讲到的变量功能控制不同位置的控件的不同表现。其二是当控件数目非常多时,整个JSON UI文件将变得冗长与复杂,极大降低了可读性。因此,我们的编辑器也默认使用了继承的方式来制作UI文件。 + +## 变量、绑定与按钮映射 + +![](./images/14.1_button_added.png) + +在编辑器中,我们可以非常简单地通过“**按钮**”按钮在我们的面板上再添加一个按钮,并重命名为`demo_button`。我们此时来查看添加了该按钮的JSON代码: + +```json +{ + "namespace" : "ui_demo_screen", + "ui_demo_screen" : { + "type" : "screen", + "absorbs_input" : true, + "always_accepts_input" : false, + "force_render_below" : false, + "is_showing_menu" : true, + "render_game_behind" : true, + "render_only_when_topmost" : true, + "should_steal_mouse" : false, + "controls" : [ + { + "ui_demo_panel@ui_demo_screen.ui_demo_panel" : {} + } + ] + }, + "ui_demo_panel" : { + "type" : "panel", + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "enabled" : true, + "layer" : 1, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ 0, 0 ], + "priority" : 0, + "propagate_alpha" : false, + "size" : [ 100, 100 ], + "visible" : true, + "controls" : [ + { + "bg_image@ui_demo_screen.bg_image" : {} + }, + { + "demo_button@ui_demo_screen.demo_button" : {} + } + ] + }, + "bg_image" : { + "type" : "image", + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "clip_direction" : "left", + "clip_offset" : [ 0, 0 ], + "clip_ratio" : 0.0, + "clips_children" : false, + "enabled" : true, + "fill" : false, + "grayscale" : false, + "is_new_nine_slice" : false, + "keep_ratio" : true, + "layer" : 1, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "nine_slice_buttom" : 0, + "nine_slice_left" : 0, + "nine_slice_right" : 0, + "nine_slice_top" : 0, + "nineslice_size" : [ 0, 0, 0, 0 ], + "offset" : [ 0, 0 ], + "priority" : 0, + "propagate_alpha" : false, + "size" : [ 100, 100 ], + "texture" : "textures/ui/dialog_background_opaque", + "uv" : [ 0, 0 ], + "uv_size" : [ 0, 0 ], + "visible" : true + }, + "default" : { + "type" : "image", + "alpha" : "$control_alpha", + "is_new_nine_slice" : "$is_new_nine_slice", + "layer" : "$texture_layer", + "nine_slice_buttom" : "$nine_slice_buttom", + "nine_slice_left" : "$nine_slice_left", + "nine_slice_right" : "$nine_slice_right", + "nine_slice_top" : "$nine_slice_top", + "nineslice_size" : "$nineslice_size", + "texture" : "$default_texture" + }, + "hover" : { + "type" : "image", + "alpha" : "$control_alpha", + "is_new_nine_slice" : "$is_new_nine_slice", + "layer" : "$texture_layer", + "nine_slice_buttom" : "$nine_slice_buttom", + "nine_slice_left" : "$nine_slice_left", + "nine_slice_right" : "$nine_slice_right", + "nine_slice_top" : "$nine_slice_top", + "nineslice_size" : "$nineslice_size", + "texture" : "$hover_texture" + }, + "pressed" : { + "type" : "image", + "alpha" : "$control_alpha", + "is_new_nine_slice" : "$is_new_nine_slice", + "layer" : "$texture_layer", + "nine_slice_buttom" : "$nine_slice_buttom", + "nine_slice_left" : "$nine_slice_left", + "nine_slice_right" : "$nine_slice_right", + "nine_slice_top" : "$nine_slice_top", + "nineslice_size" : "$nineslice_size", + "texture" : "$pressed_texture" + }, + "button_label" : { + "type" : "label", + "alpha" : "$control_alpha", + "color" : "$label_color", + "font_scale_factor" : "$label_font_scale_factor", + "font_size" : "$label_font_size", + "font_type" : "smooth", + "layer" : "$label_layer", + "max_size" : [ "100%", "100%" ], + "offset" : "$label_offset", + "shadow" : false, + "text" : "$label_text", + "text_alignment" : "center" + }, + "demo_button@common.button" : { + "$control_alpha" : 1.0, + "$default_texture" : "textures/netease/common/button/default", + "$hover_texture" : "textures/netease/common/button/hover", + "$is_new_nine_slice" : false, + "$label_color" : [ 1, 1, 1 ], + "$label_font_scale_factor" : 1.0, + "$label_font_size" : "large", + "$label_layer" : 3, + "$label_offset" : [ 0, 0 ], + "$label_text" : "Button", + "$nine_slice_buttom" : 0, + "$nine_slice_left" : 0, + "$nine_slice_right" : 0, + "$nine_slice_top" : 0, + "$nineslice_size" : [ 0, 0, 0, 0 ], + "$pressed_button_name" : "%ui_demo_screen.click", + "$pressed_texture" : "textures/netease/common/button/pressed", + "$texture_layer" : 2, + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "button_mappings" : [], + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "draggable" : "not_draggable", + "enabled" : true, + "is_handle_button_move_event" : true, + "layer" : 2, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ 0, 0 ], + "priority" : 0, + "propagate_alpha" : false, + "size" : [ 100, 50 ], + "visible" : true, + "bindings" : [ + { + "binding_collection_name" : "", + "binding_condition" : "always_when_visible", + "binding_type" : "collection_details" + } + ], + "controls" : [ + { + "default@ui_demo_screen.default" : {} + }, + { + "hover@ui_demo_screen.hover" : {} + }, + { + "pressed@ui_demo_screen.pressed" : {} + }, + { + "button_label@ui_demo_screen.button_label" : {} + } + ] + } +} +``` + +我们可以看到,在JSON UI文件的根节点上出现了五个新控件,分别是`default`、`hover`、`pressed`、`button_label`和`demo_button`,如果带着命名空间来称呼,就是`ui_demo_screen.default`、`ui_demo_screen.hover`、`ui_demo_screen.pressed`、`ui_demo_screen.button_label`和`ui_demo_screen.demo_button`。其中,这里列出的前四个控件又分别在最后一个`demo_button`按钮下被该按钮的四个子控件继承。 + +前四个控件地类型我们可以看得非常明晰,分别是三个`image`类型和一个`label`类型,但是最后一个`demo_button`控件虽然从名称来看是按钮,但是我们却找不到他的`type`属性,这是为什么呢?其实,我们可以发现,`demo_button`控件继承了名为`common.button`的控件。`common`是原版JSON UI文件`ui_common.json`所使用的命名空间,我们可以打开`ui_common.json`文件来查看原版的按钮控件的定义: + +```json +{ + "namespace": "common", + + //--------------------------------------------------------------------------- + // Common controls + //--------------------------------------------------------------------------- + + "button": { + "type": "button", + + "$focus_id|default": "", + "$focus_override_down|default": "", + "$focus_override_up|default": "", + "$focus_override_left|default": "", + "$focus_override_right|default": "", + "focus_identifier": "$focus_id", + "focus_change_down": "$focus_override_down", + "focus_change_up": "$focus_override_up", + "focus_change_left": "$focus_override_left", + "focus_change_right": "$focus_override_right", + "focus_enabled": true, + "focus_magnet_enabled": true, + + "$button_focus_precedence|default": 0, + "default_focus_precedence": "$button_focus_precedence", + + "$button_tts_name|default": "accessibility.button.tts.title", + "$button_tts_header|default": "", + "$tts_section_header|default": "", + "$button_tts_control_type_order_priority|default": 100, + "$button_tts_index_priority|default": 150, + + "tts_name": "$button_tts_name", + "tts_control_header": "$button_tts_header", + "tts_section_header": "$tts_section_header", + "tts_control_type_order_priority": "$button_tts_control_type_order_priority", + "tts_index_priority": "$button_tts_index_priority", + + "layer": 1, + "sound_name": "random.click", + "sound_volume": 1.0, + "sound_pitch": 1.0, + "locked_control": "", + "default_control": "default", + "hover_control": "hover", + "pressed_control": "pressed", + "button_mappings": [ + { + "from_button_id": "button.menu_select", + "to_button_id": "$pressed_button_name", + "mapping_type": "pressed" + }, + { + "from_button_id": "button.menu_ok", + "to_button_id": "$pressed_button_name", + "mapping_type": "focused" + } + ], + + "$button_bindings|default": [], + "bindings": "$button_bindings" + }, + + // ... +} +``` + +可以看到,`common.button`控件里其实已经定义了`"type": "button"`属性。我们之前说过,继承得到的新控件会默认拥有源控件的所有属性,虽然也可以覆写一些属性为新的值(比如我们可以看到`ui_demo_screen.demo_button`和`common.button`中都有`layer`属性,这边是`ui_demo_screen.demo_button`的`"layer" : 2`属性覆写了之前`common.button`中定义的`"layer": 1`属性),但是没有覆写的值自然是和源控件相同。所以说,我们的`ui_demo_screen.demo_button`通过继承得到了`common.button`中定义的`"type": "button"`属性,自然成为了一个按钮控件。 + +### 变量 + +![](./images/14.1_var.png) + +现在,我们将目光转向所有**以`$`开头**的字符串上,这种字符串被称为**变量**(**Variable**)。比如`ui_demo_screen.demo_button`下的`$control_alpha`、`$default_texture`、`$hover_texture`等等,便都是变量。变量的用途十分广泛,比如,开发者可以在继承的得到的控件中覆写继承的源控件中的变量,实现对源控件功能的自定义改变。在`ui_demo_screen.demo_button`控件中,我们为`$pressed_button_name`变量赋了值`%ui_demo_screen.click`,而该控件从源控件`common.button`处继承到了一个`button_mappings`属性,`button_mappings`属性内便会读取`$pressed_button_name`的值,此时它便读取到了`%ui_demo_screen.click`。而如果有另一个控件也继承了`common.button`,那么它也可以为`$pressed_button_name`赋上不同的值,那么它的`button_mappings`也会读取到不同于`ui_demo_screen.demo_button`的值,以此实现和`ui_demo_screen.demo_button`不同的效果。 + +变量还可以用于在父控件中定义并在子控件中使用,这多用于子控件中要多次使用同一个值的情况,这样可以做到在父控件中一键修改所有子控件中的某些同种属性。比如上述示例中,`ui_demo_screen.demo_button`的`$control_alpha`变量便指定了一个透明度的值,而它的子控件`default`、`hover`、`pressed`和`button_label`中便都通过`"alpha" : "$control_alpha"`读取了`$control_alpha`的值作为自己透明度`alpha`属性的值。 + +值得注意的是,这些变量的生存周期是在整个节点链上而非继承链上的。换言之,`ui_demo_screen.demo_button`虽然是继承自`common.button`,但是`ui_demo_screen.demo_button`中的`$pressed_button_name`和`common.button`中的`$pressed_button_name`并不是同一个变量。前者仅仅是在继承时从后者拷贝过来的同名变量。修改`ui_demo_screen.demo_button`中变量的值只会影响`ui_demo_screen.demo_button`中的效果,而不会影响`common.button`中的效果。同理,`ui_demo_screen.demo_button/default`中的`$control_alpha`和`ui_demo_screen.default`中的`$control_alpha`本质上也不是同一个变量,不会相互影响。不过,`ui_demo_screen.demo_button`和`ui_demo_screen.demo_button/default`中的`$control_alpha`确实同一个变量,即父节点和子节点中的同名变量本质上是同一个变量,这也是为什么在`ui_demo_screen.demo_button`中为`$control_alpha`赋的值可以在`ui_demo_screen.demo_button/default`中读取的原因。 + +变量不仅能直接被读取,也可以参与运算后再读取。比如,`$some_var`的值目前是`some_value`,那么`"some_property": "($some_var - '_value')"`的意思就是将`some_value`的末尾去掉`_value`之后的`some`提供给属性`some_property`。字符串运算时需要将表达式加上圆括号,圆括号内的“字符串”两侧需要用单直引号来表示。同理,`+`也可以做到在变量末尾新追加字符串。 + +此外,变量不仅可以作为属性的值来传递,还可以作为控件名或者继承的控件名来写在控件的键名里。为了方便说明这一点,我们将我们的`ui_demo_screen.ui_demo_screen`屏幕稍加改造: + +```json +{ + "namespace" : "ui_demo_screen", + "ui_demo_screen@common.base_screen" : { + "$screen_content": "ui_demo_screen.ui_demo_panel" + }, + // ... 后面的内容不进行改变 +} +``` + +然后我们来查看原版`ui_common.json`文件中的`common.base_screen`控件及其子控件的定义,为了便于观察,我们删去了一些不必要的部分: + +```json +{ + "namespace": "common", + + // ... + + //--------------------------------------------------------------------------- + // Screens, survival inventory and crafting + //--------------------------------------------------------------------------- + + "base_screen": { + "type": "screen", + "vr_mode": "$is_holographic", + "$screen_content|default": "common.base_screen_empty_panel", + "$screen_bg_content|default": "common.base_screen_empty_panel", + "$screen_animations|default": [ + "@common.screen_exit_animation_push_offset", + "@common.screen_exit_animation_pop_offset", + "@common.screen_entrance_animation_push_offset", + "@common.screen_entrance_animation_pop_offset", + "@common.screen_exit_animation_push_fade", + "@common.screen_exit_animation_pop_fade", + "@common.screen_entrance_animation_push_fade", + "@common.screen_entrance_animation_pop_fade" + ], + "$background_animations|default": [ + "@common.screen_exit_animation_push_alpha", + "@common.screen_exit_animation_pop_alpha", + "@common.screen_entrance_animation_push_alpha", + "@common.screen_entrance_animation_pop_alpha" + ], + "$use_loading_bars|default": true, + "$is_full_screen_layout|default": false, + "$safezone_screen_matrix_layer|default": 2, + "controls": [ + { + "variables_button_mappings_and_controls": { + "type": "input_panel", + // ... + "variables": [ + // ... + } + ], + "button_mappings": [ + // ... + ], + "controls": [ + { + "bg_no_safezone_screen_panel@$screen_bg_content": { + // ... + } + }, + { + "safezone_screen_matrix@common.safezone_outer_matrix": { + "anims": "$screen_animations", + "animation_reset_name": "screen_animation_reset", + "propagate_alpha": true, + "disable_anim_fast_forward": true, + "layer": "$safezone_screen_matrix_layer" + } + }, + { + "screen_background@common_dialogs.full_screen_background": { + // ... + } + } + ] + } + }, + { + "loading_bars_background": { + "type": "image", + // ... + + "controls": [ + { + "loading_bars@common_store.progress_loading_bars": { + // ... + } + } + ], + + "anims": [ + "@common.loading_bar_init_animation_push", + "@common.loading_bar_init_animation_pop" + ] + } + } + ] + }, + + // ... + + "safezone_inner_matrix": { + "type": "stack_panel", + "size": [ "fill", "100%" ], + "orientation": "vertical", + "$header_safezone_control|default": "common.empty_panel", + "controls": [ + { + "outer_top@common.top_safezone_vertical_buffer": { + // ... + "controls": [ + { + "top_side_control@$header_safezone_control": { + // ... + } + } + ] + } + }, + { + "inner_top@common.top_safezone_vertical_buffer": { + // ... + "controls": [ + { + "top_side_control@$header_safezone_control": { + // ... + } + } + ] + } + }, + { "safezone_screen_panel@common.screen_panel": {} }, + { + "inner_bottom@common.bottom_safezone_vertical_buffer": { + // ... + } + }, + { + "outer_bottom@common.bottom_safezone_vertical_buffer": { + // ... + } + } + ] + }, + + "safezone_outer_matrix": { + "type": "stack_panel", + "orientation": "horizontal", + "size": [ "100%", "100%" ], + "controls": [ + { + "outer_left@common.left_safezone_horizontal_buffer": { + // ... + "controls": [ + { + "outer_left_safe_zone_stack@common.safe_zone_stack": {} + } + ] + } + }, + { + "inner_left@common.left_safezone_horizontal_buffer": { + // ... + "controls": [ + { + "inner_left_safe_zone_stack@common.safe_zone_stack": {} + } + ] + } + }, + { "inner_matrix@common.safezone_inner_matrix": {} }, + { + "inner_right@common.right_safezone_horizontal_buffer": { + // ... + "controls": [ + { + "inner_right_safe_zone_stack@common.safe_zone_stack": {} + } + ] + } + }, + { + "outer_right@common.right_safezone_horizontal_buffer": { + // ... + "controls": [ + { + "outer_right_safe_zone_stack@common.safe_zone_stack": {} + } + ] + } + } + ] + }, + + "screen_panel": { + "type": "panel", + "size": [ "100%", "fill" ], + "controls": [ + { "root_screen_panel@$screen_content": {} }, + { + "popup_dialog_factory": { + "type": "factory", + "control_ids": { + // ... + } + } + } + ] + }, + + // ... +} +``` + +在`base_screen`的一开始,我们便可以看到有一句定义`"$screen_content|default": "common.base_screen_empty_panel"`。这里便是定义了`$screen_content`的默认值。`|default`的意思便是定义**默认值**(**Default Value**)。每个节点链中每个变量只能有一处为默认值,其余的位置皆可以认为是在重写这个默认值为别的值。 + +这个`base_screen`控件和它继承的子控件都比较复杂,我们可以用一个节点树状图来表示这些控件在JSON UI文件中的结构: + +```shell +ui_common.json|common +├─button +│ └─ # 其他子控件 +├─base_screen +│ ├─variables_button_mappings_and_controls +│ │ ├─bg_no_safezone_screen_panel +│ │ ├─safezone_screen_matrix # @common.safezone_outer_matrix +│ │ └─screen_background +│ └─loading_bars_background +│ └─loading_bars +├─safezone_inner_matrix +│ ├─outer_top +│ │ └─top_side_control +│ ├─inner_top +│ │ └─top_side_control +│ ├─safezone_screen_panel # @common.screen_panel +│ ├─inner_bottom +│ └─outer_bottom +├─safezone_outer_matrix +│ ├─outer_left +│ │ └─outer_left_safe_zone_stack +│ ├─inner_left +│ │ └─inner_left_safe_zone_stack +│ ├─inner_matrix # @common.safezone_inner_matrix +│ ├─inner_right +│ │ └─inner_right_safe_zone_stack +│ └─outer_right +│ └─outer_right_safe_zone_stack +├─screen_panel +│ ├─root_screen_panel # @$screen_content +│ └─popup_dialog_factory +└─ #其他控件 +``` + +我们依照上图中的注释便可以找到`$screen_content`最终应用的位置,即我们重点关注`base_screen/variables_button_mappings_and_controls/safezone_screen_matrix`控件,它继承自`common.safezone_outer_matrix`。然后我们找到`safezone_outer_matrix`控件,找到其下的`safezone_outer_matrix/inner_matrix`控件,它继承自`common.safezone_inner_matrix`。于是我们再找到`safezone_inner_matrix`下的`safezone_inner_matrix/safezone_screen_panel`,它继承自`common.screen_panel`。最后,我们找到`screen_panel`。可以看到,它使用了一个`"root_screen_panel@$screen_content": {}`的写法。这样,当我们为`$screen_content`变量赋予新值的时候,我们需要使用控件名为其赋值,同时,该变量最终便会应用到`screen_panel/root_screen_panel`控件的继承位置上。比如在我们上述对`ui_demo_screen.ui_demo_screen`屏幕的修改示例中,在最外面,由于`ui_demo_screen@common.base_screen`即我们的`ui_demo_screen`继承了`common.base_screen`,所以意味着我们的`ui_demo_screen`拥有了和`common.base_screen`一样的结构。`$screen_content`变量通过层层向下级传递,最终在`ui_demo_screen/variables_button_mappings_and_controls/safezone_screen_matrix/inner_matrix/safezone_screen_panel/root_screen_panel`处通过`root_screen_panel@$screen_content`应用为`root_screen_panel@ui_demo_screen.ui_demo_panel`。 + +同理,变量不仅可以在用于继承时语法的`@`之后,也可以用于`@`之前。在此便不再赘述。 + +在这一段落的最后,我们介绍另一种定义变量的方法:条件定义。顾名思义,我们可以在满足某些条件时定义一个或一些变量。事实上,我们刚才看过的`common.base_screen`中便存在这种定义形式,我们重新将其节选出来并观察它的用法,我们依旧将不必要的部分删去,仅观察变量的定义: + +```json +"base_screen": { + "type": "screen", + // ... + "controls": [ + { + "variables_button_mappings_and_controls": { + "type": "input_panel", + // ... + "variables": [ + // Screen sizes - you should be using one of these unless explicitly told otherwise - in which case that size should be added as a variable here + // Win10 + { + //Desktop Texel Size: [376, 250] + "requires": "$desktop_screen", + "$narrow_screen_size": [ 280, "100% - 10px" ], // Realms Pending Invitations, Add Players + "$play_screen_size": [ 282, "100% - 50px" ], // Play Screen + "$tabbed_upsell_screen_size": [ 310, 173 ], // Trial Tabbed Upsell Screen + "$realms_create_screen_size": [ 267, 240 ], // Realms Create + "$large_screen_size": [ 317, "100% - 10px" ], // Multiplayer Invitations + "$store_screen_size": [ "100% - 4px", "100% - 4px" ], // Store Home + "$skin_pack_screen_size": [ "100% - 4px", "100% - 50px" ], // Skin Pack + "$resource_pack_screen_size": [ "100% - 50px", "45%x + 65px" ], // Texture Pack + "$upsell_screen_size": [ "177%y - 208.5px", "100% - 4px" ], // Skin Pack Upsell + "$create_world_upsell_screen_size": [ 360, 183 ], + "$create_realm_upsell_screen_size": [ 360, 183 ], + "$create_realm_upsell_play_screen_size": [ 360, 213 ], + "$max_create_world_upsell_screen_size": [ 360, "100% - 4px" ], + "$min_resource_pack_screen_size": [ 372, 232.4 ], + "$max_resource_pack_screen_size": [ 400, 246 ], + "$max_upsell_screen_size": [ 250, 259.125 ], + "$rating_prompt_screen_size": [ 250, 87 ], + "$max_skin_pack_screen_size": [ 310, "56.25%x - 65.25px + 118.5px" ], + "$extra_large_screen_size": [ "100% - 4px", "100% - 4px" ], // Skin Picker, Command Block screen + "$extra_large_max_screen_size": [ 370, "100% - 50px" ], + "$xbl_optional_signin_screen_size": [ 317, 158 ], // XBL Optional Signin Popup + "$xbl_console_signin_screen_size": [ 316, 161 ], // XBL Console Signin Popup + "$xbl_console_signin_succeeded_screen_size": [ 230, "100% - 50px" ], // XBL Console Signin Succeeded Popup + "$xbl_first_launch_screen_size": [ 317, 146 ], // XBL First Launch Popup, Trial Upsell + "$xbl_gamer_profile_screen_size": [ 260, "100% - 50px" ], // XBL Console Signin Succeeded Popup + "$modal_screen_size": [ 204, 140 ], // Pop-up dialogue boxes and modal progress screens + "$tall_modal_screen_size": [ 204, 172 ], + "$patch_notes_screen_size": [ 300, "100% - 4px" ], // Patch Notes Screen + "$sign_screen_size": [ 185, 90 ], // Sign Screen + "$mob_effect_screen_size": [ "90%", "90%" ], // Mob Effect Screen + "$purchase_coin_screen_size": [ "90%", "17%x + 82px" ], //Coin Purchase Screen + "$purchase_coin_screen_size_extended": [ "90%", "17%x + 117px" ], //Coin Purchase Screen, with FAQ + "$purchase_coin_screen_size_not_enough": [ "90%", "17%x + 97px" ], //Coin Purchase Screen, when not enough coins for current purchase + "$purchase_coin_screen_size_extended_not_enough": [ "90%", "17%x + 132px" ], //Coin Purchase Screen, when not enough coins for current purchase, with FAQ + "$choose_realm_screen_size": [ "70%", "85%" ], //Choose Realm Screen + "$custom_templates_screen_size": [ "70%", "85%" ], //Custom Templates Screen + "$world_modal_screen_size": [ 290, 100 ], // world convert and world loading modal progress screens + "$day_one_experience_intro_screen_size": [ "60%", "85%" ], // Day One Experience intro popup + "$day_one_experience_import_progress_screen_size": [ "80%", "90%" ], // Day One Experience import progress modal screen + "$world_conversion_complete_screen_size": [ 290, 160 ], // World Conversion complete modal screen + "$gamepad_disconnect_screen_size": [ 300, 100 ] //custom gamepad disconnected modal size, to contain all languages in title + }, + // PE edition and VR + { + //Pocket Texel Size: [320, 210] + "requires": "($pocket_screen or $is_holographic)", + "$narrow_screen_size": [ "90.3225%", "100% - 4px" ], // Realms Pending Invitations, Add Players + "$play_screen_size": [ "83.4375%", "100% - 4px" ], // Play Screen + "$tabbed_upsell_screen_size": [ 310, 173 ], // Trial Tabbed Upsell Screen + "$realms_create_screen_size": [ "71.2766%", "100% - 4px" ], // Realms Create + "$large_screen_size": [ "70.3215%", "100% - 4px" ], // Multiplayer Invitations + "$store_screen_size": [ "100% - 4px", "100% - 4px" ], // Store Home, purchase Skin Pack + "$skin_pack_screen_size": [ "100% - 4px", "100% - 4px" ], // Skin Pack + "$resource_pack_screen_size": [ "100% - 50px", "45%x + 65px" ], // Texture Pack + "$upsell_screen_size": [ "100% - 76px", "100% - 4px" ], // Skin Pack Upsell + "$create_world_upsell_screen_size": [ "100% - 4px", 223 ], + "$create_realm_upsell_screen_size": [ "100% - 4px", 223 ], + "$create_realm_upsell_play_screen_size": [ "100% - 4px", 219 ], + "$max_create_world_upsell_screen_size": [ "100% - 4px", "100% - 4px" ], + "$min_resource_pack_screen_size": [ 306, 202.7 ], + "$max_resource_pack_screen_size": [ 313.3, 206 ], + "$max_upsell_screen_size": [ "100% - 76px", "56.25%x + 118.5px" ], + "$max_skin_pack_screen_size": [ "100% - 4px", "56.25%x - 65.25px + 118.5px" ], + "$extra_large_screen_size": [ "100% - 4px", "100% - 4px" ], // Skin Picker, Command Block screen + "$extra_large_max_screen_size": [ 370, "100% - 50px" ], + "$xbl_optional_signin_screen_size": [ 316, 153 ], // XBL Optional Signin Popup + "$xbl_console_signin_screen_size": [ 316, 161 ], // XBL Console Signin Popup + "$xbl_console_signin_succeeded_screen_size": [ 230, "100% - 4px" ], // XBL Console Signin Succeeded Popup + "$xbl_first_launch_screen_size": [ 316, 146 ], // XBL First Launch Popup, Trial Upsell + "$xbl_gamer_profile_screen_size": [ 316, "100% - 4px" ], // XBL Console Signin Succeeded Popup + "$modal_screen_size": [ 204, 140 ], // Pop-up dialogue boxes and modal progress screens + "$rating_prompt_screen_size": [ 250, 87 ], + "$tall_modal_screen_size": [ 204, 172 ], + "$patch_notes_screen_size": [ 300, "100% - 4px" ], // Patch Notes Screen + "$sign_screen_size": [ 185, 90 ], // Sign Screen + "$mob_effect_screen_size": [ "90%", "90%" ], // Mob Effect Screen + "$purchase_coin_screen_size": [ "90%", "17%x + 82px" ], //Coin Purchase Screen + "$purchase_coin_screen_size_extended": [ "90%", "17%x + 117px" ], //Coin Purchase Screen, with FAQ + "$purchase_coin_screen_size_not_enough": [ "90%", "17%x + 97px" ], //Coin Purchase Screen, when not enough coins for current purchase + "$purchase_coin_screen_size_extended_not_enough": [ "90%", "17%x + 132px" ], //Coin Purchase Screen, when not enough coins for current purchase, with FAQ + "$choose_realm_screen_size": [ "70%", "85%" ], //Choose Realm Screen + "$custom_templates_screen_size": [ "70%", "85%" ], //Custom Templates Screen + "$world_modal_screen_size": [ 290, 100 ], // world convert and world loading modal progress screens + "$day_one_experience_intro_screen_size": [ "60%", "85%" ], // Day One Experience intro popup + "$day_one_experience_import_progress_screen_size": [ "80%", "90%" ], // Day One Experience import progress modal screen + "$world_conversion_complete_screen_size": [ 290, 160 ], // World Conversion complete modal screen + "$gamepad_disconnect_screen_size": [ 300, 100 ] //custom gamepad disconnected modal size, to contain all languages in title + } + ], + "button_mappings": [ + // ... + ], + "controls": [ + // ... + ] + } + }, + // ... + ] +} +``` + +我们可以观察`base_screen/variables_button_mappings_and_controls`控件的`variables`属性,这里通过条件定义了一系列变量。事实上,这些变量定义了原版的各种不同屏幕的尺寸。我们将其抽象为如下结构: + +```json +"variables": [ + { + "requires": "$some_condition", + "$var1": [ 280, "100% - 10px" ], + "$var2": [ 280, "100% - 50px" ] + }, + { + "requires": "/* logic expression */", + "$var3": [ "90.3225%", "100% - 4px" ] + } +] +``` + +`variables`是一个数组,数组中是一个个的对象,每一个对象都有一个`requires`字段,“requires”直译为“需要”,即代表对象中定义的变量需要满足的条件。当`requires`后面的值为`true`时,其后面所跟的变量才会被定义或赋值。上面的抽象结构中,当`$some_condition`变量为`true`时,`$var1`和`$var2`就会被定义。`requires`字段的值也可以是一个逻辑表达式。与字符串变量、数字变量可以使用`+`、`-`运算出一个字符串或数字类似,布尔值变量可以使用逻辑运算符`and`、`or`和`not`运算出一个新的布尔值,比如`"requires": "($condition1 and $condition2)"`表示`$condition1`和`$condition2`皆为`true`时执行该对象中的变量定义,放在上面的示例中就是执行`$var3`的定义。事实上,字符串和数字变量之间也可以使用`=`运算符做不等关系比较,得出一个布尔值;数字变量还可以使用`>`和`<`来做序关系比较,同样可以得到一个布尔值。 + +在上面`base_screen/variables_button_mappings_and_controls`控件的`variables`属性中,我们可以看到,当`$desktop_screen`变量为`true`时,将定义一系列桌面屏幕尺寸变量;当`($pocket_screen or $is_holographic)`为`true`,即要么是携带屏幕要么是VR屏幕时,将定义一系列携带屏幕尺寸变量。`$desktop_screen`、`$pocket_screen`与`$is_holographic`与普通的变量不同,他们都是游戏引擎硬编码定义的变量,所以他们可以在整个JSON UI中的任意位置直接调用,换言之,他们的生存周期是全局的。 + +当然,除了游戏引擎,我们自己也可以定义一系列生存周期为全局的变量,我们称之为**全局变量**(**Global Variable**)。与定义了该附加包中所有的JSON UI文件的`_ui_defs.json`文件一样,我们有另一种技术性UI文件,命名为`_global_variables.json`。它和`_ui_defs.json`一样都位于资源包的`ui`文件夹下,不过该文件中定义的是全部开发者自定义的全局变量,格式也非常简单,如下所示: + +```json +{ + "$global_var1": "some_value", + "$global_var2": "some_value", + // ... +} +``` + +之后,`$global_var1`、`$global_var2`等变量便可以在UI的任何位置直接使用。 + +### 绑定 + +我们回到我们用界面编辑器制作的`ui_demo_screen`上,我们将其格式稍微手动整理后再将其代码复制到这里,以便我们后面继续讲解。由于一些属性具有默认值,而我们又没有修改这些值,所以我们将不影响的属性先行删除,缩短文件长度: + +```json +{ + "namespace" : "ui_demo_screen", + "ui_demo_screen@common.base_screen" : { + "$screen_content": "ui_demo_screen.ui_demo_panel" + }, + "ui_demo_panel" : { + "type" : "panel", + "size" : [ 100, 100 ], + "controls" : [ + { + "bg_image" : { + "type" : "image", + "size" : [ "100%", "100%" ], + "texture" : "textures/ui/dialog_background_opaque" + } + }, + { + "demo_button@ui_demo_screen.demo_button" : {} + } + ] + }, + "demo_button@common.button" : { + "$control_alpha" : 1.0, + "$default_texture" : "textures/netease/common/button/default", + "$hover_texture" : "textures/netease/common/button/hover", + "$is_new_nine_slice" : false, + "$label_color" : [ 1, 1, 1 ], + "$label_font_scale_factor" : 1.0, + "$label_font_size" : "large", + "$label_layer" : 3, + "$label_offset" : [ 0, 0 ], + "$label_text" : "Button", + "$nine_slice_buttom" : 0, + "$nine_slice_left" : 0, + "$nine_slice_right" : 0, + "$nine_slice_top" : 0, + "$nineslice_size" : [ 0, 0, 0, 0 ], + "$pressed_button_name" : "%ui_demo_screen.click", + "$pressed_texture" : "textures/netease/common/button/pressed", + "$texture_layer" : 2, + "bindings" : [ + { + "binding_collection_name" : "", + "binding_condition" : "always_when_visible", + "binding_type" : "collection_details" + } + ], + "controls" : [ + { + "default" : { + "type" : "image", + "alpha" : "$control_alpha", + "is_new_nine_slice" : "$is_new_nine_slice", + "layer" : "$texture_layer", + "nine_slice_buttom" : "$nine_slice_buttom", + "nine_slice_left" : "$nine_slice_left", + "nine_slice_right" : "$nine_slice_right", + "nine_slice_top" : "$nine_slice_top", + "nineslice_size" : "$nineslice_size", + "texture" : "$default_texture" + } + }, + { + "hover" : { + "type" : "image", + "alpha" : "$control_alpha", + "is_new_nine_slice" : "$is_new_nine_slice", + "layer" : "$texture_layer", + "nine_slice_buttom" : "$nine_slice_buttom", + "nine_slice_left" : "$nine_slice_left", + "nine_slice_right" : "$nine_slice_right", + "nine_slice_top" : "$nine_slice_top", + "nineslice_size" : "$nineslice_size", + "texture" : "$hover_texture" + }, + }, + { + "pressed" : { + "type" : "image", + "alpha" : "$control_alpha", + "is_new_nine_slice" : "$is_new_nine_slice", + "layer" : "$texture_layer", + "nine_slice_buttom" : "$nine_slice_buttom", + "nine_slice_left" : "$nine_slice_left", + "nine_slice_right" : "$nine_slice_right", + "nine_slice_top" : "$nine_slice_top", + "nineslice_size" : "$nineslice_size", + "texture" : "$pressed_texture" + } + }, + { + "button_label" : { + "type" : "label", + "alpha" : "$control_alpha", + "color" : "$label_color", + "font_scale_factor" : "$label_font_scale_factor", + "font_size" : "$label_font_size", + "font_type" : "smooth", + "layer" : "$label_layer", + "max_size" : [ "100%", "100%" ], + "offset" : "$label_offset", + "shadow" : false, + "text" : "$label_text", + "text_alignment" : "center" + } + } + ] + } +} +``` + +现在,我们继续关注`demo_button`控件。我们可以看到,该控件下有一个`bindings`属性,这里便是用于定义该控件的**绑定**(**Binding**)的地方。绑定其实指的是一个控件与控制该控件逻辑的代码的绑定,这里的代码可以是一个回调函数,比如,我们可以在我们的Python脚本中注册一个回调函数,当绑定生效时,便执行该回调的内容,修改该控件的逻辑。回调函数有时也会返回一些值,这些值也会被传回JSON UI中作为绑定的“值”使用。游戏引擎也自带一系列硬编码的绑定。绑定的名称简称**绑定名**(**Binding Name**),一般**以`#`开头**。 + +编辑器自动生成的按钮的`bindings`属性并不完整,因为绑定一般都需要我们自己来填写。我们先以原版的绑定为示例学习。我们先来查看原版的旧版成就屏幕UI文件`achievement_screen.json`中的`achievement.gamer_score_value_label`控件: + +```json +{ + "namespace": "achievement", + + // ... + + "gamer_score_value_label": { + "type": "label", + "size": [ "default", 10 ], + "text": "#gamerscore_value", + "color": "$achievement_text_color", + "shadow": false, + "bindings": [ + { + "binding_name": "#gamerscore_value" + } + ] + }, + + // ... +} +``` + +这是一个标签控件,即一个用于显示一段文本的控件。`bindings`里只有一个绑定对象,而这个绑定中又只有一个字段`binding_name`,它的值便是一个绑定。这个绑定的名称为`#gamerscore_value`,那么源代码中便存在一个**绑定器**(**Binder**)用于执行该绑定名的回调函数,以执行其中的逻辑并得到一个返回值。这里便是得到了`#gamerscore_value`的值。然后,我们可以看到该控件的`text`属性使用了这个值。最终的效果便是,该控件上显示出了玩家的游戏分数。 + +当然,`binding_name`后面并不一定必须只能是单独的一个绑定名,还可以是一个包含一个或多个绑定名的表达式。当其是表达式时,效果便是计算整个表达式的值。这种时候,往往需要另一个字段`binding_name_override`配合使用。我们来看下面的示例,依旧是成就屏幕: + +```json +{ + "namespace": "achievement", + + + // ----------------------------------------------- + // + // Screen Window and Border + // + // ----------------------------------------------- + + "main_content_panel": { + "type": "panel", + "size": [ "100%", "100%" ], + "anchor_to": "top_left", + "anchor_from": "top_left", + "controls": [ + // ... + { + "scrolling_panel@common.scrolling_panel": { + "anchor_to": "top_left", + "anchor_from": "top_left", + "$show_background": false, + "size": [ "100%", "100%" ], + "$scrolling_content": "achievement.inside_header_panel", + "$scroll_size": [ 5, "100% - 4px" ], + "$scrolling_pane_size": [ "100% - 4px", "100%" ], + "$scrolling_pane_offset": [ 2, 0 ], + "$scroll_bar_right_padding_size": [ 0, 0 ], + "bindings": [ + { + "binding_name": "(not #loading_achievement_panel_visible)", + "binding_name_override": "#visible" + } + ] + } + } + ] + }, + + // ... +} +``` + +`main_content_panel`是该文件中第一个定义的控件,我们关注它的子控件`scrolling_panel`。这里定义了一个绑定,它的作用是计算`(not #loading_achievement_panel_visible)`的值,然后将值赋给另一个绑定名`#visible`。有些人可能会问,为什么绑定名还可以被“赋予”值?事实上,一些绑定名被称作**属性绑定名**(**Property Binding Name**),他们可以用来“存值”,然后像普通的属性一样发挥作用,改变控件的外观或逻辑,同时因为其本身是绑定,所以可以像变量一样参与表达式的计算。这里的`#visible`便是一个属性绑定名,可以用于控制该控件是否隐藏而不可见。这里的含义便是,如果通过计算`#loading_achievement_panel_visible`得到其值为`false`,便将`(not false)`即`true`赋值给`#visible`,使控件可见;反之,使控件隐藏。`binding_name_override`的意义便是将其后面的绑定名的值覆盖为前面`binding_name`计算得到的值。同时,一些开发者可能在查找JSON UI文件时会看到有一些控件会使用一个属性叫做`property_bag`。`property_bag`叫做**属性袋**(**Property Bag**),属性袋便是一个可以直接给一个或一些属性绑定名像变量一样直接赋值的地方,一般是用于赋该控件中属性绑定名的初值。 + +再次回到我们用界面编辑器自动生成的按钮的绑定上。我们将这一部分节选出来: + +```json +"bindings" : [ + { + "binding_collection_name" : "", + "binding_condition" : "always_when_visible", + "binding_type" : "collection_details" + } +] +``` + +我们之前之所以说它不完整,是因为这个绑定中需要我们手动指定一个`binding_collection_name`才能运作,不过在此之前,我们先看另外两条我们之前没见过的属性:`binding_type`和`binding_condition`。`binding_condition`是该绑定启用的条件和时机,分别支持**无**(`none`)、**单次**(`once`)、**始终**(`always`)、**当可见时始终**(`always_when_visible`)、**可见**(`visible`)、**可见性变更**(`visibility_changed`)六种。`binding_type`为绑定的类型,支持**无**(`none`)、**全局**(`global`)、**合集**(`collection`,*集合*)、**合集详情**(`collection_details`)、**查看**(`view`)五种类型,当该字段不存在,即不指定类型时,默认为合集类型。在不同的类型下绑定的作用域可能有所不同,一般来说,一个绑定都是为本控件计算值或本控件赋值,但是全局绑定下,计算的值可以应用到全局。在不同的类型下绑定中的字段也稍微有所区别,比如,在合集和合集详情类型下,一般我们都需要指定`binding_collection_name`,用于定义绑定的**合集名**(**Collection Name**)。合集一般都用于一个网格控件控制其下的模板控件,在原版中多见于开发、滑块和编辑框。当然,这并不意味着合集只能用于这些控件,当我们的按钮称为一个网格的模板控件时,我们的按钮也可以被合集控制。我们来看一个原版的合集绑定示例,依旧是成就屏幕: + +```json +{ + "namespace": "achievement", + + // ... + + "achievement_locked_title": { + "type": "panel", + "visible": "#is_locked", + "bindings": [ + { + "binding_name": "#is_locked", + "binding_name_override": "#visible", + "binding_type": "collection", + "binding_collection_name": "achievement_list" + } + ], + "controls": [ + // ... + ] + }, + + // ... + + "achievement_list_grid": { + "type": "grid", + "grid_item_template": "achievement.achievement_grid_item", + "grid_dimension_binding": "#achievement_grid_dimension", + "collection_name": "achievement_list", + "anchor_to": "top_left", + "anchor_from": "top_left", + "size": [ "100%", "default" ], + "$grid_size|default": [ "100%", 54 ], + "bindings": [ + { + "binding_name": "#achievement_grid_dimension", + "binding_type": "global" + } + ] + }, + + // ... +} +``` + +`achievement_locked_title`控件便是一个典型的合集绑定,执行`#is_locked`的计算,此时由于是一个合集绑定,该绑定名的绑定器执行回调函数时会向函数内传入合集名为`achievement_list`的合集的一些数据,配合这些数据,成功计算出对应成就的`#is_locked`值,将其赋值给`#visible`属性绑定名。而后面的`achievement_list_grid`网格控件,其实就是控制该合集名数据的网格控件。在这个示例中,该网格控件用于存储一系列成就,每个成就都是一些相同定义的控件(即该网格的模板`achievement.achievement_grid_item`及其子控件),他们全部通过合集绑定来区分彼此的文本、图像和逻辑。 + +查看(`view`)类型的绑定,顾名思义,是查看一个控件的一个属性,然后将其赋值给自己的一个属性。这里查看和赋值的属性都是属性绑定名。我们来看设置屏幕里的控制段落的JSON UI文件`controls_section.json`下的一个控件: + +```json +{ + "namespace": "controls_section", + + // ... + + "keyboard_and_mouse_section": { + "type": "stack_panel", + "size": [ "100%", "100%c" ], + "$keymapping_grid_dimension": "#keyboard_standard_grid_dimension", + "$keymapping_collection": "keyboard_standard_collection", + "anchor_from": "top_left", + "anchor_to": "top_left", + "bindings": [ + { + "binding_type": "view", + "source_control_name": "keyboard_and_mouse_button_toggle", + "source_property_name": "#toggle_state", + "target_property_name": "#visible" + } + ], + "controls": [ + // ... + ] + }, + + // ... +} +``` + +这里便使用了查看绑定。查看绑定没有`binding_name`字段,然而,其拥有`source_control_name`和`source_property_name`字段,这两个字段配合使用,可以用来查看(计算)另外一个控件的一个绑定名,然后将其值赋给本控件的`target_property_name`后面指定的绑定名上。这里便是将`controls_section.keyboard_and_mouse_button_toggle`的一个属性绑定名`#toggle_state`的值赋到自己的属性绑定名`#visible`上,以控制自己(这里是键鼠控制段落)的可见性。当然,和`binding_name`一样,`source_property_name`虽说是写的是一个属性名,但是依旧可以写一个包含绑定名的表达式,用于进行稍微复杂的计算。 + +最后,我们介绍一种可以直接用于控制的绑定,即不需要绑定名的绑定。这一种绑定在我们编辑器自动生成的按钮里就能找到。我们关注这一行: + +```json +"demo_button@common.button" : { + // ... + "$pressed_button_name" : "%ui_demo_screen.click", + // ... + "bindings" : [ + // ... + ], + "controls" : [ + // ... + ] +} +``` + +此处的`%ui_demo_screen.click`便是这种绑定。这种绑定**以`%`开头**,其格式为`%python_module_file_name.method_name`。`python_module_file_name`指我们在模组API中该JSON UI的**代理**(**Proxy**)模块,即控制该JSON UI逻辑的`ScreenNode`类所在的模块位于的Python文件的文件名,而`method_name`则是该`ScreenNode`类中写入的绑定器的回调函数方法的方法名。这相当于我们绕过绑定名直接指定一个绑定对应的回调函数。这种方式指定绑定在模组API中也非常好用与实用。关于这一内容,我们将在第三节的挑战中详细介绍与应用。 + +### 按钮映射 + +在基岩版的前身携带版制作之初,游戏只支持移动设备的游玩。所以,为了使玩家能够进行控制,所有的控制都是通过屏幕上的按钮来进行的。所以游戏为此硬编码了一系列被称为**按钮ID**(**Button ID**)的硬编码绑定名,用于对应不同功能的按钮,他们大部分都是以`button.`为前缀的。后来,游戏为多种平台进行了适配,控制器的操纵方式也多种多样。不同的按钮ID就硬编码地绑定到了不同的控制器的实体键位或按钮上。比如,`button.menu_cancel`就会在玩家在键盘上按下`Esc`键或手柄上按下`B`键时触发。然而,一般而言,不管是按钮,还是滑块、开关、输入面板等控件,一个功能一般而言就只对应一个绑定。那么,为了使多种输入方式皆执行一种功能,我们就需要将多种输入方式映射到同一个担任主要逻辑任务的绑定上。一般而言,这个担任主要逻辑任务的绑定还可以是一个按钮ID,但是也可以是我们自定义的一个`#`开头或者`%`开头的绑定名。 + +我们以原版文件`account_banned_screen.json`中的`account_banned_screen`屏幕控件为例: + +```json +{ + "namespace": "account_banned", + + // ... + + // ----------------------------------------------- + // + // screen + // + // ----------------------------------------------- + "account_banned_screen@common.base_screen": { + // When the player hits Esc or exit button (menu_cancel button), they can exit the screen + "button_mappings": [ + { + "from_button_id": "button.menu_cancel", + "to_button_id": "button.menu_exit", + "mapping_type": "global" + } + ], + "$screen_content": "account_banned.screen_dialog" + }, + + // ... +} +``` + +可以看到,这里我们将`button.menu_cancel`映射到了`button.menu_exit`,映射类型是`global`,代表着全局映射,即该屏幕控件下所有的地方都存在这种映射。那么我们在键盘上按`Esc`键时,这个屏幕就会关闭。映射类型`mapping_type`可以接受**全局**(`global`)、**双按**(`double_pressed`)、**按下**(`pressed`)、**获得焦点**(`focused`)四种,分别代表何时或何种情况下发生映射,当没有指定该字段时默认是按下。 + +其实,我们之前提到`common.button`和变量时,便已经涉及到了按钮映射。我们的`%ui_demo_screen.click`绑定随着`$pressed_button_name`变量被传入了按钮映射,`button.menu_select`在按下时被映射到该绑定上,`button.menu_ok`在获得焦点时被映射到该绑定上。 + +## 动画 + +细心的开发者可能在之前我们第一次展示`common.base_screen`控件的节选的时候便发现了,`base_screen/loading_bars_background`控件下我们特地留了一个`anims`属性没有删除。那么,`anims`属性里面引用的都是什么东西呢?事实上,在JSON UI文件中,除了代表界面元素的控件之外,我们还可以定义另一种对象,那便是**动画**(**Animation**)。与控件中`type`代表其类型不同,动画中使用`anim_type`代表其类型,可以有**透明度**(`alpha`)、**裁剪**(`clip`)、**颜色**(`color`)、**翻书动画**(`flip_book`)、**Aseprite翻书动画**(`aseprite_flip_book`)、**偏移**(`offset`)、**等待**(`wait`)、**UV**(`uv`)和**尺寸**(`size`)九种类型。我们可以看一些原版的`ui_common.json`文件中定义的动画: + +```json +{ + //... + + "loading_bar_init_animation_push": { + "anim_type": "alpha", + "easing": "out_cubic", + "duration": 0.0, + "from": 0.0, + "to": 0.0, + "play_event": "screen.exit_push", + "next": "@common.loading_bar_wait_animation" + }, + "loading_bar_init_animation_pop@common.loading_bar_init_animation_push": { + "play_event": "screen.exit_pop" + }, + + "loading_bar_wait_animation": { + "anim_type": "wait", + "duration": "$loading_bar_transition", + "next": "@common.loading_bar_fade_animation" + }, + + "loading_bar_fade_animation": { + "anim_type": "alpha", + "easing": "out_cubic", + "duration": 1.0, + "from": 0.0, + "to": 1.0 + }, + + "bar_animation": { + "anim_type": "flip_book", + "initial_uv": [ 0, 0 ], + "frame_count": 10, + "frame_step": 64, + "fps": 10, + "reversible": true, + "easing": "linear" + }, + + //... +} +``` + +我们可以看到,动画不仅可以使用`next`属性指定该动画之后的下一个动画,还可以向控件一样通过继承来产生新的动画。动画可以指定在对应类型的控件属性中(比如上述`loading_bar_init_animation_push`动画是`alpha`类型,那么某个控件便可以使用`"alpha": "@common.loading_bar_init_animation_push"`来使自己的透明度由该动画操纵),也可以像我们一开始说的一样,放在`anims`属性中。`anims`属性中可以防止任意类型的动画,用于操纵该控件的各种属性的变化。 + +值得注意的是,`wait`类型的动画可以使动画等待一段时间再继续。善用这种类型的动画可以使控件的变化更加丰富。 + +## 对既有JSON UI进行二次修改 + +有时候,我们不仅想要创建一个新的UI,也想对已有的UI进行二次修改,比如,我们希望修改原版的某些屏幕上的控件,那么这个时候,我们便需要知道如何妥善地二次修改JSON UI文件。事实上,二次修改JSON UI文件并不需要把原版的UI代码全部复制过来,然后再进行修改。我们只需要声明对应的控件,然后修改对应的属性即可。无需修改的部分可以全部跳过不写。 + +我们不妨假设我们本节中一直作为示例的,也就是我们本节中一起使用编辑器制作的JSON UI为一个原版JSON UI文件,然后我们尝试在我们的包中二次修改这个文件。为了方便讲解,我们再次将文件简化后将代码复制到此处: + +```json +{ + "namespace" : "ui_demo_screen", + "ui_demo_screen@common.base_screen" : { + "$screen_content": "ui_demo_screen.ui_demo_panel" + }, + "ui_demo_panel" : { + "type" : "panel", + "size" : [ 100, 100 ], + "controls" : [ + { + "bg_image" : { + "type" : "image", + "size" : [ "100%", "100%" ], + "texture" : "textures/ui/dialog_background_opaque" + } + }, + { + "demo_button@ui_demo_screen.demo_button" : {} + } + ] + }, + "demo_button@common.button" : { + "$default_texture" : "textures/netease/common/button/default", + "$hover_texture" : "textures/netease/common/button/hover", + "$pressed_texture" : "textures/netease/common/button/pressed", + "$label_color" : [ 1, 1, 1 ], + "$label_font_scale_factor" : 1.0, + "$label_font_size" : "large", + "$label_layer" : 3, + "$label_offset" : [ 0, 0 ], + "$label_text" : "Button", + "$pressed_button_name" : "%ui_demo_screen.click", + "$texture_layer" : 2, + "bindings" : [ + { + "binding_collection_name" : "", + "binding_condition" : "always_when_visible", + "binding_type" : "collection_details" + } + ], + "controls" : [ + { + "default" : { + "type" : "image", + "layer" : "$texture_layer", + "texture" : "$default_texture" + } + }, + { + "hover" : { + "type" : "image", + "layer" : "$texture_layer", + "texture" : "$hover_texture" + }, + }, + { + "pressed" : { + "type" : "image", + "layer" : "$texture_layer", + "texture" : "$pressed_texture" + } + }, + { + "button_label" : { + "type" : "label", + "color" : "$label_color", + "font_scale_factor" : "$label_font_scale_factor", + "font_size" : "$label_font_size", + "font_type" : "smooth", + "layer" : "$label_layer", + "max_size" : [ "100%", "100%" ], + "offset" : "$label_offset", + "shadow" : false, + "text" : "$label_text", + "text_alignment" : "center" + } + } + ] + } +} +``` + +现在我们已经假设上述代码是原版的UI代码,我们想对这个原版的`ui_demo_screen.json`文件进行二次修改。我们在我们资源包的`ui`文件夹中手动创建一个同名的`ui_demo_screen.json`文件,然后我们先将命名空间写入该文件: + +```json +{ + "namespace" : "ui_demo_screen" +} +``` + +现在我们依次实现各种修改功能。首先,我们想修改`ui_demo_panel`控件的尺寸。之前的尺寸是X、Y轴向上皆为100px,我们想在想修改成皆为200。我们无需将整个`ui_demo_panel`控件都复制过来,我们只需要这么做: + +```json +{ + "namespace" : "ui_demo_screen", + "ui_demo_panel" : { + "size" : [ 200, 200 ] + } +} +``` + +这样,就相当于我们修改了`ui_demo_panel`的尺寸为200px。 + +现在,我们又想将我们的按钮上的文字字号由`large`修改为`noraml`。那么我们只需这么做: + +```json +{ + "namespace" : "ui_demo_screen", + "ui_demo_panel" : { + "size" : [ 200, 200 ] + }, + "demo_button" : { + "$label_font_size" : "normal" + } +} +``` + +我们注意到,虽然我们原始文件中的`demo_button`继承了`common.button`,但是我们的二次修改文件中无需再将这一点用`@`点名。我们只需要单纯写`demo_button`,游戏引擎便可以知道我们修改的是哪个控件。 + +现在我们想修改`demo_button`的子控件`button_label`的字体,由`smooth`修改为`MinecraftTen`。我们只需要这么做: + +```json +{ + "namespace" : "ui_demo_screen", + "ui_demo_panel" : { + "size" : [ 200, 200 ] + }, + "demo_button" : { + "$label_font_size" : "normal" + }, + "demo_button/button_label" : { + "font_type" : "MinecraftTen" + }, +} +``` + +可以看到,对于子控件的修改,我们也只需要将其路径给出,然后修改对应的属性即可。 + +最后,我们想在按钮的绑定中新增一个绑定。我们知道,如果我们直接覆写`bindings`属性,那么之前的绑定必须也重新写一遍。当绑定特别多时,这并不利于我们简明扼要地进行二次修改,而且将面临着损失兼容性的风险。事实上,对于所有的数组类型的数组,我们都可以使用一个叫做`modifications`的属性来做到动态增删。比如,我们想在`demo_button`的`bindings`的末尾新增一个绑定,我们可以这么做: + +```json +{ + "namespace" : "ui_demo_screen", + "ui_demo_panel" : { + "size" : [ 200, 200 ] + }, + "demo_button" : { + "$label_font_size" : "normal", + "modifications" : [ + { + "array_name": "bindings", + "operation": "insert_back", + "value": [ + { + "binding_name": "#my_binding", + "binding_type": "global" + } + ] + } + ] + }, + "demo_button/button_label" : { + "font_type" : "MinecraftTen" + }, +} +``` + +同理,我们可以使用这种方法增删一个控件的子控件。 + +至此,我们已经学习了创建和修改UI的基本方法。在下一节中,我们将一起使用编辑器为我们之前制作的箱子制作一个UI,并使用脚本SDK实现特定时刻(比如打开箱子时)将该UI压入UI的场景栈,以实现将自定义UI显示在世界中的功能。介于篇幅问题,在下一节中我们并不会给新UI添加绑定和赋予逻辑。我们将在第三节的挑战中来实现通过绑定为UI添加逻辑的功能。 diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/2-为自定义箱子绘制界面.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/2-为自定义箱子绘制界面.md new file mode 100644 index 0000000..38ecb8f --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/2-为自定义箱子绘制界面.md @@ -0,0 +1,919 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 进阶 +time: 40分钟 +--- + +# 为自定义箱子绘制界面 + +现在,我们一起为我们之前在第十三章中的自定义箱子绘制一个界面。由于篇幅限制,我们并不再在此介绍如何为该界面添加逻辑。不过,灵活使用编辑器绘制界面也是一项不可或缺的本领。因此,在本节中,我们着重介绍绘制一个复杂的UI的各种方法。在本节末,我们会介绍如何将我们的UI显示在游戏中。 + +## 创建UI + +![](./images/14.2_create_ui.png) + +![](./images/14.2_created_ui.png) + +打开我们的编辑器,通过新建UI文件对话框创建一个新的UI文件,不妨命名为`custom_chest`。 + +![](./images/14.2_screen_rename.png) + +编辑器自动为我们创建一个`main`屏幕控件,我们不妨将其重命名为更加易记的名字`custom_chest_screen`。我们可以在右侧的“属性“窗格中来进行控件的重命名。 + +现在,我们先进行一个预期UI的设想。我们期望制作一个分为上下两部分的UI,上面一个面板用于展示箱子内容中的物品,下面一个面板用于展示玩家物品栏中的物品。中间可以使用一小段图片控件连接,就像原版的物品栏屏幕中左侧展示可合成的物品,右侧展示物品栏或合成网格, 中间用一小段图片连接一样。因此,我们的UI的主体其实是从上往下排列的三个主要部分。 + +## 向UI中添加控件 + +一般而言,一个屏幕下都存在且只存在一个面板。我们知道,面板是用来放置其他各种控件的一种技术性控件。不过,也可以轻松想到,在屏幕这种根节点上,我们其实也只需要一块面板便可以在其下放置各种控件了。因此,在没有特殊情况下,两个以上的面板便是多余的,这也是为什么我们一般都习惯于在一个屏幕下只放置一个面板,然后在这个面板上再进行各种操作或放置其他控件。 + +从刚才我们对UI的设想中我们可以得知,我们期望UI的各个主要部分竖向排列。因此,栈面板是我们这里首选的面板。栈面板可以是其中的控件严格横向或纵向排列,而无需单独指定他们各自的位置。 + +![](./images/14.2_stack_create.png) + +我们创建一个栈面板,并不妨将其重命名为`custom_chest_screen_content`。栈面板默认的朝向便是从上到下(垂直),所以我们无需改变其**定向**(**Orientation**),对应到JSON文件中就是无需改变其`orientation`。 + +### 上半部分 + +![](./images/14.2_top_half_create.png) + +现在,我们为其加入上半主要部分的面板,这里我们使用普通面板,因为我们此时无需再使上半部分面板内部按顺序排列。不妨将其重命名为`custom_chest_top_half`。此时的面板依旧是默认尺寸,我们应为其指定尺寸。但是并不用这么着急,我们可以先将我们上半部分的背景图片制作好,然后根据背景图片的尺寸来计算并指定面板尺寸。 + +![](./images/14.2_top_half_bg_create.png) + +我们在该面板下加入一个图像控件,当做我们上半部分UI的背景图片,不妨重命名为`top_half_bg_image`。不过,此时该图像控件使用的的纹理图片是默认图片,我们需要对其进行更改。 + +![](./images/14.2_top_half_bg_change.png) + +我们在“属性“窗格中下拉到底,看到“图片”部分。我们可以在这里为该图像控件添加图片。为了能够使用原版的UI纹理,我们这里可以选择“**原生**”。 + +![](./images/14.2_top_half_bg_select.png) + +![](./images/14.2_dialog_background_hollow_3.png) + +我们在弹出的对话框中点击进入`texture/ui`文件夹,然后我们不妨可以选择`dialog_backgroud_hollow_3.png`作为我们的背景图片。这样,上方可以留出一部分方便我们之后加入窗口的标题。 + +![](./images/14.2_top_half_bg_created.png) + +应用成功后,我们可以看到我们的UI具备了一个常规的样貌。但是此时,我们又生出了一点疑惑。从我们选择的这个纹理图片来看,其形状其实与应用之后编辑器中显示的样子很不一样。这样小的一副图片到底是怎样应用成这么大的一个背景的呢?事实上,这得已于一个被称作**纹理九切片**(**Texture Nineslice**,*纹理九宫图*)的功能。 + +![](./images/14.2_top_half_bg_nineslice.png) + +微软提供了一种原生的适配纹理九切片的数据驱动方式。我们找到该UI纹理原文件所在位置,可以看到,这里存在一个和该纹理文件名同名的JSON文件。游戏便是先自动检测纹理所在文件夹有无这样的同名JSON文件,如果有,便会使用九切片的方式来将该纹理拉伸变形,最终应用成我们看到的样子。我们打开这个`dialog_backgroud_hollow_3.json`文件: + +```json +{ + "nineslice_size": [ + 8, + 23, + 8, + 8 + ], + "base_size": [ + 18, + 33 + ] +} +``` + +可以看到,这里面有两个属性,其一是`nineslice_size`,便是九切片的尺寸;而另一个是`base_size`,直译为基尺寸,其实就是这个纹理文件本身的尺寸。`nineslice_size`是主要指定九切片需要从纹理的哪个位置开始的属性,其四个值代表着纹理在四个方向上的切片点,单位为像素(px)。其中这个例子便代表着从上方23个像素处,其余方向8个像素处进行切片。 + +![](./images/14.2_top_half_bg_slice.png) + +上、下、左、右、左上、右上、左下、右下四个方向皆以我们指定的像素宽度进行切片,然后拉伸剩余的部分,以适配控件的大小。于是,我们便在游戏内看到了这副模样。由于纹理九切片的存在,我们也知道了该控件除了上述八个部分外的最后一部分,中央部分,是在哪个位置开始的了。一般而言,我们可以将该面板上其余的控件放在中央位置,以求的比较美观的UI样式。那么,我们便可以使用九切片的四个尺寸来计算我们中央控件的位置与偏移。 + +我们现在就来计算我们的这个上半部分的面板的尺寸究竟应该为多少比较合适。我们目前并不想让箱子存储太多物品,比如,我们可以只让箱子存储九格物品,即只有一行,一共九个物品槽位。我们知道,物品本身应设置为16×16像素。然后物品的格子大小应该比物品本身大一圈,即各个方向上都大1px,那么,物品槽位本身应该是18×18像素。那么该面板的宽度应该为: +$$ +x=width=8+18\cross9+8=178 +$$ +而该面板的高度为: +$$ +y=height=23+18+8=49 +$$ +然而,我们希望背景图片的内框边缘的那一圈内凹质感的像素与我们处于边界的物品槽位的边缘重合,这样我们的物品槽位就不会出现从视觉上“陷得太深”的感觉。于是我们将背景图边界的计算数分别减一: +$$ +x=width=7+18\cross9+7=176\\ +y=height=22+18+7=47 +$$ +![](./images/14.2_stack_size_change.png) + +由于我们知道,我们最外层的栈面板是竖向排布的,所以整个UI的宽度是不变的,所以我们直接将176应用到我们最外层的栈面板的宽度(**尺寸X**)上。这对应JSON中的`"size" : [ 176, 100 ]` + +![](./images/14.2_top_half_size_change.png) + +然后将我们的上半部面板的宽度设为“**适应**”,高度(尺寸Y)设置为我们计算出的高度47。这对应JSON中的`"size" : [ "default", 47 ]`。`default`对于面板、图片这一类控件而言就等价于`100%`,即继承其父控件的对应值。 + +![](./images/14.2_top_half_bg_size_change.png) + +最后,我们将背景图片的图像控件的宽度和高度也皆设置为“适应”,这对应JSON中的`"size" : [ "default", "default" ]`。 + +现在,我们可以继续添加其他控件了。在添加物品堆叠槽位之前,我们先添加关闭按钮和标题。 + +![](./images/14.2_close_button_create.png) + +我们的关闭按钮无需自行设计,可以直接继承一个原版的关闭按钮控件。我们点击功能区中最后一个按钮,以在编辑器中加入一个继承控件。 + +![](./images/14.2_close_button_created.png) + +我们保持“属性”窗格中命名空间为`common`,然后将两个名称都更改为`close_button`,这相当于`close_button@common.close_button`。然后,我们可以看到一个和原版的关闭按钮一样的按钮出现在了右上角。 + +![](./images/14.2_title_created.png) + +现在,我们为上半部分添加标题,不妨将其重命名为`custom_chest_title`。 + +![](./images/14.2_title_text_changed.png) + +我们在“属性”窗格中找到“文本”部分,将内容修改为我们想要的内容,不妨修改为“自定义箱子”。这样,我们的文本便成为了“自定义箱子”。 + +![](./images/14.2_title_changed.png) + +然后我们将该文本的位置设置到合适的位置。首先,我们将其两个轴向的尺寸都设置为“适应”,也即是`default`默认。对于文本来说,默认并不意味着适应到父级尺寸,相反,其意味着适应为自身文本的最小尺寸,就如上图那样。然后,我们通过修改**锚点**(**Anchor**)使其位置移动到面板的左上角。 + +![](./images/14.2_anchor_relationship.png) + +锚点是一个父控件的子控件用于将自身的合适的位置挂接到父控件的合适的位置上的一种属性。在上图中,不妨设蓝色为父控件,红色的为子控件,则他们两种控件上分别都有九个锚点,分别是一周的八个和中央的一个。默认来说,子控件的中央锚点是挂接在父控件的中央锚点上的,如第一幅图所示。如果我们将子控件左上角的锚点挂接在父控件的中央锚点上,则就会变成第二幅图的样子。最后,如果我们把子控件的左上锚点挂接在父控件的左上锚点上,就是这里第三幅图的样子,也是我们的标题文本想要挂接在面板上所采取的的方式。锚点属性对应到JSON文件有`anchor_from`和`anchor_to`两种。顾名思义,`anchor_from`即“来自的锚点”,也就是父锚点位,`anchor_to`即“挂接至的锚点”,即子锚点位。 + +不过,我们可以看到,我们的文本不能仅使其挂接在父控件的左上角。如果单单是挂接在左上角的话,文本就会太靠近面板的边缘。我们需要将其右移和下移,使其移动到合适的位置。如上面截图中的X、Y所示,整个UI中其实是存在坐标系的,而X轴的正方向就是从左向右,Y轴的正方向就是从上向下。那么我们想让其向右移动,就相当于增加正的X坐标;向下移动,就相当于增加正的Y坐标。所以我们将“**位移X**”和“**位移Y**”分别设置为正的8,这样,我们就成功使其向右且向下偏移了8个像素。 + +现在,我们开始制作箱子的物品栏。 + +![](./images/14.2_chest_slot_stack_create.png) + +我们点击功能区中的“网格”,来创建一个网格控件。网格控件是一种可以使其模板中的元素呈现一种网格排列的控件,非常适合制作由重复的元素组成的界面。 + +![](./images/14.2_chest_slot_stack_created.png) + +我们不妨将其重命名为`custom_chest_slot_grid`。我们可以看到,现在的网格是四个默认图片组成的,且非常丑陋。这是因为我们还没有为其设置网格的模板。 + +![](./images/14.2_chest_slot_stack_select_other_screen.png) + +我们在右侧“属性”窗格中拉到底部,可以看到“内容”属性中显示,我们必须选择一个其他屏幕(*画布*)的控件作为该网格的模板。所以,我们再在该JSON UI中创建一个新的屏幕。一个JSON UI中可以创建多个屏幕,每个屏幕都可以单独发生作用。不过,我们这里创建的屏幕仅仅是为了使我们的模板有一个可以承载的位置(这是由于当前编辑器不支持显示落单的独立控件,只支持显示挂接在屏幕下的控件),所以我们仅仅是在创建一个“技术性屏幕”,不将其实际显示。 + +![](./images/14.2_slot_template_create.png) + +我们点击功能区中的“画布”以创建一个新的屏幕,不妨将其命名为`inventory_slot_template`。 + +![](./images/14.2_slot_panel_create.png) + +我们在其下创建一个面板,作为我们打算最终应用到网格中充当模板的面板,不妨重命名为`inventory_slot_panel`。同时,我们将其尺寸更改为18×18。这是因为我们这一个模板便是一个物品槽位,而物品槽位我们之前已经计算过其尺寸,为18×18。 + +![](./images/14.2_slot_bg_create.png) + +我们再在其下创建一个图像控件,重命名为`inventory_cell_bg_image`,并将其尺寸设置为“适应”。 + +![](./images/14.2_slot_bg_image_changed.png) + +我们选择原版资源包下的`textures/ui/cell_image.png`作为我们的纹理,其也满足九切片条件,被自动拉伸至宛如原版物品槽位的样貌。 + +![](./images/14.2_slot_item_panel_create.png) + +现在,我们再为其添加一个面板,该面板用于承载物品的**渲染器**(**Renderer**)和物品数量文本标签两个控件的面板。渲染器是一种只有控件类型为自定义(`custom`)时才需要的属性。说白了,就是自定义控件这种控件支持我们通过一个渲染器来自定义其上的渲染。而原版中就存在一个渲染器`inventory_item_renderer`可以用于渲染物品。 + +我们将该面板重命名为`inventory_item`,并将两个轴向上的尺寸调整为“跟随父控件的100%然后减少2px”,这是因为我们希望该面板的大小为16×16。这相当于在JSON文件中使用这种写法:`"size" : [ "100% - 2px", "100% - 2px" ]`,其中减号两侧的空格是可省的,甚至,你可以写成`"size" : [ "100% + -2px", "100% + -2px" ]`,同理,加号两侧的空格也是可省的。那么,这种写法是什么意义呢?事实上,控件的尺寸中是可以将**父控件**(**Parent Control**)、**兄弟控件**(**Sibling Control**)或**子控件**(**Child Control**)对应尺寸的百分比作为运算式的一部分参与运算,然后再得出一个最终值使用的。直接使用`%`得到的便是父控件的百分比,使用`%c`得到的是子控件对应方向的总尺寸的百分比,`%cm`是子控件中对应方向中尺寸最大的控件的尺寸的百分比,`%sm`是兄弟控件中对应方向中尺寸最大的控件的尺寸的百分比。这些百分比都可以在编辑器中直接操作和更改。 + +![](./images/14.2_slot_renderer_create.png) + +我们点击功能区中的“物品渲染”便能创建一个带有渲染器`inventory_item_renderer`的自定义类型控件,不妨重命名为`item_renderer`。我们将其尺寸设置为“适应”。 + +![](./images/14.2_slot_label_create.png) + +然后我们依旧打算通过继承一个原版控件来实现物品数量的文本。 + +![](./images/14.2_slot_label_created.png) + +我们将不妨其命名为`stack_count_label`,并继承`common.stack_count_label`。 + +![](./images/14.2_chest_slot_stack_content_changed.png) + +回到我们的网格控件,我们将“内容”设置为我们的面板`inventory_slot_template/inventory_slot_panel`。当然,编辑器中是用`.`显示的,这并不影响我们正确找到控件。同时,我们将网格规模设置为9×1。 + +![](./images/14.2_chest_slot_changed.png) + +最后,我们将该控件设置为从上方锚点挂接到上方锚点,然后向下偏移22个像素,同时尺寸设置为宽度为`100% - 14px`,高度设置为18。这相当于在JSON文件中写入以下属性: + +```json +"anchor_from" : "top_middle", +"anchor_to" : "top_middle", +"offset" : [ 0, 22 ], +"size" : [ "100% - 14px", 18 ] +``` + +这样,我们就完成了上半部分的制作。 + +### 折页 + +在上半部分和下半部分之间,我们需要一个连接。我们接下来制作这个连接用的部分,不妨称之为折页。 + +![](./images/14.2_fold_created.png) + +我们在我们的栈面板下新建一个面板,用于存放我们的折页,不妨命名为`custom_chest_fold`。根据栈面板的流动方式,它自然位于了我们上半部分面板的下方。 + +![](./images/14.2_fold_changed.png) + +与原版物品栏屏幕上的折页对应,我们将其尺寸的宽度设置为“适应”,高度设置为4个像素。 + +![](./images/14.2_fold__bg_created.png) + +现在,我们为其添加背景图片。我们新建一个图像控件,不妨重命名为`fold_bg_image`。然后,我们将其宽度设置的少窄一些,高度比原来的更高一些。这样,我们的图片应用之后就能做到一个比较美观的连接效果。 + +![](./images/14.2_fold__bg_image_changed.png) + +即我们应用了纹理之后便可以看到的连接效果。此时我们应用的是`textures/ui/recipe_back_panel.png`。其同样使用了九切片拉伸。 + +![](./images/14.2_fold__bg_image_view.png) + +但是,我们可以看到,我们的这个控件位于上半部分控件的上方。那么,我们如何使其不遮挡上半部分控件呢?我们有两种办法,其一是取消编辑器的“自动设定层级”。默认状态下,编辑器会为我们自动设置控件的**层级**(**Layer**),一旦我们取消了自动设置层级,编辑器便会要求我们为每一个控件手动设定层级。层级代表着控件之前的遮挡关系,上层的控件会遮挡下层控件。然而,当我们还希望编辑器自动为我们代理设置这一功能时,我们便需要另辟他径。另一种方法不需要我们取消自动设置层级,那便是**裁剪**(**Clip**)功能。 + +![](./images/14.2_fold__bg_image_clipped.png) + +我们勾选“裁剪内容”,以开启裁剪功能。然后,我们设置X和Y轴向上裁剪的宽度和高度。这里,我们只需要在Y上进行裁剪。那么我们将Y设置为4。这样,我们可以看到其进行了恰到好处的裁剪,这对应JSON中的`"allow_clipping": true, "clips_children": true, "clip_offset" : [ 0.0, 4.0 ]`。 + +### 下半部分 + +现在,我们开始制作下半部分。 + +![](./images/14.2_bottom_half_panel_create.png) + +我们创建下半部分的面板,不妨命名为`custom_chest_buttom_half`。并将宽度设置为“适应”,高度稍后将通过计算得出。 + +![](./images/14.2_bottom_half_bg_create.png) + +我们在其下创建一个图像控件作为我们的背景图片,不妨重命名为`buttom_half_bg_image`。同样,我们将宽度和高度都设置为“适应”。 + +![](./images/14.2_bottom_half_bg_iamge_changed.png) + +我们将`textures/ui/dialog_backgroud_opaque.png`设置为图像控件的纹理。现在,我们可以根据该文件的九切片数据计算整个下半部分的高度了。通过对应的九切片JSON文件我们可以得知,该控件上下的切片宽度都为8个像素。所以,我们通过以下算式计算高度: +$$ +y=height=7+18\cross3+4+18+7=90 +$$ +![](./images/14.2_bottom_half_panel_changed.png) + +![](./images/14.2_stack_size_second_change.png) + +我们将下半部分面板的高度修改为90,同时将整个栈面板的高度修改为三个部分相加,即141。这样我们的整个栈面板中的元素便会竖直居中。 + +![](./images/14.2_inv_stack_grid.png) + +![](./images/14.2_inv_stack_grid_changed.png) + +![](./images/14.2_hot_bar_grid.png) + +![](./images/14.2_hot_bar_grid_changed.png) + +最后,就如同我们之前在上半部分加入箱子的物品栏一样,我们通过两个网格来加入玩家的物品栏和快捷栏。我们依旧可以使用我们之前的网格模板,因为他们从样式上没有任何区别。 + +### 检查与修饰 + +至此,我们基本已经完成了UI的绘制,但是,我们需要养成检查的习惯。因为在一次UI绘制过程中,我们将接触很多变量,所以可能会存在一些细小的我们看不到的不尽如人意之处。比如,我们此时便发现了我们的标题文字的颜色可以进一步更改。 + +![](./images/14.2_title_color_change.png) + +我们可以将标题改为黑色,这样它将更加显眼。 + +![](./images/14.2_chest_slot_colection_changed.png) + +我们也可以为各个网格指定合集名,我们可以使用不同的合集名配合绑定来实现不同的网格中不同物品堆叠槽位的交互。当然,在本次示例中我们不打算完善这一过程,因此我们可以跳过这一点。 + +![](./images/14.2_chest_screen_complete.png) + +最终,我们完成了我们UI的绘制。我们可以将完成之后的JSON文件展示在此处: + +```json +{ + "bottom_half_bg_image" : { + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "clip_direction" : "left", + "clip_offset" : [ 0, 0 ], + "clip_ratio" : 0.0, + "clips_children" : false, + "enabled" : true, + "fill" : false, + "grayscale" : false, + "is_new_nine_slice" : false, + "keep_ratio" : true, + "layer" : 13, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "nine_slice_buttom" : 0, + "nine_slice_left" : 0, + "nine_slice_right" : 0, + "nine_slice_top" : 0, + "nineslice_size" : [ 0, 0, 0, 0 ], + "offset" : [ 0, 0 ], + "priority" : 0, + "propagate_alpha" : false, + "size" : [ "default", "default" ], + "texture" : "textures/ui/dialog_background_opaque", + "type" : "image", + "uv" : [ 0, 0 ], + "uv_size" : [ 0, 0 ], + "visible" : true + }, + "custom_chest_bottom_half" : { + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "controls" : [ + { + "bottom_half_bg_image@custom_chest.bottom_half_bg_image" : {} + }, + { + "inventory_stack_grid@custom_chest.inventory_stack_grid" : {} + }, + { + "hot_bar_grid@custom_chest.hot_bar_grid" : {} + } + ], + "enabled" : true, + "layer" : 12, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ 0, 0 ], + "priority" : 0, + "propagate_alpha" : false, + "size" : [ "default", 90 ], + "type" : "panel", + "visible" : true + }, + "custom_chest_fold" : { + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "controls" : [ + { + "fold_bg_image@custom_chest.fold_bg_image" : {} + } + ], + "enabled" : true, + "layer" : 10, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ 0, 0 ], + "priority" : 0, + "propagate_alpha" : false, + "size" : [ "default", 4 ], + "type" : "panel", + "visible" : true + }, + "custom_chest_screen" : { + "absorbs_input" : true, + "always_accepts_input" : false, + "controls" : [ + { + "custom_chest_screen_content@custom_chest.custom_chest_screen_content" : {} + } + ], + "force_render_below" : false, + "is_showing_menu" : true, + "render_game_behind" : true, + "render_only_when_topmost" : true, + "should_steal_mouse" : false, + "type" : "screen" + }, + "custom_chest_screen_content" : { + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "controls" : [ + { + "custom_chest_top_half@custom_chest.custom_chest_top_half" : {} + }, + { + "custom_chest_fold@custom_chest.custom_chest_fold" : {} + }, + { + "custom_chest_bottom_half@custom_chest.custom_chest_bottom_half" : {} + } + ], + "enabled" : true, + "layer" : 0, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ 0, 0 ], + "orientation" : "vertical", + "priority" : 0, + "propagate_alpha" : false, + "size" : [ 176, 141 ], + "type" : "stack_panel", + "use_priority" : false, + "visible" : true + }, + "custom_chest_slot_grid" : { + "alpha" : 1.0, + "anchor_from" : "top_middle", + "anchor_to" : "top_middle", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "collection_name" : "custom_chest_content_collection", + "enabled" : true, + "grid_dimensions" : [ 9.0, 1.0 ], + "grid_item_template" : "custom_chest.inventory_slot_panel", + "grid_rescaling_type" : "none", + "layer" : 5, + "max_size" : [ 0, 0 ], + "maximum_grid_items" : 0, + "min_size" : [ 0, 0 ], + "offset" : [ 0, 22 ], + "priority" : 0, + "propagate_alpha" : true, + "size" : [ "100.0%+-14.0px", 18 ], + "type" : "grid", + "visible" : true + }, + "custom_chest_title" : { + "alpha" : 1.0, + "anchor_from" : "top_left", + "anchor_to" : "top_left", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "color" : [ 0.0, 0.0, 0.0 ], + "enabled" : true, + "font_scale_factor" : 1.0, + "font_size" : "normal", + "font_type" : "smooth", + "layer" : 4, + "line_padding" : 0.0, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ 8, 8 ], + "priority" : 0, + "propagate_alpha" : false, + "shadow" : false, + "size" : [ "default", "default" ], + "text" : "自定义箱子", + "text_alignment" : "center", + "type" : "label", + "visible" : true + }, + "custom_chest_top_half" : { + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "controls" : [ + { + "top_half_bg_image@custom_chest.top_half_bg_image" : {} + }, + { + "close_button@common.close_button" : { + "layer" : 3 + } + }, + { + "custom_chest_title@custom_chest.custom_chest_title" : {} + }, + { + "custom_chest_slot_grid@custom_chest.custom_chest_slot_grid" : {} + } + ], + "enabled" : true, + "layer" : 1, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ 0, 0 ], + "priority" : 0, + "propagate_alpha" : false, + "size" : [ "default", 47 ], + "type" : "panel", + "visible" : true + }, + "fold_bg_image" : { + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "clip_direction" : "left", + "clip_offset" : [ 0.0, 4.0 ], + "clip_ratio" : 0.0, + "clips_children" : true, + "enabled" : true, + "fill" : false, + "grayscale" : false, + "is_new_nine_slice" : false, + "keep_ratio" : true, + "layer" : 11, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "nine_slice_buttom" : 0, + "nine_slice_left" : 0, + "nine_slice_right" : 0, + "nine_slice_top" : 0, + "nineslice_size" : [ 0, 0, 0, 0 ], + "offset" : [ 0, 0 ], + "priority" : 0, + "propagate_alpha" : false, + "size" : [ "100.0%+-6.0px", "100.0%+8.0px" ], + "texture" : "textures/ui/recipe_back_panel", + "type" : "image", + "uv" : [ 0, 0 ], + "uv_size" : [ 0, 0 ], + "visible" : true + }, + "hot_bar_grid" : { + "alpha" : 1.0, + "anchor_from" : "bottom_middle", + "anchor_to" : "bottom_middle", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "collection_name" : "test_grid", + "enabled" : true, + "grid_dimensions" : [ 9.0, 1.0 ], + "grid_item_template" : "custom_chest.inventory_slot_panel", + "grid_rescaling_type" : "none", + "layer" : 19, + "max_size" : [ 0, 0 ], + "maximum_grid_items" : 0, + "min_size" : [ 0, 0 ], + "offset" : [ 0, -7 ], + "priority" : 0, + "propagate_alpha" : true, + "size" : [ "100.0%+-14.0px", 18 ], + "type" : "grid", + "visible" : true + }, + "inventory_cell_bg_image" : { + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "clip_direction" : "left", + "clip_offset" : [ 0, 0 ], + "clip_ratio" : 0.0, + "clips_children" : false, + "enabled" : true, + "fill" : false, + "grayscale" : false, + "is_new_nine_slice" : false, + "keep_ratio" : true, + "layer" : 1, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "nine_slice_buttom" : 0, + "nine_slice_left" : 0, + "nine_slice_right" : 0, + "nine_slice_top" : 0, + "nineslice_size" : [ 0, 0, 0, 0 ], + "offset" : [ 0, 0 ], + "priority" : 0, + "propagate_alpha" : false, + "size" : [ "default", "default" ], + "texture" : "textures/ui/cell_image", + "type" : "image", + "uv" : [ 0, 0 ], + "uv_size" : [ 0, 0 ], + "visible" : true + }, + "inventory_item" : { + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "controls" : [ + { + "item_renderer@custom_chest.item_renderer" : {} + }, + { + "stack_count_label@common.stack_count_label" : { + "layer" : 4 + } + } + ], + "enabled" : true, + "layer" : 2, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ 0, 0 ], + "priority" : 0, + "propagate_alpha" : false, + "size" : [ "100.0%+-2.0px", "100.0%+-2.0px" ], + "type" : "panel", + "visible" : true + }, + "inventory_slot_panel" : { + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "controls" : [ + { + "inventory_cell_bg_image@custom_chest.inventory_cell_bg_image" : {} + }, + { + "inventory_item@custom_chest.inventory_item" : {} + } + ], + "enabled" : true, + "layer" : 0, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ 0, 0 ], + "priority" : 0, + "propagate_alpha" : false, + "size" : [ 18, 18 ], + "type" : "panel", + "visible" : true + }, + "inventory_slot_template" : { + "absorbs_input" : true, + "always_accepts_input" : false, + "controls" : [ + { + "inventory_slot_panel@custom_chest.inventory_slot_panel" : {} + } + ], + "force_render_below" : false, + "is_showing_menu" : true, + "render_game_behind" : true, + "render_only_when_topmost" : true, + "should_steal_mouse" : false, + "type" : "screen" + }, + "inventory_stack_grid" : { + "alpha" : 1.0, + "anchor_from" : "top_middle", + "anchor_to" : "top_middle", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "collection_name" : "test_grid", + "enabled" : true, + "grid_dimensions" : [ 9.0, 3.0 ], + "grid_item_template" : "custom_chest.inventory_slot_panel", + "grid_rescaling_type" : "none", + "layer" : 14, + "max_size" : [ 0, 0 ], + "maximum_grid_items" : 0, + "min_size" : [ 0, 0 ], + "offset" : [ 0, 7 ], + "priority" : 0, + "propagate_alpha" : true, + "size" : [ "100.0%+-14.0px", 54 ], + "type" : "grid", + "visible" : true + }, + "item_renderer" : { + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "enabled" : true, + "layer" : 3, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ 0, 0 ], + "priority" : 0, + "propagate_alpha" : false, + "property_bag" : { + "#item_id_aux" : 131072 + }, + "renderer" : "inventory_item_renderer", + "size" : [ "default", "default" ], + "type" : "custom", + "visible" : true + }, + "namespace" : "custom_chest", + "top_half_bg_image" : { + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "clip_direction" : "left", + "clip_offset" : [ 0, 0 ], + "clip_ratio" : 0.0, + "clips_children" : false, + "enabled" : true, + "fill" : false, + "grayscale" : false, + "is_new_nine_slice" : false, + "keep_ratio" : true, + "layer" : 2, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "nine_slice_buttom" : 0, + "nine_slice_left" : 0, + "nine_slice_right" : 0, + "nine_slice_top" : 0, + "nineslice_size" : [ 0, 0, 0, 0 ], + "offset" : [ 0, 0 ], + "priority" : 0, + "propagate_alpha" : false, + "size" : [ "default", "default" ], + "texture" : "textures/ui/dialog_background_hollow_3", + "type" : "image", + "uv" : [ 0, 0 ], + "uv_size" : [ 0, 0 ], + "visible" : true + } +} +``` + +由于该文件是编辑器自动生成的,所以控件是按照字母序排序的,看起来有些乱。不过,由于我们不打算在JSON层面进行二次修改,所以我们也无需关注这一点。接下来,我们可以将其显示在游戏中。我们只需要知道,我们想要显示的屏幕控件名为`custom_chest.custom_chest_screen`。 + +## 在游戏中显示自定义UI + +现在,以我们在第十三章中制作的自定义箱子为基础,我们一起来制作一个将该JSON UI显示在游戏内的功能。我们知道,我们之前的自定义箱子的脚本位于行为包的`BlockEntityScripts`的文件夹。为了将UI显示在游戏中,我们的自定义UI必须在Python代码中具备一个**代理类**(**Proxy Class**),用于代理该UI的各种功能。就算我们的UI不具备什么功能,它也必须需要代理类的存在才可以正确显示。 + +我们在该文件夹中新建一个`ChestScreenNode.py`,用于代理我们的屏幕节点。由于我们的UI不具备什么功能,我们只需在其中简单地输入如下内容: + +```python +# -*- coding: utf-8 -*- +import mod.client.extraClientApi as clientApi + +ScreenNode = clientApi.GetScreenNodeCls() + + +class Main(ScreenNode): + + def __init__(self, namespace, name, params): + ScreenNode.__init__(self, namespace, name, params) + + def Create(self): + pass + +``` + +然后,我们需要在我们的系统中注册该类,使其注册为UI。在游戏中,所有的自定义UI都必须在游戏内UI全部初始化之后才能注册,所以我们此处需要用到一个引擎系统事件`UiInitFinished`。事实上,我们已经在之前制作存储箱子的方块实体数据功能(上一章挑战中最后的练习)中用到了该事件。我们基于它的回调`chunk_first_loaded`修改如下: + +```python +# -*- coding: UTF-8 -*- +from mod.client.system.clientSystem import ClientSystem +import mod.client.extraClientApi as clientApi +import time + + +class Main(ClientSystem): + + def __init__(self, namespace, system_name): + ClientSystem.__init__(self, namespace, system_name) + namespace = clientApi.GetEngineNamespace() + system_name = clientApi.GetEngineSystemName() + self.ListenForEvent(namespace, system_name, 'ClientBlockUseEvent', self, self.block_used) + self.ListenForEvent(namespace, system_name, 'ChunkLoadedClientEvent', self, self.chunk_first_loaded) + self.ListenForEvent(namespace, system_name, 'UiInitFinished', self, self.chunk_first_loaded) + self.ListenForEvent('tutorial_demo', 'BlockEntityServer', 'OpenChestFinished', self, self.chest_opened) + self.ListenForEvent('tutorial_demo', 'BlockEntityServer', 'InitChestRotation', self, self.chest_rotation) + self.block_interact_cooldown = {} + self.rotation_queue = [] + + def block_used(self, event): + player_id = event['playerId'] + block_name = event['blockName'] + x = event['x'] + y = event['y'] + z = event['z'] + if block_name == 'tutorial_demo:custom_chest': + if player_id not in self.block_interact_cooldown: + self.block_interact_cooldown[player_id] = time.time() + elif time.time() - self.block_interact_cooldown[player_id] < 0.15: + return + else: + self.block_interact_cooldown[player_id] = time.time() + game_comp = clientApi.GetEngineCompFactory().CreateGame(clientApi.GetLevelId()) + dimension_id = game_comp.GetCurrentDimension() + self.NotifyToServer('TryOpenChest', {'dimensionId': dimension_id, 'pos': [x, y, z]}) + + def chest_opened(self, event): + data = event['data'] + block_comp = clientApi.GetEngineCompFactory().CreateBlockInfo(clientApi.GetLevelId()) + for block_data in data: + block_pos = tuple(block_data['pos']) + block_comp.SetBlockEntityMolangValue(block_pos, "variable.mod_states", float(block_data['states'])) + + def chunk_first_loaded(self, event): + self.NotifyToServer('GetChestInit', {'playerId': clientApi.GetLocalPlayerId()}) + # 注册自定义箱子的屏幕为脚本内的UI,第一个参数是注册为的UI的命名空间,第二个参数为注册为的UI的键名,即标识符,第三个参数为代理类所在路径,第四个参数为JSON UI中屏幕控件名 + clientApi.RegisterUI('tutorial_demo', 'custom_chest', 'BlockEntityScripts.ChestScreenNode.Main', 'custom_chest.custom_chest_screen') + + def chest_rotation(self, event): + print event + new_event = {tuple(map(int, k.split(','))): v for k, v in event.items()} + block_comp = clientApi.GetEngineCompFactory().CreateBlockInfo(clientApi.GetLevelId()) + + def rotate_chest(): + index = 0 + count = len(new_event.items()) + for pos, data in new_event.items(): + block_data = block_comp.GetBlock(pos) + if block_data[0] == 'tutorial_demo:custom_chest': + block_comp.SetBlockEntityMolangValue(pos, "variable.mod_rotation", data['rotation']) + block_comp.SetBlockEntityMolangValue(pos, "variable.mod_invert", float(data['invert']) if data['invert'] != 0 else 0.0) + index += 1 + if index == count: + return True + else: + return False + if new_event: + self.rotation_queue.append([rotate_chest, 0]) + + def Update(self): + _die = [] + for index, value in enumerate(self.rotation_queue): + if value[0](): + value[1] += 1 + if value[1] == 2: + _die.append(index) + for i in _die: + self.rotation_queue[i] = None + if self.rotation_queue: + self.rotation_queue = filter(None, self.rotation_queue) +``` + +然后,我们可以在箱子打开时将该UI使用额外API中的`PushScreen`方法压入场景栈。当然,我们也可以使用`CreateUI`来将其显示,但是后者使用的方法是将UI直接创建在当前场景栈屏幕中,并使用大层级将其显示在最上方。而`PushScreen`则是相当于在场景栈中新压入一个屏幕,二者侧重点有所不同。 + +```python +# -*- coding: UTF-8 -*- +from mod.client.system.clientSystem import ClientSystem +import mod.client.extraClientApi as clientApi +import time + + +class Main(ClientSystem): + + def __init__(self, namespace, system_name): + ClientSystem.__init__(self, namespace, system_name) + namespace = clientApi.GetEngineNamespace() + system_name = clientApi.GetEngineSystemName() + self.ListenForEvent(namespace, system_name, 'ClientBlockUseEvent', self, self.block_used) + self.ListenForEvent(namespace, system_name, 'ChunkLoadedClientEvent', self, self.chunk_first_loaded) + self.ListenForEvent(namespace, system_name, 'UiInitFinished', self, self.chunk_first_loaded) + self.ListenForEvent('tutorial_demo', 'BlockEntityServer', 'OpenChestFinished', self, self.chest_opened) + self.ListenForEvent('tutorial_demo', 'BlockEntityServer', 'InitChestRotation', self, self.chest_rotation) + self.block_interact_cooldown = {} + self.rotation_queue = [] + + def block_used(self, event): + player_id = event['playerId'] + block_name = event['blockName'] + x = event['x'] + y = event['y'] + z = event['z'] + if block_name == 'tutorial_demo:custom_chest': + if player_id not in self.block_interact_cooldown: + self.block_interact_cooldown[player_id] = time.time() + elif time.time() - self.block_interact_cooldown[player_id] < 0.15: + return + else: + self.block_interact_cooldown[player_id] = time.time() + game_comp = clientApi.GetEngineCompFactory().CreateGame(clientApi.GetLevelId()) + dimension_id = game_comp.GetCurrentDimension() + self.NotifyToServer('TryOpenChest', {'dimensionId': dimension_id, 'pos': [x, y, z]}) + + def chest_opened(self, event): + data = event['data'] + block_comp = clientApi.GetEngineCompFactory().CreateBlockInfo(clientApi.GetLevelId()) + for block_data in data: + block_pos = tuple(block_data['pos']) + block_comp.SetBlockEntityMolangValue(block_pos, "variable.mod_states", float(block_data['states'])) + # 将注册的屏幕压入场景栈,第三个参数是可以选择传入给代理类的参数,我们此处不传入任何参数 + clientApi.PushScreen('tutorial_demo', 'custom_chest', {}) + # game引擎组件的SimulateTouchWithMouse方法可以用于在PC端防止从屏幕中退出时HUD屏幕变成触控输入模式 + clientApi.GetEngineCompFactory().CreateGame(clientApi.GetLocalPlayerId()).SimulateTouchWithMouse(False) + + def chunk_first_loaded(self, event): + self.NotifyToServer('GetChestInit', {'playerId': clientApi.GetLocalPlayerId()}) + clientApi.RegisterUI('tutorial_demo', 'custom_chest', 'BlockEntityScripts.ChestScreenNode.Main', 'custom_chest.custom_chest_screen') + + def chest_rotation(self, event): + print event + new_event = {tuple(map(int, k.split(','))): v for k, v in event.items()} + block_comp = clientApi.GetEngineCompFactory().CreateBlockInfo(clientApi.GetLevelId()) + + def rotate_chest(): + index = 0 + count = len(new_event.items()) + for pos, data in new_event.items(): + block_data = block_comp.GetBlock(pos) + if block_data[0] == 'tutorial_demo:custom_chest': + block_comp.SetBlockEntityMolangValue(pos, "variable.mod_rotation", data['rotation']) + block_comp.SetBlockEntityMolangValue(pos, "variable.mod_invert", float(data['invert']) if data['invert'] != 0 else 0.0) + index += 1 + if index == count: + return True + else: + return False + if new_event: + self.rotation_queue.append([rotate_chest, 0]) + + def Update(self): + _die = [] + for index, value in enumerate(self.rotation_queue): + if value[0](): + value[1] += 1 + if value[1] == 2: + _die.append(index) + for i in _die: + self.rotation_queue[i] = None + if self.rotation_queue: + self.rotation_queue = filter(None, self.rotation_queue) +``` + +![](./images/14.2_screen_in-game.png) + +我们可以看到,屏幕显示十分正常,符合预期!在下一节中,我们将以原版箱子为例为箱子添加一个箱子锁,并制作一套带有完整逻辑与功能的JSON UI和脚本。我们将使用绑定器在代理类中响应JSON UI中的绑定,并实现功能。 diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/3-挑战:设计箱子锁.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/3-挑战:设计箱子锁.md new file mode 100644 index 0000000..abc6bcb --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/3-挑战:设计箱子锁.md @@ -0,0 +1,2317 @@ +--- +front: https://nie.res.netease.com/r/pic/20220408/77becd17-77ad-4cd6-a559-f75aca7e140d.png +hard: 进阶 +time: 50分钟 +selection: true +--- + +# 挑战:设计箱子锁 + +在这一节中,我们一起设计一个箱子锁。由于我们的自定义箱子还不完善,我们暂时使用原版箱子作为示例。它们在本质原理上是一样的。现在,我们开始我们的挑战。可以点击链接:[箱子锁Demo](https://g79.gdl.netease.com/addonguide-14.zip)下载到完整包体。 + +## 准备资源 + +![](./images/14.3_lock_model.png) + +我们使用自定义方块中的自定义方块实体来完成箱子锁外观的设计。我们通过Blockbench设计好我们的箱子锁模型,并将其导出为JSON文件: + +```json +{ + "format_version": "1.12.0", + "minecraft:geometry": [ + { + "description": { + "identifier": "geometry.chest_lock", + "texture_width": 32, + "texture_height": 32, + "visible_bounds_width": 4, + "visible_bounds_height": 2.5, + "visible_bounds_offset": [0, 0.75, 0] + }, + "bones": [ + { + "name": "root", + "pivot": [-9, 5, 15] + }, + { + "name": "handle", + "parent": "root", + "pivot": [-9, 5, 15], + "cubes": [ + {"origin": [-10, 12, 15], "size": [4, 1, 1], "uv": [0, 9]}, + {"origin": [-7, 6, 15], "size": [1, 6, 1], "uv": [11, 9]}, + {"origin": [-10, 10, 15], "size": [1, 2, 1], "uv": [0, 12]} + ] + }, + { + "name": "butt", + "parent": "root", + "pivot": [-9, 5, 15], + "cubes": [ + {"origin": [-11, 5, 14], "size": [6, 5, 3], "uv": [0, 0]} + ] + } + ] + } + ] +} +``` + +同时,我们为其制作好客户端实体定义文件: + +```json +{ + "format_version":"1.10.0", + "minecraft:client_entity":{ + "description":{ + "identifier":"design:chest_lock", + "materials":{ + "default":"entity_alphatest" + }, + "textures":{ + "default":"textures/entity/chest_lock" + }, + "geometry":{ + "default":"geometry.chest_lock" + }, + "render_controllers":[ + "controller.render.default" + ] + } + } +} +``` + +下面,我们制作自定义方块。我们先创建服务端方块定义: + +```json +{ + "format_version": "1.16", + "minecraft:block": { + "description": { + "identifier": "design:chest_lock", + "register_to_creative_menu": true, + "category": "items" + }, + "components": { + "minecraft:destroy_time": 2, + "minecraft:explosion_resistance": 1000, + "minecraft:block_light_absorption": 0, + "netease:tier": { + "digger": "pickaxe", + "destroy_special": true, + "level": 1 + }, + "netease:aabb": { + "collision": { + "min": [0.3125, 0.3125, 0.875], + "max": [0.6875, 0.8125, 1.0] + }, + "clip": { + "min": [0.3125, 0.3125, 0.875], + "max": [0.6875, 0.8125, 1.0] + } + }, + "netease:render_layer": { + "value": "alpha" + }, + "netease:solid": { + "value": false + }, + "netease:face_directional": { + "type": "direction" + }, + "netease:block_entity": { + "tick": false + }, + "netease:listen_block_remove": { + "value": true + } + } + } +} +``` + +我们使用了`netease:block_entity`设置了方块实体,同时使用了`netease:listen_block_remove`使它的移除事件能够被模组SDK监听。之后,我们再写入客户端方块定义,挂接我们的客户端实体: + +```json +{ + "format_version": [ + 1, + 1, + 0 + ], + "design:chest_lock": { + "sound": "metal", + "client_entity": { + "identifier": "design:chest_lock", + "block_icon": "design:chest_lock", + "hand_model_use_client_entity": true + } + } +} +``` + +然后我们定义我们的地形图集文件: + +```json +{ + "resource_pack_name": "vanilla", + "texture_name": "atlas.terrain", + "texture_data": { + "design:chest_lock": { + "textures": "textures/blocks/chest_lock" + } + } +} +``` + +至此,我们不带有UI和逻辑的箱子锁已经制作完成了。现在,我们为其添加UI。 + +## 制作UI + +通过前两节的学习,我们已经熟练掌握了使用编辑器制作JSON UI。现在,我们一起来简短地浏览一下如何为箱子锁制作一个UI。 + +![](./images/14.3_bg_panel.png) + +首先我们在屏幕下创建一个栈面板。由于我们希望我们最终的UI是上方一行提示语,下方一个锁面板,所以我们可以使用栈面板将其排列。我们希望锁面板中的按键是9×9的网格,而一个按钮占地40×40像素。我们希望使用`textures/ui/achievements_dialog.png`来作为背景图片,根据其对应的九切片JSON文件的描述,我们的面板侧边距为6个像素。因此我们计算出面板的宽度。同理,我们再根据设想计算出面板的高度。 + +![](./images/14.3_main_panel.png) + +我们建立锁的主面板,宽度为“适应”。高度我们这里可以设置为最大子控件尺寸,即`100%cm`。这样,我们可以使其高度保持为我们背景图像控件所设置的高度。 + +![](./images/14.3_main_panel_bg.png) + +![](./images/14.3_main_panel_bg_image.png) + +我们为主面板添加背景,宽度为“适应”,高度为我们计算出的高度。 + +![](./images/14.3_main_panel_close_button.png) + +![](./images/14.3_main_panel_close_button_image.png) + +我们为面板添加关闭按钮。由于我们想实现一种功能——比如要在屏幕中将密码输入完成才可以关闭屏幕,所以我们此处的关闭按钮通过自定义实现,而不继承原版的按钮。我们使用原版的三张关闭按钮纹理,以实现和原版的关闭按钮相一致。 + +![](./images/14.3_main_panel_text_box.png) + +![](./images/14.3_main_panel_text_box_binding.png) + +我们加入一个编辑框控件,用于在我们输入密码时显示密码。我们将“文本输入框”部分中两个绑定按照我们的意愿进行输入。之后,我们将通过脚本SDK对此进行修改。这两个绑定分别需要绑定当编辑框检测到有输入时需要执行的回调和编辑框显示文本的部分需要显示的内容的计算回调。 + +![](./images/14.3_grid_button.png) + +![](./images/14.3_grid_button_imgae.png) + +我们制作密码按键按钮,将其宽度和高度设置为我们预期的40像素。 + +![](./images/14.3_main_panel_grid.png) + +![](./images/14.3_main_panel_grid_property.png) + +我们将按键按钮作为模板应用到网格控件中,设置为3×3。同时,我们将合集名`password_grid`设置到网格中。该网格的模板控件将使用该合集名进行绑定。在此处,我们将通过合集绑定显示按钮上的文本(1、2、3、……、9)。 + +![](./images/14.3_main_panel_title.png) + +在主面板的最后,我们为其添加一个标题。 + +![](./images/14.3_notice_label.png) + +然后,我们的主面板就制作完成了。我们在主面板的上方加入一个提示语标签控件。 + +![](./images/14.3_bg_panel_second_changed.png) + +然后我们再返回来修改栈面板的高度,使整个面板再次回到居中状态。这样,我们的界面便初步绘制完成了。接下来,我们需要将该JSON UI的文件二次手动修改为符合之后我们利用模组API制作逻辑的样子。这也是我们本节中着重讲解的部分之一。我们先来观察我们刚通过编辑器制作完成的JSON UI: + +```json +{ + "button_label" : { + "alpha" : "$control_alpha", + "color" : "$label_color", + "font_scale_factor" : "$label_font_scale_factor", + "font_size" : "$label_font_size", + "font_type" : "smooth", + "layer" : "$label_layer", + "max_size" : [ "100%", "100%" ], + "offset" : "$label_offset", + "shadow" : false, + "text" : "$label_text", + "text_alignment" : "center", + "type" : "label" + }, + "chest_lock_screen" : { + "absorbs_input" : true, + "always_accepts_input" : false, + "controls" : [ + { + "panel_bg@chest_lock.panel_bg" : {} + } + ], + "force_render_below" : false, + "is_showing_menu" : true, + "render_game_behind" : true, + "render_only_when_topmost" : true, + "should_steal_mouse" : false, + "type" : "screen" + }, + "chest_lock_title" : { + "alpha" : 1.0, + "anchor_from" : "top_left", + "anchor_to" : "top_left", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "color" : [ 0.0, 0.0, 0.0 ], + "enabled" : true, + "font_scale_factor" : 1.0, + "font_size" : "normal", + "font_type" : "smooth", + "layer" : 14, + "line_padding" : 0.0, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ 6, 6 ], + "priority" : 0, + "propagate_alpha" : false, + "shadow" : false, + "size" : [ "default", "default" ], + "text" : "金锁子", + "text_alignment" : "center", + "type" : "label", + "visible" : true + }, + "close_button@common.button" : { + "$control_alpha" : 1.0, + "$default_texture" : "textures/ui/close_button_default", + "$hover_texture" : "textures/ui/close_button_pressed", + "$is_new_nine_slice" : false, + "$label_color" : [ 1, 1, 1 ], + "$label_font_scale_factor" : 1.0, + "$label_font_size" : "large", + "$label_layer" : 3, + "$label_offset" : [ 0, 0 ], + "$label_text" : "", + "$nine_slice_buttom" : 0, + "$nine_slice_left" : 0, + "$nine_slice_right" : 0, + "$nine_slice_top" : 0, + "$nineslice_size" : [ 0, 0, 0, 0 ], + "$pressed_button_name" : "%chest_lock.click", + "$pressed_texture" : "textures/ui/close_button_hover", + "$texture_layer" : 2, + "alpha" : 1.0, + "anchor_from" : "top_right", + "anchor_to" : "top_right", + "bindings" : [ + { + "binding_collection_name" : "", + "binding_condition" : "always_when_visible", + "binding_type" : "collection_details" + } + ], + "button_mappings" : [], + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "controls" : [ + { + "default@chest_lock.default" : {} + }, + { + "hover@chest_lock.hover" : {} + }, + { + "pressed@chest_lock.pressed" : {} + }, + { + "button_label@chest_lock.button_label" : {} + } + ], + "draggable" : "not_draggable", + "enabled" : true, + "is_handle_button_move_event" : true, + "layer" : 4, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ -3, 3 ], + "priority" : 0, + "propagate_alpha" : false, + "size" : [ 16, 16 ], + "visible" : true + }, + "default" : { + "alpha" : "$control_alpha", + "is_new_nine_slice" : "$is_new_nine_slice", + "layer" : "$texture_layer", + "nine_slice_buttom" : "$nine_slice_buttom", + "nine_slice_left" : "$nine_slice_left", + "nine_slice_right" : "$nine_slice_right", + "nine_slice_top" : "$nine_slice_top", + "nineslice_size" : "$nineslice_size", + "texture" : "$default_texture", + "type" : "image" + }, + "edit_box_background_default" : { + "is_new_nine_slice" : "$is_new_nine_slice", + "nine_slice_buttom" : "$nine_slice_buttom", + "nine_slice_left" : "$nine_slice_left", + "nine_slice_right" : "$nine_slice_right", + "nine_slice_top" : "$nine_slice_top", + "nineslice_size" : "$nineslice_size", + "texture" : "$edit_box_default_texture", + "type" : "image" + }, + "edit_box_background_hover" : { + "is_new_nine_slice" : "$is_new_nine_slice", + "nine_slice_buttom" : "$nine_slice_buttom", + "nine_slice_left" : "$nine_slice_left", + "nine_slice_right" : "$nine_slice_right", + "nine_slice_top" : "$nine_slice_top", + "nineslice_size" : "$nineslice_size", + "texture" : "$edit_box_hover_texture", + "type" : "image" + }, + "grid_button@common.button" : { + "$control_alpha" : 1.0, + "$default_texture" : "textures/ui/button_borderless_light", + "$hover_texture" : "textures/ui/button_borderless_darkpressed", + "$is_new_nine_slice" : false, + "$label_color" : [ 1, 1, 1 ], + "$label_font_scale_factor" : 0.9399999976158142, + "$label_font_size" : "large", + "$label_layer" : 3, + "$label_offset" : [ 0, 0 ], + "$label_text" : "", + "$nine_slice_buttom" : 0, + "$nine_slice_left" : 0, + "$nine_slice_right" : 0, + "$nine_slice_top" : 0, + "$nineslice_size" : [ 0, 0, 0, 0 ], + "$pressed_button_name" : "%chest_lock.click", + "$pressed_texture" : "textures/ui/button_borderless_lighthover", + "$texture_layer" : 2, + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "bindings" : [ + { + "binding_collection_name" : "", + "binding_condition" : "always_when_visible", + "binding_type" : "collection_details" + } + ], + "button_mappings" : [], + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "controls" : [ + { + "default@chest_lock.default" : {} + }, + { + "hover@chest_lock.hover" : {} + }, + { + "pressed@chest_lock.pressed" : {} + }, + { + "button_label@chest_lock.button_label" : {} + } + ], + "draggable" : "not_draggable", + "enabled" : true, + "is_handle_button_move_event" : true, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ 0, 0 ], + "priority" : 0, + "propagate_alpha" : false, + "size" : [ 40, 40 ], + "visible" : true + }, + "hover" : { + "alpha" : "$control_alpha", + "is_new_nine_slice" : "$is_new_nine_slice", + "layer" : "$texture_layer", + "nine_slice_buttom" : "$nine_slice_buttom", + "nine_slice_left" : "$nine_slice_left", + "nine_slice_right" : "$nine_slice_right", + "nine_slice_top" : "$nine_slice_top", + "nineslice_size" : "$nineslice_size", + "texture" : "$hover_texture", + "type" : "image" + }, + "image_bg" : { + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "clip_direction" : "left", + "clip_offset" : [ 0, 0 ], + "clip_ratio" : 0.0, + "clips_children" : false, + "enabled" : true, + "fill" : false, + "grayscale" : false, + "is_new_nine_slice" : false, + "keep_ratio" : true, + "layer" : 3, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "nine_slice_buttom" : 0, + "nine_slice_left" : 0, + "nine_slice_right" : 0, + "nine_slice_top" : 0, + "nineslice_size" : [ 0, 0, 0, 0 ], + "offset" : [ 0, 0 ], + "priority" : 0, + "propagate_alpha" : false, + "size" : [ "default", 162 ], + "texture" : "textures/ui/achievements_dialog", + "type" : "image", + "uv" : [ 0, 0 ], + "uv_size" : [ 0, 0 ], + "visible" : true + }, + "lock_password@common.text_edit_box" : { + "$edit_box_default_texture" : "textures/ui/edit_box_indent", + "$edit_box_hover_texture" : "textures/ui/edit_box_indent_hover", + "$font_scale_factor" : 1.0, + "$is_new_nine_slice" : false, + "$nine_slice_buttom" : 0, + "$nine_slice_left" : 0, + "$nine_slice_right" : 0, + "$nine_slice_top" : 0, + "$nineslice_size" : [ 0, 0, 0, 0 ], + "$place_holder_text" : "请输入内容", + "$place_holder_text_color" : [ 0.50, 0.50, 0.50 ], + "$text_background_default" : "chest_lock.edit_box_background_default", + "$text_background_hover" : "chest_lock.edit_box_background_hover", + "$text_box_name" : "%chest_lock.edit_password_str", + "$text_box_text_color" : [ 1, 1, 1 ], + "$text_edit_box_content_binding_name" : "#chest_lock.message_content_text_edit_box0", + "alpha" : 1.0, + "anchor_from" : "top_middle", + "anchor_to" : "top_middle", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "draggable" : "not_draggable", + "enabled" : false, + "enabled_newline" : false, + "layer" : 8, + "max_length" : 512, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ 0, 20 ], + "priority" : 0, + "propagate_alpha" : true, + "size" : [ "100.0%+-12.0px", 16 ], + "visible" : true + }, + "namespace" : "chest_lock", + "notice_label" : { + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "color" : [ 1, 1, 1 ], + "enabled" : true, + "font_scale_factor" : 0.9399999976158142, + "font_size" : "large", + "font_type" : "smooth", + "layer" : 1, + "line_padding" : 0.0, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ 0, 0 ], + "priority" : 0, + "propagate_alpha" : false, + "shadow" : true, + "size" : [ "100.0%+0.0px", 36 ], + "text" : "必须先设置密码才能关闭界面!", + "text_alignment" : "center", + "type" : "label", + "visible" : true + }, + "panel_bg" : { + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "controls" : [ + { + "notice_label@chest_lock.notice_label" : {} + }, + { + "panel_main@chest_lock.panel_main" : {} + } + ], + "enabled" : true, + "layer" : 0, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ 0, 0 ], + "orientation" : "vertical", + "priority" : 0, + "propagate_alpha" : false, + "size" : [ 132, 198 ], + "type" : "stack_panel", + "use_priority" : false, + "visible" : true + }, + "panel_main" : { + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "controls" : [ + { + "image_bg@chest_lock.image_bg" : {} + }, + { + "close_button@chest_lock.close_button" : {} + }, + { + "lock_password@chest_lock.lock_password" : {} + }, + { + "password_button_grid@chest_lock.password_button_grid" : {} + }, + { + "chest_lock_title@chest_lock.chest_lock_title" : {} + } + ], + "enabled" : true, + "layer" : 2, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ 0, 0 ], + "priority" : 0, + "propagate_alpha" : false, + "size" : [ "default", "100.0%cm+0.0px" ], + "type" : "panel", + "visible" : true + }, + "password_button_grid" : { + "alpha" : 1.0, + "anchor_from" : "top_middle", + "anchor_to" : "top_middle", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "collection_name" : "test_grid", + "enabled" : true, + "grid_dimensions" : [ 3.0, 3.0 ], + "grid_item_template" : "chest_lock.grid_button", + "grid_rescaling_type" : "none", + "layer" : 10, + "max_size" : [ 0, 0 ], + "maximum_grid_items" : 0, + "min_size" : [ 0, 0 ], + "offset" : [ 0, 36 ], + "priority" : 0, + "propagate_alpha" : true, + "size" : [ "100.0%+-12.0px", 120 ], + "type" : "grid", + "visible" : true + }, + "password_button_grid_content" : { + "absorbs_input" : true, + "always_accepts_input" : false, + "controls" : [ + { + "root@chest_lock.root" : {} + } + ], + "force_render_below" : false, + "is_showing_menu" : true, + "render_game_behind" : true, + "render_only_when_topmost" : true, + "should_steal_mouse" : false, + "type" : "screen" + }, + "pressed" : { + "alpha" : "$control_alpha", + "is_new_nine_slice" : "$is_new_nine_slice", + "layer" : "$texture_layer", + "nine_slice_buttom" : "$nine_slice_buttom", + "nine_slice_left" : "$nine_slice_left", + "nine_slice_right" : "$nine_slice_right", + "nine_slice_top" : "$nine_slice_top", + "nineslice_size" : "$nineslice_size", + "texture" : "$pressed_texture", + "type" : "image" + }, + "root" : { + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "controls" : [ + { + "grid_button@chest_lock.grid_button" : {} + } + ], + "enabled" : true, + "layer" : 0, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ 0, 0 ], + "priority" : 0, + "propagate_alpha" : false, + "size" : [ "100.0%cm+100.0px", "100.0%cm+100.0px" ], + "type" : "panel", + "visible" : true + } +} +``` + +目前的控件是使用了字母序排序。我们先将其排列为易读的顺序: + +```json +{ + "namespace" : "chest_lock", + "chest_lock_screen" : { + "absorbs_input" : true, + "always_accepts_input" : false, + "controls" : [ + { + "panel_bg@chest_lock.panel_bg" : {} + } + ], + "force_render_below" : false, + "is_showing_menu" : true, + "render_game_behind" : true, + "render_only_when_topmost" : true, + "should_steal_mouse" : false, + "type" : "screen" + }, + "panel_bg" : { + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "controls" : [ + { + "notice_label@chest_lock.notice_label" : {} + }, + { + "panel_main@chest_lock.panel_main" : {} + } + ], + "enabled" : true, + "layer" : 0, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ 0, 0 ], + "orientation" : "vertical", + "priority" : 0, + "propagate_alpha" : false, + "size" : [ 132, 198 ], + "type" : "stack_panel", + "use_priority" : false, + "visible" : true + }, + "notice_label" : { + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "color" : [ 1, 1, 1 ], + "enabled" : true, + "font_scale_factor" : 0.9399999976158142, + "font_size" : "large", + "font_type" : "smooth", + "layer" : 1, + "line_padding" : 0.0, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ 0, 0 ], + "priority" : 0, + "propagate_alpha" : false, + "shadow" : true, + "size" : [ "100.0%+0.0px", 36 ], + "text" : "必须先设置密码才能关闭界面!", + "text_alignment" : "center", + "type" : "label", + "visible" : true + }, + "panel_main" : { + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "controls" : [ + { + "image_bg@chest_lock.image_bg" : {} + }, + { + "close_button@chest_lock.close_button" : {} + }, + { + "lock_password@chest_lock.lock_password" : {} + }, + { + "password_button_grid@chest_lock.password_button_grid" : {} + }, + { + "chest_lock_title@chest_lock.chest_lock_title" : {} + } + ], + "enabled" : true, + "layer" : 2, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ 0, 0 ], + "priority" : 0, + "propagate_alpha" : false, + "size" : [ "default", "100.0%cm+0.0px" ], + "type" : "panel", + "visible" : true + }, + "image_bg" : { + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "clip_direction" : "left", + "clip_offset" : [ 0, 0 ], + "clip_ratio" : 0.0, + "clips_children" : false, + "enabled" : true, + "fill" : false, + "grayscale" : false, + "is_new_nine_slice" : false, + "keep_ratio" : true, + "layer" : 3, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "nine_slice_buttom" : 0, + "nine_slice_left" : 0, + "nine_slice_right" : 0, + "nine_slice_top" : 0, + "nineslice_size" : [ 0, 0, 0, 0 ], + "offset" : [ 0, 0 ], + "priority" : 0, + "propagate_alpha" : false, + "size" : [ "default", 162 ], + "texture" : "textures/ui/achievements_dialog", + "type" : "image", + "uv" : [ 0, 0 ], + "uv_size" : [ 0, 0 ], + "visible" : true + }, + "default" : { + "alpha" : "$control_alpha", + "is_new_nine_slice" : "$is_new_nine_slice", + "layer" : "$texture_layer", + "nine_slice_buttom" : "$nine_slice_buttom", + "nine_slice_left" : "$nine_slice_left", + "nine_slice_right" : "$nine_slice_right", + "nine_slice_top" : "$nine_slice_top", + "nineslice_size" : "$nineslice_size", + "texture" : "$default_texture", + "type" : "image" + }, + "hover" : { + "alpha" : "$control_alpha", + "is_new_nine_slice" : "$is_new_nine_slice", + "layer" : "$texture_layer", + "nine_slice_buttom" : "$nine_slice_buttom", + "nine_slice_left" : "$nine_slice_left", + "nine_slice_right" : "$nine_slice_right", + "nine_slice_top" : "$nine_slice_top", + "nineslice_size" : "$nineslice_size", + "texture" : "$hover_texture", + "type" : "image" + }, + "pressed" : { + "alpha" : "$control_alpha", + "is_new_nine_slice" : "$is_new_nine_slice", + "layer" : "$texture_layer", + "nine_slice_buttom" : "$nine_slice_buttom", + "nine_slice_left" : "$nine_slice_left", + "nine_slice_right" : "$nine_slice_right", + "nine_slice_top" : "$nine_slice_top", + "nineslice_size" : "$nineslice_size", + "texture" : "$pressed_texture", + "type" : "image" + }, + "button_label" : { + "alpha" : "$control_alpha", + "color" : "$label_color", + "font_scale_factor" : "$label_font_scale_factor", + "font_size" : "$label_font_size", + "font_type" : "smooth", + "layer" : "$label_layer", + "max_size" : [ "100%", "100%" ], + "offset" : "$label_offset", + "shadow" : false, + "text" : "$label_text", + "text_alignment" : "center", + "type" : "label" + }, + "close_button@common.button" : { + "$control_alpha" : 1.0, + "$default_texture" : "textures/ui/close_button_default", + "$hover_texture" : "textures/ui/close_button_pressed", + "$is_new_nine_slice" : false, + "$label_color" : [ 1, 1, 1 ], + "$label_font_scale_factor" : 1.0, + "$label_font_size" : "large", + "$label_layer" : 3, + "$label_offset" : [ 0, 0 ], + "$label_text" : "", + "$nine_slice_buttom" : 0, + "$nine_slice_left" : 0, + "$nine_slice_right" : 0, + "$nine_slice_top" : 0, + "$nineslice_size" : [ 0, 0, 0, 0 ], + "$pressed_button_name" : "%chest_lock.click", + "$pressed_texture" : "textures/ui/close_button_hover", + "$texture_layer" : 2, + "alpha" : 1.0, + "anchor_from" : "top_right", + "anchor_to" : "top_right", + "bindings" : [ + { + "binding_collection_name" : "", + "binding_condition" : "always_when_visible", + "binding_type" : "collection_details" + } + ], + "button_mappings" : [], + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "controls" : [ + { + "default@chest_lock.default" : {} + }, + { + "hover@chest_lock.hover" : {} + }, + { + "pressed@chest_lock.pressed" : {} + }, + { + "button_label@chest_lock.button_label" : {} + } + ], + "draggable" : "not_draggable", + "enabled" : true, + "is_handle_button_move_event" : true, + "layer" : 4, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ -3, 3 ], + "priority" : 0, + "propagate_alpha" : false, + "size" : [ 16, 16 ], + "visible" : true + }, + "edit_box_background_default" : { + "is_new_nine_slice" : "$is_new_nine_slice", + "nine_slice_buttom" : "$nine_slice_buttom", + "nine_slice_left" : "$nine_slice_left", + "nine_slice_right" : "$nine_slice_right", + "nine_slice_top" : "$nine_slice_top", + "nineslice_size" : "$nineslice_size", + "texture" : "$edit_box_default_texture", + "type" : "image" + }, + "edit_box_background_hover" : { + "is_new_nine_slice" : "$is_new_nine_slice", + "nine_slice_buttom" : "$nine_slice_buttom", + "nine_slice_left" : "$nine_slice_left", + "nine_slice_right" : "$nine_slice_right", + "nine_slice_top" : "$nine_slice_top", + "nineslice_size" : "$nineslice_size", + "texture" : "$edit_box_hover_texture", + "type" : "image" + }, + "lock_password@common.text_edit_box" : { + "$edit_box_default_texture" : "textures/ui/edit_box_indent", + "$edit_box_hover_texture" : "textures/ui/edit_box_indent_hover", + "$font_scale_factor" : 1.0, + "$is_new_nine_slice" : false, + "$nine_slice_buttom" : 0, + "$nine_slice_left" : 0, + "$nine_slice_right" : 0, + "$nine_slice_top" : 0, + "$nineslice_size" : [ 0, 0, 0, 0 ], + "$place_holder_text" : "请输入内容", + "$place_holder_text_color" : [ 0.50, 0.50, 0.50 ], + "$text_background_default" : "chest_lock.edit_box_background_default", + "$text_background_hover" : "chest_lock.edit_box_background_hover", + "$text_box_name" : "%chest_lock.edit_password_str", + "$text_box_text_color" : [ 1, 1, 1 ], + "$text_edit_box_content_binding_name" : "#chest_lock.message_content_text_edit_box0", + "alpha" : 1.0, + "anchor_from" : "top_middle", + "anchor_to" : "top_middle", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "draggable" : "not_draggable", + "enabled" : false, + "enabled_newline" : false, + "layer" : 8, + "max_length" : 512, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ 0, 20 ], + "priority" : 0, + "propagate_alpha" : true, + "size" : [ "100.0%+-12.0px", 16 ], + "visible" : true + }, + "password_button_grid" : { + "alpha" : 1.0, + "anchor_from" : "top_middle", + "anchor_to" : "top_middle", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "collection_name" : "test_grid", + "enabled" : true, + "grid_dimensions" : [ 3.0, 3.0 ], + "grid_item_template" : "chest_lock.grid_button", + "grid_rescaling_type" : "none", + "layer" : 10, + "max_size" : [ 0, 0 ], + "maximum_grid_items" : 0, + "min_size" : [ 0, 0 ], + "offset" : [ 0, 36 ], + "priority" : 0, + "propagate_alpha" : true, + "size" : [ "100.0%+-12.0px", 120 ], + "type" : "grid", + "visible" : true + }, + "chest_lock_title" : { + "alpha" : 1.0, + "anchor_from" : "top_left", + "anchor_to" : "top_left", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "color" : [ 0.0, 0.0, 0.0 ], + "enabled" : true, + "font_scale_factor" : 1.0, + "font_size" : "normal", + "font_type" : "smooth", + "layer" : 14, + "line_padding" : 0.0, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ 6, 6 ], + "priority" : 0, + "propagate_alpha" : false, + "shadow" : false, + "size" : [ "default", "default" ], + "text" : "金锁子", + "text_alignment" : "center", + "type" : "label", + "visible" : true + }, + + "password_button_grid_content" : { + "absorbs_input" : true, + "always_accepts_input" : false, + "controls" : [ + { + "root@chest_lock.root" : {} + } + ], + "force_render_below" : false, + "is_showing_menu" : true, + "render_game_behind" : true, + "render_only_when_topmost" : true, + "should_steal_mouse" : false, + "type" : "screen" + }, + "root" : { + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "controls" : [ + { + "grid_button@chest_lock.grid_button" : {} + } + ], + "enabled" : true, + "layer" : 0, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ 0, 0 ], + "priority" : 0, + "propagate_alpha" : false, + "size" : [ "100.0%cm+100.0px", "100.0%cm+100.0px" ], + "type" : "panel", + "visible" : true + }, + "grid_button@common.button" : { + "$control_alpha" : 1.0, + "$default_texture" : "textures/ui/button_borderless_light", + "$hover_texture" : "textures/ui/button_borderless_darkpressed", + "$is_new_nine_slice" : false, + "$label_color" : [ 1, 1, 1 ], + "$label_font_scale_factor" : 0.9399999976158142, + "$label_font_size" : "large", + "$label_layer" : 3, + "$label_offset" : [ 0, 0 ], + "$label_text" : "", + "$nine_slice_buttom" : 0, + "$nine_slice_left" : 0, + "$nine_slice_right" : 0, + "$nine_slice_top" : 0, + "$nineslice_size" : [ 0, 0, 0, 0 ], + "$pressed_button_name" : "%chest_lock.click", + "$pressed_texture" : "textures/ui/button_borderless_lighthover", + "$texture_layer" : 2, + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "bindings" : [ + { + "binding_collection_name" : "", + "binding_condition" : "always_when_visible", + "binding_type" : "collection_details" + } + ], + "button_mappings" : [], + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "controls" : [ + { + "default@chest_lock.default" : {} + }, + { + "hover@chest_lock.hover" : {} + }, + { + "pressed@chest_lock.pressed" : {} + }, + { + "button_label@chest_lock.button_label" : {} + } + ], + "draggable" : "not_draggable", + "enabled" : true, + "is_handle_button_move_event" : true, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ 0, 0 ], + "priority" : 0, + "propagate_alpha" : false, + "size" : [ 40, 40 ], + "visible" : true + } +} +``` + +我们依次修改我们的控件。 + +```json +"chest_lock_screen@common.base_screen" : { + "$screen_content": "chest_lock.panel_bg" +} +``` + +首先,我们可以将我们的屏幕控件修改为继承自原版的`common.base_screen`。我们在第一节中提到过,这样修改有助于我们的屏幕适配不同类型的异形屏。然后,我们开始为控件添加绑定。 + +```json +"close_button@common.button" : { + "$control_alpha" : 1.0, + "$default_texture" : "textures/ui/close_button_default", + "$hover_texture" : "textures/ui/close_button_pressed", + "$is_new_nine_slice" : false, + "$label_color" : [ 1, 1, 1 ], + "$label_font_scale_factor" : 1.0, + "$label_font_size" : "large", + "$label_layer" : 3, + "$label_offset" : [ 0, 0 ], + "$label_text" : "", + "$nine_slice_buttom" : 0, + "$nine_slice_left" : 0, + "$nine_slice_right" : 0, + "$nine_slice_top" : 0, + "$nineslice_size" : [ 0, 0, 0, 0 ], + "$pressed_button_name" : "#on_close", // 为我们的关闭按钮加入一个#on_close绑定。当然,我们也可以使用“%”开头的绑定 + "$pressed_texture" : "textures/ui/close_button_hover", + "$texture_layer" : 2, + "alpha" : 1.0, + "anchor_from" : "top_right", + "anchor_to" : "top_right", + // 删除此处用不到的bindings和button_mappings + "button_mappings" : [], + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "controls" : [ + { + "default@chest_lock.default" : {} + }, + { + "hover@chest_lock.hover" : {} + }, + { + "pressed@chest_lock.pressed" : {} + }, + { + "button_label@chest_lock.button_label" : {} + } + ], + "draggable" : "not_draggable", + "enabled" : true, + "is_handle_button_move_event" : true, + "layer" : 4, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ -3, 3 ], + "priority" : 0, + "propagate_alpha" : false, + "size" : [ 16, 16 ], + "visible" : true +} +``` + +我们为关闭按钮加入了绑定,同时,我们希望添加几个按钮映射,不过并不是添加在按钮中。 + +```json +"chest_lock_screen@common.base_screen" : { + "$screen_content": "chest_lock.panel_bg", + "button_mappings": [ + { + "from_button_id": "button.menu_inventory_cancel", + "to_button_id": "#on_close", + "mapping_type": "global" + }, + { + "from_button_id": "button.menu_cancel", + "to_button_id": "#on_close", + "mapping_type": "global" + } + ] +} +``` + +我们希望通过全局映射的方式将映射放在屏幕控件中。其中这里的写法意味着我们将可以通过Esc等返回按键来退出这个屏幕。 + +```json +"lock_password@common.text_edit_box" : { + "$edit_box_default_texture" : "textures/ui/edit_box_indent", + "$edit_box_hover_texture" : "textures/ui/edit_box_indent_hover", + "$font_scale_factor" : 1.0, + "$is_new_nine_slice" : false, + "$nine_slice_buttom" : 0, + "$nine_slice_left" : 0, + "$nine_slice_right" : 0, + "$nine_slice_top" : 0, + "$nineslice_size" : [ 0, 0, 0, 0 ], + "$place_holder_text" : "点击下方按钮输入密码", // 稍微更改一下输入框提示文本 + "$place_holder_text_color" : [ 0.50, 0.50, 0.50 ], + "$text_background_default" : "chest_lock.edit_box_background_default", + "$text_background_hover" : "chest_lock.edit_box_background_hover", + "$text_box_name" : "%ChestLock.edit_password_str", // 指定编辑框中的文本框的绑定名 + "$text_box_text_color" : [ 1, 1, 1 ], + "$text_edit_box_binding_condition": "always_when_visible", // 指定文本编辑框内容绑定发生的条件,为当可见时便始终进行绑定 + "$text_edit_box_content_binding_name" : "#chest_lock.message_content_text_edit_box0", // 指定文本编辑框内容的绑定名 + "alpha" : 1.0, + "anchor_from" : "top_middle", + "anchor_to" : "top_middle", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "draggable" : "not_draggable", + "enabled" : false, + "enabled_newline" : false, + "layer" : 8, + "max_length" : 512, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ 0, 20 ], + "priority" : 0, + "propagate_alpha" : true, + "size" : [ "100.0%+-12.0px", 16 ], + "visible" : true +} +``` + +我们为编辑框添加指定两个绑定名,分别用于执行输入逻辑和显示输入内容。 + +```json +"password_button_grid" : { + "alpha" : 1.0, + "anchor_from" : "top_middle", + "anchor_to" : "top_middle", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "$password_collection": "password_grid", // 将password_grid更改为使用变量存储 + "collection_name" : "$password_collection", // 然后将该变量应用到自身的collection_name上以及所有模板控件的合集绑定中 + "enabled" : true, + "grid_dimensions" : [ 3.0, 3.0 ], + "grid_item_template" : "chest_lock.grid_button", + "grid_rescaling_type" : "none", + "layer" : 10, + "max_size" : [ 0, 0 ], + "maximum_grid_items" : 0, + "min_size" : [ 0, 0 ], + "offset" : [ 0, 36 ], + "priority" : 0, + "propagate_alpha" : true, + "size" : [ "100.0%+-12.0px", 120 ], + "type" : "grid", + "visible" : true +} +``` + +我们将输入键盘的网格的合集名属性稍加修改,使其模板控件能够更加方便地进行绑定。 + +```json +"grid_button@common.button" : { + "$control_alpha" : 1.0, + "$default_texture" : "textures/ui/button_borderless_light", + "$hover_texture" : "textures/ui/button_borderless_darkpressed", + "$is_new_nine_slice" : false, + "$label_color" : [ 1, 1, 1 ], + "$label_font_scale_factor" : 1.0, + "$label_font_size" : "large", + "$label_layer" : 3, + "$label_offset" : [ 0, 0 ], + "$label_text" : "", + "$nine_slice_buttom" : 0, + "$nine_slice_left" : 0, + "$nine_slice_right" : 0, + "$nine_slice_top" : 0, + "$nineslice_size" : [ 0, 0, 0, 0 ], + "$pressed_button_name" : "#click", // 为我们的关闭按钮加入一个#click绑定。当然,我们也可以使用“%”开头的绑定 + "$pressed_texture" : "textures/ui/button_borderless_lighthover", + "$texture_layer" : 2, + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "bindings" : [ + { + "binding_collection_name" : "$password_collection", // 指定合集名所在的变量 + "binding_condition" : "always_when_visible", + "binding_type" : "collection_details" + } + ], + // 删除此处用不到的button_mappings + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "controls" : [ + { + "default@chest_lock.default" : {} + }, + { + "hover@chest_lock.hover" : {} + }, + { + "pressed@chest_lock.pressed" : {} + }, + { + "button_label@chest_lock.button_label" : { + "text": "#text", + "bindings": [ + { + "binding_condition": "visible", + "binding_collection_name": "$password_collection", + "binding_type": "collection", + "binding_name_override": "#text", + "binding_name": "#password_number" + } + ] // 此处在继承之后二次修改了button_label控件,加入了一个合集绑定,用于之后为该标签赋予不同的按键文本 + } + } + ], + "draggable" : "not_draggable", + "enabled" : true, + "is_handle_button_move_event" : true, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ 0, 0 ], + "priority" : 0, + "propagate_alpha" : false, + "size" : [ 40, 40 ], + "visible" : true +} +``` + +我们为按钮加入了一个可以用来响应按下该按钮的合集绑定,同时为按钮的文本加入了一个合集绑定,用于显示按钮上的文字信息。至此,我们完成了JSON UI文件的二次修改,修改完成的完整JSON文件如下: + +```json +{ + "namespace" : "chest_lock", + "chest_lock_screen@common.base_screen" : { + "$screen_content": "chest_lock.panel_bg", + "button_mappings": [ + { + "from_button_id": "button.menu_inventory_cancel", + "to_button_id": "#on_close", + "mapping_type": "global" + }, + { + "from_button_id": "button.menu_cancel", + "to_button_id": "#on_close", + "mapping_type": "global" + } + ] + }, + "panel_bg" : { + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "controls" : [ + { + "notice_label@chest_lock.notice_label" : {} + }, + { + "panel_main@chest_lock.panel_main" : {} + } + ], + "enabled" : true, + "layer" : 0, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ 0, 0 ], + "orientation" : "vertical", + "priority" : 0, + "propagate_alpha" : false, + "size" : [ 132, 198 ], + "type" : "stack_panel", + "use_priority" : false, + "visible" : true + }, + "notice_label" : { + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "color" : [ 1, 1, 1 ], + "enabled" : true, + "font_scale_factor" : 1.0, + "font_size" : "large", + "font_type" : "smooth", + "layer" : 1, + "line_padding" : 0.0, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ 0, 0 ], + "priority" : 0, + "propagate_alpha" : false, + "shadow" : true, + "size" : [ "100.0%+0.0px", 36 ], + "text" : "必须先设置密码才能关闭界面!", + "text_alignment" : "center", + "type" : "label", + "visible" : true + }, + "panel_main" : { + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "controls" : [ + { + "image_bg@chest_lock.image_bg" : {} + }, + { + "close_button@chest_lock.close_button" : {} + }, + { + "lock_password@chest_lock.lock_password" : {} + }, + { + "password_button_grid@chest_lock.password_button_grid" : {} + }, + { + "chest_lock_title@chest_lock.chest_lock_title" : {} + } + ], + "enabled" : true, + "layer" : 2, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ 0, 0 ], + "priority" : 0, + "propagate_alpha" : false, + "size" : [ "default", "100.0%cm+0.0px" ], + "type" : "panel", + "visible" : true + }, + "image_bg" : { + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "clip_direction" : "left", + "clip_offset" : [ 0, 0 ], + "clip_ratio" : 0.0, + "clips_children" : false, + "enabled" : true, + "fill" : false, + "grayscale" : false, + "is_new_nine_slice" : false, + "keep_ratio" : true, + "layer" : 3, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "nine_slice_buttom" : 0, + "nine_slice_left" : 0, + "nine_slice_right" : 0, + "nine_slice_top" : 0, + "nineslice_size" : [ 0, 0, 0, 0 ], + "offset" : [ 0, 0 ], + "priority" : 0, + "propagate_alpha" : false, + "size" : [ "default", 162 ], + "texture" : "textures/ui/achievements_dialog", + "type" : "image", + "uv" : [ 0, 0 ], + "uv_size" : [ 0, 0 ], + "visible" : true + }, + "default" : { + "alpha" : "$control_alpha", + "is_new_nine_slice" : "$is_new_nine_slice", + "layer" : "$texture_layer", + "nine_slice_buttom" : "$nine_slice_buttom", + "nine_slice_left" : "$nine_slice_left", + "nine_slice_right" : "$nine_slice_right", + "nine_slice_top" : "$nine_slice_top", + "nineslice_size" : "$nineslice_size", + "texture" : "$default_texture", + "type" : "image" + }, + "hover" : { + "alpha" : "$control_alpha", + "is_new_nine_slice" : "$is_new_nine_slice", + "layer" : "$texture_layer", + "nine_slice_buttom" : "$nine_slice_buttom", + "nine_slice_left" : "$nine_slice_left", + "nine_slice_right" : "$nine_slice_right", + "nine_slice_top" : "$nine_slice_top", + "nineslice_size" : "$nineslice_size", + "texture" : "$hover_texture", + "type" : "image" + }, + "pressed" : { + "alpha" : "$control_alpha", + "is_new_nine_slice" : "$is_new_nine_slice", + "layer" : "$texture_layer", + "nine_slice_buttom" : "$nine_slice_buttom", + "nine_slice_left" : "$nine_slice_left", + "nine_slice_right" : "$nine_slice_right", + "nine_slice_top" : "$nine_slice_top", + "nineslice_size" : "$nineslice_size", + "texture" : "$pressed_texture", + "type" : "image" + }, + "button_label" : { + "alpha" : "$control_alpha", + "color" : "$label_color", + "font_scale_factor" : "$label_font_scale_factor", + "font_size" : "$label_font_size", + "font_type" : "smooth", + "layer" : "$label_layer", + "max_size" : [ "100%", "100%" ], + "offset" : "$label_offset", + "shadow" : false, + "text" : "$label_text", + "text_alignment" : "center", + "type" : "label" + }, + "close_button@common.button" : { + "$control_alpha" : 1.0, + "$default_texture" : "textures/ui/close_button_default", + "$hover_texture" : "textures/ui/close_button_pressed", + "$is_new_nine_slice" : false, + "$label_color" : [ 1, 1, 1 ], + "$label_font_scale_factor" : 1.0, + "$label_font_size" : "large", + "$label_layer" : 3, + "$label_offset" : [ 0, 0 ], + "$label_text" : "", + "$nine_slice_buttom" : 0, + "$nine_slice_left" : 0, + "$nine_slice_right" : 0, + "$nine_slice_top" : 0, + "$nineslice_size" : [ 0, 0, 0, 0 ], + "$pressed_button_name" : "#on_close", + "$pressed_texture" : "textures/ui/close_button_hover", + "$texture_layer" : 2, + "alpha" : 1.0, + "anchor_from" : "top_right", + "anchor_to" : "top_right", + "button_mappings" : [], + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "controls" : [ + { + "default@chest_lock.default" : {} + }, + { + "hover@chest_lock.hover" : {} + }, + { + "pressed@chest_lock.pressed" : {} + }, + { + "button_label@chest_lock.button_label" : {} + } + ], + "draggable" : "not_draggable", + "enabled" : true, + "is_handle_button_move_event" : true, + "layer" : 4, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ -3, 3 ], + "priority" : 0, + "propagate_alpha" : false, + "size" : [ 16, 16 ], + "visible" : true + }, + "edit_box_background_default" : { + "is_new_nine_slice" : "$is_new_nine_slice", + "nine_slice_buttom" : "$nine_slice_buttom", + "nine_slice_left" : "$nine_slice_left", + "nine_slice_right" : "$nine_slice_right", + "nine_slice_top" : "$nine_slice_top", + "nineslice_size" : "$nineslice_size", + "texture" : "$edit_box_default_texture", + "type" : "image" + }, + "edit_box_background_hover" : { + "is_new_nine_slice" : "$is_new_nine_slice", + "nine_slice_buttom" : "$nine_slice_buttom", + "nine_slice_left" : "$nine_slice_left", + "nine_slice_right" : "$nine_slice_right", + "nine_slice_top" : "$nine_slice_top", + "nineslice_size" : "$nineslice_size", + "texture" : "$edit_box_hover_texture", + "type" : "image" + }, + "lock_password@common.text_edit_box" : { + "$edit_box_default_texture" : "textures/ui/edit_box_indent", + "$edit_box_hover_texture" : "textures/ui/edit_box_indent_hover", + "$font_scale_factor" : 1.0, + "$is_new_nine_slice" : false, + "$nine_slice_buttom" : 0, + "$nine_slice_left" : 0, + "$nine_slice_right" : 0, + "$nine_slice_top" : 0, + "$nineslice_size" : [ 0, 0, 0, 0 ], + "$place_holder_text" : "点击下方按钮输入密码", + "$place_holder_text_color" : [ 0.50, 0.50, 0.50 ], + "$text_background_default" : "chest_lock.edit_box_background_default", + "$text_background_hover" : "chest_lock.edit_box_background_hover", + "$text_box_name" : "%ChestLock.edit_password_str", + "$text_box_text_color" : [ 1, 1, 1 ], + "$text_edit_box_binding_condition": "always_when_visible", + "$text_edit_box_content_binding_name" : "#chest_lock.message_content_text_edit_box0", + "alpha" : 1.0, + "anchor_from" : "top_middle", + "anchor_to" : "top_middle", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "draggable" : "not_draggable", + "enabled" : false, + "enabled_newline" : false, + "layer" : 8, + "max_length" : 512, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ 0, 20 ], + "priority" : 0, + "propagate_alpha" : true, + "size" : [ "100.0%+-12.0px", 16 ], + "visible" : true + }, + "password_button_grid" : { + "alpha" : 1.0, + "anchor_from" : "top_middle", + "anchor_to" : "top_middle", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "$password_collection": "password_grid", + "collection_name" : "$password_collection", + "enabled" : true, + "grid_dimensions" : [ 3.0, 3.0 ], + "grid_item_template" : "chest_lock.grid_button", + "grid_rescaling_type" : "none", + "layer" : 10, + "max_size" : [ 0, 0 ], + "maximum_grid_items" : 0, + "min_size" : [ 0, 0 ], + "offset" : [ 0, 36 ], + "priority" : 0, + "propagate_alpha" : true, + "size" : [ "100.0%+-12.0px", 120 ], + "type" : "grid", + "visible" : true + }, + "chest_lock_title" : { + "alpha" : 1.0, + "anchor_from" : "top_left", + "anchor_to" : "top_left", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "color" : [ 0.0, 0.0, 0.0 ], + "enabled" : true, + "font_scale_factor" : 1.0, + "font_size" : "normal", + "font_type" : "smooth", + "layer" : 14, + "line_padding" : 0.0, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ 6, 6 ], + "priority" : 0, + "propagate_alpha" : false, + "shadow" : false, + "size" : [ "default", "default" ], + "text" : "金锁子", + "text_alignment" : "center", + "type" : "label", + "visible" : true + }, + + "password_button_grid_content" : { + "absorbs_input" : true, + "always_accepts_input" : false, + "controls" : [ + { + "root@chest_lock.root" : {} + } + ], + "force_render_below" : false, + "is_showing_menu" : true, + "render_game_behind" : true, + "render_only_when_topmost" : true, + "should_steal_mouse" : false, + "type" : "screen" + }, + "root" : { + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "controls" : [ + { + "grid_button@chest_lock.grid_button" : {} + } + ], + "enabled" : true, + "layer" : 0, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ 0, 0 ], + "priority" : 0, + "propagate_alpha" : false, + "size" : [ "100.0%cm+100.0px", "100.0%cm+100.0px" ], + "type" : "panel", + "visible" : true + }, + "grid_button@common.button" : { + "$control_alpha" : 1.0, + "$default_texture" : "textures/ui/button_borderless_light", + "$hover_texture" : "textures/ui/button_borderless_darkpressed", + "$is_new_nine_slice" : false, + "$label_color" : [ 1, 1, 1 ], + "$label_font_scale_factor" : 1.0, + "$label_font_size" : "large", + "$label_layer" : 3, + "$label_offset" : [ 0, 0 ], + "$label_text" : "", + "$nine_slice_buttom" : 0, + "$nine_slice_left" : 0, + "$nine_slice_right" : 0, + "$nine_slice_top" : 0, + "$nineslice_size" : [ 0, 0, 0, 0 ], + "$pressed_button_name" : "#click", + "$pressed_texture" : "textures/ui/button_borderless_lighthover", + "$texture_layer" : 2, + "alpha" : 1.0, + "anchor_from" : "center", + "anchor_to" : "center", + "bindings" : [ + { + "binding_collection_name" : "$password_collection", + "binding_condition" : "always_when_visible", + "binding_type" : "collection_details" + } + ], + "clip_offset" : [ 0, 0 ], + "clips_children" : false, + "controls" : [ + { + "default@chest_lock.default" : {} + }, + { + "hover@chest_lock.hover" : {} + }, + { + "pressed@chest_lock.pressed" : {} + }, + { + "button_label@chest_lock.button_label" : { + "text": "#text", + "bindings": [ + { + "binding_condition": "visible", + "binding_collection_name": "$password_collection", + "binding_type": "collection", + "binding_name_override": "#text", + "binding_name": "#password_number" + } + ] + } + } + ], + "draggable" : "not_draggable", + "enabled" : true, + "is_handle_button_move_event" : true, + "max_size" : [ 0, 0 ], + "min_size" : [ 0, 0 ], + "offset" : [ 0, 0 ], + "priority" : 0, + "propagate_alpha" : false, + "size" : [ 40, 40 ], + "visible" : true + } +} +``` + +## 编写逻辑 + +接下来,我们使用模组API来编写箱子锁的逻辑。首先,我们这里引入一种分模块的管理方式。我们将脚本的文件夹和文件排布如下: + +```shell +ChestLockBeh +├─ChestLockScripts +│ │ __init__.py +│ │ modMain.py +│ │ +│ ├─client +│ │ │ __init__.py +│ │ └─ listener.py +│ ├─server +│ │ │ __init__.py +│ │ └─ listener.py +│ ├─common +│ │ │ __init__.py +│ │ └─ ChestLock.py +│ └─config +│ │ __init__.py +│ │ helper.py +│ └─ sys.py +└─ #其他文件夹 +``` + +我们的`modMain.py`写入如下: + +```python +# -*- coding: utf-8 -*- +import mod.client.extraClientApi as clientApi +import mod.server.extraServerApi as serverApi +from mod.common.mod import Mod + +from ChestLockScripts.config.sys import MOD_NAME, MOD_VERSION, MOD_CLIENT_SYSTEM, MOD_SERVER_SYSTEM + + +# config的MOD_NAME、MOD_VERSION来自config文件下的sys.py +@Mod.Binding(name=MOD_NAME, version=MOD_VERSION) +class ModMain(object): + + def __init__(self): + pass + + @Mod.InitServer() + def init_server(self): + for data in MOD_SERVER_SYSTEM: + mod_name = data[0] + system_name = data[1] + system_cls_path = data[2] + serverApi.RegisterSystem(mod_name, system_name, system_cls_path) + + @Mod.DestroyServer() + def destroy_server(self): + pass + + @Mod.InitClient() + def init_client(self): + for data in MOD_CLIENT_SYSTEM: + mod_name = data[0] + system_name = data[1] + system_cls_path = data[2] + clientApi.RegisterSystem(mod_name, system_name, system_cls_path) + + @Mod.DestroyClient() + def destroy_client(self): + pass + +``` + +其中,我们的各种常量来自于`ChestLockScripts.config.sys`模块。这种通过常量来控制各种常用配置属性的方式也是我们想介绍的另一种比较好的编程方式。养成这种习惯有助于你在编程过程中更加得心应手。 + +我们可以先在`sys.py`中写入常量: + +```python +# -*- coding: utf-8 -*- + +""" +使用全局变量的好处是: +① 其他文件只访问变量,而所赋的值集中在一个文件内,便于管理,修其一动全身 +""" + +# Mod名字 +MOD_NAME = "design:chest_lock" +# Mod版本 +MOD_VERSION = "1.0.0" + +# Ui 键 +MOD_UI_NAME = 'ChestLockUi' + +# UI Class +MOD_UI_CLS_PATH = 'ChestLockScripts.common.ChestLock.Main' + +# 监听器系统-系统名字 +MOD_LISTENER_SYSTEM_NAME = 'ListenerSystem' + +# +MOD_INIT_LOCK_EVENT = 'ChestLockInitPassword' + +# 保存金锁子密码时触发的事件 +MOD_PASSWORD_SAVE_EVENT = 'ChestLockSavePassword' + +# +MOD_LOCK_CACHE_SAVE_EVENT = 'ClientCacheLockData' + +# 校验金锁子密码时触发的事件 +MOD_PASSWORD_CHECK_EVENT = 'ChestLockPasswordCheck' + +# 客户端自定义系统 +MOD_CLIENT_SYSTEM = [ + (MOD_NAME, MOD_LISTENER_SYSTEM_NAME, 'ChestLockScripts.client.listener.ListenerSystem') +] + +# 服务端自定义系统 +MOD_SERVER_SYSTEM = [ + (MOD_NAME, MOD_LISTENER_SYSTEM_NAME, 'ChestLockScripts.server.listener.ListenerSystem') +] + +``` + +下面,我们来写入`client/listener.py`和`server/listener.py`。首先是客户端: + +```python +# -*- coding: utf-8 -*- +import mod.client.extraClientApi as clientApi +import ChestLockScripts.config.helper as helper +import time +from ChestLockScripts.config.sys import MOD_NAME, MOD_UI_NAME, MOD_UI_CLS_PATH, MOD_LISTENER_SYSTEM_NAME, MOD_LOCK_CACHE_SAVE_EVENT, MOD_INIT_LOCK_EVENT + +ClientSystem = clientApi.GetClientSystemCls() +comp_factory = clientApi.GetEngineCompFactory() + + +class ListenerSystem(ClientSystem): + + def __init__(self, namespace, system_name): + ClientSystem.__init__(self, namespace, system_name) + self.engine_namespace = clientApi.GetEngineNamespace() + self.engine_system_name = clientApi.GetEngineSystemName() + self.ListenForEvent(self.engine_namespace, self.engine_system_name, 'UiInitFinished', self, self.ui_init) + comp_factory.CreateItem(clientApi.GetLevelId()).GetUserDataInEvent('ClientItemUseOnEvent') + # “*”代表任意附加值 + comp_factory.CreateBlockUseEventWhiteList(clientApi.GetLevelId()).AddBlockItemListenForUseEvent('minecraft:chest:*') + self.ListenForEvent(self.engine_namespace, self.engine_system_name, 'ClientItemUseOnEvent', self, self.on_item_use_on) + self.ListenForEvent(self.engine_namespace, self.engine_system_name, 'PlayerTryDestroyBlockClientEvent', self, self.destroy_block_before) + self.ListenForEvent(self.engine_namespace, self.engine_system_name, 'ClientBlockUseEvent', self, self.on_interact_block) + self.ListenForEvent(MOD_NAME, MOD_LISTENER_SYSTEM_NAME, MOD_LOCK_CACHE_SAVE_EVENT, self, self.cache_lock) + self.ListenForEvent(MOD_NAME, MOD_LISTENER_SYSTEM_NAME, MOD_INIT_LOCK_EVENT, self, self.init_lock) + self.player_id = clientApi.GetLocalPlayerId() + self.local_lock_cache = {} + + def ui_init(self, event): + clientApi.RegisterUI(MOD_NAME, MOD_UI_NAME, MOD_UI_CLS_PATH, 'chest_lock.chest_lock_screen') + + def on_item_use_on(self, event): + item_dict = event['itemDict'] + block_name = event['blockName'] + face = event['face'] + pos = (event['x'], event['y'], event['z']) + if item_dict and item_dict['newItemName'] == 'design:chest_lock': + if block_name != 'minecraft:chest': + event['ret'] = True + else: + block_data = comp_factory.CreateBlockInfo(clientApi.GetLevelId()).GetBlock(pos) + if block_data[1] != face: + event['ret'] = True + + def destroy_block_before(self, event): + full_name = event['blockName'] + pos = (event['x'], event['y'], event['z']) + aux_data = event['auxData'] + if full_name == 'minecraft:chest': + offset_pos = helper.get_real_lock_pos(pos, aux_data) + block_data = comp_factory.CreateBlockInfo(clientApi.GetLevelId()).GetBlock(offset_pos) + if block_data and block_data[0] == 'design:chest_lock': + event['cancel'] = True + + def on_interact_block(self, event): + player_id = event['playerId'] + block_name = event['blockName'] + pos = (event['x'], event['y'], event['z']) + if block_name == 'minecraft:chest': + dimension_id = comp_factory.CreateGame(self.player_id).GetCurrentDimension() + # 箱子的方块旋转数据取值区间在2~5之间 + for i in xrange(2, 6): + offset_pos = helper.get_real_lock_pos(pos, i) + block_data = comp_factory.CreateBlockInfo(clientApi.GetLevelId()).GetBlock(offset_pos) + if block_data and block_data[0] == 'design:chest_lock': + # 当房客玩家第一次进入主机游戏时 + if pos in self.local_lock_cache: + owner = self.local_lock_cache[pos]['owner'] + if owner != player_id: + if dimension_id == self.local_lock_cache[pos]['dimension_id']: + guests = self.local_lock_cache[pos]['guests'] + if player_id not in guests: + event['cancel'] = True + else: + event['cancel'] = True + break + + def cache_lock(self, event): + owner = event['owner'] + pos = event['pos'] + dimension_id = event['dimensionId'] + guests = event.get('guests', []) + key = event.get('key', '') + if tuple(pos) not in self.local_lock_cache: + self.local_lock_cache[tuple(pos)] = {'owner': owner, 'guests': set(), 'dimension_id': dimension_id} + self.local_lock_cache[tuple(pos)]['owner'] = owner + self.local_lock_cache[tuple(pos)]['guests'].update(guests) + if key: + screen = clientApi.GetTopScreen() + if screen and getattr(screen, 'my_ui_name') and screen.my_ui_name == 'chest_lock': + clientApi.PopScreen() + + def init_lock(self, event): + dimension_id = event['dimensionId'] + pos = event['pos'] + face = event['face'] + self.create_chest_lock_ui( + { + 'dimension': dimension_id, + 'pos': pos, + 'client': self, + 'face': face, + 'ui_name': 'chest_lock' + } + ) + + # 创建自定义箱子密码界面 + def create_chest_lock_ui(self, params): + clientApi.PushScreen(MOD_NAME, MOD_UI_NAME, params) + comp_factory.CreateGame(self.player_id).SimulateTouchWithMouse(False) + +``` + +然后是服务端: + +```python +# -*- coding: utf-8 -*- +import mod.server.extraServerApi as serverApi +from mod.common.minecraftEnum import ItemPosType +import ChestLockScripts.config.helper as helper +from ChestLockScripts.config.sys import MOD_NAME, MOD_LISTENER_SYSTEM_NAME, MOD_PASSWORD_SAVE_EVENT, MOD_LOCK_CACHE_SAVE_EVENT, MOD_PASSWORD_CHECK_EVENT, MOD_INIT_LOCK_EVENT + +ServerSystem = serverApi.GetServerSystemCls() +comp_factory = serverApi.GetEngineCompFactory() + + +class ListenerSystem(ServerSystem): + + def __init__(self, namespace, system_name): + ServerSystem.__init__(self, namespace, system_name) + self.engine_namespace = serverApi.GetEngineNamespace() + self.engine_system_name = serverApi.GetEngineSystemName() + self.ListenForEvent(self.engine_namespace, self.engine_system_name, 'CommandEvent', self, self.on_command) + comp_factory.CreateItem(serverApi.GetLevelId()).GetUserDataInEvent('ServerItemUseOnEvent') + # 将原版方块放入交互方块事件里 + comp_factory.CreateBlockUseEventWhiteList(serverApi.GetLevelId()).AddBlockItemListenForUseEvent('minecraft:chest:*') + # 监听放置金锁子的事件 + self.ListenForEvent(self.engine_namespace, self.engine_system_name, 'ServerItemUseOnEvent', self, self.on_item_use_on) + self.ListenForEvent(self.engine_namespace, self.engine_system_name, 'ServerPlayerTryDestroyBlockEvent', self, self.destroy_block_before) + # 监听方块交互事件 + self.ListenForEvent(self.engine_namespace, self.engine_system_name, 'ServerBlockUseEvent', self, self.on_interact_block) + self.ListenForEvent(MOD_NAME, MOD_LISTENER_SYSTEM_NAME, MOD_PASSWORD_SAVE_EVENT, self, self.password_saved) + self.ListenForEvent(MOD_NAME, MOD_LISTENER_SYSTEM_NAME, MOD_PASSWORD_CHECK_EVENT, self, self.password_checked) + self.chest_lock_guest_cache = {} + + def on_item_use_on(self, event): + player_id = event['entityId'] + item_dict = event['itemDict'] + block_name = event['blockName'] + dimension_id = event['dimensionId'] + face = event['face'] + pos = (event['x'], event['y'], event['z']) + if item_dict and item_dict['newItemName'] == 'design:chest_lock': + tip_comp = comp_factory.CreateGame(serverApi.GetLevelId()) + if block_name != 'minecraft:chest': + tip_comp.SetOneTipMessage(player_id, '金锁子必须放在箱子正面!') + event['ret'] = True + else: + # 获取箱子的转向方块数据 + chest_state = comp_factory.CreateBlockState(serverApi.GetLevelId()).GetBlockStates(pos, dimension_id) + if chest_state['facing_direction'] != face: + tip_comp.SetOneTipMessage(player_id, '金锁子只能放在箱子正面!') + event['ret'] = True + else: + self.NotifyToClient(player_id, MOD_INIT_LOCK_EVENT, {'face': face, 'pos': pos, 'dimensionId': dimension_id}) + + def destroy_block_before(self, event): + full_name = event['fullName'] + pos = (event['x'], event['y'], event['z']) + dimension = event['dimensionId'] + player_id = event['playerId'] + if full_name == 'minecraft:chest': + chest_state = comp_factory.CreateBlockState(serverApi.GetLevelId()).GetBlockStates(pos, dimension) + offset_pos = helper.get_real_lock_pos(pos, chest_state['facing_direction']) + block_data = comp_factory.CreateBlockInfo(serverApi.GetLevelId()).GetBlockNew(offset_pos, dimension) + if block_data and block_data['name'] == 'design:chest_lock': + event['cancel'] = True + if full_name == 'design:chest_lock': + block_entity_data_comp = comp_factory.CreateBlockEntityData(serverApi.GetLevelId()) + block_entity_data = block_entity_data_comp.GetBlockEntityData(dimension, pos) + if block_entity_data['password'] and block_entity_data['owner']: + if player_id != block_entity_data['owner']: + event['cancel'] = True + else: + connect_pos = block_entity_data['connect_pos'] + del self.chest_lock_guest_cache[tuple(connect_pos)] + + def on_interact_block(self, event): + player_id = event['playerId'] + block_name = event['blockName'] + pos = (event['x'], event['y'], event['z']) + dimension_id = event['dimensionId'] + if block_name == 'minecraft:chest': + face = comp_factory.CreateBlockState(serverApi.GetLevelId()).GetBlockStates(pos, dimension_id)['facing_direction'] + offset_pos = helper.get_real_lock_pos(pos, face) + block_dict = comp_factory.CreateBlockInfo(serverApi.GetLevelId()).GetBlockNew(offset_pos, dimension_id) + if block_dict and block_dict['name'] == 'design:chest_lock': + # 获取箱子旁的金锁子的方块数据 + block_entity_data = comp_factory.CreateBlockEntityData(serverApi.GetLevelId()).GetBlockEntityData(dimension_id, offset_pos) + if player_id != block_entity_data['owner']: + if pos not in self.chest_lock_guest_cache or player_id not in self.chest_lock_guest_cache[pos]: + event['cancel'] = True + self.NotifyToClient(player_id, MOD_INIT_LOCK_EVENT, + {'face': -1, 'pos': offset_pos, 'dimensionId': dimension_id}) + else: + if pos not in self.chest_lock_guest_cache: + self.chest_lock_guest_cache[pos] = [] + + def password_saved(self, event): + player_id = event['playerId'] + dimension_id = event['dimension'] + # 这里的坐标是连接的箱子坐标 + pos = event['pos'] + face = event['face'] + block_entity_data_comp = comp_factory.CreateBlockEntityData(serverApi.GetLevelId()) + block_entity_data = block_entity_data_comp.GetBlockEntityData(dimension_id, helper.get_real_lock_pos(pos, face)) + block_entity_data['password'] = event['password'] + block_entity_data['owner'] = player_id + block_entity_data['connect_pos'] = pos + self.BroadcastToAllClient(MOD_LOCK_CACHE_SAVE_EVENT, {'pos': pos, 'owner': player_id, 'dimensionId': dimension_id}) + + def password_checked(self, event): + player_id = event['playerId'] + dimension_id = event['dimension'] + # 这里的坐标是金锁子的坐标 + pos = event['pos'] + password = event['password'] + block_entity_data_comp = comp_factory.CreateBlockEntityData(serverApi.GetLevelId()) + block_entity_data = block_entity_data_comp.GetBlockEntityData(dimension_id, tuple(pos)) + if password == block_entity_data['password']: + chest_pos = block_entity_data['connect_pos'] + if tuple(chest_pos) not in self.chest_lock_guest_cache: + self.chest_lock_guest_cache[tuple(chest_pos)] = [player_id] + else: + self.chest_lock_guest_cache[tuple(chest_pos)].append(player_id) + self.BroadcastToAllClient(MOD_LOCK_CACHE_SAVE_EVENT, + {'pos': chest_pos, 'owner': player_id, 'dimensionId': dimension_id, 'guests': self.chest_lock_guest_cache[tuple(chest_pos)], + 'key': player_id}) + + # 事件绑定:触发以/开头的聊天输入文字 + def on_command(self, event): + entity_id = event['entityId'] + command = event['command'] + if command.startswith('/lock '): + command_args = command.split(' ') + tip_comp = comp_factory.CreateGame(serverApi.GetLevelId()) + if len(command_args) == 2 and command_args[1].isdigit(): + if len(command_args[1]) != 4: + tip_comp.SetOneTipMessage(entity_id, '/lock指令执行失败,金锁子密码必须为4个数字!') + event['cancel'] = True + return + item_comp = comp_factory.CreateItem(entity_id) + carried_item = item_comp.GetEntityItem(ItemPosType.CARRIED, 0, True) + if carried_item and carried_item['newItemName'] == 'design:chest_lock': + item_comp.SetCustomName(carried_item, command_args[1]) + item_comp.SpawnItemToPlayerCarried(carried_item, entity_id) + tip_comp.SetOneTipMessage(entity_id, '/lock指令执行成功,已将金锁子钥匙密码改为:{}'.format(command_args[1])) + else: + tip_comp.SetOneTipMessage(entity_id, '/lock指令执行失败,你手上拿的不是金锁子!') + else: + tip_comp.SetOneTipMessage(entity_id, '/lock指令执行失败,金锁子密码必须为4个数字。指令示例: /lock 0123') + event['cancel'] = True + +``` + +在这两个文件中,我们主要想实现箱子锁的三个基本功能,分别是:客户端的`ClientItemUseOnEvent`(`self.on_item_use_on`)和服务端的`ServerItemUseOnEvent`(`self.on_item_use_on`)实现的锁主人通过箱子锁的方块物品在箱子方块上使用时触发的“上锁”逻辑,客户端的`PlayerTryDestroyBlockClientEvent`(`self.destroy_block_before`)和服务端的`ServerPlayerTryDestroyBlockEvent`(`self.destroy_block_before`)实现的破坏锁时释放锁的各种信息的摧毁逻辑,还有客户端的`ClientBlockUseEvent`(`self.on_interact_block`)和服务端的`ServerBlockUseEvent`(`self.on_interact_block`)实现的其他非主人玩家对箱子右键(使用该箱子方块)时触发的必须输对密码才能“开锁”的逻辑。 + +然后,客户端的`UiInitFinished`(`self.ui_init`)、`MOD_LOCK_CACHE_SAVE_EVENT`即我们自定义的事件名`ChestLockPasswordCheck` (`self.cache_lock`)和`MOD_INIT_LOCK_EVENT`即`ChestLockInitPassword` (`self.init_lock`)用于控制客户端上UI的屏幕的显示以及设置目前能够访问箱子的玩家的缓存。其中设置缓存是为了当玩家对箱子方块进行使用时,我们希望直接在客户端拦截那些”不能使用“的玩家。否则,由于网络传输有一定延迟,客户端中的玩家可能会出现看到箱子被使用随即又被服务端传回的数据包制止该次使用所造成的画面波动。 + +服务端的`MOD_PASSWORD_SAVE_EVENT`即`ChestLockSavePassword` (`self.password_saved`)和`MOD_PASSWORD_CHECK_EVENT`即`ChestLockPasswordCheck` (`self.password_checked`)用于在服务端保存密码和检测密码是否正确。将密码保存至服务端并在服务端直接检测有助于防止客户端玩家的作弊。 + +其中我们可以看到客户端和服务端都多次使用了`helper`下的`get_real_lock_pos`。这是一种将常用的函数单独注册到一个文件中的做法,方便在各个其他类中进行导入和使用。`helper.py`: + +```python +# -*- coding: utf-8 -*- + +def get_real_lock_pos(pos, face): + """ + 通过箱子的坐标和点击箱子放置金锁子的面,获得金锁子的坐标 + :param pos: 箱子坐标 + :param face: 点击箱子的面 + :return: 金锁子坐标Tuple + """ + face_offset_position = { + 2: [0, 0, -1], # 箱子面向世界北方 + 3: [0, 0, 1], # 箱子面向世界南方 + 4: [-1, 0, 0], # 箱子面向世界西方 + 5: [1, 0, 0] # 箱子面向世界东方 + } + offset = face_offset_position[face] + return pos[0] + offset[0], pos[1], pos[2] + offset[2] +``` + +我们在本节的关注点在于客户端UI的部分。我们可以在客户端系统的`ui_init`方法中看到我们自定义UI的注册,然后在`init_lock`方法中进行了UI的屏幕在场景栈中的压入,最后在`cache_lock`方法中实现了UI的屏幕在场景栈中的弹出。其中,我们在压入屏幕时可以看到,我们的客户端系统向UI的代理类中发送了很多参数(即第三个参数`params`中的字典中的参数)。我们下面就来看看我们的代理类代理是如何处理这些参数,以及如何进行和JSON UI系统的绑定的。代理类`ChestLock.py`: + +```python +# -*- coding: utf-8 -*- +import mod.client.extraClientApi as clientApi +from ChestLockScripts.config.sys import MOD_PASSWORD_SAVE_EVENT, MOD_PASSWORD_CHECK_EVENT + +ScreenNode = clientApi.GetScreenNodeCls() +ViewBinder = clientApi.GetViewBinderCls() +ViewRequest = clientApi.GetViewViewRequestCls() + +# 所有继承base_screen的自定义界面都携带一串长的路径 +ROOT_PANEL = '/variables_button_mappings_and_controls/safezone_screen_matrix/inner_matrix/safezone_screen_panel/root_screen_panel' + + +class Main(ScreenNode): + + def __init__(self, namespace, name, param): + ScreenNode.__init__(self, namespace, name, param) + self.password = [] + self.client_sys = param['client'] + self.dimension = param['dimension'] + self.pos = param['pos'] + self.face = param['face'] + self.my_ui_name = param['ui_name'] + + @ViewBinder.binding(ViewBinder.BF_EditChanged | ViewBinder.BF_EditFinished) + def edit_password_str(self, event): + text = event['Text'] + password = [int(i) for i in list(text)] + self.password = password + return ViewRequest.Refresh + + @ViewBinder.binding(ViewBinder.BF_BindString, '#chest_lock.message_content_text_edit_box0') + def return_password_str(self): + return ''.join(self.password) + + @ViewBinder.binding_collection(ViewBinder.BF_BindString, 'password_grid', '#password_number') + def return_password_number(self, index): + return '{}'.format(index + 1) + + # 绑定回调函数:点击关闭UI按钮 + @ViewBinder.binding(ViewBinder.BF_ButtonClickUp, '#on_close') + def close(self, event): + if len(self.password) == 4: + # 通过判断点击面是否存在来判断玩家是否第一次放置了金锁子 + if self.face != -1: + self.client_sys.NotifyToServer(MOD_PASSWORD_SAVE_EVENT, {'pos': list(self.pos), 'dimension': self.dimension, + 'face': self.face, 'playerId': clientApi.GetLocalPlayerId(), + 'password': self.password}) + clientApi.PopScreen() + else: + self.client_sys.NotifyToServer(MOD_PASSWORD_CHECK_EVENT, + {'pos': list(self.pos), 'dimension': self.dimension, + 'playerId': clientApi.GetLocalPlayerId(), + 'password': self.password}) + return ViewRequest.Refresh + + # 绑定回调函数:点击9键按钮 + @ViewBinder.binding(ViewBinder.BF_ButtonClickUp, '#click') + def click(self, event): + """ + :param event: dict({'#collection_index': 按在第几个网格按钮下,返回它的所处在的合集位置数字}) + :return: + """ + # 按下数字按钮时,最多只能保存4个元素在密码列表里 + if len(self.password) < 4: + self.password.append(str(event['#collection_index'] + 1)) + return ViewRequest.Refresh + + def Create(self): + if self.face == -1: + self.GetBaseUIControl(ROOT_PANEL + '/notice_label').asLabel().SetText('请输入正确密码以打开金锁子!') + +``` + +我们可以看到,我们所有的绑定都需要在这里得到实现,之后它们才能具备一定的功能。每个绑定所在的回调函数都必须存在一个绑定器的修饰器函数,即`ViewBinder.binding`或`ViewBinder.binding_collection`。我们首先来看编辑框的两个绑定,由于文本框绑定名是我们直接指定了回调函数`edit_password_str`的,所以这里的修饰器中不需要再提及`#`开头的绑定名。`edit_password_str`在修饰器的第一个参数中指定了**绑定旗标**(**Bind Flag**)`BF_EditChanged`**或**`BF_EditFinished`,这意味着该文本框每次发生输入或者输入完成时都会触发该回调。这里传入了一个事件数据`event`,它是一个字典。其中它有一个`Text`字段,用于存储该输入框截止到目前为止已输入的文本。我们将这个文本存储在代理类的成员中,最后返回一个`ViewRequest.Refresh`用于请求刷新玩家的视图。而`return_password_str`回调函数通过绑定器绑定了`#chest_lock.message_content_text_edit_box0`,并使用了字符串的`BF_BindString`绑定旗标。字符串旗标是一种计算用绑定旗标,只要绑定条件允许,他会无时无刻来计算其中的值。这里就是将我们代理类中存储的密码文本字符串返回,用于显示在UI上。 + +`return_password_number`回调函数通过合集绑定器绑定了一个`password_grid`合集下的绑定`#password_number`,这正是我们的密码按键的文本标签所使用的的绑定。这里我们可以通过合集绑定给到的事件数据来指定合集中不同的按钮具备不同的文本。事实上,如果仅适用合集绑定而不是用合集详情绑定,那么事件数据本身仅仅只有一个合集中元素的索引序号,也就是说,我们只能在回调函数中得到当前元素在合集中是第几个,不过这就足够了。序号是从0开始的,我们将其加一之后返回。由于我们这里也是使用的字符串绑定旗标,我们可以返回该值作为按钮上的按键文本。 + +`close`回调函数是我们的关闭按钮绑定的回调函数,这里使用了`BF_ButtonClickUp`旗标,即按钮按下时触发。这里我们广泛用到了客户端唤起UI时传来的参数信息。首先我们通过`face`参数判定了是否是第一次放置箱子锁出现的UI。因为物品使用的事件和方块使用的事件存在一个本质区别,那便是方块使用事件不会返回方块的使用面向。这也是理所当然的,因为方块使用时我们关注的是方块本身,我们不关心方块是从哪个方向被使用的,从哪个方向被使用是该方块与物品的交互所应该关心的事。所以,方块的使用事件的数据中没有方块的面向。我们可以通过这个区别来做到两种情况下分别向服务端通知不同的事件,以使服务端调起不同的回调。这里,我们将客户端传来的参数信息在UI关闭时一并又发送给了服务端,方便服务端存储密码或验证密码的正确性。 + +`click`回调函数是我们合集中代表每个密码按键的按钮按下时执行的回调。由于我们给按钮写入了一个合集详情的不带绑定名的绑定,我们因此能够使按钮的其他绑定获得到更详细的事件数据,其中便包括属性袋中的`#collection_index`属性绑定名存储的数据。这个数据其实和上面我们的`#password_number`绑定得到的事件数据一样,是该元素的索引序号。我们将该序号加一之后追加到我们的密码文本里,用于实现按按钮输入数字。 + +最后,我们在`Create`生命周期函数中判断是否为非第一次打开放置箱子锁,非第一次时我们改变`notice_label`控件的文本,以适配不同的场景。我们只需要注意,由于我们使用了`common.base_screen`的继承,如第一节中所述,我们的`notice_label`控件所在的路径便改变了,这里的代码也说明了这一点。还需要注意,这里使用控件对象API获取基UI控件时使用的路径是相对于屏幕的,所以屏幕本身的控件名并没有出现在控件中,毕竟该代理类本身就是该屏幕的代理类,我们无需再传入屏幕的控件名。 + +![](./images/14.3_in-game_1.png) + +我们在游戏中在箱子上放置一个箱子锁,可以看到,我们的箱子锁屏幕成功打开了。 + +![](./images/14.3_in-game_2.png) + +我们为其设置密码,之后,我们便可以使用右上的关闭按钮将其关闭了。 + +![](./images/14.3_in-game_3.png) + +此时,我们知道我们的密码已经被保存进入了服务端。 + +![](./images/14.3_in-game_5.png) + +此时,我们可以打开箱子了,我们不妨向箱子中放置一个物品。接下来,我们测试非箱子的主人的玩家加入后的表现。 + +![](./images/14.3_in-game_8.png) + +我们可以通过在我的世界开发工作台中的“工具箱”中的“ModPC开发包”按钮再手动打开一个电脑开发版的实例。我们切换到“好友”标签页。可以看到局域网内存在一个我们另一个已开启实例的游戏。这样,我们便可以实现“伪”的联机测试。但这样的联机测试和真正的联机测试本质上并没有什么两样。 + +![](./images/14.3_in-game_4.png) + +我们用新的开发包实例进入游戏后尝试打开箱子,可以看到我们被箱子锁屏幕阻拦了,同时上方的文本发生了改变。 + +![](./images/14.3_in-game_7.png) + +我们输入正确的密码,然后箱子如预期般得以打开了!至此,从方块本身到方块实体到UI,我们完成了一个完整的箱子锁的制作。开发者们可以在课后自行尝试和消化,通过多看原版JSON UI代码和UI部分模组API的API文档的方法来学习使用更多的JSON UI强大的功能! diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/README.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_add_panel.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_add_panel.png new file mode 100644 index 0000000..0439e65 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_add_panel.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_button_added.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_button_added.png new file mode 100644 index 0000000..e49359e Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_button_added.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_change_screen_name.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_change_screen_name.png new file mode 100644 index 0000000..7fb6ced Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_change_screen_name.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_create_ui.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_create_ui.png new file mode 100644 index 0000000..c039206 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_create_ui.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_create_ui_input.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_create_ui_input.png new file mode 100644 index 0000000..ef92405 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_create_ui_input.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_hud_screen.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_hud_screen.png new file mode 100644 index 0000000..6151486 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_hud_screen.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_image_added.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_image_added.png new file mode 100644 index 0000000..efd8b5f Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_image_added.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_inherit.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_inherit.png new file mode 100644 index 0000000..e984002 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_inherit.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_interface_editor.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_interface_editor.png new file mode 100644 index 0000000..dfa8c81 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_interface_editor.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_interface_editor_created.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_interface_editor_created.png new file mode 100644 index 0000000..9896b7e Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_interface_editor_created.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_inv_screen.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_inv_screen.png new file mode 100644 index 0000000..326c172 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_inv_screen.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_native_image.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_native_image.png new file mode 100644 index 0000000..6141224 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_native_image.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_open_folder.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_open_folder.png new file mode 100644 index 0000000..0d19e7f Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_open_folder.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_panel_added.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_panel_added.png new file mode 100644 index 0000000..c781c0f Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_panel_added.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_property.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_property.png new file mode 100644 index 0000000..c9977dc Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_property.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_property_image.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_property_image.png new file mode 100644 index 0000000..0adcd24 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_property_image.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_title_screen.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_title_screen.png new file mode 100644 index 0000000..1bb84af Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_title_screen.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_ui_folder.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_ui_folder.png new file mode 100644 index 0000000..6d3d303 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_ui_folder.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_var.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_var.png new file mode 100644 index 0000000..05cda52 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.1_var.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_anchor_relationship.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_anchor_relationship.png new file mode 100644 index 0000000..5f435ea Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_anchor_relationship.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_bottom_half_bg_create.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_bottom_half_bg_create.png new file mode 100644 index 0000000..03debcd Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_bottom_half_bg_create.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_bottom_half_bg_iamge_changed.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_bottom_half_bg_iamge_changed.png new file mode 100644 index 0000000..e2c58ba Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_bottom_half_bg_iamge_changed.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_bottom_half_panel_changed.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_bottom_half_panel_changed.png new file mode 100644 index 0000000..74084d5 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_bottom_half_panel_changed.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_bottom_half_panel_create.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_bottom_half_panel_create.png new file mode 100644 index 0000000..9bc4b48 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_bottom_half_panel_create.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_chest_screen_complete.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_chest_screen_complete.png new file mode 100644 index 0000000..602b0f9 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_chest_screen_complete.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_chest_slot_changed.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_chest_slot_changed.png new file mode 100644 index 0000000..ceab78d Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_chest_slot_changed.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_chest_slot_colection_changed.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_chest_slot_colection_changed.png new file mode 100644 index 0000000..60e500f Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_chest_slot_colection_changed.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_chest_slot_stack_content_changed.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_chest_slot_stack_content_changed.png new file mode 100644 index 0000000..4176440 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_chest_slot_stack_content_changed.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_chest_slot_stack_create.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_chest_slot_stack_create.png new file mode 100644 index 0000000..c68b051 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_chest_slot_stack_create.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_chest_slot_stack_created.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_chest_slot_stack_created.png new file mode 100644 index 0000000..715cca0 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_chest_slot_stack_created.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_chest_slot_stack_select_other_screen.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_chest_slot_stack_select_other_screen.png new file mode 100644 index 0000000..c9e40c5 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_chest_slot_stack_select_other_screen.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_close_button_create.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_close_button_create.png new file mode 100644 index 0000000..c91391f Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_close_button_create.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_close_button_created.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_close_button_created.png new file mode 100644 index 0000000..640a23d Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_close_button_created.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_create_ui.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_create_ui.png new file mode 100644 index 0000000..11bc195 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_create_ui.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_created_ui.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_created_ui.png new file mode 100644 index 0000000..5594103 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_created_ui.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_dialog_background_hollow_3.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_dialog_background_hollow_3.png new file mode 100644 index 0000000..5d567b7 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_dialog_background_hollow_3.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_fold__bg_created.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_fold__bg_created.png new file mode 100644 index 0000000..4126ec3 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_fold__bg_created.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_fold__bg_image_changed.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_fold__bg_image_changed.png new file mode 100644 index 0000000..9f5b99c Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_fold__bg_image_changed.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_fold__bg_image_clipped.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_fold__bg_image_clipped.png new file mode 100644 index 0000000..1069535 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_fold__bg_image_clipped.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_fold__bg_image_view.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_fold__bg_image_view.png new file mode 100644 index 0000000..16eb524 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_fold__bg_image_view.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_fold_changed.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_fold_changed.png new file mode 100644 index 0000000..8c76cae Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_fold_changed.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_fold_created.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_fold_created.png new file mode 100644 index 0000000..de253c2 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_fold_created.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_hot_bar_grid.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_hot_bar_grid.png new file mode 100644 index 0000000..25654f6 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_hot_bar_grid.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_hot_bar_grid_changed.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_hot_bar_grid_changed.png new file mode 100644 index 0000000..77de3ed Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_hot_bar_grid_changed.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_inv_stack_grid.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_inv_stack_grid.png new file mode 100644 index 0000000..deea7ad Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_inv_stack_grid.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_inv_stack_grid_changed.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_inv_stack_grid_changed.png new file mode 100644 index 0000000..54d3691 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_inv_stack_grid_changed.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_screen_in-game.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_screen_in-game.png new file mode 100644 index 0000000..1b0a9b0 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_screen_in-game.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_screen_rename.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_screen_rename.png new file mode 100644 index 0000000..c56414d Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_screen_rename.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_slot_bg_create.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_slot_bg_create.png new file mode 100644 index 0000000..b308aad Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_slot_bg_create.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_slot_bg_image_changed.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_slot_bg_image_changed.png new file mode 100644 index 0000000..d962760 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_slot_bg_image_changed.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_slot_item_panel_create.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_slot_item_panel_create.png new file mode 100644 index 0000000..dcb92e9 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_slot_item_panel_create.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_slot_label_create.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_slot_label_create.png new file mode 100644 index 0000000..72d24b4 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_slot_label_create.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_slot_label_created.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_slot_label_created.png new file mode 100644 index 0000000..16501c2 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_slot_label_created.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_slot_panel_create.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_slot_panel_create.png new file mode 100644 index 0000000..f1b3336 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_slot_panel_create.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_slot_renderer_create.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_slot_renderer_create.png new file mode 100644 index 0000000..7858a6b Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_slot_renderer_create.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_slot_template_create.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_slot_template_create.png new file mode 100644 index 0000000..9035cea Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_slot_template_create.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_stack_create.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_stack_create.png new file mode 100644 index 0000000..9983899 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_stack_create.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_stack_size_change.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_stack_size_change.png new file mode 100644 index 0000000..2dee51b Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_stack_size_change.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_stack_size_second_change.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_stack_size_second_change.png new file mode 100644 index 0000000..0e57174 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_stack_size_second_change.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_title_changed.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_title_changed.png new file mode 100644 index 0000000..146e6a8 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_title_changed.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_title_color_change.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_title_color_change.png new file mode 100644 index 0000000..d1a55f4 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_title_color_change.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_title_created.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_title_created.png new file mode 100644 index 0000000..d34e35e Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_title_created.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_title_text_changed.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_title_text_changed.png new file mode 100644 index 0000000..b39a3f4 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_title_text_changed.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_top_half_bg_change.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_top_half_bg_change.png new file mode 100644 index 0000000..67b41ee Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_top_half_bg_change.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_top_half_bg_create.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_top_half_bg_create.png new file mode 100644 index 0000000..279511f Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_top_half_bg_create.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_top_half_bg_created.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_top_half_bg_created.png new file mode 100644 index 0000000..f8a00b6 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_top_half_bg_created.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_top_half_bg_nineslice.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_top_half_bg_nineslice.png new file mode 100644 index 0000000..4357eb0 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_top_half_bg_nineslice.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_top_half_bg_select.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_top_half_bg_select.png new file mode 100644 index 0000000..f326da7 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_top_half_bg_select.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_top_half_bg_size_change.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_top_half_bg_size_change.png new file mode 100644 index 0000000..d2bd986 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_top_half_bg_size_change.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_top_half_bg_slice.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_top_half_bg_slice.png new file mode 100644 index 0000000..ab6495f Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_top_half_bg_slice.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_top_half_create.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_top_half_create.png new file mode 100644 index 0000000..f96336c Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_top_half_create.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_top_half_size_change.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_top_half_size_change.png new file mode 100644 index 0000000..cd7db7d Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.2_top_half_size_change.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_bg_panel.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_bg_panel.png new file mode 100644 index 0000000..1de4447 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_bg_panel.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_bg_panel_second_changed.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_bg_panel_second_changed.png new file mode 100644 index 0000000..3031c68 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_bg_panel_second_changed.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_grid_button.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_grid_button.png new file mode 100644 index 0000000..63cd87a Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_grid_button.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_grid_button_imgae.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_grid_button_imgae.png new file mode 100644 index 0000000..fd0c3b7 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_grid_button_imgae.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_in-game_1.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_in-game_1.png new file mode 100644 index 0000000..5b6c342 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_in-game_1.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_in-game_2.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_in-game_2.png new file mode 100644 index 0000000..b5f5655 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_in-game_2.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_in-game_3.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_in-game_3.png new file mode 100644 index 0000000..8f3a1d6 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_in-game_3.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_in-game_4.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_in-game_4.png new file mode 100644 index 0000000..a515df9 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_in-game_4.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_in-game_5.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_in-game_5.png new file mode 100644 index 0000000..cbc0ebc Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_in-game_5.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_in-game_7.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_in-game_7.png new file mode 100644 index 0000000..5841579 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_in-game_7.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_in-game_8.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_in-game_8.png new file mode 100644 index 0000000..0d801bb Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_in-game_8.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_lock_model.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_lock_model.png new file mode 100644 index 0000000..ca8fac1 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_lock_model.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_main_panel.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_main_panel.png new file mode 100644 index 0000000..68dda40 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_main_panel.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_main_panel_bg.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_main_panel_bg.png new file mode 100644 index 0000000..e83906c Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_main_panel_bg.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_main_panel_bg_image.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_main_panel_bg_image.png new file mode 100644 index 0000000..4229228 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_main_panel_bg_image.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_main_panel_close_button.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_main_panel_close_button.png new file mode 100644 index 0000000..0b86647 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_main_panel_close_button.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_main_panel_close_button_image.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_main_panel_close_button_image.png new file mode 100644 index 0000000..0adbb11 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_main_panel_close_button_image.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_main_panel_grid.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_main_panel_grid.png new file mode 100644 index 0000000..f27f19f Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_main_panel_grid.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_main_panel_grid_property.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_main_panel_grid_property.png new file mode 100644 index 0000000..4b06d4c Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_main_panel_grid_property.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_main_panel_text_box.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_main_panel_text_box.png new file mode 100644 index 0000000..8a438c1 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_main_panel_text_box.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_main_panel_text_box_binding.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_main_panel_text_box_binding.png new file mode 100644 index 0000000..faac153 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_main_panel_text_box_binding.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_main_panel_title.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_main_panel_title.png new file mode 100644 index 0000000..4192b89 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_main_panel_title.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_notice_label.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_notice_label.png new file mode 100644 index 0000000..c3813d4 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/13-创建UI/images/14.3_notice_label.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/0-摘要.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/0-摘要.md new file mode 100644 index 0000000..7fc913d --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/0-摘要.md @@ -0,0 +1,16 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 5分钟 +--- + +# 摘要 + +在本章中,我们将一起学习**维度**(**Dimension**)的制作。了解维度的构成,以及如何制作维度中的**生物群系**(**Biome**)。 + +- 在第一节(*开始创建新维度*)中,我们将使用编辑器配置一个维度,学习维度的基本功能。 +- 在第二节(*设计维度传送门*)中,我们将设计一个维度传送门方块,用于传送至我们的新维度。 +- 在第三节(*改变维度的生物群系*)中,我们将学习如何改变我们新维度的生物群系。 +- 在最后一节(*挑战:海洋世界*)中,我们将一起制作一个挑战,创造一个海洋世界。 + +本章关键词:维度 地形生成器 生物群系 生物群系源 噪声 梯度 倍频 高度图 传送门 气候 转化 海洋世界 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/1-开始创建新维度.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/1-开始创建新维度.md new file mode 100644 index 0000000..06791e9 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/1-开始创建新维度.md @@ -0,0 +1,68 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 10分钟 +--- + +# 开始创建新维度 + +**维度**(**Dimension**)是世界中重要的组成部分。在一个世界中,我们往往存在多个维度。在原版游戏中,主世界、下界和末路之地便是三个原生的维度。每个维度的都是相互独立的,其中的玩家、实体、方块和各种逻辑都互不干涉。我们可以认为,一个维度便是一个独立的“世界”,而一个玩家可以通过种种方式在这些“世界”中进行穿梭跳跃,来回于不同的维度。 + +在模组开发中,如果开发者能够向游戏中新增一些新的自定义维度,那么模组的可玩性将大大增强。在本节中,我们就通过我的世界开发工作台的编辑器来新建一个维度。 + +## 使用编辑器配置新维度 + +![](./images/15.1_dim_create.png) + +![](./images/15.1_dim_creating.png) + +我们打开编辑器,在创建配置中找到“**维度**”,即可创建一个**维度配置文件**(**Dimension Config File**)。这个文件将告诉编辑器和游戏我们的模组都自定义了哪些维度。 + +![](./images/15.1_dim_created.png) + +我们可以看到,这个维度配置文件中包含一个当前模组已创建的维度列表和**维度标识符**(**Dimension Identifier**,简称**维度ID**),列表中已经自动为我们创建了一个维度。我们可以通过修改维度名和维度ID来修改维度的信息。该文件的JSON内容如下: + +```json +{ + "netease:dimension": { + "modId": "tutorial_demo", + "modDimensionId": [ + 1688560817 + ] + } +} +``` + +在编辑器中,我们每通过“+”按钮添加了一个维度,编辑器都会在行为包的`netease_dimension`文件夹中创建一个`dm.json`的文件,其中``代表该维度的数字ID,比如上面的1688560817。编辑器在创建维度配置文件时会自动创建一个空白的维度文件,比如上面演示中编辑器就除了`dimension_config.json`文件之外还创建了一个`dm1688560817.json`文件。维度是使用一个数字ID作为其唯一标识符的,所以如果不同的模组使用了相同的数字ID,将会造成存档和生成器冲突。所以编辑器在创建维度时会尽可能随机生成一个维度ID。目前玩家可以自定义的维度ID区间为[22, 2147483647]。 + +类似于`dm1688560817.json`的文件是我们的**维度信息文件**(**Dimension Info File**)。每个维度信息文件都存储着一个维度必要的信息数据。维度的信息数据是使用组件的形式存储的。我们来看这里编辑器自动给我们生成的`dm1688560817.json`文件: + +```json +{ + "format_version": "1.14.0", + "netease:dimension_info": { + "components": { + "netease:dimension_type": "minecraft:overworld", + "netease:generator_noise": {} + } + } +} +``` + +我们可以看到,格式版本为`1.14.0`,模式标识符为`netease:dimension_info`。在组件中,我们可以填写如下几种主要组件: + +- `netease:dimension_type`:字符串类型,维度所继承的原版维度的类型。如果该文件正在修改的是原版维度,这个组件是无效的。这里可以填写`minecraft:overworld`、`minecraft:nether`或`minecraft:the_end`。 +- `netease:generator_noise`、`netease:generator_flat`或`netease:generator_legacy`:空对象,世界**地形生成器**(**Terrain Generator**)的类型,分别是无限世界的噪声生成器、平坦世界的平坦生成器和旧世界的旧版生成器。对于三种生成器我们至多只能填写一个。如果该文件正在修改的是原版维度,该组件也是无效的。 +- `netease:ban_vanilla_feature`:空对象,阻止原版特征(又译地物)的生成。如不欲阻止,则无需填写该对象。 +- `netease:spawn_biomes`:字符串数组,该维度中允许玩家出生的生物群系的标识符列表。 +- `netease:biome_source`:对象,用于定义该维度的**生物群系源**(**Biome Source**)。定义了生物群系源的维度将自动开启中国版自定义生物群系的生成流程,若没有定义生物群系源,则使用国际版原版依赖气候和噪声的生物群系生成流程。 + +目前世界的地形生成器只能定义使用原版的生成器,也就是上述列出的噪声、平坦和旧版生成器,尚不能自定义地形生成器,也不能进一步自定义基础地形生成器的噪声。不过,我们这里依旧稍微提点一下噪声生成地形的概念,方便各位开发者对维度有一个更深入的理解。 + +原版的地形是使用**噪声**(**Noise**)来生成的,特别地,使用的是分形的**Perlin噪声**(**Perlin Noise**,又译**柏林噪声**)。Perlin噪声的基本原理是利用在**格**(**Lattice**)上,也可以理解为在坐标系整数格点上,生成一系列随机数作为该点的**梯度**(**Gradient**)。然后在其他的点上分别使用邻近格点(二维为4个,三维为8个)上的梯度值来进行一个插值,得出一个噪声值。事实上,这里格的顶点不一定是整点,而是符合步长采样要求的点即可。而分形的Perlin噪声则利用了多个不同“采样”的噪声值叠加而到到最终值。分形过程中,会生成同一个种子下的多个单噪声,每个单噪声在生成时频率都为上一次的二倍,而振幅都为上一次的一半,因此,每个单噪声称为一个**倍频**(**Octave**)。最后所有的倍频叠加起来就是我们最终的噪声。 + +原版在生成基础地形时分别使用了一个三维噪声和一个二维噪声,三维噪声用于生成地形的竖直结构,而二维噪声又称**高度图噪声**(**Heightmap Noise**),用于生成地形的起伏。 + +事实上,噪声不仅用于基础地形的生成,生物群系映射、特征(又译地物)的生成也需要额外的噪声。虽然我们目前还不能自定义维度生成基础地形时的噪声算法,但是我们可以在接下来自定义生物群系或特征时自定义噪声的参数。 + +至此,我们成功自定义了一个维度。下一节中,我们将为其制作一个传送门方块,用于进入该维度。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/2-设计维度传送门.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/2-设计维度传送门.md new file mode 100644 index 0000000..b94402f --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/2-设计维度传送门.md @@ -0,0 +1,619 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 进阶 +time: 30分钟 +--- + +# 设计维度传送门 + +在本节中,我们为维度设计一个传送门,我们需要使用自定义方块和模组SDK相配合来完成这一逻辑。 + +## 自定义传送门方块 + +传送门方块是自定义传送门必须的一个方块。我们可以使用`base_block`为`portal`的自定义方块配合`netease:portal`组件来完成这一配置。我们在编辑器中新建两个方块,分别用于传到我们对应的维度和传回主世界。这两个方块的行为包定义分别设置如下: + +```json +{ + "format_version": "1.10", + "minecraft:block": { + "description": { + "identifier": "tutorial_demo:custom_dim_gate", + "register_to_creative_menu": true, + "base_block": "portal" + }, + "components": { + "minecraft:destroy_time": { + "value": 9999 + }, + "minecraft:loot": { + "table": "loot_tables/empty.json" + }, + "minecraft:block_light_emission": { + "emission": 1.0 + }, + "netease:portal": { + "target_dimension": 1688560817, + "particle_east_west": "minecraft:portal_east_west", + "particle_north_south": "minecraft:portal_north_south" + }, + "netease:listen_block_remove": { + "value": true + } + } + } +} +``` + +```json +{ + "format_version": "1.10", + "minecraft:block": { + "description": { + "identifier": "tutorial_demo:custom_dim_gate_back", + "register_to_creative_menu": true, + "base_block": "portal" + }, + "components": { + "minecraft:destroy_time": { + "value": 9999 + }, + "minecraft:loot": { + "table": "loot_tables/empty.json" + }, + "minecraft:block_light_emission": { + "emission": 1.0 + }, + "netease:portal": { + "target_dimension": 0, + "particle_east_west": "minecraft:portal_east_west", + "particle_north_south": "minecraft:portal_north_south" + }, + "netease:listen_block_remove": { + "value": true + } + } + } +} +``` + +其中`netease:portal`中的`target_dimension`分别设置为我们自定义的维度ID和原版的主世界维度ID(0),`particle_east_west`和`particle_north_south`为东西朝向的传送门方块散发的粒子和南北朝向散发的粒子,我们不妨先设置为国际版原版的传送门方块粒子。为了于模组SDK配合,我们将`netease:listen_block_remove`打开。 + +## 设计传送门结构 + +我们这部分代码参考演示示例包portalGateDemo中的代码,并将对应的方块ID和维度ID替换成我们的自己的ID。然后我们一起来分析传送门的代码。事实上,我们如欲设计一个传送门结构,只需要在生成传送门前使用`portal`引擎组件的`DetectStructure`方法来判定是否为我们需要的结构即可。如果是我们的结构,我们就将传送门中的空气替换为我们的传送门方块,如果不是,就什么也不执行。我们这里使用的示例为在荧石框架上使用骨粉来激活传送门,所有的逻辑都在服务端执行。 + +```python +# -*- coding: utf-8 -*- +import time +import math +import mod.server.extraServerApi as serverApi +from mod_log import engine_logger as logger +ServerSystem = serverApi.GetServerSystemCls() +compFactory = serverApi.GetEngineCompFactory() + +# 服务端类 +# 处理传送门逻辑 +class Main(ServerSystem): + def __init__(self, namespace, system): + ServerSystem.__init__(self, namespace, system) + # 自定义维度1688560817 + self.TARGET_DIMENSION_ID = 1688560817 + # 从主世界去自定义维度自定义方块 + self.telePortBlockName = 'tutorial_demo:custom_dim_gate' + # 从自定义维度回主世界自定义方块 + self.backPortBlockName = 'tutorial_demo:custom_dim_gate_back' + # portal forcer功能常量 + self.PORTAL_SEARCH_RADIUS = 128 + self.PORTAL_CREATION_RADIUS = 16 + self.PORTAL_RECORDS_KEY = 'tutorial_demo' + self.PORTAL_RECORD_DIMID = 'DimId' + self.PORTAL_RECORD_SPAN = 'Span' + self.PORTAL_RECORD_TPX = 'TpX' + self.PORTAL_RECORD_TPY = 'TpY' + self.PORTAL_RECORD_TPZ = 'TpZ' + + # 传送门结构方块的形状 + self.pattern = [ + '####', + '#**#', + '#**#', + '####', + ] + #传送门形状参数 + self.defines = { + '#': 'minecraft:glowstone', + '*': 'minecraft:air' + } + + # 设置传送门边框可激活的位置 + self.touchPos =[(3,1), (3,2)] + + self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), 'ServerItemUseOnEvent', self, self.OnServerItemUseOnEvent) + self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), 'DimensionChangeFinishServerEvent', self, self.OnPortalForcerServerEvent) + self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), 'BlockRemoveServerEvent', self, self.OnBlockRemoveServerEvent) + self.ShowMsg("Portal Main init!") + + #region 功能函数 + def ShowMsg(self, msg, color='RED', isServer=True): + customComp = compFactory.CreateGame(serverApi.GetLevelId()) + text = ("[服务端]" if isServer else "[客户端]") + msg + customComp.SetNotifyMsg(text, serverApi.GenerateColor(color)) + + def SetBlock(self, playerID, blockPos, blockName): + block = compFactory.CreateBlockInfo(playerID) + return block.SetBlockNew(blockPos, {'name': blockName, 'aux': 0}) + + def AddPostion(self, origin, offset): + return tuple(map(sum, zip(origin, offset))) + + def MulPostion(self, origin, step): + return tuple(step * x for x in origin) + #end region 功能函数 + + #region 构建传送门函数 + + # 填充传送门 填充 传送门方块 + def FillGateAirBlock(self, playerID, originPos, horizontalDir): + """ + param playerID: 玩家Id + param originPos: 传送门起始位置(左上角/右上角的位置) + param horizontalDir: 传送门方向,如(-1,0,0) + """ + logger.info("FillGameAirBlock pos:({},{},{}) dir:({},{},{})".format(originPos[0], originPos[1], originPos[2], horizontalDir[0], horizontalDir[1], horizontalDir[2])) + comp = compFactory.CreateDimension(playerID) + dimensionId = comp.GetEntityDimensionId() + blockName = self.backPortBlockName if dimensionId == self.TARGET_DIMENSION_ID else self.telePortBlockName + bottomLeftPos = originPos + linePos = originPos + for line in self.pattern: + for i in range(len(line)): + name = self.defines.get(line[i], 'minecraft:air') + pos = self.AddPostion(linePos, self.MulPostion(horizontalDir, i)) + if (pos[0] == bottomLeftPos[0] and pos[1] <= bottomLeftPos[1] and pos[2] <= bottomLeftPos[2]) or \ + pos[2] == bottomLeftPos[2] and pos[1] <= bottomLeftPos[1] and pos[0] <= bottomLeftPos[0]: + # Y最小同时X最小(或者Z最小)的点 + bottomLeftPos = pos + logger.info("FillGameAirBlock cur pos:({},{},{})".format(pos[0], pos[1], pos[2])) + if name == 'minecraft:air': + self.SetBlock(playerID, pos, blockName) + linePos = self.AddPostion(linePos, (0, -1, 0)) + # 保存传送门到存档 + self.AddPortalRecord(dimensionId, bottomLeftPos, self.getSpan()) + + # 使用骨粉激活传送门 + def OnServerItemUseOnEvent(self, eventData): + itemName = eventData['itemName'] + auxValue = eventData['auxValue'] + playerID = eventData['entityId'] + if itemName == "minecraft:dye" and auxValue == 15: + # 骨粉激活传送门 + pos = (eventData['x'], eventData['y'], eventData['z']) + # 检测自定义门的结构 + portalComp = compFactory.CreatePortal(playerID) + ret = portalComp.DetectStructure(playerID, self.pattern, self.defines, self.touchPos, pos) + if ret[0]: + self.ShowMsg('门构建成功!') + self.FillGateAirBlock(playerID, ret[1], ret[2]) + self.ShowMsg('传送方块已经填充!') + #消耗物品"骨粉” + def consumeDye(): + itemComp = compFactory.CreateItem(playerID) + item = itemComp.GetPlayerItem(serverApi.GetMinecraftEnum().ItemPosType.CARRIED, 0) + item["count"] = item["count"] - 1 + newRet = itemComp.SpawnItemToPlayerCarried(item, playerID) + if newRet: + self.ShowMsg("消耗物品成功") + else: + self.ShowMsg("消耗物品失败") + comp = compFactory.CreateGame(serverApi.GetLevelId()) + comp.AddTimer(0.1, consumeDye) + else: + self.ShowMsg('传送门构建失败!') + + def OnBlockRemoveServerEvent(self, args): + dimension = args['dimension'] + blockName = args['fullName'] + # 以“底部最左边的点”为key寻找并删除传送门记录 + self.RemovePortalRecord(dimension, args['x'], args['y'], args['z'], blockName) + + # 保存传送门数据到level extraData中 + def AddPortalRecord(self, dimensionId, pos, span): + logger.info("add portal record dim:{} pos:({},{},{})".format(dimensionId, pos[0], pos[1], pos[2])) + entitycomp = compFactory.CreateExtraData(serverApi.GetLevelId()) + portalsRecordDict = entitycomp.GetExtraData(self.PORTAL_RECORDS_KEY) + if not portalsRecordDict: + portalsRecordDict = {} + if dimensionId not in portalsRecordDict: + portalsRecordDict[dimensionId] = [] + # 以‘底部最左边的点’作为key存储该传送门 + record = { + self.PORTAL_RECORD_DIMID: dimensionId, + self.PORTAL_RECORD_TPX: pos[0], + self.PORTAL_RECORD_TPY: pos[1], + self.PORTAL_RECORD_TPZ: pos[2], + self.PORTAL_RECORD_SPAN: span, + } + portalsRecordDict[dimensionId].append(record) + entitycomp.SetExtraData(self.PORTAL_RECORDS_KEY, portalsRecordDict) + return record + + # 以“底部最左边的点”为key删除level extraData保存的数据 + def RemovePortalRecord(self, dimensionId, x, y, z, blockName): + name = self.backPortBlockName if dimensionId == self.TARGET_DIMENSION_ID else self.telePortBlockName + defines = { + '#': 'minecraft:glowstone', + '*': name + } + + if blockName != name: + return False + patternLen = len(self.pattern) + portalPosArray = [] + for i in xrange(patternLen): # 高度 + line = self.pattern[i] + for j in range(len(line)): # 宽度 + name = defines.get(line[j], 'minecraft:air') + if name == blockName: + bottomLeftMostPos = (x - j, y + i - patternLen + 1, z) + if self.RemoveAndSavePortalRecordAt(bottomLeftMostPos, dimensionId): + return True + bottomLeftMostPos = (x, y + i - patternLen + 1, z - j) + if self.RemoveAndSavePortalRecordAt(bottomLeftMostPos, dimensionId): + return True + return False + + def RemoveAndSavePortalRecordAt(self, pos, dimensionId): + entitycomp = compFactory.CreateExtraData(serverApi.GetLevelId()) + portalsRecordDict = entitycomp.GetExtraData(self.PORTAL_RECORDS_KEY) + if not portalsRecordDict or dimensionId not in portalsRecordDict: + return False + records = portalsRecordDict[dimensionId] + for rec in records: + if rec[self.PORTAL_RECORD_TPX] == pos[0] and \ + rec[self.PORTAL_RECORD_TPY] == pos[1] and \ + rec[self.PORTAL_RECORD_TPZ] == pos[2]: + records.remove(rec) + entitycomp.SetExtraData(self.PORTAL_RECORDS_KEY, portalsRecordDict) + self.ShowMsg("成功删除传送门信息") + return True + return False + #end region 构建传送门函数 +``` + +## 在传送终点生成返回传送门 + +我们的传送门不能有去无回,所以我们还需要在玩家进入对应维度后在终点处生成一个返回的传送门。关于这一功能我们其实分两种情况考虑,第一种是在目标点周围没有发现之前存在的传送门,那么我们应该在目标点附近找一个空旷点生成一个传送门,然后将玩家移动至该传送门生成的位置。另一种情况是目标点附近存在一个传送门,我们只需要将玩家的位置移动到该传送门处即可。我们查看补充完整的服务端脚本文件。 + +```python +# -*- coding: utf-8 -*- +import time +import math +import mod.server.extraServerApi as serverApi +from mod_log import engine_logger as logger +ServerSystem = serverApi.GetServerSystemCls() +compFactory = serverApi.GetEngineCompFactory() + +# 服务端类 +# 处理传送门逻辑 +class Main(ServerSystem): + def __init__(self, namespace, system): + ServerSystem.__init__(self, namespace, system) + # 自定义维度23333 + self.TARGET_DIMENSION_ID = 23333 + # 从主世界去自定义维度自定义方块 + self.telePortBlockName = 'portalGateDemo:gateProtal' + # 从自定义维度回主世界自定义方块 + self.backPortBlockName = 'portalGateDemo:gateProtalBack' + # portal forcer功能常量 + self.PORTAL_SEARCH_RADIUS = 128 + self.PORTAL_CREATION_RADIUS = 16 + self.PORTAL_RECORDS_KEY = 'ModPortalRecords' + self.PORTAL_RECORD_DIMID = 'DimId' + self.PORTAL_RECORD_SPAN = 'Span' + self.PORTAL_RECORD_TPX = 'TpX' + self.PORTAL_RECORD_TPY = 'TpY' + self.PORTAL_RECORD_TPZ = 'TpZ' + + # 传送门结构方块的形状 + self.pattern = [ + '####', + '#**#', + '#**#', + '####', + ] + #传送门形状参数 + self.defines = { + '#': 'minecraft:glowstone', + '*': 'minecraft:air' + } + + # 设置传送门边框可激活的位置 + self.touchPos =[(3,1), (3,2)] + + self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), 'ServerItemUseOnEvent', self, self.OnServerItemUseOnEvent) + self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), 'DimensionChangeFinishServerEvent', self, self.OnPortalForcerServerEvent) + self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), 'BlockRemoveServerEvent', self, self.OnBlockRemoveServerEvent) + self.ShowMsg("Portal Main init!") + + #region 功能函数 + def ShowMsg(self, msg, color='RED', isServer=True): + customComp = compFactory.CreateGame(serverApi.GetLevelId()) + text = ("[服务端]" if isServer else "[客户端]") + msg + customComp.SetNotifyMsg(text, serverApi.GenerateColor(color)) + + def SetBlock(self, playerID, blockPos, blockName): + block = compFactory.CreateBlockInfo(playerID) + return block.SetBlockNew(blockPos, {'name': blockName, 'aux': 0}) + + def AddPostion(self, origin, offset): + return tuple(map(sum, zip(origin, offset))) + + def MulPostion(self, origin, step): + return tuple(step * x for x in origin) + #end region 功能函数 + + #region 构建传送门函数 + + # 填充传送门 填充 传送门方块 + def FillGateAirBlock(self, playerID, originPos, horizontalDir): + """ + param playerID: 玩家Id + param originPos: 传送门起始位置(左上角/右上角的位置) + param horizontalDir: 传送门方向,如(-1,0,0) + """ + logger.info("FillGameAirBlock pos:({},{},{}) dir:({},{},{})".format(originPos[0], originPos[1], originPos[2], horizontalDir[0], horizontalDir[1], horizontalDir[2])) + comp = compFactory.CreateDimension(playerID) + dimensionId = comp.GetEntityDimensionId() + blockName = self.backPortBlockName if dimensionId == self.TARGET_DIMENSION_ID else self.telePortBlockName + bottomLeftPos = originPos + linePos = originPos + for line in self.pattern: + for i in range(len(line)): + name = self.defines.get(line[i], 'minecraft:air') + pos = self.AddPostion(linePos, self.MulPostion(horizontalDir, i)) + if (pos[0] == bottomLeftPos[0] and pos[1] <= bottomLeftPos[1] and pos[2] <= bottomLeftPos[2]) or \ + pos[2] == bottomLeftPos[2] and pos[1] <= bottomLeftPos[1] and pos[0] <= bottomLeftPos[0]: + # Y最小同时X最小(或者Z最小)的点 + bottomLeftPos = pos + logger.info("FillGameAirBlock cur pos:({},{},{})".format(pos[0], pos[1], pos[2])) + if name == 'minecraft:air': + self.SetBlock(playerID, pos, blockName) + linePos = self.AddPostion(linePos, (0, -1, 0)) + # 保存传送门到存档 + self.AddPortalRecord(dimensionId, bottomLeftPos, self.getSpan()) + + # 使用骨粉激活传送门 + def OnServerItemUseOnEvent(self, eventData): + itemName = eventData['itemName'] + auxValue = eventData['auxValue'] + playerID = eventData['entityId'] + if itemName == "minecraft:dye" and auxValue == 15: + # 骨粉激活传送门 + pos = (eventData['x'], eventData['y'], eventData['z']) + # 检测自定义门的结构 + portalComp = compFactory.CreatePortal(playerID) + ret = portalComp.DetectStructure(playerID, self.pattern, self.defines, self.touchPos, pos) + if ret[0]: + self.ShowMsg('门构建成功!') + self.FillGateAirBlock(playerID, ret[1], ret[2]) + self.ShowMsg('传送方块已经填充!') + #消耗物品"骨粉” + def consumeDye(): + itemComp = compFactory.CreateItem(playerID) + item = itemComp.GetPlayerItem(serverApi.GetMinecraftEnum().ItemPosType.CARRIED, 0) + item["count"] = item["count"] - 1 + newRet = itemComp.SpawnItemToPlayerCarried(item, playerID) + if newRet: + self.ShowMsg("消耗物品成功") + else: + self.ShowMsg("消耗物品失败") + comp = compFactory.CreateGame(serverApi.GetLevelId()) + comp.AddTimer(0.1, consumeDye) + else: + self.ShowMsg('传送门构建失败!') + + def OnBlockRemoveServerEvent(self, args): + dimension = args['dimension'] + blockName = args['fullName'] + # 以“底部最左边的点”为key寻找并删除传送门记录 + self.RemovePortalRecord(dimension, args['x'], args['y'], args['z'], blockName) + + + def OnPortalForcerServerEvent(self, args): + logger.info("OnPortalForcerServerEvent:{}".format(args)) + entityId = args['playerId'] + toDimensionId = args['toDimensionId'] + self.PortalForcer(entityId, toDimensionId) + + # portal forcer实例 + def PortalForcer(self, entityId, toDimensionId): + pos = compFactory.CreatePos(entityId).GetPos() + if not pos: + return False + ret = self.FindPortal(toDimensionId, pos, self.PORTAL_SEARCH_RADIUS) + if ret[0]: + self.TravelPlayerToPortal(entityId, ret[1], toDimensionId) + self.ShowMsg("在玩家附近找到传送门({}),并且把玩家从{}移动到该传送门附近".format(ret[1], pos)) + return + + record = self.CreatePortal(entityId, toDimensionId, self.PORTAL_CREATION_RADIUS) + target = self.FindClosestBlockPosToPortal(entityId, record) + self.TravelPlayerToPortal(entityId, target, toDimensionId) + self.ShowMsg("在玩家附近创建一个传送门,并且把传送门信息成功存储到存档中") + + # 寻找附近一个位置设置传送门 + def FindNearPosAndSetPortal(self, entityId, dimensionId, pos): + defines = { + '#': 'minecraft:glowstone', + '*': self.backPortBlockName if dimensionId == self.TARGET_DIMENSION_ID else self.telePortBlockName + } + blockComp = compFactory.CreateBlockInfo(entityId) + for line in self.pattern: # 高度 + for i in range(len(line)): # 宽度 + blockName = defines.get(line[i], 'minecraft:air') + blockPos = (pos[0] + i, pos[1], pos[2]) + logger.info("create portal with pos:{}".format(blockPos)) + blockComp.SetBlockNew(blockPos, {'name': blockName, 'aux': 0}) + # 坐标自减 + pos[1] -= 1 + # 返回传送门“底部最左边的点” + return pos + + def GetPortalHeight(self): + return len(self.pattern) + + def GetPortalWidth(self): + width = 0 + for line in self.pattern: + if len(line) > width: + width = len(line) + return width + + # 在玩家附近查找的传送门,返回是否存在传送门、传送门的坐标 + def FindPortal(self, dimensionId, centerBlockPos, radius): + closest = -1 # 最近的距离 + targetBlockPos = (0, 0, 0) + entitycomp = compFactory.CreateExtraData(serverApi.GetLevelId()) + # 在存档中读取传送门信息 + portalsRecordDict = entitycomp.GetExtraData(self.PORTAL_RECORDS_KEY) + if not portalsRecordDict: + portalsRecordDict = {} + if dimensionId not in portalsRecordDict: + # 存储的信息中不存在传送门 + return False, targetBlockPos + + for rec in portalsRecordDict[dimensionId]: + # “底部最左边的点”作为key + baseBlockPos = (rec[self.PORTAL_RECORD_TPX], rec[self.PORTAL_RECORD_TPY], rec[self.PORTAL_RECORD_TPZ]) + for span in xrange(0, rec[self.PORTAL_RECORD_SPAN]): + recordBlockPos = (baseBlockPos[0] + span, baseBlockPos[1] + span, baseBlockPos[2]) + # search area x by y (at all heights) + xd = abs(recordBlockPos[0] - centerBlockPos[0]) + zd = abs(recordBlockPos[1] - centerBlockPos[1]) + if xd <= radius and zd <= radius: + # 根据欧拉距离选择最新的点 + dist = self.EuclideanDistance(recordBlockPos, centerBlockPos) + if closest < 0 or dist < closest: + closest = dist + targetBlockPos = recordBlockPos + + return closest >= 0, targetBlockPos + + # 创建传送门 + def CreatePortal(self, entityId, dimensionId, radius): + entityPos = compFactory.CreatePos(entityId).GetPos() + entityBlockPos =[math.floor(entityPos[0]) - self.GetPortalWidth() / 2, math.floor(entityPos[1]) - 2 + self.GetPortalHeight(), math.floor(entityPos[2])] + logger.info("player current position:{} portal begin position:{}".format(entityPos, entityBlockPos)) + entityBlockPos = self.FindNearPosAndSetPortal(entityId, dimensionId, entityBlockPos) + return self.AddPortalRecord(dimensionId, entityBlockPos, self.getSpan()) + + # 保存传送门数据到level extraData中 + def AddPortalRecord(self, dimensionId, pos, span): + logger.info("add portal record dim:{} pos:({},{},{})".format(dimensionId, pos[0], pos[1], pos[2])) + entitycomp = compFactory.CreateExtraData(serverApi.GetLevelId()) + portalsRecordDict = entitycomp.GetExtraData(self.PORTAL_RECORDS_KEY) + if not portalsRecordDict: + portalsRecordDict = {} + if dimensionId not in portalsRecordDict: + portalsRecordDict[dimensionId] = [] + # 以‘底部最左边的点’作为key存储该传送门 + record = { + self.PORTAL_RECORD_DIMID: dimensionId, + self.PORTAL_RECORD_TPX: pos[0], + self.PORTAL_RECORD_TPY: pos[1], + self.PORTAL_RECORD_TPZ: pos[2], + self.PORTAL_RECORD_SPAN: span, + } + portalsRecordDict[dimensionId].append(record) + entitycomp.SetExtraData(self.PORTAL_RECORDS_KEY, portalsRecordDict) + return record + + # 获取玩家离传送门相对比较近的位置 + def FindClosestBlockPosToPortal(self, entityId, record): + pos = (record[self.PORTAL_RECORD_TPX], record[self.PORTAL_RECORD_TPY], record[self.PORTAL_RECORD_TPZ]) + # 该位置开发者可以根据需要设定 + # 注意该位置有可能不是空气,所以在选择位置时需要注意 + return tuple((x + 2 for x in pos)) + + def LengthSquared(self, pos): + return pos[0] * pos[0] + pos[1] * pos[1] + pos[2] * pos[2] + + # 把玩家的位置设置到传送门附近 + def TravelPlayerToPortal(self, entityId, targetBlockPos, dimensionId): + entityTargetPos = tuple((x + 0.5 for x in targetBlockPos)) + originRot = compFactory.CreateRot(entityId).GetRot() + entityRotY = originRot[1] + if dimensionId == self.TARGET_DIMENSION_ID: + entityRotY += 90 + else: + entityRotY -= 90 + newRot = (originRot[0], entityRotY) + compFactory.CreateRot(entityId).SetRot(newRot) + compFactory.CreatePos(entityId).SetPos(entityTargetPos) + logger.info("set position:({}, {})".format(entityTargetPos[0], entityTargetPos[1])) + + # 计算两点的欧拉距离 + def EuclideanDistance(self, pos1, pos2): + dx = pos1[0] - pos2[0] + dy = pos1[1] - pos2[1] + dz = pos1[2] - pos2[2] + return dx * dx + dy * dy + dz * dz + + # 自定义传送门的最大宽度 + def getSpan(self): + span = 1 + for line in self.pattern: + if len(line) > span: + span = len(line) + return span + + # 以“底部最左边的点”为key删除level extraData保存的数据 + def RemovePortalRecord(self, dimensionId, x, y, z, blockName): + name = self.backPortBlockName if dimensionId == self.TARGET_DIMENSION_ID else self.telePortBlockName + defines = { + '#': 'minecraft:glowstone', + '*': name + } + + if blockName != name: + return False + patternLen = len(self.pattern) + portalPosArray = [] + for i in xrange(patternLen): # 高度 + line = self.pattern[i] + for j in range(len(line)): # 宽度 + name = defines.get(line[j], 'minecraft:air') + if name == blockName: + bottomLeftMostPos = (x - j, y + i - patternLen + 1, z) + if self.RemoveAndSavePortalRecordAt(bottomLeftMostPos, dimensionId): + return True + bottomLeftMostPos = (x, y + i - patternLen + 1, z - j) + if self.RemoveAndSavePortalRecordAt(bottomLeftMostPos, dimensionId): + return True + return False + + def RemoveAndSavePortalRecordAt(self, pos, dimensionId): + entitycomp = compFactory.CreateExtraData(serverApi.GetLevelId()) + portalsRecordDict = entitycomp.GetExtraData(self.PORTAL_RECORDS_KEY) + if not portalsRecordDict or dimensionId not in portalsRecordDict: + return False + records = portalsRecordDict[dimensionId] + for rec in records: + if rec[self.PORTAL_RECORD_TPX] == pos[0] and \ + rec[self.PORTAL_RECORD_TPY] == pos[1] and \ + rec[self.PORTAL_RECORD_TPZ] == pos[2]: + records.remove(rec) + entitycomp.SetExtraData(self.PORTAL_RECORDS_KEY, portalsRecordDict) + self.ShowMsg("成功删除传送门信息") + return True + return False + #end region 构建传送门函数 +``` + +![](./images/15.2_overworld_to_custom_dim.png) + +![](./images/15.2_transfering.png) + +![](./images/15.2_custom_dim_to_overworld.png) + +这样,我们便完成了一个传送门的设计。接下来,为更多地维度设计不同样式的传送门吧! \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/3-改变维度的生物群系.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/3-改变维度的生物群系.md new file mode 100644 index 0000000..45aa770 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/3-改变维度的生物群系.md @@ -0,0 +1,241 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 30分钟 +--- + +# 改变维度的生物群系 + +**生物群系**(**Biome**)是一个维度中用于控制地形地貌、生物植被、大气环境、遗迹结构等各种地理要素的区域。一个维度中往往有多种生物群系,一种生物群系又可以转化为多种子生物群系,这使得维度的环境变得十分丰富。 + +在本节中,我们一起来学习如何自定义生物群系。 + +## 创建生物群系 + +在国际版提供的功能中,我们可以开启实验性玩法来修改原版的生物群系或在主世界中自定义新的生物群系,但是,这个功能十分不稳定,而且需要实验性玩法的支持。网易的开发组在国际版生物群系接口的基础上开发了中国版的自定义生物群系系统,并且在实现上与国际版的稍有不同。目前而言,中国版的自定义生物群系是与维度绑定的。每种维度,包括原版维度和自定义维度,都必须有自己独立的一套生物群系,并且互不影响,而每套生物群系都必须只能通过修改原版的生物群系来实现。换句话说,我们目前无法在中国版的自定义生物群系中如国际版的生物群系那样增加一个完完全全崭新的生物群系,所以的生物群系都必须继承并覆盖一个原版的生物群系,而且每个原版的生物群系只能被一个新的群系继承并覆盖。说得更直白一些,中国版的一个新的自定义维度会默认复制并使用一套原版主世界的生物群系,而我们能做的就是在这套与主世界相同的生物群系的基础上修改他们的各种属性,包括各地形层方块、生成噪声参数、气候和温湿度等属性,以将其修改成为一个个看似是新生物群系的生物群系。 + +针对每种维度,我们都有两种方式创建它的一个生物群系。 + +### 使用脚本批量生成一个维度的生物群系 + +上面我们了解到,我们能且只能在原版生物群系种类的基础上修改生物群系的各种属性。但是,这一过程要如何进行呢?对于每个维度而言,可以修改的生物群系的所有标识符事实上已经固定了,那就是与所有原版生物群系对应的带有`dm_`前缀的生物群系。比如,继承了`desert`生物群系的在数字ID为1688560817的维度中的生物群系的标识符就是`dm1688560817_desert`。虽然我们也可以手动对应于每个维度建立我们需要的所有生物群系,但是那将耗费大量的工作。我们有一个可以使用脚本快速生成某个维度所有生物群系的方法。 + +我们找到演示示例包中的CustomBiomesMod模组,在行为包下可以发现`tools`文件夹。这个文件夹是我们用于批量生成生物群系的脚本。我们在这个文件夹下可以找到`remake.py`。我们执行该文件来自动生成一个特定维度ID的生物群系副本。比如,我们为ID为1688560817维度生成一套生物群系。 + +```shell +python .\remake.py dm1688560817 +``` + +通过执行该命令即可生成一套该维度的生物群系,生物群系文件将自动被命名为`dm1688560817_`前缀,生物群系标识符也将被命名为`dm1688560817_`前缀,同时会为生物群系加上`dm1688560817`的标签。 + +### 使用编辑器创建生物群系 + +![](./images/15.3_biome_create.png) + +我们在配置中选择“**生物群系**”,来创建一个新的生物群系。 + +![](./images/15.3_biome_creating.png) + +我们目前在新建文件向导中只能创建空的生物群系,这没关系,我们之后可以将生物群系绑定到维度上。 + +![](./images/15.3_biome_created.png) + +![](./images/15.3_biome_resource.png) + +我们可以看到,我们在生物群系中可以修改多种属性。此时我们可以查看新创建的文件的JSON内容: + +```json +{ + "format_version": "1.14.0", + "minecraft:biome": { + "description": { + "identifier": "dm_desert", + "inherits": "desert" + }, + "components": { + "minecraft:overworld_generation_rules": { + "generate_for_climates": [ + [ + "frozen", + 0 + ], + [ + "cold", + 0 + ], + [ + "medium", + 0 + ], + [ + "lukewarm", + 0 + ], + [ + "warm", + 0 + ] + ], + "hills_transformation": "dm_desert", + "mutate_transformation": "dm_desert" + }, + "minecraft:overworld_height": { + "noise_type": "default" + }, + "minecraft:surface_parameters": { + "foundation_material": "minecraft:iron_block", + "mid_material": "minecraft:gold_block", + "sea_floor_depth": 7, + "sea_floor_material": "minecraft:emerald_block", + "sea_material": "minecraft:water", + "top_material": "minecraft:diamond_block" + }, + "dm": {} + } + } +} +``` + +此时,我们的群系并没有绑定特定的维度,所有的`dm_`前缀都以`dm_`的形式出现。我们在编辑器中选择我们的维度绑定。 + +![](./images/15.3_biome_select_dim.png) + +我们在“**基础属性**”的“**适用维度**”选择我们需要绑定的维度。然后编辑器便会自动修改我们的维度文件,同时创建一个如同我们用脚本方法创建出一样的一套原版维度。 + +![](./images/15.3_biome_selected.png) + +![](./images/15.3_biome_dim_auto.png) + +这套维度会随着我们自定义的生物群系的绑定与否自动增删,其对应的那个生物群系文件也会随着我们使用编辑器对外面根目录的这个文件的更改而同步更改。我们来看看此时的JSON文件内容: + +```json +{ + "format_version": "1.14.0", + "minecraft:biome": { + "description": { + "identifier": "dm1688560817_desert", + "inherits": "desert" + }, + "components": { + "minecraft:overworld_generation_rules": { + "generate_for_climates": [ + [ + "frozen", + 0 + ], + [ + "cold", + 0 + ], + [ + "medium", + 0 + ], + [ + "lukewarm", + 0 + ], + [ + "warm", + 0 + ] + ], + "hills_transformation": "dm1688560817_desert", + "mutate_transformation": "dm1688560817_desert" + }, + "minecraft:overworld_height": { + "noise_type": "default" + }, + "minecraft:surface_parameters": { + "foundation_material": "minecraft:iron_block", + "mid_material": "minecraft:gold_block", + "sea_floor_depth": 7, + "sea_floor_material": "minecraft:emerald_block", + "sea_material": "minecraft:water", + "top_material": "minecraft:diamond_block" + }, + "dm1688560817": {} + } + } +} +``` + +此时,我们的生物群系文件便完成创建了,之后,我们只需要在编辑器中或者手动修改JSON文件的内容便可以做到自定义生物群系的属性。 + +## 修改生物群系属性 + +通过联合分析编辑器中的“属性”窗格和生物群系的JSON文件,我们来一起学习生物群系的写法。我们可以看到,生物群系目前使用的是`1.14.0`的格式版本,`minecraft:biome`的模式标识符。事实上,我们也可以使用旧版的`1.12.0`或`1.13.0`格式版本,但是由于版本迭代,这些格式版本的语法已经与`1.14.0`较为不同,我们不推荐使用,同时在这里也不进行介绍与学习。 + +### 行为包组件 + +![](./images/15.3_biome_height.png) + +“**地形高度**”属性对应的是`minecraft:overworld_height`组件,用于在主世界中修改噪声地形的高度补偿,我们可以通过修改噪声预设的值和噪声的参数来修改噪声地形的高度效果。由于我们的自定义维度是继承的主世界生物群系,因此我们可以使用该组件来控制生物群系的平均高度即高度分布。 + +![](./images/15.3_biome_surface.png) + +“**地表**”对应的是`minecraft:surface_parameters`组件,用于指定海底深度和地表各地形层的**物质**(**Material**)参数。“水底方块”即`sea_floor_material`字段,用于指定**海底物质**(**Sea Floor Material**);“水体方块”即`sea_material`字段,用于指定**海体物质**(**Sea Material**);“浅表方块”即`top_material`字段,用于指定**顶层物质**(**Top Material**);“中层方块”即`mid_material`字段,用于指定**中层物质**(**Middle Material**);“深层方块”即`foundation_material`字段,用于指定**地基物质**(**Foundation Material**)。 + +![](./images/15.3_biome_transformation.png) + +![](./images/15.3_biome_climate.png) + +“生成规则”对应的是`minecraft:overworld_generation_rules`组件。该组件用于定义主世界生物群系的**生成规则**(**Generation Rule**),不过,和之前所说的一样,该组件也可以在中国版的自定义维度中使用,以模仿主世界的生成规则。生成规则分为两部分,分别是**转化**(**Transformation**)规则和针对**气候**(**Climate**)的生成规则。转化是一种增加生物群系多样性的操作。我们在定义生物群系时,往往倾向于同时定义一组同类型的生物群系,其中只有一个生物群系会定义`generate_for_climates`字段,也就是针对气候的生成规则,被称为**基生物群系**(**Base Biome**)。而其他的生物群系都会根据转化规则在生成基生物群系后通过生物群系的转化得到。我们将某个基生物群系和其转化得到的生物群系统称为一个**群丛**(**Association**)。目前,我们可以定义多种转化,分别是**丘陵转化**(**Hills Transformation**)、**突变转化**(**Mutate Transformation**)、**河流转化**(**River Transformation**)和**海岸转化**(**Shore Transformation**),我们可以在编辑器中选择或者直接在JSON文件中输入对应的转化后的生物群系作为字段的值即可。比如,原版丛林的基生物群系只生成在中性气候,权重为1(只有一个的时候这意味着100%),并且会转化成丛林丘陵和丛林变种,它的JSON组件是这么写的: + +```json +"minecraft:overworld_generation_rules": { + "hills_transformation": "jungle_hills", + "mutate_transformation": "jungle_mutated", + "generate_for_climates": [ + [ "medium", 1 ] + ] +} +``` + +丛林山丘和丛林变种不会进行其他转化,也不会作为基生物群系生成,因此不具备`minecraft:overworld_generation_rules`组件。事实上,丛林生物群系还可能硬编码地转化为丛林边缘或竹林。丛林边缘不会作为基生物群系生成,但是会转化为丛林边缘变种,它的JSON组件内容为: + +```json +"minecraft:overworld_generation_rules": { + "mutate_transformation": "jungle_edge_mutated" +} +``` + +竹林也不会作为基生物群系生成,但是会转化为竹林丘陵,它的JSON组件内容为: + +```json +"minecraft:overworld_generation_rules": { + "hills_transformation": "bamboo_jungle_hills" +} +``` + +丛林边缘变种和竹林丘陵不会再转化为其他生物群系。河流生物群系虽然可以通过JSON组件控制,但是依旧存在硬编码部分,那就是如果不指定河流转化的生物群系,则`river`生物群系会自动作为河流转换的生物群系。上述所有群系都存在一个默认的`river`河流转化。这便是整个丛林群丛的生成转化链。 + +除了编辑器中可以调整的参数外,我们还有许多可以自行在JSON文件中手动写入的组件,比如`minecraft:climate`组件用于指定该生物群系各气候参数的值,比如温度、降雨量、积雪量等。`minecraft:forced_features`用于生成**强制型特征**(**Forced Feature**,又译**强制型地物**)。`minecraft:ignore_automatic_features`用于忽略**自动型特征**(**Automatic Feature**,又译**自动型地物**,即非强制型特征)的生成,只生成上一个组件中指定的强制型特征。强制型特征又称**显式特征**(**Explicit Feature**,又译**显式地物**),自动型特征又称**隐式特征**(**Implicit Feature**,又译**隐式地物**)。另外,还有`minecraft:surface_material_adjustments`组件用于精细地调整地表物质,`minecraft:legacy_world_generation_rules`组件用于指定旧版的有限世界生成规则等。这些组件,包括旧格式版本的组件,都可以在[bedrock.dev上托管的生物群系文档](https://bedrock.dev/zh/b/Biomes)上找到更详细的用法。 + +除了这些组件之外, 中国版还存在一些独占的组件,比如`netease:no_spawn_end_dragon`用于在自定义末地的生物群系中取消生成末影龙及其相关逻辑。而且需要注意的是,如果在生物群系绑定的维度中启用了生物群系源,即`netease:biome_source`组件,则其下的生物群系均不再使用国际版原版的生物群系生成规则来生成,即生物群系的`minecraft:overworld_generation_rules`组件将失效,我们上述说明的生成过程也将无效,一切将按照维度中`netease:biome_source`组件所定义的规则进行生成。 + +### 生物群系标签 + +在生物群系的行为包定义文件中,我们除了行为包组件之外还可以定义生物群系**标签**(**Tag**)。生物群系标签是用于标记生物群系的一种功能,可以在Molang中使用`query.has_biome_tag`查询得到。我们通过在`minecraft:biome/components`对象中直接定义一个空对象的方式来定义一个标签,空对象的值就是标签的值。 + +比如,上述示例中,编辑器便自动为我们通过配置自定义的新生物群系添加了`dm1688560817`标签,供我们之后使用。具体代码如下 + +```json +{ + "format_version": "1.14.0", + "minecraft:biome": { + "description": { + "identifier": "dm1688560817_desert", + "inherits": "desert" + }, + "components": { + // ... + "dm1688560817": {} + } + } +} +``` + +我们可以为一个生物群系添加多个标签,比如原版的丛林生物群系便添加了`animal`、`jungle`、`monster`、`overworld`和`rare`标签。这些标签和特征、实体的生成规则以及一些硬编码内容相配合可以做到允许动物生成、允许丛林神庙生成、允许怪物生成、允许在无限世界的主世界和有限世界中生成和作为中性气候下的稀有群系生成。 + +至此,我们便完成了一个生物群系的定义。开发者们可以根据自己的意愿改造更多的生物群系了! \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/4-挑战:海洋世界.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/4-挑战:海洋世界.md new file mode 100644 index 0000000..a139961 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/4-挑战:海洋世界.md @@ -0,0 +1,29 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 15分钟 +--- + +# 挑战:海洋世界 + +在本节中,我们一起制作一个海洋世界。海洋世界,顾名思义就是几乎全是海的世界。为了制作出这样的世界,我们有两种方案。第一种方案是将除了海洋之外其他的基生物群系的针对气候的生成规则全部关闭,即权重设为0;第二种是将所有的陆地生物群系高度都调整成与海洋一致的高度,依旧可以制作出海洋世界。我们下面演示第二种操作模式。 + +## 修改陆地群系高度 + +![](./images/15.4_biome_height.png) + +如果我们的生物群系是从编辑器中建立的,我们可以调整“地形高度”中的“类型”,并将其设置为“海洋”。这等价于在JSON的`minecraft:overworld_height`组件中将`noise_type`字段设置为`ocean`。 + +当然,除了编辑器中配置的生物群系之外,我们还有其他的默认生物群系。我们使用文本编辑器将它们全部修改成或者添加上如下的组件: + +```json +"minecraft:overworld_height": { + "noise_type": "ocean" +} +``` + +这样,所有的生物群系在噪声生成地形时就会像原版海洋那样生成低于海平面高度的地形。这样,我们的海洋世界就完成了。 + +![](./images/15.4_waterlandt.png) + +我们进入游戏自测,可以看到除了村庄等硬编码生成在水面的特征(又译地物)以外,所有的方块都没入了水面以下。这说明我们的自定义海洋世界成功了! diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/README.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.1_dim_create.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.1_dim_create.png new file mode 100644 index 0000000..235e413 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.1_dim_create.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.1_dim_created.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.1_dim_created.png new file mode 100644 index 0000000..2cc1f17 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.1_dim_created.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.1_dim_creating.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.1_dim_creating.png new file mode 100644 index 0000000..19ca17e Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.1_dim_creating.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.2_custom_dim_to_overworld.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.2_custom_dim_to_overworld.png new file mode 100644 index 0000000..9ac3c1f Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.2_custom_dim_to_overworld.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.2_overworld_to_custom_dim.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.2_overworld_to_custom_dim.png new file mode 100644 index 0000000..7913eb7 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.2_overworld_to_custom_dim.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.2_transfering.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.2_transfering.png new file mode 100644 index 0000000..c7a1566 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.2_transfering.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.3_biome_climate.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.3_biome_climate.png new file mode 100644 index 0000000..003dcf5 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.3_biome_climate.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.3_biome_create.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.3_biome_create.png new file mode 100644 index 0000000..60f9e7f Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.3_biome_create.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.3_biome_created.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.3_biome_created.png new file mode 100644 index 0000000..12c32e2 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.3_biome_created.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.3_biome_creating.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.3_biome_creating.png new file mode 100644 index 0000000..47f3499 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.3_biome_creating.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.3_biome_dim_auto.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.3_biome_dim_auto.png new file mode 100644 index 0000000..8e3aed7 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.3_biome_dim_auto.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.3_biome_height.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.3_biome_height.png new file mode 100644 index 0000000..6f04f84 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.3_biome_height.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.3_biome_resource.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.3_biome_resource.png new file mode 100644 index 0000000..ae22ea3 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.3_biome_resource.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.3_biome_select_dim.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.3_biome_select_dim.png new file mode 100644 index 0000000..d13878d Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.3_biome_select_dim.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.3_biome_selected.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.3_biome_selected.png new file mode 100644 index 0000000..e735192 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.3_biome_selected.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.3_biome_surface.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.3_biome_surface.png new file mode 100644 index 0000000..95277d4 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.3_biome_surface.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.3_biome_transformation.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.3_biome_transformation.png new file mode 100644 index 0000000..594d749 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.3_biome_transformation.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.4_biome_height.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.4_biome_height.png new file mode 100644 index 0000000..51f5ea6 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.4_biome_height.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.4_waterlandt.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.4_waterlandt.png new file mode 100644 index 0000000..be3e591 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/14-创造新的世界/images/15.4_waterlandt.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/0-摘要.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/0-摘要.md new file mode 100644 index 0000000..4b15a75 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/0-摘要.md @@ -0,0 +1,23 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 5分钟 +--- + +# 摘要 + +在本章中,我们将一起学习**特征**(**Feature**,又译**地物**)和**特征规则**(**Feature Rule**,又译**地物规则**),了解什么是特征,以及如何生成一个特征。 + +- 在第一节(*了解自定义特征*)中,我们将一起了解什么是特征。我们将一起认识各种种类的特征,以及如何自定义这些特征。 +- 在第二节(*了解自定义特征规则*)中,我们将一起学习特征规则,了解到自定义特征生成的方法。 +- 在第三节(*了解自定义矿石特征规则*)中,我们将一起自定义一个矿石特征及其特征规则。 +- 在第四节(*了解自定义单方块特征规则*)中,我们将一起自定义一个单方块特征及其特征规则。 +- 在第五节(*了解自定义散植特征规则*)中,我们将一起自定义一个散植特征及其特征规则。 +- 在第六节(*了解自定义聚合特征规则*)中,我们将一起自定义一个聚合特征及其特征规则。 +- 在第七节(*了解搜索特征规则*)中,我们将一起自定义一个搜索特征。 +- 在第八节(*了解序列特征规则*)中,我们将一起自定义一个序列特征及其特征规则。 +- 在第九节(*了解加权随机特征规则*)中,我们将一起自定义一个加权随机特征及其特征规则。 +- 在第十节(*了解树特征规则*)中,我们将一起自定义一个树特征及其特征规则。 +- 在第十一节(*挑战:制作沿河畔生长的植物特征*)中,我们将一起进行一个挑战,自定义一种可以沿着河畔生成的植物。 + +本章关键词:特征 地物 矿石 单方块 散植 聚合 搜索 序列 加权随机 树 diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/1-了解自定义特征.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/1-了解自定义特征.md new file mode 100644 index 0000000..40d173d --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/1-了解自定义特征.md @@ -0,0 +1,408 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 25分钟 +--- + +# 了解自定义特征 + +**特征**(**Feature**,又译**地物**)是一种由一个或多个方块组成的在世界中发挥点缀地形效果的结构,我们在原版世界中看到的树木、花草、矿石乃至遗迹、村庄、湖泊、晶洞等地形都是通过特征来生成的。特征充当了玩家所看到的世界的各种微观细节的角色,有了特征的生成,世界中由噪声和生物群系产生的局部一成不变的地形结构才有了更多地细节变化。在本节中,我们一起来了解如何自定义一个特征。 + +## 了解特征文件的结构 + +在第六章中,我们曾经一起学习了如何通过编辑器自定义一个特征,不过同时也了解到了当前编辑器暂时只支持配置一个**结构特征**(**Structure Feature**,又译**结构地物**)。这个结构特征其实是一种中国版独占的特征类型。除了这种中国版独占的特征类型之外,我们还有很多国际版提供的特征类型可以用于自定义。为了自定义更多类型的特征,我们需要手动编辑JSON文件来更改特征的类型和参数。在此之前,我们先一窥之前所创建的中国版结构特征的JSON内容: + +```json +{ + "format_version": "1.14.0", + "netease:structure_feature": { + "description": { + "identifier": "tutorial_demo:demo_feature" + }, + "places_structure": "tutorial_demo:pondwaterland" + } +} +``` + +我们可以看到,我们的结构模板特征存放在行为包的`netease_features`文件夹中的。在这个文件夹中的结构的生成不需要开启实验性玩法,并且同时支持所有国际版的特征类型,所以我们之后的特征可以全部都放置在这个文件夹中。 + +`netease:structure_feature`是该特征的**模式标识符**(**Schema Identifier**)。顾名思义,模式标识符就是用于确定该JSON文件的**模式**(**Schema**)的标识符。之前我们了解过,JSON文件的模式决定了JSON文件使用的文件结构,比如,`netease:structure_feature`的模式便决定了其下有`description`和`places_structure`两个字段,缺一不可。再比如,`minecraft:client_entity`的模式决定了其下只能有一个`description`字段,不过`description`中倒是可以有很多各种功能的字段;而`minecraft:entity`模式决定了下面可以有一个`description`和一个`components`,而且`components`中可以存在许多行为包组件。而**格式版本**(**Format Version**)则是在确定了模式标识符之后对各个字段的结构和属性根据游戏版本的变更进行的修改,这包括字段名称的修改、字段的必需和可选性的修改以及字段的增删等。比如,`minecraft:client_entity`的`1.8.0`格式版本的`description`中存在`animation_controllers`数组,用于定义动画控制器,而`1.10.0`的格式版本则删除了这一字段,将动画控制器统一定义到了与动画相同的位置`animations`对象中。 + +事实上,如果你查看过原版的特征JSON文件,你应该可以注意到,特征的模式标识符并不像方块的`minecraft:block`、物品的`minecraft:item`、实体的客户端和服务端的`minecraft:client_entity`和`minecraft:entity`那样是统一的。特征的模式标识符对于不同种类的特征将有所不同。比如中国版的结构特征的模式标识符便使用了`netease:structure_feature`,而国际版提供的各种特征类型却使用了其他的模式标识符。如`minecraft:ore_feature`、`minecraft:single_block_feature`等。也就是说,不像物品、方块、实体在同一个模式标识符下定义描述和组件那样,特征是依据不同的特征类型使用不同的模式标识符,同时使用不同的模式结构的。所以对于每种类型的特征,我们都需要单独学习它的文件结构。 + +因为模式标识符并不一致,因此格式版本也应随之变化。中国版的结构特征`netease:structure_feature`的格式版本为`1.14.0`。除此之外,国际版的各种类型的特征的格式版本不尽相同,但是大部分都为`1.13.0`,另有少部分模式标识符使用`1.16.0`和`1.16.100`的格式版本。 + +接下来,我们通过分模式标识符来学习一些基本类型的特征。 + +## 自定义矿石特征 + +矿石特征的模式标识符为`minecraft:ore_feature`,惯用格式版本为`1.13.0`,也可以使用`1.16.0`。矿石特征旨在模拟矿石生成的矿床结构,事实上,任何方块都可以作为矿石特征的输入方块,并不一定必须是一个矿石。我们来看一个例子: + +```json +{ + "format_version": "1.13.0", + "minecraft:ore_feature": { + "description": { + "identifier": "example:malachite_ore_feature" + }, + "count": 12, + "replace_rules": [ + { + "places_block": "example:malachite_ore", + "may_replace": [ + "minecraft:stone" + ] + }, + { + "places_block": "example:granite_malachite_ore", + "may_replace": [ + "minecraft:granite" + ] + }, + { + "places_block": "example:andesite_malachite_ore", + "may_replace": [ + "minecraft:andesite" + ] + } + ] + } +} +``` + +`count`为该矿石特征尝试替换的次数,而`replace_rules`则是替换规则。矿石特征的原理是将指定的一个或一些方块替换成特定的方块,往往是矿石方块。比如示例中,该特征尝试把`minecraft:stone`替换成`example:malachite_ore`、`minecraft:granite`替换成`example:granite_malachite_ore`且`minecraft:andesite`替换成`example:andesite_malachite_ore`。如果不填写`replace_rules`字段,则代表着方块可以替换任何的其他方块。 + +## 自定义单方块特征 + +单方块特征的模式标识符为`minecraft:single_block_feature`,惯用格式版本为`1.13.0`,也可以使用`1.16.0`。单方块特征旨在放置一个单独的方块,比如花草等小方块。在放置期间,我们可以可以像矿石特征那样设置可以替换的方块,也可以设置可以附着的方块,即我们可以使这个方块只能被附着地放置在某种或某些方块的某些面上。 + +```json +{ + "format_version": "1.13.0", + "minecraft:single_block_feature": { + "description": { + "identifier": "minecraft:cocoa_age_0_feature" + }, + "places_block": { + "name": "minecraft:cocoa", + "states": { + "age": 0 + } + }, + "enforce_survivability_rules": false, + "enforce_placement_rules": false, + "may_attach_to": { + "min_sides_must_attach": 1, + "sides": { + "name": "minecraft:log", + "states": { + "old_log_type": "jungle" + } + } + } + } +} +``` + +`places_block`是将要被放置的那个方块,这里使用了一个**方块引用**(**Block Reference**)的格式。不仅是这里,所有可以单独填写一个方块标识符的位置也都可以像这样填写一个方块引用对象`{"name": "", "states": {"": , "": ... }}`。在不指定方块状态`states`字段时,单独写一个方块标识符和写一个方块引用对象是等价的。而方块引用的好处就是可以用于指定特定的方块状态,在这里就是用于在特征中直接生成一个特定状态的方块。`may_replace`字段用于指定可以被替换的方块,是一个和上面矿石特征中`may_replace`一样的数组。在我们这个示例中没有使用这个`may_replace`字段,这个字段和`places_block`是平级的。`may_attach_to`也和`places_block`平级,用于指定可以附着到哪些方块的哪些面上。`enforce_survivability_rules`和`enforce_placement_rules`分别用于告知引擎是否开启方块的可生存性和可放置性检验。方块可以通过在行为包中设置`minecraft:placement_filter`方块组件来定义可放置性和可生存性。不过值得注意的是,这个方块组件是`1.16.100`之后的方块组件,如需使用请注意格式版本。 + +## 自定义散植特征 + +散植特征的模式标识符为`minecraft:scatter_feature`,惯用格式版本为`1.13.0`。该特征旨在将一个特征进行一次或多次**散植**(**Scatter**),即相对于特征的起始点在其周围散布一次或多次某个指定的特征。 + +```json +{ + "format_version": "1.13.0", + "minecraft:scatter_feature": { + "description": { + "identifier": "minecraft:grass_double_plant_patch_feature" + }, + "iterations": 64, + "scatter_chance": { + "numerator": 1, + "denominator": 80 + }, + "coordinate_eval_order": "zyx", + "x": { + "extent": [ -8, 8 ], + "distribution": "gaussian" + }, + "z": 10, + "y": "math.cos(math.random(0, 10))", + "project_input_to_floor": false, + "places_feature": "minecraft:grass_double_plant_feature" + } +} +``` + +这是一个改造过的双层高草的斑块特征的示例,`iterations`代表迭代的次数,即散植的次数。`scatter_chance`代表散植的可能性,该字段可以为一个数字,代表百分比,比如50就代表“50%”的概率;也可以使用一个对象,该对象代表一个分数,其中`numerator`为分子,`denominator`为分母。我们称这种代表一个分数的对象为一个**可能性信息**(**Chance Information**)。`scatter_chance`如果判定通过,则会进行`iterations`次数的散植迭代,如果不通过,则一次迭代都不进行。`coordinate_eval_order`为坐标串演算顺序,代表下述XYZ三个轴向上的值的演算顺序。一般来说,三个轴向上的演算顺序并不影响最终的结果,但是如果三个轴向上使用Molang表达式进行运算时使用了变量来保存信息, 则演算顺序将造成结果差异。`x`、`y`、`z`三个字段可以使用一个值、一个Molang表达式或者一个对象来指定一个分布函数。上述示例中X轴向上便使用了高斯分布来计算散植的目的坐标。 + +## 自定义聚合特征 + +聚合特征的模式标识符为`minecraft:aggregate_feature`,惯用格式版本为`1.13.0`。聚合特征旨在将一个或多个特征集中生成在一个位置,即将它们**聚合**(**Aggregate**)在一起。 + +```json +{ + "format_version": "1.13.0", + "minecraft:aggregate_feature": { + "description": { + "identifier": "minecraft:bamboo_then_podzol_feature" + }, + "early_out": "first_failure", + "features": [ + "minecraft:bamboo_feature", + "minecraft:optional_podzol_feature" + ] + } +} +``` + +`features`数组即我们想要聚合的特征列表。`early_out`字段可以用于指定该次聚合是否会因为什么原因而提前退出。比如这里`first_failure`便代表在聚合过程中一旦有一个特征放置失败,便终止整次聚合。 + +## 自定义序列特征 + +序列特征的模式标识符为`minecraft:sequence_feature`,惯用格式版本为`1.13.0`。序列特征和聚合特征非常类似,都是用于放置多个其他的特征的特征,但是,序列特征的放置起始点并不是固定在某个位置的,而是在放置列表中的下一个特征的起始点会使用上一个特征的结束点。 + +```json +{ + "format_version": "1.13.0", + "minecraft:sequence_feature": { + "description": { + "identifier": "minecraft:jungle_tree_with_cocoa_feature" + }, + "features": [ + "minecraft:jungle_tree_feature", + "minecraft:optional_jungle_tree_cocoa_feature" + ] + } +} +``` + +`features`数组便是我们想要放置的特征列表。 + +## 自定义搜索特征 + +搜索特征的模式标识符是`minecraft:search_feature`,惯用的格式版本为`1.13.0`。搜索特征旨在在一个轴对其边界框中按特定的顺序进行搜索,一旦搜索到适合放置的位置即可放置一个特征。默认而言,搜索特征在成功放置一次后边会结束,但是我们可以更改这个结束之前的成功次数。 + +```json +{ + "format_version": "1.13.0", + "minecraft:search_feature": { + "description": { + "identifier": "minecraft:beehive_search_feature" + }, + "places_feature": "minecraft:select_beehive_feature", + "search_volume": { + "min": [ 0, 0, 0 ], + "max": [ 0, 6, 0 ] + }, + "search_axis": "+y", + "required_successes": 1 + } +} +``` + +`required_successes`是在停止搜索前所需要的成功次数。特征会在搜索满足成果次数或者搜索尽`search_volume`中指定的轴对其边界框体积后停止搜索和放置。`search_axis`代表搜索顺序,其中`+y`便代表Y轴正方向,我们自然可以推知`-y`、`±x`和`±z`的意义。对于`+y`来说,搜索的顺序便是从Y值低的层搜索至Y值高的层,然后在每个Y层中从面相搜索方向的视角来看的左下角搜索至右上角。一旦搜索过程中有满足放置要求的位置,即可放置特征并使搜索次数加1。 + +## 自定义加权随机特征 + +加权随机特征的模式标识符为`minecraft:weighted_random_feature`,惯用的格式版本为`1.13.0`。加权随机特征旨在在众多特征中通过权重随机选出一个特征进行放置。 + +```json +{ + "format_version": "1.13.0", + "minecraft:weighted_random_feature": { + "description": { + "identifier": "minecraft:random_meadow_flower_feature" + }, + "features": [ + ["minecraft:yellow_flower_feature", 5], + ["minecraft:cornflower_feature", 5], + ["minecraft:tall_grass_feature", 1] + ] + } +} +``` + +一个加权随机特征只能放置一次,在上述示例中,在一次放置中,`minecraft:yellow_flower_feature`具有$\frac{5}{11}$的概率被选中,`minecraft:cornflower_feature`也具有$\frac{5}{11}$的概率被选中,`minecraft:tall_grass_feature`具有$\frac{1}{11}$的概率被选中。 + +## 自定义树特征 + +树特征是最复杂的特征之一,它的模式标识符为`minecraft:tree_feature`,惯用格式版本为`1.13.0`。通过树特征,我们可以制作出一些树状的方块结构。我们来看一个原版的桦树特征: + +```json +{ + "format_version": "1.13.0", + "minecraft:tree_feature": { + "description": { + "identifier": "minecraft:birch_tree_feature" + }, + "trunk": { + "trunk_height": { + "range_min": 5, + "range_max": 8 + }, + "trunk_block": { + "name": "minecraft:log", + "states": { + "old_log_type": "birch" + } + } + }, + "canopy": { + "canopy_offset": { + "min": -3, + "max": 0 + }, + "variation_chance": [ + { + "numerator": 1, + "denominator": 2 + }, + { + "numerator": 1, + "denominator": 2 + }, + { + "numerator": 1, + "denominator": 2 + }, + { + "numerator": 1, + "denominator": 1 + } + ], + "leaf_block": { + "name": "minecraft:leaves", + "states": { + "old_leaf_type": "birch" + } + } + }, + "base_block": [ + "minecraft:dirt", + { + "name": "minecraft:dirt", + "states": { + "dirt_type": "coarse" + } + } + ], + "may_grow_on": [ + "minecraft:dirt", + "minecraft:grass", + "minecraft:podzol", + "minecraft:dirt_with_roots", + "minecraft:moss_block", + // Block aliases sure would be sweet + { + "name": "minecraft:dirt", + "states": { + "dirt_type": "coarse" + } + }, + { + "name": "minecraft:farmland", + "states": { + "moisturized_amount": 0 + } + }, + { + "name": "minecraft:farmland", + "states": { + "moisturized_amount": 1 + } + }, + { + "name": "minecraft:farmland", + "states": { + "moisturized_amount": 2 + } + }, + { + "name": "minecraft:farmland", + "states": { + "moisturized_amount": 3 + } + }, + { + "name": "minecraft:farmland", + "states": { + "moisturized_amount": 4 + } + }, + { + "name": "minecraft:farmland", + "states": { + "moisturized_amount": 5 + } + }, + { + "name": "minecraft:farmland", + "states": { + "moisturized_amount": 6 + } + }, + { + "name": "minecraft:farmland", + "states": { + "moisturized_amount": 7 + } + } + ], + "may_replace": [ + "minecraft:air", + { + "name": "minecraft:leaves", + "states": { + "old_leaf_type": "oak" + } + }, + { + "name": "minecraft:leaves", + "states": { + "old_leaf_type": "spruce" + } + }, + { + "name": "minecraft:leaves", + "states": { + "old_leaf_type": "birch" + } + }, + { + "name": "minecraft:leaves", + "states": { + "old_leaf_type": "jungle" + } + }, + { + "name": "minecraft:leaves2", + "states": { + "new_leaf_type": "acacia" + } + }, + { + "name": "minecraft:leaves2", + "states": { + "new_leaf_type": "dark_oak" + } + } + ], + "may_grow_through": [ + "minecraft:dirt", + "minecraft:grass", + { + "name": "minecraft:dirt", + "states": { + "dirt_type": "coarse" + } + } + ] + } +} +``` + +对于树特征来说,首先需要指定一个**树干**(**Trunk**)的形状,这里使用了`trunk`字段。事实上,我们有多种树干型状可以选择,比如`trunk`、`fallen_trunk`、`acacia_trunk`、`mega_trunk`、`fancy_trunk`等。我们只能在众多树干形状字段中选择其一。不同的树干型状对象中的子字段也不尽相同,而一般都会有`trunk_height`和`trunk_block`,分别代表树干的高度和树干使用的方块。我们可以看到,`trunk_block`使用一个方块引用对象,通过前面的学习我们可以知道,任何可以用方块引用对象的地方都可以直接用一个方块标识符字符串。因此这里`trunk_block`后面其实也可以直接跟一个方块标识符。接下来是和`trunk`平级的`canopy`字段,代表**树冠**(**Canopy**)的属性。`canopy_offset`代表树冠偏移量,而`variation_chance`代表树冠变异的可能性。而树冠使用的树叶方块则由`leaf_block`指定。与树干一样,树冠也可以有其他的类型,比如往往和金合欢树干一起使用的随机传播型树冠`random_spread_canopy`。之后,`base_block`用于指定该树特征的**树基**(**Base**)方块,即树生成后其脚下应有的方块。如果生成之前树脚下不是该方块,则树将会使脚下替换为该方块。`may_grow_on`用于指定树可以生成在哪些方块上,我们可以看到,虽然桦树的树基方块中没有草方块,但是桦树是可以生成在草方块上的,这可以在我们能够在草方块上种植桦树树苗这一点上体现。`may_replace`则是树特征生长过程中可以用于替换的方块,这些方块会被随机替换为树的一部分,成为组成树的方块。最后`may_grow_through`是树可以穿破的方块,换句话说,树可以将这些方块替换为树自己的方块。 + +树特征非常复杂但是也非常高效。开发者们可以通过阅读原版的各种特征的JSON文件来充分地学习树特征的写法。当然,其他类型的特征也无一不可通过阅读代码的形式学习。这种学习方式既高效又可以保证正确率,无疑是一种非常有效的学习方式。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/10-了解树特征规则.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/10-了解树特征规则.md new file mode 100644 index 0000000..0cc1f87 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/10-了解树特征规则.md @@ -0,0 +1,243 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 15分钟 +--- + +# 了解树特征规则 + +最后,我们来到了树特征。我们仿照原版的橡树,制作一个红色的橡树特征并将其放置在世界中。 + +## 使用编辑器配置自定义树桩方块 + +![](./images/16.10_red_oak.png) + +我们准备好红色橡木原木的纹理资源,在编辑器中创建,并将其设置为固体不透明。 + +## 使用编辑器配置自定义树叶方块 + +![](./images/16.10_red_oak_leaves.png) + +同样,我们准备好红色橡木树叶的纹理资源,在编辑器中创建,并将其设置为非实体透明。 + +## 使用树木特征设计自定义树木 + +我们手动创建`red_oak_tree_feature.json`文件,并仿照原版的`minecraft:oak_tree_feature`书写如下: + +```json +{ + "format_version": "1.13.0", + "minecraft:tree_feature": { + "description": { + "identifier": "tutorial_demo:red_oak_tree_feature" + }, + "trunk": { + "trunk_height": { + "range_min": 4, + "range_max": 7 + }, + "trunk_block": "tutorial_demo:red_oak_log" + }, + "canopy": { + "canopy_offset": { + "min": -3, + "max": 0 + }, + "variation_chance": [ + { + "numerator": 1, + "denominator": 2 + }, + { + "numerator": 1, + "denominator": 2 + }, + { + "numerator": 1, + "denominator": 2 + }, + { + "numerator": 1, + "denominator": 1 + } + ], + "leaf_block": "tutorial_demo:red_oak_leaves" + }, + "base_block": [ + "minecraft:dirt", + { + "name": "minecraft:dirt", + "states": { + "dirt_type": "coarse" + } + } + ], + "may_grow_on": [ + "minecraft:dirt", + "minecraft:grass", + "minecraft:podzol", + "minecraft:dirt_with_roots", + "minecraft:moss_block", + { + "name": "minecraft:dirt", + "states": { + "dirt_type": "coarse" + } + }, + { + "name": "minecraft:farmland", + "states": { + "moisturized_amount": 0 + } + }, + { + "name": "minecraft:farmland", + "states": { + "moisturized_amount": 1 + } + }, + { + "name": "minecraft:farmland", + "states": { + "moisturized_amount": 2 + } + }, + { + "name": "minecraft:farmland", + "states": { + "moisturized_amount": 3 + } + }, + { + "name": "minecraft:farmland", + "states": { + "moisturized_amount": 4 + } + }, + { + "name": "minecraft:farmland", + "states": { + "moisturized_amount": 5 + } + }, + { + "name": "minecraft:farmland", + "states": { + "moisturized_amount": 6 + } + }, + { + "name": "minecraft:farmland", + "states": { + "moisturized_amount": 7 + } + } + ], + "may_replace": [ + "minecraft:air", + { + "name": "minecraft:leaves", + "states": { + "old_leaf_type": "oak" + } + }, + { + "name": "minecraft:leaves", + "states": { + "old_leaf_type": "spruce" + } + }, + { + "name": "minecraft:leaves", + "states": { + "old_leaf_type": "birch" + } + }, + { + "name": "minecraft:leaves", + "states": { + "old_leaf_type": "jungle" + } + }, + { + "name": "minecraft:leaves2", + "states": { + "new_leaf_type": "acacia" + } + }, + { + "name": "minecraft:leaves2", + "states": { + "new_leaf_type": "dark_oak" + } + }, + "tutorial_demo:red_oak_leaves" + ], + "may_grow_through": [ + "minecraft:dirt", + "minecraft:grass", + { + "name": "minecraft:dirt", + "states": { + "dirt_type": "coarse" + } + } + ] + } +} +``` + +这意味着我们的树会以`tutorial_demo:red_oak_log`为树干,以`tutorial_demo:red_oak_leaves`为树叶,同时要求脚下必须为土或砂土,树叶生成时能够替换其他类型的树叶的位置,且树干可以在土、草或砂土上生成。 + +树特征会在成功生成树时判定成功,其余情况判定失败。 + +## 连接特征规则 + +我们新建一个`overworld_red_oak_tree_feature.json`文件,内容如下: + +```json +{ + "format_version": "1.13.0", + "minecraft:feature_rules": { + "description": { + "identifier": "tutorial_demo:overworld_red_oak_tree_feature", + "places_feature": "tutorial_demo:red_oak_tree_feature" + }, + "conditions": { + "placement_pass": "surface_pass", + "minecraft:biome_filter": [ + { + "any_of": [ + { + "test": "has_biome_tag", + "operator": "==", + "value": "overworld" + }, + { + "test": "has_biome_tag", + "operator": "==", + "value": "overworld_generation" + } + ] + } + ] + }, + "distribution": { + "iterations": 1, + "x": { + "distribution": "uniform", + "extent": [ 0, 16 ] + }, + "y": "query.heightmap(variable.worldx, variable.worldz)", + "z": { + "distribution": "uniform", + "extent": [ 0, 16 ] + } + } + } +} +``` + +![](./images/16.10_red_oak_in-game.png) + +进入游戏,可以看到正如我们期望的那样,每个区块平均生成了一个树特征! \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/11-挑战:制作沿河畔生长的植物特征.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/11-挑战:制作沿河畔生长的植物特征.md new file mode 100644 index 0000000..5d1a9e1 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/11-挑战:制作沿河畔生长的植物特征.md @@ -0,0 +1,156 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 20分钟 +--- + +# 挑战:制作沿河畔生长的植物特征 + +在实践过程中,我们常常将特征分为多种类型。一般而言,我们可以将特征分为**内容特征**(**Content Feature**,又译**内容地物**)、**代理特征**(**Proxy Feature**,又译**代理地物**)、**场景特征**(**Scene Feature**,又译**场景地物**)和**雕刻器特征**(**Carver Feature**,又译**雕刻器地物**)。内容特征顾名思义是用于填充内容(方块)的特征,而代理特征也顾名思义是“代其他特征而理(放置)之”的特征,也就是说,代理特征往往是用于放置一个子特征的特征,只不过在放置之前往往会先执行一些逻辑。我们前面学习的结构特征、矿石特征、单方块特征和树特征都是内容特征,而散植特征、聚合特征、序列特征和随机加权特征都属于代理特征。通常而言,代理特征都是与内容特征相配合而放置的。这一节中,我们就将通过制作一个可以沿着河畔生成的竹子特征来加强内容特征与代理特征的学习。 + +## 制作设想 + +我们希望制作一种特征可以使竹子沿着河边生成。但是,依据我们目前所学习到的特征类型,如果单单只使用一种特征,这一设想可能会比较难以实现。我们希望能够使用一些代理特征配合一些内容特征来完成这一点。事实上,在这个过程中,我们只需要完成两个要点,第一个要点便是要寻找一个水方块,第二个要点是在该水方块相邻的固体方块,比如草方块上,分散放置一些我们的竹子。 + +第一个要点其实有两种实现方式,第一种便是使用搜索特征,当搜索特征在水平面上搜索时,搜索到第一个水方块便意味着这是一个邻岸的方块。第二种是在特征规则中进行多次迭代,总有一些迭代会选择到邻岸的水方块。但是,无论是第一种还是第二种方案,我们如何保证选出来的方块是水呢?这边需要单方块特征的附着和替换放置规则了。事实上,我们只需要制作一个“仅可以通过替换水而放置的水”单方块特征即可。这样的特征只要成功放置,则一定可以保证是放在了水里。但是,我们要注意一点。单方块特征自己替换自己并不能判定为成功,所以我们无法用水来替换水。不过,不知各位开发者们还记得我们在第五章最后一节中曾讲过的静止水和流动水吗?我们可以用流动水来替换静止水,来做到不改变原有的水同时成功替换到水。 + +第二个要点也比较简单,需要拆分成两点,其一是使用散植特征来放置我们的植物,第二是植物特征本身需要有其下必须是固体方块,比如草方块,的判定。这种判定往往也可以使用单方块特征的附着规则来实现。不过,幸运的是,我们目前还处于硬编码的竹子特征`minecraft:bamboo_feature`本身带有这种判定,我们便无需画蛇添足了。 + +我们选取第一个要点的第二种方案,同时配合二个要点的设想来制作一个特征层阶树: + +```shell +特征规则 +└─序列特征(代理) + ├─水替换水特征(内容) + └─散植竹子特征(代理) + └─竹子特征(内容) +``` + +## 制作水替换水特征 + +我们新建一个`replace_water_feature.json`文件,内容如下: + +```json +{ + "format_version": "1.13.0", + "minecraft:single_block_feature": { + "description": { + "identifier": "tutorial_demo:replace_water_feature" + }, + "places_block": "minecraft:flowing_water", + "enforce_survivability_rules": false, + "enforce_placement_rules": true, + "may_replace": [ + "minecraft:water" + ] + } +} +``` + +## 制作散植竹子特征 + +我们创建一个`bamboo_scatter_feature.json`文件,内容如下: + +```json +{ + "format_version": "1.13.0", + "minecraft:scatter_feature": { + "description": { + "identifier": "tutorial_demo:bamboo_scatter_feature" + }, + "places_feature": "minecraft:bamboo_feature", + "iterations": 8, + "scatter_chance": 100, + "coordinate_eval_order": "zxy", + "project_input_to_floor": true, + "x": { + "distribution": "uniform", + "extent": [ -1, 1 ] + }, + "y": "1", + "z": { + "distribution": "uniform", + "extent": [ -1, 1 ] + } + } +} +``` + +注意,我们这里开启了`project_input_to_floor`,即将输入点投影到地板的功能。这代表着该散植特征的Y坐标能像特征规则中的`query.heightmap(variable.worldx, variable.worldz)`那样定位到地形的最上层。由于特征的作用域中无法访问`variable.worldx`和`variable.worldz`,所以`project_input_to_floor`也是一种非常常见的替代方案。 + +## 制作序列特征将它们连接在一起 + +我们创建一个`water_then_bamboo_scatter_feature.json`文件,内容填充如下: + +```json +{ + "format_version": "1.13.0", + "minecraft:sequence_feature": { + "description": { + "identifier": "tutorial_demo:water_then_bamboo_scatter_feature" + }, + "features": [ + "tutorial_demo:replace_water_feature", + "tutorial_demo:bamboo_scatter_feature" + ] + } +} +``` + +## 连接特征规则 + +最后,我们挂接特征规则。我们创建`overworld_bamboo_near_water_feature.json`文件: + +```json +{ + "format_version": "1.13.0", + "minecraft:feature_rules": { + "description": { + "identifier": "tutorial_demo:overworld_bamboo_near_water_feature", + "places_feature": "tutorial_demo:water_then_bamboo_scatter_feature" + }, + "conditions": { + "placement_pass": "surface_pass", + "minecraft:biome_filter": [ + { + "any_of": [ + { + "test": "has_biome_tag", + "operator": "==", + "value": "overworld" + }, + { + "test": "has_biome_tag", + "operator": "==", + "value": "overworld_generation" + } + ] + } + ] + }, + "distribution": { + "iterations": 20, + "x": { + "distribution": "uniform", + "extent": [ + 0, + 16 + ] + }, + "y": "query.heightmap(variable.worldx, variable.worldz)-1", + "z": { + "distribution": "uniform", + "extent": [ + 0, + 16 + ] + }, + "scatter_chance": 100 + } + } +} +``` + +![](./images/16.11_bamboo_in-game.png) + +可以看到,竹子如期在河边生成了! diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/2-了解自定义特征规则.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/2-了解自定义特征规则.md new file mode 100644 index 0000000..46c140a --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/2-了解自定义特征规则.md @@ -0,0 +1,103 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 15分钟 +--- + +# 了解自定义特征规则 + +在第六章中,我们已经初步学期了如何通过编辑器制作一个**特征规则**(**Feature Rule**,又译**地物规则**)。在本节中,我们将从JSON的层面更详细地了解特征规则。 + +## 了解特征规则文件 + +我们先来查看我们之前使用编辑器制作的那个特征规则的代码: + +```json +{ + "format_version": "1.14.0", + "minecraft:feature_rules": { + "description": { + "identifier": "tutorial_demo:demo_feature_rules", + "places_feature": "tutorial_demo:demo_feature" + }, + "conditions": { + "placement_pass": "surface_pass" + }, + "distribution": { + "coordinate_eval_order": "xzy", + "iterations": 1, + "scatter_chance": 10, + "x": { + "distribution": "uniform", + "extent": [ + 0, + 16 + ] + }, + "y": "query.get_height_at(variable.originx,variable.originz)-3", + "z": { + "distribution": "uniform", + "extent": [ + 0, + 16 + ] + } + } + } +} +``` + +我们可以看到,特征规则是可以放在`netease_feature_rules`文件夹中的,使用的模式标识符皆为`minecraft:feature_rules`,并且在这里我们使用的是`1.14.0`的格式版本。事实上,我们也可以使用`1.13.0`和`1.16.0`的格式版本。 + +在模式标识符下有三个字段,分别是`description`、`conditions`和`distribution`,分别是该特征规则的描述、条件和分布。其中,我们可以看到分布对象`distribution`中的内容非常地熟悉。没错,这里和散植特征的格式是完全一致的。其实,每一个已定义的特征规则都会在游戏生成生物群系的不同阶段上**逐区块**地执行一次类似于散植特征的逻辑。简单地说,在每个区块上,特征规则都会随机选取一个点,然后以该点为起始点做一次散植,因此,我们可以在`distribution`对象中定义一套与散植特征相同格式的字段。唯一的不同是,散植特征中的`places_feature`字段在这里被移动到了描述对象`description`中。 + +条件对象`conditions`则用于定义特征规则放置特征的条件,其中`placement_pass`便是我们之前在第六章中便接触过的特征**放置阶段**(**Placement Pass**)的定义。这里我们使用了`surface_pass`来代表在生成器生成地形中的地表阶段放置。 + +在我们这个示例中,三个轴向的坐标的串演算顺序为`xzy`。同时,我们可以看到`y`字段中使用了获得X轴和Z轴起始点处的世界高度的Molang查询函数。如果Y轴先于X、Z轴演算,则我们无法通过变量得到X和Z轴的起始点坐标。那么我们目前的逻辑就会失效,这也是为什么我们将串演算顺序调整成了`xzy`的原因。 + +我们再来看一个原版特征规则的示例: + +```json +{ + "format_version": "1.13.0", + "minecraft:feature_rules": { + "description": { + "identifier": "minecraft:bamboo_jungle_after_surface_bamboo_feature", + "places_feature": "minecraft:bamboo_then_podzol_feature" + }, + "conditions": { + "placement_pass": "after_surface_pass", + "minecraft:biome_filter": [ + { + "test": "has_biome_tag", + "operator": "==", + "value": "bamboo" + }, + { + "test": "has_biome_tag", + "operator": "==", + "value": "jungle" + } + ] + }, + "distribution": { + "iterations": "math.clamp(math.trunc(math.ceil((query.noise(math.trunc(variable.originx / 80), math.trunc(variable.originz / 80)) + 0.3) * 160)), 15, 160)", + "x": { + "distribution": "uniform", + "extent": [ 0, 16 ] + }, + "y": "query.heightmap(variable.worldx, variable.worldz)", + "z": { + "distribution": "uniform", + "extent": [ 0, 16 ] + } + } + } +} +``` + +这里我们可以看到,`conditions`字段下多了一个`minecraft:biome_filter`条件。这个条件是用于过滤生物群系的。通过一个或多个过滤器的线性排列或逻辑组合,我们可以使特征规则仅在特定的生物群系下放置特征,这实现了规则驱动的特征放置,这样放置出的特征也正是我们先前所说的**自动型特征**(**Automatic Feature**,又译**自动型地物**),即**隐式特征**(**Implicit Feature**,又译**隐式地物**)。 + +所以,从宏观上看,我们有两种放置特征的方式,一种是通过特征规则通过条件自动控制放置的隐式特征,另一种是通过生物群系的定义强制放置的显式特征。 + +至此,我们便基本学习了特征和特征规则的写法。接下来的几个小节,我们一起通过各种演示亲自操作,将各种特征的定义与生成的编写落到实处。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/3-了解自定义矿石特征规则.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/3-了解自定义矿石特征规则.md new file mode 100644 index 0000000..978664c --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/3-了解自定义矿石特征规则.md @@ -0,0 +1,115 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 10分钟 +--- + +# 了解自定义矿石特征规则 + +矿石特征往往用来生成一种矿物的矿石或者类似于矿石那样成矿脉状生成的方块。在本节中,我们一起配置一个红宝石矿石的矿脉特征。 + +## 使用编辑器配置自定义红宝石方块 + +![](./images/16.3_ruby.png) + +我们通过Photoshop等图片编辑器准备一个红宝石矿石方块纹理贴图。比如,我们可以使用修改绿宝石矿石纹理的色相来获取一个红宝石矿石纹理。 + +![](./images/16.3_ruby_block.png) + +我们通过编辑器新建一个方块,并为其赋予我们的纹理。我们使用这个方块`tutorial_demo:ruby_ore`作为我们生成红宝石矿脉的基本方块。 + +## 设置红宝石矿石特征 + +我们在行为包的`netease_features`文件夹中新建一个JSON文件,并写入如下内容 + +```json +{ + "format_version": "1.13.0", + "minecraft:ore_feature": { + "description": { + "identifier": "tutorial_demo:ruby_ore_feature" + }, + "count": 8, + "replace_rules": [ + { + "places_block": "tutorial_demo:ruby_ore", + "may_replace": [ + "minecraft:stone" + ] + } + ] + } +} +``` + +我们将该文件命名为`ruby_ore_feature.json`。注意,特征和特征规则文件的文件名必须和其本身不带有命名空间的标识符匹配。比如我们这里的赋命名空间标识符为`tutorial_demo:ruby_ore_feature`,那么我们的文件名就必须为`ruby_ore_feature`,否则特征可能会加载失败。 + +我们将`places_block`设置成我们需要放置的方块`tutorial_demo:ruby_ore`,并设置其可以替换方块为原版的石头`minecraft:stone`。这意味着该特征在放置时会**输入位置**处按照矿脉逻辑生成一个矿脉状的替换区域,并将替换区域中的石头替换成我们的红宝石矿石方块。 + +每个特征都存在一个**输入位置**(**Input Position**)和一个**输出位置**(**Output Position**)。我的世界中术语**位置**(**Position**,简称**Pos**)一般指一个三维的或二维的坐标元组,其中世界位置往往是一个浮点数三元组,而方块位置往往是一个整数三元组。特征的输入位置和输出位置都是方块位置,也就是各分量为整数的一组坐标。每个特征都会以其输入位置为起始点开始放置方块,以其输出位置为结束。输出位置往往是一个特征放置最后一个方块的位置。 + +我们的红宝石矿石特征使用了矿石特征,会在至少有一个矿石方块替换成功时**成功**(**Succeed**),在全部矿石皆放置失败时**失败**(**Fail**)。特征的成功和失败一般不会影响到玩家的游玩层面,但是可能会被一些其他的特征检查以作为放置的判定标准。 + +## 挂接特征规则 + +我们在行为包的`netease_feature_rules`文件夹中新建一个`overworld_underground_ruby_ore_feature.json`文件,然后填入如下特征规则即可完成特征到特征规则的挂接。 + +```json +{ + "format_version": "1.13.0", + "minecraft:feature_rules": { + "description": { + "identifier": "tutorial_demo:overworld_underground_ruby_ore_feature", + "places_feature": "tutorial_demo:ruby_ore_feature" + }, + "conditions": { + "placement_pass": "underground_pass", + "minecraft:biome_filter": [ + { + "any_of": [ + { + "test": "has_biome_tag", + "operator": "==", + "value": "overworld" + }, + { + "test": "has_biome_tag", + "operator": "==", + "value": "overworld_generation" + } + ] + } + ] + }, + "distribution": { + "iterations": 50, + "coordinate_eval_order": "zyx", + "x": { + "distribution": "uniform", + "extent": [ 0, 16 ] + }, + "y": { + "distribution": "gaussian", + "extent": [ 32, 481 ] + }, + "z": { + "distribution": "uniform", + "extent": [ 0, 16 ] + } + } + } +} + +``` + +`overworld`和`overworld_generation`是主世界生物群系都有的生物群系标签。准确的说,在主世界生物群系中,每个生物群系都会具有`overworld`和`overworld_generation`的其中一个。`overworld`代表主世界地形和旧版世界地形,而`overworld_generation`代表仅主世界的地形生成。 + +![](./images/16.3_ruby_feature.png) + +我们还可以在编辑器中制作特征规则。但是当前阶段我们建议不要在编辑器中进行国际版提供的特征类型的挂接,这出于两点考虑,其一是当前编辑器尚不支持国际版提供的特征类型的特征挂接,其二是不仅编辑器不支持这些特征的挂接,并且还会将已经挂接了国际版特征的特征规则中的挂接关系删除。如上图,由于编辑器的打开,我们手动在JSON中进行的挂接被删除了。 + +但不论怎样,我们的红宝石矿石特征就制作完成了。显然,这是一个隐式特征,我们来进入世界查看效果。 + +![](./images/16.3_ruby_feature_in-game.png) + +可以看到,红宝石矿石如期生成! \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/4-了解自定义单方块特征规则.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/4-了解自定义单方块特征规则.md new file mode 100644 index 0000000..d6e5614 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/4-了解自定义单方块特征规则.md @@ -0,0 +1,117 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 15分钟 +--- + +# 了解自定义单方块特征规则 + +在本节中,我们继续来看另外一种特征,也就是单方块特征的生成。我们制作一个蓝色蕨方块来进行演示。 + +## 使用编辑器配置蕨类方块 + +![](./images/16.4_blue_fern.png) + +我们在编辑器中配置一个蓝色的蕨。注意,我们需要将其配置为非固体、透明且不吸收光线,同时具备一个裁剪体积却不具备碰撞体积的样子。我们将其标识符设置为`tutorial_demo:blue_fern`。 + +然后,为了使其具备蕨类的形状,我们需要使用原版的方块形状来做到这一点。我们在资源包的`blocks.json`中手动配置如下: + +```json +{ + "format_version": [1, 1, 0], + "tutorial_demo:blue_fern": { + "blockshape": "cross_texture", + "textures": "tutorial_demo:blue_fern" + } +} +``` + +这样,我们就成功使用了交叉纹理的方块形状,使其具备了如原版的草和树苗等方块的交叉形状。 + +## 设置蕨类单方块特征 + +我们在特征文件夹中新建一个`blue_fern_feature.json`文件,并填充如下: + +```json +{ + "format_version": "1.13.0", + "minecraft:single_block_feature": { + "description": { + "identifier": "tutorial_demo:blue_fern_feature" + }, + "places_block": "tutorial_demo:blue_fern", + "enforce_survivability_rules": true, + "enforce_placement_rules": true, + "may_attach_to": { + "auto_rotate": false, + "min_sides_must_attach": 1, + "bottom": [ + "minecraft:grass", + "minecraft:dirt" + ] + }, + "may_replace": [ + "minecraft:air" + ] + } +} +``` + +我们希望我们的蓝色蕨只能在泥土和草方块上生成,并且只能替换空气,这是因为我们想避免其在空中、水中生成或者直接替换了其他的不该替换的方块比如树干方块,所以我们修改`may_attach_to`和`may_replace`来达到这一效果。 + +单方块特征的输入和输出位置为同一个坐标,同时如若方块成功放置即可判定成功,方块放置失败即为判定失败。 + +## 挂接特征规则 + +我们在特征规则文件夹中建立`overworld_first_blue_fern_feature.json`文件,然后写入: + +```json +{ + "format_version": "1.13.0", + "minecraft:feature_rules": { + "description": { + "identifier": "tutorial_demo:overworld_first_blue_fern_feature", + "places_feature": "tutorial_demo:blue_fern_feature" + }, + "conditions": { + "placement_pass": "first_pass", + "minecraft:biome_filter": [ + { + "any_of": [ + { + "test": "has_biome_tag", + "operator": "==", + "value": "overworld" + }, + { + "test": "has_biome_tag", + "operator": "==", + "value": "overworld_generation" + } + ] + } + ] + }, + "distribution": { + "iterations": 1, + "x": { + "distribution": "uniform", + "extent": [ 0, 16 ] + }, + "y": "query.heightmap(variable.worldx, variable.worldz)", + "z": { + "distribution": "uniform", + "extent": [ 0, 16 ] + } + } + } +} +``` + +习惯上我们命名特征规则时将生成的位置,比如主世界还是哪些生物群系放在最前面,然后生成的放置阶段放在中间,生成的物体名称放在阶段之后,最后和特征一样使用`feature`结尾。不过,每个开发者也有自己的命名习惯,不必拘泥于原版我的世界的命名习惯。 + +我们在每个区块中生成一个我们的蓝色蕨方块来试验效果。所以我们将迭代次数`iterations`设为1。 + +![](./images/16.4_blue_fern_in-game.png) + +可以看到,我们的蓝色蕨确实如我们希望的那样进行生成了! \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/5-了解自定义散植特征规则.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/5-了解自定义散植特征规则.md new file mode 100644 index 0000000..3d82c79 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/5-了解自定义散植特征规则.md @@ -0,0 +1,93 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 10分钟 +--- + +# 了解自定义散植特征规则 + +我们继续使用上一节的蓝色蕨方块来编写散植特征。我们希望在世界中生成更多的蓝色蕨,使其形成一个簇。 + +## 设置蕨类散植特征 + +我们在特征文件夹中建立`blue_fern_cluster_feature.json`文件。 + +```json +{ + "format_version": "1.13.0", + "minecraft:scatter_feature": { + "description": { + "identifier": "tutorial_demo:blue_fern_cluster_feature" + }, + "places_feature": "tutorial_demo:blue_fern_feature", + "iterations": 10, + "scatter_chance": 50.0, + "x": { + "distribution": "uniform", + "extent": [ 0, 5 ] + }, + "y": 0, + "z": { + "distribution": "uniform", + "extent": [ 0, 5 ] + } + } +} +``` + +注意,在命名上我们习惯于将均匀分布的植物群体称为一个**簇**(**Cluster**),而高斯分布的植物群系称为一个**斑块**(**Patch**)。当然,对于游戏引擎本身来说,命名或许没那么重要,但是对于可读性而言,一个良好的命名还是必需的。 + +散植特征在放置时,只要至少一次迭代中的目标特征成功放置就会判定成功,全部迭代的目标特征全部失败就会判定失败。 + +## 挂接特征规则 + +我们在特征规则文件夹中新建`overworld_blue_fern_cluster_feature.json`文件。 + +```json +{ + "format_version": "1.13.0", + "minecraft:feature_rules": { + "description": { + "identifier": "tutorial_demo:overworld_blue_fern_cluster_feature", + "places_feature": "tutorial_demo:blue_fern_cluster_feature" + }, + "conditions": { + "placement_pass": "surface_pass", + "minecraft:biome_filter": [ + { + "any_of": [ + { + "test": "has_biome_tag", + "operator": "==", + "value": "overworld" + }, + { + "test": "has_biome_tag", + "operator": "==", + "value": "overworld_generation" + } + ] + } + ] + }, + "distribution": { + "iterations": 5, + "x": { + "distribution": "uniform", + "extent": [ 0, 16 ] + }, + "y": "query.heightmap(variable.worldx, variable.worldz)", + "z": { + "distribution": "uniform", + "extent": [ 0, 16 ] + } + } + } +} +``` + +我们迭代5次,加之以特征本身有50%的概率放置,相当于我们平均在一个区块中放置2.5个蓝色蕨簇。 + +![](./images/16.5_blue_fern_cluster_in-game.png) + +可以看到,生成符合我们的预期! \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/6-了解自定义聚合特征规则.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/6-了解自定义聚合特征规则.md new file mode 100644 index 0000000..9d6ebd3 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/6-了解自定义聚合特征规则.md @@ -0,0 +1,86 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 15分钟 +--- + +# 了解自定义聚合特征规则 + +我们承接前面的蓝色蕨散植特征,我们希望将这种特征和橡树相结合,生成一种树荫下带有蓝色蕨簇的橡树,我们此时需要使用聚合特征。 + +## 使用聚合特征连接蕨类与树木 + +我们建立`oak_tree_with_blue_fern_cluster_feature.json`文件,并填充如下: + +```json +{ + "format_version": "1.13.0", + "minecraft:aggregate_feature": { + "description": { + "identifier": "tutorial_demo:oak_tree_with_blue_fern_cluster_feature" + }, + "early_out": "first_failure", + "features": [ + "minecraft:oak_tree_feature", + "tutorial_demo:blue_fern_cluster_feature" + ] + } +} +``` + +聚合特征会使列表中的特征全部以同一个起始点为输入点,也就是自己的输入点为输入点来放置多个特征。比如这里我们将橡树特征和蓝色蕨簇特征以同一个点为基础放置。橡树的输入点在树根部,而散植特征则是围绕输入点在周围多次散植,因此我们可以营造一种树荫下带有蓝色蕨簇的橡树。 + +`early_out`指定了何时退出本次特征放置,`first_failure`代表在列表中出现第一个判定失败的特征时便退出本次放置。但是这并不意味着本次聚合特征放置也会失败。聚合特征的判定为列表中只要有至少一个特征放置成功便是成功。注意,聚合特征的放置列表的顺序并不一定是实际放置的顺序,其顺序不能得到保证。 + +## 挂接特征规则 + +我们建立`overworld_oak_tree_with_blue_fern_cluster_feature.json`文件。 + +```json +{ + "format_version": "1.13.0", + "minecraft:feature_rules": { + "description": { + "identifier": "tutorial_demo:overworld_oak_tree_with_blue_fern_cluster_feature", + "places_feature": "tutorial_demo:oak_tree_with_blue_fern_cluster_feature" + }, + "conditions": { + "placement_pass": "surface_pass", + "minecraft:biome_filter": [ + { + "any_of": [ + { + "test": "has_biome_tag", + "operator": "==", + "value": "overworld" + }, + { + "test": "has_biome_tag", + "operator": "==", + "value": "overworld_generation" + } + ] + } + ] + }, + "distribution": { + "iterations": 5, + "x": { + "distribution": "uniform", + "extent": [ 0, 16 ] + }, + "y": "query.heightmap(variable.worldx, variable.worldz)", + "z": { + "distribution": "uniform", + "extent": [ 0, 16 ] + } + } + } +} +``` + +并进入游戏查看效果。 + +![](./images/16.6_fern_tree_in-game.png) + +我们可以看到,确实出现了很多与橡树伴随生成的蓝色蕨! \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/7-了解搜索特征规则.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/7-了解搜索特征规则.md new file mode 100644 index 0000000..1b18713 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/7-了解搜索特征规则.md @@ -0,0 +1,92 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 15分钟 +--- + +# 了解搜索特征规则 + +本节中,我们继续一起了解搜索特征。我们尝试使用搜索特征为我们的橡树附着上我们之前在第十章中制作的自定义苹果方块,使其形成一个会结果实的橡树。 + +## 设置苹果单方块特征 + +我们还记得,我们的方块标识符为`tutorial_demo:apple`。我们新建一个单方块特征`apple_feature.json`。 + +```json +{ + "format_version": "1.13.0", + "minecraft:single_block_feature": { + "description": { + "identifier": "tutorial_demo:apple_feature" + }, + "places_block": "tutorial_demo:custom_apple", + "enforce_survivability_rules": true, + "enforce_placement_rules": true, + "may_attach_to": { + "auto_rotate": false, + "min_sides_must_attach": 2, + "top": { + "name": "minecraft:leaves", + "states": { + "old_leaf_type": "oak" + } + }, + "west": { + "name": "minecraft:leaves", + "states": { + "old_leaf_type": "oak" + } + }, + "north": { + "name": "minecraft:leaves", + "states": { + "old_leaf_type": "oak" + } + }, + "east": { + "name": "minecraft:leaves", + "states": { + "old_leaf_type": "oak" + } + }, + "south": { + "name": "minecraft:leaves", + "states": { + "old_leaf_type": "oak" + } + } + }, + "may_replace": [ + "minecraft:air" + ] + } +} +``` + +这里我们为其进行了严格的替换设置。我们期望一个苹果在上、前、后、左、右五个方向上至少有两个方向是连接像树叶的。这样,我们的苹果才会看起来是挂在橡树上一样,不至于特别突兀。 + +## 使用搜索特征连接苹果特征 + +我们新建`apple_search_feature.json`文件: + +```json +{ + "format_version": "1.13.0", + "minecraft:search_feature": { + "description": { + "identifier": "tutorial_demo:apple_search_feature" + }, + "places_feature": "tutorial_demo:apple_feature", + "search_volume": { + "min": [ -4, -16, -4 ], + "max": [ 4, 0, 4 ] + }, + "search_axis": "-y", + "required_successes": 1 + } +} +``` + +我们这里从输入位置开始向Y轴负方向也就是向下开始搜索,当成功获取一个能够使苹果单方块特征`tutorial_demo:apple_feature`的位置时便停止搜索并放置该单方块特征。当找到的“能够使要放置的特征成功”的位置少于`required_successes`的数值时就会使该搜索特征放置判定失败。 + +我们在下一节中继续通过序列特征实现该搜索特征的放置。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/8-了解序列特征规则.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/8-了解序列特征规则.md new file mode 100644 index 0000000..5051959 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/8-了解序列特征规则.md @@ -0,0 +1,36 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 10分钟 +--- + +# 了解序列特征规则 + +在本节中,我们使用序列特征将上一节中的苹果挂在橡树上。 + +## 使用序列规则连接苹果特征 + +我们新建`oak_tree_then_apple_feature.json`文件: + +```json +{ + "format_version": "1.13.0", + "minecraft:sequence_feature": { + "description": { + "identifier": "tutorial_demo:oak_tree_then_apple_feature" + }, + "features": [ + "minecraft:oak_tree_feature", + "tutorial_demo:apple_search_feature" + ] + } +} +``` + +序列特征的放置列表中的特征的放置顺序是固定的,从列表中第一个特征放置到最后一个,同时上一个特征的输出位置会变成下一个特征的输入位置。树特征的输出位置在树顶,所以我们的苹果搜索特征才需要从上到下搜索,否则将搜索不到满足的位置。 + +和聚合特征不同,序列特征需要列表中全部特征都完成放置才会判定成功,而如果中途某个特征放置失败则整个特征都会被判定为失败。不过,列表中已放置的特征不会消失,但是失败特征之后的特征将全部被跳过,不再放置。 + +![](./images/16.8_apple_tree_in-game.png) + +我们可以看大, 我们的橡树上果然挂上了一个个苹果。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/9-了解加权随机特征规则.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/9-了解加权随机特征规则.md new file mode 100644 index 0000000..a515454 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/9-了解加权随机特征规则.md @@ -0,0 +1,154 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 10分钟 +--- + +# 了解加权随机特征规则 + +加权随机特征可以用于按比重生成多种特征。我们使用加权随机特征来生成一些各种样式的水井。 + +## 使用编辑器导出多种水井结构 + +![](./images/16.9_save_structures.png) + +我们在地图编辑器中制作多种水井,并将其导出。导出的结构会自动进入行为包的`structures/`文件夹中。这里,由于我们在编辑器中设置的命名空间为`tutorial_demo`,我们的结构会放置于`structures/tutorial_demo`文件夹中。 + +## 使用结构特征配置水井 + +我们使用中国版的结构特征来制作四种水井的特征。 + +```json +{ + "format_version": "1.14.0", + "netease:structure_feature": { + "description": { + "identifier": "tutorial_demo:woodwell_structure_feature" + }, + "places_structure": "tutorial_demo:woodwell" + } +} +``` + +```json +{ + "format_version": "1.14.0", + "netease:structure_feature": { + "description": { + "identifier": "tutorial_demo:stonewell_structure_feature" + }, + "places_structure": "tutorial_demo:stonewell" + } +} +``` + +```json +{ + "format_version": "1.14.0", + "netease:structure_feature": { + "description": { + "identifier": "tutorial_demo:cobblestonewell_structure_feature" + }, + "places_structure": "tutorial_demo:cobblestonewell" + } +} +``` + +```json +{ + "format_version": "1.14.0", + "netease:structure_feature": { + "description": { + "identifier": "tutorial_demo:stonebrickswell_structure_feature" + }, + "places_structure": "tutorial_demo:stonebrickswell" + } +} +``` + +由于我们的结构位于`tutorial_demo`文件夹中,因此命名空间是`tutorial_demo`。我们将其挂接到`places_structure`中,即可实现结构特征的制作。 + +## 使用加权随机特征分配水井权重 + +我们新建`random_well_feature.json`文件: + +```json +{ + "format_version": "1.13.0", + "minecraft:weighted_random_feature": { + "description": { + "identifier": "tutorial_demo:random_well_feature" + }, + "features": [ + ["tutorial_demo:woodwell_structure_feature", 1], + ["tutorial_demo:stonewell_structure_feature", 1], + ["tutorial_demo:cobblestonewell_structure_feature", 1], + ["tutorial_demo:stonebrickswell_structure_feature", 1] + ] + } +} +``` + +我们将所有水井的权重设置为1,这样每个水井都有$\frac{1}{1+1+1+1}=\frac{1}{4}$的概率生成。当选中的水井成功生成时该特征会判定成功,否则就会判定失败。 + +## 挂接特征规则 + +我们创建`overworld_well_structure_feature.json`文件,并写入如下: + +```json +{ + "format_version": "1.13.0", + "minecraft:feature_rules": { + "description": { + "identifier": "tutorial_demo:overworld_well_structure_feature", + "places_feature": "tutorial_demo:random_well_feature" + }, + "conditions": { + "placement_pass": "surface_pass", + "minecraft:biome_filter": [ + { + "any_of": [ + { + "test": "has_biome_tag", + "operator": "==", + "value": "overworld" + }, + { + "test": "has_biome_tag", + "operator": "==", + "value": "overworld_generation" + } + ] + } + ] + }, + "distribution": { + "iterations": 1, + "x": { + "distribution": "uniform", + "extent": [ + 0, + 16 + ] + }, + "y": "query.heightmap(variable.worldx, variable.worldz)-3", + "z": { + "distribution": "uniform", + "extent": [ + 0, + 16 + ] + }, + "scatter_chance": 100 + } + } +} +``` + +![](./images/16.9_in-game_1.png) + +我们可以看到,水井特征如期生成了。 + +![](./images/16.9_in-game_2.png) + +当然,虽然我们的特征规则设置为了每个区块放置一次加权随机特征,但是也是有几率出现这种三联水井的情况的。这代表我们的特征放置非常成功! \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/README.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.10_red_oak.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.10_red_oak.png new file mode 100644 index 0000000..de24543 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.10_red_oak.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.10_red_oak_in-game.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.10_red_oak_in-game.png new file mode 100644 index 0000000..bb9bbe4 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.10_red_oak_in-game.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.10_red_oak_leaves.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.10_red_oak_leaves.png new file mode 100644 index 0000000..9716079 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.10_red_oak_leaves.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.11_bamboo_in-game.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.11_bamboo_in-game.png new file mode 100644 index 0000000..0c40200 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.11_bamboo_in-game.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.3_ruby.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.3_ruby.png new file mode 100644 index 0000000..34e9e9e Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.3_ruby.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.3_ruby_block.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.3_ruby_block.png new file mode 100644 index 0000000..881598a Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.3_ruby_block.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.3_ruby_feature.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.3_ruby_feature.png new file mode 100644 index 0000000..eed17b7 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.3_ruby_feature.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.3_ruby_feature_in-game.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.3_ruby_feature_in-game.png new file mode 100644 index 0000000..9e1ed7f Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.3_ruby_feature_in-game.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.4_blue_fern.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.4_blue_fern.png new file mode 100644 index 0000000..b509364 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.4_blue_fern.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.4_blue_fern_in-game.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.4_blue_fern_in-game.png new file mode 100644 index 0000000..ec7c897 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.4_blue_fern_in-game.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.5_blue_fern_cluster_in-game.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.5_blue_fern_cluster_in-game.png new file mode 100644 index 0000000..b7e326c Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.5_blue_fern_cluster_in-game.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.6_fern_tree_in-game.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.6_fern_tree_in-game.png new file mode 100644 index 0000000..6cd31e7 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.6_fern_tree_in-game.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.8_apple_tree_in-game.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.8_apple_tree_in-game.png new file mode 100644 index 0000000..e55702d Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.8_apple_tree_in-game.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.9_in-game_1.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.9_in-game_1.png new file mode 100644 index 0000000..502b49e Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.9_in-game_1.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.9_in-game_2.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.9_in-game_2.png new file mode 100644 index 0000000..5a1646d Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.9_in-game_2.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.9_save_structures.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.9_save_structures.png new file mode 100644 index 0000000..56fc3f6 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/15-自定义特征和特征规则/images/16.9_save_structures.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/0-摘要.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/0-摘要.md new file mode 100644 index 0000000..05d563c --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/0-摘要.md @@ -0,0 +1,17 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 5分钟 +--- + +# 摘要 + +我们曾早在第二章中就学习过了**粒子**(**Particle**)和**粒子发射器**(**Particle Emitter**),了解到了国际版的粒子和中国版的粒子特效与序列帧是基于同一个底层的两套不同的系统。在本章中,我们将一起通过我们曾经推荐过的网络应用**Snowstorm**来制作国际版的自定义粒子。 + +- 在第一节(*开始制作下雪粒子*)中,我们将一起学习制作一个雪花粒子,并通过雪花粒子的制作来学习Snowstorm的使用和粒子JSON文件的组成。 +- 在第二节(*开始制作带有翻书动画的粒子*)中,我们将一起学习制作一个加载光环,通过加载光环的制作了解一种类似于中国版序列帧特效的国际版动态粒子——**翻书动画**(**Flipbook Animation**)粒子的制作。 +- 在第三节(*使用Molang增加粒子动感*)中,我们一起学习如何使用Molang为粒子添加更多的功能。 +- 在第四节(*了解粒子事件*)中,我们将一起通过事件的学习掌握事件的定义和触发。 +- 在最后一节(*挑战:设计一个烟花粒子*)中,我们将一起进行一个挑战,制作一个烟花粒子。 + +本章关键词:粒子 发射器 下雪 生命周期 运动 动力学 UV 翻书动画 变量 曲线 事件 烟花 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/1-开始制作下雪粒子.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/1-开始制作下雪粒子.md new file mode 100644 index 0000000..5cf1e9c --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/1-开始制作下雪粒子.md @@ -0,0 +1,332 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 30分钟 +--- + +# 开始制作下雪粒子 + +在本节中,我们将一起来学习如何制作一个国际版粒子。我们使用知名的粒子制作软件**Snowstorm**来进行粒子的制作。 + +## 认识Snowstorm + +曾在第三章,我们便介绍过Snowstorm软件的打开方法。我们打开Snowstorm,观察它的界面。 + +![](./images/17.1_snowstorm.png) + +我们可以看到,Snowstorm的界面非常简单,分为左右两个部分。左边的部分为**属性面板**,记录着该粒子可编辑的属性;右侧是**预览窗**,可以用于实时预览编辑的结果。 + +我们之前已经学习过,国际版的自定义粒子本质上并不是一个单独的粒子,而是一个带有粒子发射器的**粒子系统**(**Particle System**)。下面,我们将更详细地介绍这一概念。 + +正如Snowstorm属性面板中划分的那样,一个粒子可以大致分为三部分。第一部分是该粒子的定义部分,包括了自身元数据的定义(即自己的名字的定义)、各种后续要是用的Molang变量和曲线的定义。 + +第二部分用于定义一个**发射器**(**Emitter**)。一个发射器可以理解为**发射**(**Emit**)或**喷射**(**Burst**)粒子的一个来源或一个区域。在发射器具备形状和位置的同时,发射器本身还可以具备一个粒子发射逻辑,这包含了发射器发射模式、发射时间和是否循环等属性,这通常称为发射器的**生命周期**(**Lifetime**)属性,决定着发射器的存活和消逝。 + +第三部分便是定义**粒子**(**Particle**)的属性,这里的粒子往往又称作**粒子实例**(**Particle Instance**),也就是我们所说的单个粒子。一个单个粒子便是发射器用于发射的一个基本单位,往往具有**纹理**、**材质**、**面向**(**Facing**或**Orientation**)、**运动**(**Motion**)方式等属性和理所当然也具有的**生命周期**。 + +一个国际版粒子便可以理解为一个粒子系统,其中存在一个粒子发射器,而该发射器便用于发射该粒子系统中定义的粒子实例。每个被发射出的粒子便通过生命周期控制何时消亡,而发射器本身也有发射器的生命周期来控制何时销毁。一个粒子系统便可以通过从生成到销毁的过程在世界中展现其效果。 + +![](./images/17.1_code_mode.png) + +在Snowstorm的右上角点击**Code**(**代码**)按钮,便可以从**Preview**(**预览**)模式切换到Code模式。在Code模式中,我们可以查看当前粒子的源代码。这极大地方便了我们学习各个粒子组件的进程。 + +我们可以看到,粒子的模式标识符不带有命名空间,是单纯的一个`particle_effect`。Snowstorm制作的粒子格式版本默认为`1.10.0`。 + +![](./images/17.1_example.png) + +Snowstorm提供一些预设示例,我们可以在顶部的菜单栏中找到。 + +## 制作下雪粒子 + +我们接下来手把手地一起逐步制作一个和Snowstorm提供的预设下雪粒子效果相同的下雪粒子。 + +### 设置粒子定义 + +![](./images/17.1_meta.png) + +我们在**Effect**(**效果**)栏组中找到**Meta**(**元**)栏,填写我们粒子的**Identifier**(**标识符**)。我们不妨将其填写为`tutroial_demo:snow`。这代表着我们对JSON文件进行了如下修改: + +```json +{ + "format_version": "1.10.0", + "particle_effect": { + "description": { + "identifier": "tutroial_demo:snow", + } + } +} +``` + +由于我们不需要设置其他的变量,因此我们暂时忽略其他的选项栏。 + +### 设置粒子发射器 + +![](./images/17.1_emitter.png) + +相对于粒子来说,粒子发射器较为简单,我们首先为我们的粒子设置粒子发射器。**Emitter**(**发射器**)栏组便是用来设置发射器的位置。 + +我们来设想,我们的粒子发射器应该为什么形状。我们的想制作一个下雪的粒子效果。所以粒子发射器应该在我们的头顶上喷射大量的单一雪花粒子,所以我们的粒子发射器可以呈立方体,不过我们可以允许其没有厚度。紧接着,我们希望雪一直下,而不是在特定时间结束,所以我们可以将其设置为循环。最后,我们希望其喷射时能够把粒子稳定地喷射出来,而不是一股脑全部瞬间喷出。 + +因此,我们可以在**Rate**(**速率**)栏中将**Mode**(**模式**)设置为**Steady**(**稳定**)而非**Instant**(**瞬时**),调整好喷射**Rate**(**速率**)和**Maximum**(**最大**)粒子数。 + +在**Emitter Lifetime**(**发射器生命周期**)栏,中**Mode**(**模式**)设置为**Looping**(**循环**),**Active Time**(**激活时间**)可以设置为1。这配合上述的稳定模式,意味着在1秒内稳定以80个/秒的速率稳定最大喷出4000个粒子。当然,由于我们的激活时间短,所以最大粒子数是远远达不到的。由于我们不希望粒子的喷射有喷射间期,我们将**Sleep Time**(**睡眠时间**)保留不填写,这等价于填写为0。 + +在**Shape**(**形状**)栏,将中**Mode**(**模式**)设置为**Box**(**立方体**)便可以使其粒子发射器具备立方体的发射区域,我们将**Box Size**(**立方体尺寸**)的Y坐标设置为0,同时将**Offset**(**偏移**)的Y设置为正20,这代表粒子发射器会在生成的位置想上20格的位置进行发射,发射区域是一个36×36的平面。事实上,我们的形状模式还有很多这里用不到的其他选择,比如**Point**(**点**)、**Sphere**(**球**)、**Disc**(**圆盘**)和**Entity Bounding Box**(**实体包围盒**)等。 + +上述这些操作相当于在JSON文件中进行了如下的补充: + +```json +{ + "format_version": "1.10.0", + "particle_effect": { + "description": { + "identifier": "tutroial_demo:snow" + }, + "components": { + "minecraft:emitter_rate_steady": { + "spawn_rate": 80, + "max_particles": 4000 + }, // 发射器速率稳定模式的组件 + "minecraft:emitter_lifetime_looping": { + "active_time": 1 + }, // 发射器生命周期循环模式的组件 + "minecraft:emitter_shape_box": { + "offset": [0, 20, 0], + "half_dimensions": [36, 0, 36] + } // 发射器形状立方体模式的组件 + } + } +} +``` + +这样,我们便完成了粒子发射器的制作。接下来只需要制作好雪花粒子,即可将其组装为一个下雪的粒子发射器了。 + +### 制作粒子外观和纹理 + +![](./images/17.1_texture_0.png) + +我们先选择粒子的纹理。我们找到**Particle**(**粒子**)栏组的**Texture**(**纹理**)栏。我们可以看到这里是一个用于设置纹理的面板。我们在**Texture**(**纹理**)中选择我们需要的纹理贴图。我们这里可以使用原版的`particles.png`文件,也就是原版的粒子纹理的图集文件作为我们的粒子纹理贴图。我们可以选择这个图集中的第10行粒子的**精灵图**(**Sprite**)作为我们的雪花粒子。这可以通过指定该图片上的**UV**来实现。 + +UV说白了便是值U坐标和V坐标。$uOv$坐标系是一种用于在一个屏幕或图片上指定一个特定位置使用的坐标系,虽然类似于我们通常使用的$xOy$坐标系,但是坐标的方向是语气相反的。$uOv$坐标系的坐标原点在一张图的左上角。U坐标代表横轴,向右为正方向,V坐标代表竖轴,向下为正方向。这里我们可以看到,我们这张粒子纹理中每个精灵图尺寸为8×8像素,为了使其定位到第10行,我们在**UV Start**(**UV起始点**)的V坐标指定了72,即8的9倍,然后在**UV Size**(**UV尺寸**)的V坐标指定了8。也就是说,我们指定了V坐标从8的9倍到8的10倍处的粒子,因此代表第10行的粒子精灵图纹理。 + +![](./images/17.1_texture.png) + +对于Snowstorm中每一个输入框,我们都可以点击其右侧的箭头按钮来进行展开,这是方便我们进行Molang表达式的书写的。目前在本节中我们并不想深入Molang表达式的学习,我们只需要知道这里的意思为U坐标的起始点从8的0倍到8的7倍处选择其一。这样就保证了每次选出的粒子都在这一行的前8列中。 + +![](./images/17.1_appearance.png) + +接下来我们设置**Appearance**(**外观**)。**Size**(**尺寸**)是粒子的大小,我们这里依旧使用了Molang表达式来代表一个动态的大小。**Material**(**材质**)是粒子使用的材质,我们这里选择了**Alpha**(**透明**)。**Facing**(**面向**)为粒子的面向相机模式,我们有相当多的选择。这里我们选择了**Rotate XYZ**(**旋转XYZ**),代表在XYZ三个轴向上都进行一个旋转然后再生成。 + +到目前为止我们的操作又相当于在JSON中补充了如下内容: + +```json +{ + "format_version": "1.10.0", + "particle_effect": { + "description": { + "identifier": "tutroial_demo:snow", + "basic_render_parameters": { + "material": "particles_alpha", + "texture": "textures/particle/particles" + } // 该字段是必需字段,用于设置材质和纹理贴图 + }, + "components": { + "minecraft:emitter_rate_steady": { + "spawn_rate": 80, + "max_particles": 4000 + }, + "minecraft:emitter_lifetime_looping": { + "active_time": 1 + }, + "minecraft:emitter_shape_box": { + "offset": [0, 20, 0], + "half_dimensions": [36, 0, 36] + }, + "minecraft:particle_appearance_billboard": { + "size": ["0.07+variable.particle_random_1/6", "0.07+variable.particle_random_1/6"], + "facing_camera_mode": "rotate_xyz", + "uv": { + "texture_width": 128, + "texture_height": 128, + "uv": ["math.floor(variable.particle_random_2*8)*8", 72], + "uv_size": [8, 8] + } + } // 粒子的纹理UV、大小和面向都是在粒子外观公告板模式组件中设置的 + } + } +} +``` + +### 设置粒子运动 + +![](./images/17.1_dynamic.png) + +我们接下来设置粒子的**Motion**(**运动**)。为了使粒子具有如真实世界一样的运动轨迹,我们将**Mode**(**模式**)设置为**Dynamic**(**动力**)。这样,我们便可以为粒子指定动力学参数。在粒子系统中,一旦选择动力运动模式,便意味着粒子将根据$\dot{x}=f(x,t)$的微分方程进行**模拟**(**Simulate**),然后根据欧拉方法进行物理量的计算。粒子的模拟指粒子实例在生成时和接下来的每一帧中被计算和分配速度的过程。粒子效果想要呈现在玩家面前,就必须经过模拟阶段和渲染阶段,所以粒子的模拟是非常重要的一环。而这里我们便可以填入一些动力学参数来使粒子能够正确模拟。 + +我们通过**Direction**(**方向**)来设置粒子三个轴向上的初方向向量分量,这里我们使用了随机数来使得粒子的初方向各不相同。然后我们通过**Speed**(**速度**)来设置粒子的初速度,粒子之后的速度将根据**Acceleration**(**加速度**)和**Air Drag**(**空气阻力**)设置的值通过欧拉方法进行模拟,所以我们只需要设置方向、速度和加速度的初值即可。 + +至此,我们的JSON文件已经补充为如下内容: + +```json +{ + "format_version": "1.10.0", + "particle_effect": { + "description": { + "identifier": "tutroial_demo:snow", + "basic_render_parameters": { + "material": "particles_alpha", + "texture": "textures/particle/particles" + } + }, + "components": { + "minecraft:emitter_rate_steady": { + "spawn_rate": 80, + "max_particles": 4000 + }, + "minecraft:emitter_lifetime_looping": { + "active_time": 1 + }, + "minecraft:emitter_shape_box": { + "offset": [0, 20, 0], + "half_dimensions": [36, 0, 36], + "direction": ["Math.random(-1, 1)", "-1.2-Math.random(0, 1)", "Math.random(-1, 1)"] // 粒子的初方向事实上由发射器管理,因此被填入了发射器的组件中 + }, + "minecraft:particle_initial_speed": 1, // 粒子初速度组件 + "minecraft:particle_motion_dynamic": { + "linear_acceleration": [0, -0.2, 0] + }, // 粒子运动动力模式组件 + "minecraft:particle_appearance_billboard": { + "size": ["0.07+variable.particle_random_1/6", "0.07+variable.particle_random_1/6"], + "facing_camera_mode": "rotate_xyz", + "uv": { + "texture_width": 128, + "texture_height": 128, + "uv": ["math.floor(variable.particle_random_2*8)*8", 72], + "uv_size": [8, 8] + } + } + } + } +} +``` + +## 设置粒子生命周期 + +![](./images/17.1_lifetime.png) + +我们设置粒子的**Lifetime**(**生命周期**),用来指定粒子何时存在和消亡。我们在**Mode**(**模式**)中将其更改为**Time**(**时间**)模式,然后在**Max Age**(**最大年龄**)处更改为25秒。这样,我们的粒子就会在25秒后消失。 + +```json +{ + "format_version": "1.10.0", + "particle_effect": { + "description": { + "identifier": "tutroial_demo:snow", + "basic_render_parameters": { + "material": "particles_alpha", + "texture": "textures/particle/particles" + } + }, + "components": { + "minecraft:emitter_rate_steady": { + "spawn_rate": 80, + "max_particles": 4000 + }, + "minecraft:emitter_lifetime_looping": { + "active_time": 1 + }, + "minecraft:emitter_shape_box": { + "offset": [0, 20, 0], + "half_dimensions": [36, 0, 36], + "direction": ["Math.random(-1, 1)", "-1.2-Math.random(0, 1)", "Math.random(-1, 1)"] + }, + "minecraft:particle_lifetime_expression": { + "max_lifetime": 25 + }, // 时间模式其实和终止表达式共用一个组件,即粒子生命周期表达式模式组件 + "minecraft:particle_initial_speed": 1, + "minecraft:particle_motion_dynamic": { + "linear_acceleration": [0, -0.2, 0] + }, + "minecraft:particle_appearance_billboard": { + "size": ["0.07+variable.particle_random_1/6", "0.07+variable.particle_random_1/6"], + "facing_camera_mode": "rotate_xyz", + "uv": { + "texture_width": 128, + "texture_height": 128, + "uv": ["math.floor(variable.particle_random_2*8)*8", 72], + "uv_size": [8, 8] + } + } + } + } +} +``` + +## 设置粒子颜色 + +![](./images/17.1_color.png) + +我们的雪花应该是白色的,所以我们无需改动**Color & Light**(**颜色和光照**)栏的设置,相同的,我们的JSON文件也将不会进行任何修改。 + +## 设置粒子旋转 + +![](./images/17.1_rotation.png) + +最后,我们设置粒子的**Rotation**(**旋转**)。在**Dynamic**(**动力**)模式下,我们将**Start Rotation**(**起始转角**)同样通过Molang进行一个随机。下面的参数都将影响粒子的角速度,不过我们在本粒子中并不需要填写。 + +这意味着我们的最终JSON文件为: + +```json +{ + "format_version": "1.10.0", + "particle_effect": { + "description": { + "identifier": "snowstorm:snow", + "basic_render_parameters": { + "material": "particles_alpha", + "texture": "textures/particle/particles" + } + }, + "components": { + "minecraft:emitter_rate_steady": { + "spawn_rate": 80, + "max_particles": 4000 + }, + "minecraft:emitter_lifetime_looping": { + "active_time": 1 + }, + "minecraft:emitter_shape_box": { + "offset": [0, 20, 0], + "half_dimensions": [36, 0, 36], + "direction": ["Math.random(-1, 1)", "-1.2-Math.random(0, 1)", "Math.random(-1, 1)"] + }, + "minecraft:particle_lifetime_expression": { + "max_lifetime": 25 + }, + "minecraft:particle_initial_spin": { + "rotation": "variable.particle_random_3*360" + }, // 通过粒子初始自旋组件设置初始转角 + "minecraft:particle_initial_speed": 1, + "minecraft:particle_motion_dynamic": { + "linear_acceleration": [0, -0.2, 0] + }, + "minecraft:particle_appearance_billboard": { + "size": ["0.07+variable.particle_random_1/6", "0.07+variable.particle_random_1/6"], + "facing_camera_mode": "rotate_xyz", + "uv": { + "texture_width": 128, + "texture_height": 128, + "uv": ["math.floor(variable.particle_random_2*8)*8", 72], + "uv_size": [8, 8] + } + } + } + } +} +``` + +这样,我们就完成了下雪粒子的制作!我们可以直接在Snowstorm中预览到最终效果: + +![](./images/17.1_snow.png) + +可以看到,制作出的粒子还是十分漂亮且逼真的! diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/2-开始制作带有翻书动画的粒子.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/2-开始制作带有翻书动画的粒子.md new file mode 100644 index 0000000..e096f63 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/2-开始制作带有翻书动画的粒子.md @@ -0,0 +1,101 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 20分钟 +--- + +# 开始制作带有翻书动画的粒子 + +在Snowstorm中,我们不仅可以制作静态纹理的粒子,还可以制作带有**翻书动画**(**Flipbook Animation**)的动态纹理粒子。类似于中国版序列帧动画,翻书动画是国际版支持的一种通过帧变换进行的纹理动画模式,在方块、物品、粒子等功能中都可以得到应用。本节中,我们将一起学习如何制作一个和Snowstorm自带的原版着火粒子预设一样的着火粒子。 + +## 设置翻书纹理 + +![](./images/17.2_fire_texture.png) + +翻书纹理需要用到一张动态精灵图作为图集,原版的`flame_atlas.png`恰好可以作为我们的图集文件。我们将**UV Mode**(**UV模式**)设置为**Animated**(**动画**),然后可以看到下面多出了一个**UV Step**(**UV步长**),我们将其设置为`flame_atlas.png`中一个精灵的尺寸,即U步长为0,V步长为16。然后我们开启**Stretch To Lifetime**(**拉伸至生命周期**),这意味着整个动画的时间将拉伸到与生命周期相同。正因如此,我们无需设置**FPS**,因为它将自动设置,我们将其保持为0即可。 + +## 设置局部空间 + +![](./images/17.2_fire_space.png) + +在顶部的**Space**(**空间**)栏中,我们可以为粒子设置**局部空间**(**Local Space**)属性。“局部”二字其实是形容粒子的**模拟空间**(**Simulation Space**)。我们上一节中讲了,粒子的模拟是指粒子在生成前那一刻或之后的每一帧时计算应该其具备的速度的过程。一般而言,粒子的模拟是相对于世界的,也就是绝对坐标的,因此粒子生成后会相对整个世界运动。然后,有时我们希望粒子是跟随其挂接的实体或者定位器运动,那么就要将模拟空间设置为**局部的**(**Local**)。这里的三个选项分别可以将粒子的位置模拟、旋转模拟和速度模拟分别设置为局部空间。 + +## 设置其他属性 + +![](./images/17.2_fire_life.png) + +我们将生命周期通过Molang设置为一个随机数。这样不同的火焰粒子实例将保持不同的燃烧速度。 + +![](./images/17.2_fire_appearance.png) + +我们将面向设置为**Look at XYZ**(**看向XYZ**),这意味着粒子的三个轴向将始终面向看向它的实体。 + +![](./images/17.2_fire_motion.png) + +我们将粒子运动方向设置为**Outwards**(**向外**),Y轴初加速度恒定,其余两轴初加速度随机。这样能够制作出一个向外“蔓延”的效果 + +![](./images/17.2_fire_emitter.png) + +配置好发射器,将其设为**Dics**(**圆盘**)形状,**Radius**(**半径**)设为1.2米;循环;稳定发射。 + +因此我们得到了最终的JSON文件: + +```json +{ + "format_version": "1.10.0", + "particle_effect": { + "description": { + "identifier": "tutorial_demo:fire", + "basic_render_parameters": { + "material": "particles_alpha", + "texture": "textures/particle/flame_atlas" + } + }, + "components": { + "minecraft:emitter_local_space": { + "position": true, + "rotation": true, + "velocity": true + }, + "minecraft:emitter_rate_steady": { + "spawn_rate": 20, + "max_particles": 1000 + }, + "minecraft:emitter_lifetime_looping": { + "active_time": 1 + }, + "minecraft:emitter_shape_disc": { + "offset": [0, 0.4, 0], + "radius": 1.2, + "direction": "outwards" + }, + "minecraft:particle_lifetime_expression": { + "max_lifetime": "Math.random(1, 1.4)" + }, + "minecraft:particle_initial_speed": 1, + "minecraft:particle_motion_dynamic": { + "linear_acceleration": ["(variable.particle_random_1-0.5)", 1.2, "(variable.particle_random_1-0.5)"] + }, + "minecraft:particle_appearance_billboard": { + "size": [0.4, 0.4], + "facing_camera_mode": "lookat_xyz", + "uv": { + "texture_width": 16, + "texture_height": 512, + "flipbook": { + "base_UV": [0, 0], + "size_UV": [16, 16], + "step_UV": [0, 16], + "max_frame": 32, + "stretch_to_lifetime": true + } + } + } + } + } +} +``` + +![](./images/17.2_fire.png) + +我们在Snowstorm的预览窗中将看到最终的效果,非常理想。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/3-使用Molang增加粒子动感.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/3-使用Molang增加粒子动感.md new file mode 100644 index 0000000..62e7756 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/3-使用Molang增加粒子动感.md @@ -0,0 +1,138 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 25分钟 +--- + +# 使用Molang增加粒子动感 + +在本节中,我们将通过Snowstorm自带的各种示例来学习如何在粒子中使用Molang。 + +## 设置变量 + +![](./images/17.3_loading.png) + +我们打开Loading(加载)粒子,来观察如何进行变量设置。 + +![](./images/17.3_loading_var.png) + +在左侧的**Variables**(**变量**)栏中,我们可以设置两种Molang变量,分别是**Start Variables**(**起始变量**)和**Tick Variables**(**滴答变量**)。起始变量是粒子**初始化**(**Initialize**)时便会设置的变量,只会设置一次。滴答变量是粒子每次跟随游戏滴答主循环进行设置的变量,每游戏刻都会设置一次。 + +这里,我们设置了两个起始变量`variable.size = 0.08`和`variable.radius = 0.6`。这在JSON中可以写为: + +```json +"minecraft:emitter_initialization": { + "creation_expression": " + variable.size = 0.08; + variable.radius = 0.6; + ", + "per_update_expression": "" // 没有设置逐更新变量,也就是滴答变量 +} // 发射器初始化组件 +``` + +## 动态设置粒子大小 + +粒子可以通过Molang动态设置大小,我们继续看加载粒子的示例。 + +![](./images/17.3_loading_size.png) + +这里使用了先前自定义的变量`variable.size`和一个粒子自带的原生变量`variable.particle_age`。事实上,虽然粒子是组件化的,但是粒子并不被认为是ECS框架中的一个*实体*。所以,粒子本身并不具备查询函数,粒子所有的查询函数都来自于其挂接的实体。如果粒子挂接的实体消亡,那么粒子也将无法继续使用实体的查询函数。而粒子本身自带的一些变量值皆通过`variable.`前缀来提供。也就是说,虽然粒子没有查询,但是粒子依旧拥有一些原生变量。这里的`variable.particle_age`就是一个例子,它代表粒子从生成带当前为止的时间。 + +这段内容通过JSON来表示便为: + +```json +"minecraft:particle_appearance_billboard": { + "size": [ + "variable.size*(1-variable.particle_age)", + "variable.size*(1-variable.particle_age)" + ], + "facing_camera_mode": "rotate_xyz", + "uv": { + // 纹理UV的相关信息,于此处无关 + } +}, +``` + +## 动态设置发射器位置 + +我们可以通过Molang动态设置发射器位置。我们继续看加载粒子。 + +![](./images/17.3_loading_emitter_shape.png) + +我们在发射器的Shape(形状)栏中可以看到,这里用了另一个自定义变量`variable.radius`。这是将后面通过`math.sin`和`math.cos`构造的单位圆周运动放缩成我们想要的大小。这个圆周运动是对于粒子发射器来说的,也就是说粒子除了上面所说的外观随着粒子年龄而变小之外,还会因为发射器在做圆周运动而在圆周上周期生成。 + +```json +"minecraft:emitter_shape_point": { + "offset": [ + "variable.radius*-math.sin(variable.emitter_age*360)", + "variable.radius*math.cos(variable.emitter_age*360)", + 0 + ] +}, +``` + +接下来,我们看另一种粒子的情况。我们来看Magic(魔法)粒子。 + +![](./images/17.3_magic.png) + +![](./images/17.3_magic_emitter_shape.png) + +我们可以看到,相对于加载粒子,魔法粒子的参数更加丰富。这是因为魔法粒子使用了Disc(圆盘)形状的发射器。圆盘在数学上一般指一个二维的圆面,但是我们的粒子是发射在三维空间中的,所以这里的圆盘必须指定一个**法向**(**Normal**),有了法向我们就可以将圆盘放置在垂直于法向的平面上。法向的方向我们可以用**法向量**(**Normal Vector**)来表示。**Plane Normal**(**平面法向**)便是用于指定这个法向量三个轴向分量的属性。 + +可以看到,魔法粒子除了Offset(偏移)使用了Molang做圆周运动外,还在法向量上使用了相同频率的简谐振动。也就是说,圆盘所在的平面会在做简谐振动的同时做圆周运动,这两个运动的叠加组成了圆盘最终的运动。 + +## 动态设置粒子自旋 + +为了和一般的旋转运动(比如上述我们通过圆周运动进行的伪旋转)分开,我们将粒子自身围绕一个轴所做的旋转运动称为**自旋**(**Spin**)。我们继续来看魔法粒子。 + +![](./images/17.3_magic_rotate.png) + +我们可以看到,魔法粒子的单体实例在整体运动的同时还会做自旋运动。这里便通过Molang指定了一个随机的自旋初速度。 + +## 动态设置粒子线性运动 + +粒子单体除了自旋,也就是做棱角运动且具有角速度之外,还会做线性运动且具有线速度。我们继续来看魔法粒子。 + +![](./images/17.3_magic_motion.png) + +这里我们发现,我们使用了一个`variable.particle_random_3`变量来进行了线性加速度的随机。事实上,`variable.particle_random_3`是粒子自带的一个原生变量,同样的变量还有`variable.particle_random_1`、`variable.particle_random_2`、`variable.particle_random_4`、`variable.emitter_random_1`、`variable.emitter_random_2`、`variable.emitter_random_3`、`variable.emitter_random_4`。他们分别是0至1之间不同的原生随机变量,互不相同,可以直接引用。 + +```json +"minecraft:particle_motion_dynamic": { + "linear_acceleration": [ + "math.random(0, 4)", + "math.random(0, 8)", + "variable.particle_random_3>0.2 ? -10 : -4" + ] +} +``` + +## 动态设置粒子碰撞 + +粒子的碰撞也可以通过Molang控制,只不过目前Snowstorm还不支持这一功能。事实上,粒子的碰撞,即粒子与世界中地形的碰撞是通过`minecraft:particle_motion_collision`组件来实现的,虽然Snowstorm的粒子中有一个**Collision**(**碰撞**)栏来支持碰撞参数的修改,但是它不支持该组件中唯一支持Molang的`enabled`字段的修改。 + +```json +"minecraft:particle_motion_collision": { + "enabled": "/* Molang Expression */" +} +``` + +通过该字段的Molang控制,可以动态控制一个粒子是否具备碰撞特性。 + +## 设计粒子颜色渐变 + +![](./images/17.3_rainbow.png) + +最后,我们来通过Rainbow(彩虹)粒子来学习设置颜色渐变。 + +![](./images/17.3_rainbow_curve.png) + +![](./images/17.3_rainbow_color.png) + +彩虹粒子在Color & Light(颜色和光照)栏中将**Color Mode**(**配色模式**)更改为了**Gradient**(**渐变**),并使用了**Interpolant**(**插值**)变量`variable.rainbow`。而可以看到,在最上部的**Curve**(**曲线**)栏中,我们定义了变量`variable.rainbow`的曲线。它使用`variable.particle_random_2`原生变量作为**Input**(**输入**)变量,通过**Catmull-Rom插值**产生了一个曲线,从而产生了预览窗中显示的彩虹效果。 + +至此,我们基本了解了Molang在粒子中的应用。事实上,在Snowstorm的菜单栏中,我们可以获取到所有粒子的原生Molang变量的信息,以供后续制作参考: + +![](./images/17.3_molang_ref.png) + +![](./images/17.3_molangs.png) diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/4-了解粒子事件.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/4-了解粒子事件.md new file mode 100644 index 0000000..64a7483 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/4-了解粒子事件.md @@ -0,0 +1,223 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 20分钟 +--- + +# 了解粒子事件 + +我们之前一起学习过,数据驱动实体具有定义事件和触发事件的功能。事实上,我们的粒子也可以定义和触发事件。在本节中,我们一起来学习如何定义和触发粒子的事件。 + +## 定义事件 + +目前,Snowstorm并不支持事件的定义和触发,因此,我们需要手动编辑JSON文件。事件和组件平级,是定义在模式标识符下的。 + +```json +{ + "format_version": "1.10.0", + "particle_effect": { + "description": { + "identifier": "tutorial_demo:some_particle", + "basic_render_parameters": { + "material": "particles_alpha", + "texture": "textures/particle/particles" + } + }, + "events": { + // 事件 + }, + "curves": { + // 曲线 + }, + "components": { + // 组件 + } + } +} +``` + +在`events`字段中,我们可以手动定义一些粒子的事件。和实体类似,每个粒子事件也是由一个对象组成的,对象的键名便是粒子的事件名。粒子事件也可以使用`sequence`来触发多个事件,或者使用`randomize`来加权随机触发一个事件。比如,我们可以分析如下杜撰的示例: + +```json +"events": { + "": { + "sequence": [ + { + "particle_effect": { + "effect": "tutorial_demo:particle_1", + "type": "emitter" + }, + "sound_effect": { + "event_name": "some.level.sound.event.name" + } + }, + { + "particle_effect": { + "effect": "tutorial_demo:particle_2", + "type": "emitter" + }, + "expression": "/* Some Molang Expression */" + }, + { + "sequence": [ + // 可以无限嵌套 + ] + }, + { + "randomize": [ + // 加权随机触发 + ] + } + ] // 可以写一个事件触发序列 + }, + "": { + "randomize": [ + { + "weight": 1, + "particle_effect": { + "effect": "tutorial_demo:particle_2", + "type": "particle" + }, + "expression": "/* Another Molang Expression */" + }, + { + "weight": 1, + "particle_effect": { + "effect": "tutorial_demo:particle_4", + "type": "particle_with_velocity" + } + } + ] // 可以写一个加权随机触发 + }, + "": { + "particle_effect": { + "effect": "tutorial_demo:particle_5", + "type": "emitter_bound" + } // 也可以直接写一个事件触发响应 + } +} +``` + +可以看到,这个事件的结构几乎和实体中的相一致,只是事件可以响应的内容有所不同。`particle_effect`用于在自己发射器的初始坐标处再生成另一个粒子,其中`effect`填写粒子的赋命名空间标识符,`type`填写生成的类型,可以填写`emitter`、`emitter_bound`、`particle`或`particle_with_velocity`,分别代表着生成粒子的发射器、绑定生成粒子的发射器(即如果当前粒子绑定在了一个实体或定位器上,新粒子会继承这个绑定关系)、生成一个粒子实例和生成一个继承了当前发射器速度的粒子实例。 + +`expression`用于在触发事件时执行一个Molang表达式,一般用于更改变量的值。 + +`sound_effect`用于触发一个系统声音,或者称为存档声音事件。存档声音事件即是定义在资源包`sounds.json`中的声音,不过,对于粒子来说,只能触发定义在`individual_event_sounds`字段中的系统声音,即独立存档声音事件。比如,我们看原版的蜂蜜滴粒子的示例: + +```json +{ + "format_version": "1.10.0", + "particle_effect": { + "description": { + "identifier": "minecraft:honey_drip_particle", + "basic_render_parameters": { + "material": "particles_alpha", + "texture": "textures/particle/particles" + } + }, + "events": { + "hit_ground": { + "sound_effect": { + "event_name": "block.beehive.drip" + } + } + }, + "components": { + // ... + "minecraft:particle_motion_collision": { + "coefficient_of_restitution": 0.1, + "collision_drag": 10.0, + "collision_radius": 0.01, + "events": [ + { + "event": "hit_ground", + "min_speed": 0.5 + } + ] + } + // ... + } + } +} + +``` + +以及对应的`sounds.json`文件: + +```json +{ + "individual_event_sounds": { + "events": { + "block.beehive.enter": { + "sound": "block.beehive.enter", + "volume": 0.75, + "pitch": [ 0.7, 0.8 ] + }, + "block.beehive.exit": { + "sound": "block.beehive.exit", + "volume": 0.75, + "pitch": [0.9, 1.1] + }, + "block.beehive.shear": { + "sound": "block.beehive.shear", + "volume": 0.8, + "pitch": [0.8, 1.0] + }, + "block.beehive.work": { + "sound": "block.beehive.work", + "volume": 0.6, + "pitch": 1.0 + }, + "block.beehive.drip": { + "sound": "block.beehive.drip", + "volume": 0.3, + "pitch": [0.7, 0.9] + } // 此处定义了block.beehive.drip系统声音 + } + }, + "entity_sounds": { + // ... + } + // ... +} +``` + +## 触发事件 + +粒子在多个组件中都能触发事件,其中最为主要的便是两个生命周期事件组件`minecraft:emitter_lifetime_events`和`minecraft:particle_lifetime_events`。 + +`minecraft:emitter_lifetime_events`组件为**发射器的生命周期事件**,其下具有`creation_event`、`expiration_event`、`timeline`、`travel_distance_events`和`looping_travel_distance_events`五个字段,每个字段都能以特定的方式触发组件。 + +```json +"minecraft:emitter_lifetime_events": { + "creation_event": [ "", ...], + // "creation_event": "", + "expiration_event": [ "", ...], + // "expiration_event": "", + "timeline": { + "some_time": [ "", ... ], + "another_time": "" + }, + "travel_distance_events": { + "some_distance": [ "", ... ], + "another_distance": "" + }, + "looping_travel_distance_events": [ + { + "distance": 1.0, + "effects": [ "" ] + }, + { + "distance": 2.0, + "effects": [ "" ] + } + ] + +} +``` + +我们可以看到,创建事件`creation_event`和过期事件`expiration_event`都可以指定一个事件名或者以数组的形式指定多个事件名。时间轴`timeline`和移动距离事件`travel_distance_events`分别代表发射器经过特定时间或者走过特定距离后触发一个或多个事件。其中`some_time`、`another_time`、`some_distance`、`another_distance`都可以像动画的关键帧那样填写一个数字。循环移动距离事件`looping_travel_distance_events`用于定义每走过多远触发一次的事件,这样的事件必须是生成一个新的粒子效果的事件,也就这里触发的事件必须具有`particle_effect`字段的定义。 + +`minecraft:particle_lifetime_events`为**粒子的生命周期事件**,不过其下只能定义创建事件`creation_event`、过期事件`expiration_event`和时间轴`timeline`。所有的语法是相同的。 + +至此,我们已经基本学习了粒子的大部分内容,接下来的一节,我们一起挑战创建一个烟花粒子,来完成我们本章的学习! \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/5-挑战:设计一个烟花粒子.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/5-挑战:设计一个烟花粒子.md new file mode 100644 index 0000000..cea688e --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/5-挑战:设计一个烟花粒子.md @@ -0,0 +1,202 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 25分钟 +--- + +# 挑战:设计一个烟花粒子 + +在本节中,我们一起来制作一个烟花粒子。根据我们的生活经验,烟花的绽放一般分为升起和炸裂两部分。因此,我们分别制作升起和炸裂粒子,然后通过粒子事件将它们联系在一起。 + +## 制作升起粒子 + +我们在Snowstorm中新建一个粒子,命名为`tutorial_demo:firework_rise`。 + +![](./images/17.4_rise_effect.png) + +我们模仿加载粒子,设置一个变量以代表粒子大小。 + +![](./images/17.4_rise_emitter.png) + +我们设置为稳定喷射粒子的模式,并将发射器生命周期改为**Once**(**单次**)。毕竟我们不像让烟花升起后“再升起一次”。激活时间设为5秒,同时形状的偏移量的纵向上设置为`variable.emitter_age`。所以我们的烟花会上升5米,速度1米/秒。 + +![](./images/17.4_rise_particle.png) + +我们采用和加载粒子相同的纹理,然后每个粒子的最大年龄设置为1,并采用和加载粒子相同的粒子大小变化方式,即1秒后由大到小完全消失。最后,我们将颜色设置成红色。 + +因此,我们得到了如下的JSON文件: + +```json +{ + "format_version": "1.10.0", + "particle_effect": { + "description": { + "identifier": "tutorial_demo:firework_rise", + "basic_render_parameters": { + "material": "particles_alpha", + "texture": "textures/particle/particles" + } + }, + "components": { + "minecraft:emitter_initialization": { + "creation_expression": "variable.size = 0.08;" + }, + "minecraft:emitter_rate_steady": { + "spawn_rate": 31, + "max_particles": 60 + }, + "minecraft:emitter_lifetime_once": { + "active_time": 5 + }, + "minecraft:emitter_shape_point": { + "offset": [0, "variable.emitter_age", 0] + }, + "minecraft:particle_lifetime_expression": { + "max_lifetime": 1 + }, + "minecraft:particle_initial_speed": 0, + "minecraft:particle_motion_dynamic": {}, + "minecraft:particle_appearance_billboard": { + "size": ["variable.size*(1-variable.particle_age)", "variable.size*(1-variable.particle_age)"], + "facing_camera_mode": "rotate_xyz", + "uv": { + "texture_width": 128, + "texture_height": 128, + "uv": [32, 88], + "uv_size": [8, 8] + } + }, + "minecraft:particle_appearance_tinting": { + "color": [0.93725, 0.32157, 0.32157, 1] + } + } + } +} +``` + +![](./images/17.4_rise.png) + +## 制作炸裂粒子 + +我们在Snowstorm中新建第二个粒子,命名为`tutorial_demo:firework_crack`。 + +![](./images/17.4_crack_effect.png) + +同样,我们新建一个`variable.size`变量。 + +![](./images/17.4_crack_emitter.png) + +我们希望很多粒子同时在某一点炸开,所以我们设置为Instant(瞬时)模式。同时发射器向上偏移5米,因为这是升起粒子的终点位置。 + +![](./images/17.4_crack_particle.png) + +除了和升起粒子一样的部分外,我们设置粒子的运动模拟。我们将运动方向设置为Outwards(向外),然后加速度三个轴上皆设置为-2。 + +这样,我们便得到了炸裂的粒子: + +```json +{ + "format_version": "1.10.0", + "particle_effect": { + "description": { + "identifier": "tutorial_demo:firework_crack", + "basic_render_parameters": { + "material": "particles_alpha", + "texture": "textures/particle/particles" + } + }, + "components": { + "minecraft:emitter_initialization": { + "creation_expression": "variable.size = 0.08;" + }, + "minecraft:emitter_rate_instant": { + "num_particles": 600 + }, + "minecraft:emitter_lifetime_once": { + "active_time": 1 + }, + "minecraft:emitter_shape_sphere": { + "offset": [0, 5, 0], + "radius": 2, + "direction": "outwards" + }, + "minecraft:particle_lifetime_expression": { + "max_lifetime": 1 + }, + "minecraft:particle_initial_speed": 2, + "minecraft:particle_motion_dynamic": { + "linear_acceleration": [-2, -2, -2] + }, + "minecraft:particle_appearance_billboard": { + "size": ["variable.size*(1-variable.particle_age)", "variable.size*(1-variable.particle_age)"], + "facing_camera_mode": "lookat_xyz", + "uv": { + "texture_width": 128, + "texture_height": 128, + "uv": [32, 88], + "uv_size": [8, 8] + } + }, + "minecraft:particle_appearance_tinting": { + "color": [0.93725, 0.32157, 0.32157, 1] + } + } + } +} +``` + +![](./images/17.4_crack.png) + +最后,我们将两个粒子拼合在一起。我们通过`minecraft:emitter_lifetime_events`组件来触发执行两个粒子的事件。我们手动创建`firework.particle.json`文件,并写入如下内容: + +```json +{ + "format_version": "1.10.0", + "particle_effect": { + "description": { + "identifier": "tutorial_demo:firework", + "basic_render_parameters": { + "material": "particles_alpha", + "texture": "textures/particle/particles" + } + }, + "components": { + "minecraft:emitter_lifetime_events": { + "creation_event": "rise", + "timeline": { + "5.0": "crack" + } + }, + "minecraft:emitter_rate_instant": { + "num_particles": 0 + }, + "minecraft:emitter_lifetime_once": { + "active_time": 6 + }, + "minecraft:emitter_shape_point": { + "offset": [0, "variable.emitter_age", 0] + } + }, + "events": { + "rise": { + "particle_effect": { + "effect": "tutorial_demo:firework_rise", + "type": "emitter" + } + }, + "crack": { + "particle_effect": { + "effect": "tutorial_demo:firework_crack", + "type": "emitter" + } + } + } + } +} +``` + +`basic_render_parameters`是必需存在的字段,不过我们用不到它,填写默认的材质和纹理即可。重点在于`minecraft:emitter_lifetime_events`组件。我们通过`creation_event`在粒子创建时便执行`rise`事件,而`rise`事件用于生成`tutorial_demo:firework_rise`粒子。我们通过`timeline`在5.0s处触发`crack`事件,而`crack`事件则用于生成`tutorial_demo:firework_crack`粒子。这样,我们的烟花粒子就完成了。 + +![](./images/17.4_in-game.gif) + +我们可以进入游戏通过`/particle`命令来测试效果。可以看到,粒子果然如同我们设想的那样呈现了烟花的形状! \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/README.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_appearance.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_appearance.png new file mode 100644 index 0000000..fa34f39 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_appearance.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_code_mode.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_code_mode.png new file mode 100644 index 0000000..61a03be Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_code_mode.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_color.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_color.png new file mode 100644 index 0000000..8bc9f01 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_color.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_dynamic.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_dynamic.png new file mode 100644 index 0000000..c1a7184 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_dynamic.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_emitter.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_emitter.png new file mode 100644 index 0000000..20bc230 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_emitter.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_example.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_example.png new file mode 100644 index 0000000..60890e9 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_example.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_lifetime.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_lifetime.png new file mode 100644 index 0000000..c419214 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_lifetime.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_meta.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_meta.png new file mode 100644 index 0000000..64c26d9 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_meta.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_rotation.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_rotation.png new file mode 100644 index 0000000..a6c099c Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_rotation.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_snow.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_snow.png new file mode 100644 index 0000000..2713005 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_snow.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_snowstorm.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_snowstorm.png new file mode 100644 index 0000000..ed3b9ee Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_snowstorm.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_texture.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_texture.png new file mode 100644 index 0000000..f320231 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_texture.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_texture_0.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_texture_0.png new file mode 100644 index 0000000..4e0a9bb Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.1_texture_0.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.2_fire.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.2_fire.png new file mode 100644 index 0000000..3c78175 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.2_fire.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.2_fire_appearance.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.2_fire_appearance.png new file mode 100644 index 0000000..c0107fd Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.2_fire_appearance.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.2_fire_emitter.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.2_fire_emitter.png new file mode 100644 index 0000000..4504574 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.2_fire_emitter.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.2_fire_life.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.2_fire_life.png new file mode 100644 index 0000000..4927dd2 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.2_fire_life.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.2_fire_motion.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.2_fire_motion.png new file mode 100644 index 0000000..7fea2e4 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.2_fire_motion.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.2_fire_space.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.2_fire_space.png new file mode 100644 index 0000000..5ccee14 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.2_fire_space.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.2_fire_texture.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.2_fire_texture.png new file mode 100644 index 0000000..e33a7fb Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.2_fire_texture.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_loading.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_loading.png new file mode 100644 index 0000000..b29342d Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_loading.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_loading_emitter_shape.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_loading_emitter_shape.png new file mode 100644 index 0000000..9082975 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_loading_emitter_shape.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_loading_size.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_loading_size.png new file mode 100644 index 0000000..a3b0d48 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_loading_size.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_loading_var.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_loading_var.png new file mode 100644 index 0000000..76ea735 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_loading_var.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_magic.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_magic.png new file mode 100644 index 0000000..73a9bd7 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_magic.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_magic_emitter_shape.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_magic_emitter_shape.png new file mode 100644 index 0000000..1282ee2 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_magic_emitter_shape.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_magic_motion.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_magic_motion.png new file mode 100644 index 0000000..c201a3e Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_magic_motion.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_magic_rotate.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_magic_rotate.png new file mode 100644 index 0000000..5718356 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_magic_rotate.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_molang_ref.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_molang_ref.png new file mode 100644 index 0000000..f7d28aa Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_molang_ref.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_molangs.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_molangs.png new file mode 100644 index 0000000..2869d3a Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_molangs.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_rainbow.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_rainbow.png new file mode 100644 index 0000000..39afce8 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_rainbow.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_rainbow_color.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_rainbow_color.png new file mode 100644 index 0000000..a4222d2 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_rainbow_color.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_rainbow_curve.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_rainbow_curve.png new file mode 100644 index 0000000..6976584 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.3_rainbow_curve.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.4_crack.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.4_crack.png new file mode 100644 index 0000000..eb556eb Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.4_crack.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.4_crack_effect.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.4_crack_effect.png new file mode 100644 index 0000000..fd8dd5d Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.4_crack_effect.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.4_crack_emitter.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.4_crack_emitter.png new file mode 100644 index 0000000..b5c2e05 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.4_crack_emitter.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.4_crack_particle.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.4_crack_particle.png new file mode 100644 index 0000000..340e787 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.4_crack_particle.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.4_in-game.gif b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.4_in-game.gif new file mode 100644 index 0000000..83e846b Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.4_in-game.gif differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.4_rise.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.4_rise.png new file mode 100644 index 0000000..24f679f Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.4_rise.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.4_rise_effect.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.4_rise_effect.png new file mode 100644 index 0000000..d16a550 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.4_rise_effect.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.4_rise_emitter.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.4_rise_emitter.png new file mode 100644 index 0000000..b5e8b31 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.4_rise_emitter.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.4_rise_particle.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.4_rise_particle.png new file mode 100644 index 0000000..e50cb54 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/16-自定义原版粒子/images/17.4_rise_particle.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/0-摘要.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/0-摘要.md new file mode 100644 index 0000000..ab147d5 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/0-摘要.md @@ -0,0 +1,15 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 5分钟 +--- + +# 摘要 + +在本章也就是最后一章中,我们将一起来再次学习作品的结构、自测、打包与发布。 + +- 在第一节(*认识作品结构与打包*)中,我们将一起来学习作品的结构,了解正确的作品打包方式。 +- 在第二节(*使用我的世界开发工作台导出作品*)中,我们将了解如何导出我们的作品,以备他用。 +- 在第三节(*在手机和电脑上调试作品和发布作品*)中,我们将了解如何测试和发布我们的作品。 + +本章关键词:作品 打包 导出 调试 发布 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/1-认识作品结构与打包.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/1-认识作品结构与打包.md new file mode 100644 index 0000000..cd6b655 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/1-认识作品结构与打包.md @@ -0,0 +1,79 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 10分钟 +--- + +# 认识作品结构与打包 + +当我们的作品制作完成后,我们便希望将其发布到市场中供玩家们下载。因此我们可能需要了解如何如何才能正确地打包的我们的作品,防止出现上架失败的可能。 + +## 附加包 + +在上架过程中,我们的**附加包**(**Add-on**)被称为“**功能玩法**”。这种类型的作品具有最简单的打包方式。 + +一个典型的附加包一般由一个资源包和一个行为包组成,如下所示: + +```json +附加包根目录 +├─SomeAddonRes +└─SomeAddonBeh +``` + +正确的附加包打包方式为直接选中两个文件夹,将其压缩为一个压缩文件即可。 + +![](./images/18.1_addon.png) + +如果我们选择了他们的根目录再进行压缩,将造成打包失败从而无法上架。 + +## 地图 + +**地图**也是一种发布作品的方式,往往我们希望我们的附加包配合一张地图使用,因此我们会希望直接发布一张带有附加包的地图。 + +一个地图往往具备如下格式: + +```shell +地图根目录 +│ level.dat # 存档文件 +│ level.dat_old # 旧版存档文件 +│ levelname.txt # 世界名称文件 +│ world_behavior_packs.json # 世界加载的行为包文件 +│ world_resource_packs.json # 世界加载的资源包文件 +│ +├─behavior_packs # 存放的所有行为包 +├─db # LevelDB文件 +└─resource_packs # 存放的所有资源包 +``` + +对于地图文件,我们需要使用地图根目录文件夹直接压缩。 + +![](./images/18.1_world.png) + +比如,我们的地图根目录文件夹为`9da01770-f464-42e9-8bf4-427433b86c3b`,如图,我们将其直接压缩为压缩文件即可。 + +如果你在你的地图中发现了`netease_world_behavior_packs.json`和`netease_world_resource_packs.json`,你需要手动将它们的`netease_`前缀删去,并检查这两个文件中的`pack_id`字段是否和资源包和行为包中`header`头中的UUID一致。同时,带有`netease_`前缀的这两个文件通常是没有`type`字段的,你需要手动添加`type`字段并将其值设置为`Addon`。如下所示: + +```json +[ + { + "pack_id" : "eefcf409-a40a-46f7-9a48-3147960a8c39", // 保证改行的值与附加包中UUID相一致 + "type": "Addon", // 增加该行 + "version" : [ 0, 0, 1 ] // 保证该行的值与附加包中版本相一致 + } +] +``` + +## 纹理包与光影包 + +**纹理包**与**光影包**往往都只有一个资源包,不适合作为附加包上传。因此,我们需要将其直接压缩为压缩文件上传。比如我们有下图的结构。 + +```json +SomeRootFolder +└─TexturePack +``` + +然后我们直接对`TexturePack`右键,并将其压缩为压缩文件即可完成打包。 + +## 皮肤 + +基岩版虽然存在皮肤包这一种类的附加包,但是只有国际版目前使用这种类型的包,我们并不适用这种包体。要上传皮肤,请准备好带有透明通道的PNG文件,然后直接上传该文件即可。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/2-使用我的世界开发工作台导出作品.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/2-使用我的世界开发工作台导出作品.md new file mode 100644 index 0000000..bad4147 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/2-使用我的世界开发工作台导出作品.md @@ -0,0 +1,25 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 5分钟 +--- + +# 使用我的世界开发工作台导出作品 + +在本节中,我们将学习使用我的世界开发工作台导出作品。 + +## 直接导出作品 + +![](./images/18.2_export.png) + +我们在我的世界开发工作台中我们的作品上右键或者点击“更多”按钮,即可看到“**导出**”按钮和“**导出(含编辑信息)**”按钮,他们分别可以用于导出纯粹的地图或附加包,或者导出带有我的世界开发工作台工程文件的地图或附加包。这将导出一个`.zip`格式的文件。 + +## 导出为资源包 + +![](./images/18.2_export_button.png) + +我们在编辑中底部的“资源管理”窗格中可以点击“导出”按钮并选择一部分资源将其导出为一个资源包。 + +![](./images/18.2_export_as_resource.png) + +当我们选择资源后,“导出”按钮将会亮起,然后我们点击该按钮即可保存资源包。这将允许我们导出一个`.mep`格式文件。 diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/3-在手机和电脑上调试作品和发布作品.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/3-在手机和电脑上调试作品和发布作品.md new file mode 100644 index 0000000..cadb13c --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/3-在手机和电脑上调试作品和发布作品.md @@ -0,0 +1,117 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 15分钟 +--- + +# 在手机和电脑上调试作品和发布作品 + +在我们开发完成作品后,我们往往需要进行**调试**(**Debug**),消除漏洞,然后才能**发布**(**Release**)。在本节中,我们将一起学如何调试和发布。 + +## 调试作品 + +我们可以通过电脑开发版和手机开发版来调试我们的作品在两种平台上的运行情况。其中,两种开发版的调试目标又稍有不同。 + +### 电脑开发版的调试 + +我们的电脑开发版是一个开发构建,因此我们可以在电脑开发版上使用与普通的发布版中不同的功能。大多数功能是用于方便开发者进行调试的内容。 + +#### 设置中的调试选项卡 + +![](./images/18.3_debug.png) + +我们打开设置,可以看到与常规的发布版本不同,我们的开发版中多出了几个新的选项卡。”**调试**“选项卡用于开关一些用于常规调试的功能;”**UI Debug**(**UI调试**)“选项卡用于开关一些专用于UI的调试功能;”飞行调试“用于测试一些微软远程发放的灰度**处理包**(**Treatment Pack**)和**进度**(**Progression**),这不在我们的使用范围之内;”**Automation**(**自动机**)“是用于配合国际版的行为树而开发的自动机系统,亦不在我们的使用范围之内。 + +我们将关注点放在”调试“选项卡中。调试选项卡的最上方的三个功能对开发非常有用。 + +![](./images/18.3_dev_console.png) + +“**显示调试控制台按钮**”可以使游戏的左上区域出现一个控制台按钮,按钮该按钮后就会出现一个**开发控制台**(**Dev Console**),我们可以在游戏的任意时刻在其中输入命令,控制游戏。事实上,即使我们不开启该按钮,我们也可以在游戏中通过``Ctrl+` ``的方式开启开发控制台屏幕。 + +“**调试器中的断言中断**”和“**断言显示一个模式对话**”皆用于控制**断言**(**Assertion**)的表现。由于我们是开发构建,我们的游戏可以触发相当庞大的开发人员在源码中设置的断言。开发人员在程序中的某些位置做出相信该位置的某个判定应该为真的假设,如果该假设在程序实际运行中被推翻,该位置将抛出一个**断言失败**(**Assertion Failure**)。如果我们将程序绑定到了调试器,那么断言失败可以在调试器中显示,否则,我们便需要特殊的弹窗来显示。这里“断言显示一个模式对话”选项便允许当断言失败时弹出一个**模态对话框**(**Modal Dialog**)。而通常而言,开发人员希望断言失败时可以阻止程序继续运行,同时在调试器中查看为什么会失败,这一行为被称为程序的**中断**(**Break**)。事实上,我们作为普通的开发者一般而言并不需要断言中断,因此“调试器中的断言中断”处于关闭状态即可。 + +我们在附加包开发过程中,稍有不慎就也会经常遇到源码中预先设置好的各种断言失败。因为断言失败的信息在普通的发布版中不会显示,只会在开发构建中向我们展示,因此这类错误都有可能成为我们开发的隐患。有效利用断言失败提示的信息将可以极大地方便我们对附加包进行测试。 + +在调试选项卡中再往下看,我们将看到**功能旗标**(**Feature Flag**)区域。功能旗标是一系列可以通过仅仅一个开关就影响一些底层功能的选项。我们可以妥善利用这些功能旗标以方便我们的开发。 + +![](./images/18.3_debug_2.png) + +离开了功能旗标,剩下的便是普通的调试选项区域。其中我们将重点介绍**调试HUD**,即我们常言的**调试屏幕**(**Debug Screen**)。 + +#### 调试屏幕和ImGui + +![](./images/18.3_debug_hud.png) + +**调试屏幕**是一套方便开发者进行调试游戏内容的屏幕,通常可以通过设置中的调试选项卡打开。当然,调试屏幕也可以通过快捷键`F3`和`F4`向后或向前滚动。 + +![](./images/18.3_imgui.png) + +**ImGui**是一个使用了第三方ImGui库制作的游戏内测试界面,具有丰富的功能。具体的ImGui功能请查看 开发指南中的ImGui部分 。有效利用ImGui将帮助开发者更快速地制作和调试模组。 + +#### 开发者命令 + +![](./images/18.3_command.png) + +**开发者命令**是开发构建独有的方便开发者进行调试的命令。开发者命令具有相当多的功能,妥善利用开发者命令也是高效开发的重要组成部分。 + +#### 内容日志 + +![](./images/18.3_content_log.png) + +![](./images/18.3_content_log_screen.png) + +**内容日志**(**Content Log**)可以通过设置打开,也可以在游戏中快速通过`Ctrl+H`来打开。内容日志是各位开发者接触最多的一种日志类型。虽然内容日志的输出程度并没有断言错误那么底层,但是依旧是不可或缺的具有重要参考价值的日志之一。 + +### 手机开发版的自测 + +手机自测需要手机开发版启动器来启动,启动器可以在我的世界开发工作台顶部“**开发者内容管理工具**”一栏中的“**测试版启动器下载**”。相比大家在第一章中便已经下载过了。 + +手机开发自测需要大家提交自己作品进行审核。当处于“审核中”的状态时便可以进入手机开发版找到自己的作品并下载自测。手机开发版自测的主要目的并不是为了寻找逻辑错误,而一般是用于寻找设备适配性错误。通过手机开发版的自测,我们可以对我们的模组在手机上的运行情况知根知底,从而方便我们之后正式发布作品。 + +![](./images/18.3_current.png) + +如果我们的作品在发布提审时进行了定价,那么我们需要在我的世界开发工作台中找到该按钮点击申请测试货币,否则将出现在手机开发版中无法购买的情况。 + +我们还可以通过我的世界开发工作台中的“调试工具”进行手机开发版的测试,具体方案可以参见 开发指南中的相关文章 + +## 发布作品 + +最后,我们便是要发布我们的作品了。我们的作品有两个主要的发布途径,分别是通过**网页版的开发者平台**和**我的世界开发工作台的“管理”选项卡**。他们都叫做“**开发者内容管理工具**”,界面也大同小异。接下来我们通过我的世界开发工作台中的内容进行讲解。 + +![](./images/18.3_release.png) + +![](./images/18.3_upload_page.png) + +在我的世界开发工作台中,我们有两种发布方法,分别是右击我们的作品直接“**发布**”,或者是在“管理”选项卡中点击“**发布新资源**”发布。 + +![](./images/18.3_upload.png) + +进入发布页面后,我们需要填写一些基本的资源数据。 + +![](./images/18.3_credit.png) + +如果我们的作品不是原创,请务必上传授权说明和证据的图片。 + +![](./images/18.3_payment_type.png) + +我们需要选择正确的**付费类型**,一般而言,我们可以根据我们的作品实际情况选择对应的类型即可。 + +![](./images/18.3_gameplay.png) + +在选好付费类型后,下方的**资源类别**的主要类别也一并选好了,我们需要选择正确的次要类别。 + +![](./images/18.3_content.png) + +接下来我们需要填写作品的介绍和更新日志。 + +![](./images/18.3_pic.png) + +作品的配图也是尤为重要的,我们需要根据要求上传指定大小和分辨率的各种图片,方便我们作品的宣传。 + +![](./images/18.3_video.png) + +最后,如果我们的作品有配套视频,我们也可以 上传视频 来加强宣传效果。 + +点击右上角的“**立即提交审核**”即可进入审核环节。在此期间,我们便可以进入手机开发版进行自测,发现可疑错误并及时修正了!在审核通过后,我们便可以择期正式发布我们的作品了! + +至此,我们的附加包便告一段落,如果开发者想了解更多详细的信息,可以点击官网上方的“**[开发指南](https://mc.163.com/dev/guide.html)**”和“**[API文档](https://mc.163.com/dev/apidocs.html)**”,了解更多有关开发的更深入的知识与内容! \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/README.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.1_addon.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.1_addon.png new file mode 100644 index 0000000..f72cf5a Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.1_addon.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.1_world.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.1_world.png new file mode 100644 index 0000000..142ebe8 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.1_world.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.2_export.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.2_export.png new file mode 100644 index 0000000..0509612 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.2_export.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.2_export_as_resource.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.2_export_as_resource.png new file mode 100644 index 0000000..f7edfa1 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.2_export_as_resource.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.2_export_button.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.2_export_button.png new file mode 100644 index 0000000..1986ee3 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.2_export_button.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_command.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_command.png new file mode 100644 index 0000000..cd3d10c Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_command.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_content.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_content.png new file mode 100644 index 0000000..815e774 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_content.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_content_log.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_content_log.png new file mode 100644 index 0000000..21fcfe4 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_content_log.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_content_log_screen.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_content_log_screen.png new file mode 100644 index 0000000..dcb139a Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_content_log_screen.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_credit.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_credit.png new file mode 100644 index 0000000..e89e502 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_credit.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_current.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_current.png new file mode 100644 index 0000000..c6afa07 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_current.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_debug.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_debug.png new file mode 100644 index 0000000..288ecd2 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_debug.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_debug_2.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_debug_2.png new file mode 100644 index 0000000..1dbe4fa Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_debug_2.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_debug_hud.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_debug_hud.png new file mode 100644 index 0000000..12e0741 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_debug_hud.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_dev_console.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_dev_console.png new file mode 100644 index 0000000..4a5d41c Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_dev_console.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_gameplay.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_gameplay.png new file mode 100644 index 0000000..5ca2a1c Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_gameplay.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_imgui.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_imgui.png new file mode 100644 index 0000000..f4c3f28 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_imgui.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_payment_type.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_payment_type.png new file mode 100644 index 0000000..e71056f Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_payment_type.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_pic.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_pic.png new file mode 100644 index 0000000..297dc1e Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_pic.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_release.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_release.png new file mode 100644 index 0000000..738a7ec Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_release.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_upload.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_upload.png new file mode 100644 index 0000000..ede9505 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_upload.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_upload_page.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_upload_page.png new file mode 100644 index 0000000..432f805 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_upload_page.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_video.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_video.png new file mode 100644 index 0000000..7e727f5 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/17-打包、导出并测试你的作品/images/18.3_video.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/0-摘要.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/0-摘要.md new file mode 100644 index 0000000..5a97366 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/0-摘要.md @@ -0,0 +1,14 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 5分钟 +--- + +# 摘要 + +在本章中,我们将一起认识各种创作模组的实用工具。 + +- 在第一节(*官方工具介绍*)中,我们将详细介绍官方工具——**我的世界开发工作台**(**MC Studio**)。我们将一起初步学习我的世界开发工作台中都有哪些基本功能。 +- 在第二节(*社区工具介绍*)中,我们将认识更多的社区开发工具。善于利用工具是一个优秀的开发者必备的能力,而一个有力的工具将使你在开发生涯中更加得心应手! + +本章关键词:我的世界开发工作台 Blockbench Blockception bridge. Snowstorm Chunker Mcblend \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/1-官方工具介绍.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/1-官方工具介绍.md new file mode 100644 index 0000000..6d39b17 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/1-官方工具介绍.md @@ -0,0 +1,106 @@ +--- +front: https://nie.res.netease.com/r/pic/20220408/8eef28fe-0abb-4b92-819b-0a3bcc64b1fc.png +time: 10分钟 +selection: true +--- + +# 官方工具介绍 + +在本节中,我们将重点介绍**我的世界开发工作台**(**MC Studio**)这一官方工具。这是目前唯一能够进行我的世界中国版模组制作的集成开发环境。 + +## 主界面 + +我的世界工作台的下载入口位于[我的世界开发者官网首页](https://mc.163.com/dev/index.html)。进入我的世界开发工作台主界面后,我们可以看到窗口左侧有一个竖排导航栏,这分别代表着我的世界开发工作台的基本功能。 + +![开发工作台主界面](./images/3.1_mc_studio_new_screen.png) + +- **新建**:用于新建各种作品。 + + “**基岩版组件**”选项卡:用于新建空白的**地图**或**附加包(Add-on)**组件,同时也可以用于依照模板新建一个带有一定基础功能的地图或附加包组件。 + + “**基岩版网络服**”选项卡:用于申请网络服**开发机**,同时用于创建空白的或带有模板的**网络服**模组。 + +- **最近**:展示最近编辑过的组件,可以快速进入**编辑器**、**开发测试**或选择其他的功能,如发布模组等。 + +- **作品库**:用于存放你的Java版和基岩版所有的地图、附加包、网络服或其他作品。 + +- **管理**:开发者内容管理工具。可以在这里快速进入官网、公众号,查看协议,收取官方递送的邮件。可以通过**广场**获得第一手的平台资源,通过**作品管理**管理和推广你上架的作品,通过**数据与收益**对你的作品进行可视化数据管理。还可以在这里管理你的开发者成员,查看开发者等级,参与各种优质活动等。 + +- **社区**:用于进入我的世界开发者社区论坛,与其他开发者一起讨论开发中遇到的难题和灵感,与大家共同进步。 + +- **新闻**:用于查看官方推送的各种新闻。 + +## 编辑器 + +**编辑器**指的是我的世界开发工作台内置的可以用来可视化模组编辑的一个软件,通过**最近**或**作品库**标签页中作品上的“**编辑**”按钮便可以打开编辑器并将该作品载入其中。我们下面重点介绍**新版编辑器**,关于升级为新版编辑器作品的步骤可以在本教程第一章中找到。 + +对一个作品打开“编辑”后,映入我们眼帘的便是编辑器界面。我们首先查看整个编辑器的功能外观。 + +![编辑器区域](./images/3.1_level_editor_desc.png) + +- **菜单栏**:在编辑器的顶部,我们可以看到很多其他的程序也同样都具有的一种界面元素——菜单栏。这里是编辑器的“控制中心”,可以操控编辑器中的全局功能。 +- **切换页签**:在菜单栏的下方,有一个编辑器切换页签。切换页签是用来切换不同的编辑器类型的,目前的新版编辑器中,我们可以访问“**关卡编辑器**”“**预设编辑器**”“**地图编辑器**”“**界面编辑器**”“**特效编辑器**”和“**逻辑编辑器**”。 +- **功能区**:功能区是切换页签下方一个条状区域,内置各种快捷按钮,方便我们进行一些快速操作,例如保存和运行。故而该栏又称**快捷操作栏**。 +- **内嵌游戏预览窗**:一个高度修改过的我的世界构建,具备与编辑器高度兼容的接口,可以使开发者非常方便地预览到自己所做的更改。 +- **状态栏**:我们在编辑器中几乎用不到底部的状态栏。只有在地图编辑器中我们可以通过状态栏查看当前所在的坐标。 +- 其他窗口皆为各种**功能面板**:可以通过顶部菜单栏的“**窗口**”菜单控制是否显示。 + +接下来我们依次介绍各个编辑器的基本功能。 + +### 关卡编辑器 + +![关卡编辑器](./images/3.1_level_editor.png) + +顾名思义,**关卡编辑器**是用来编辑整个关卡的各种属性的编辑器。通过关卡编辑器,我们可以方便地实现自定义配置,修改配置的各种属性。更重要的是,我们还可以通过关卡编辑器实现**自定义预设的实例化**、管理预设实例的各种信息,以此实现各种玩法。由于关卡编辑器往往只保存预设信息而不修改地图数据库中的属性,所以我们在关卡编辑器中在游戏预览窗里进行的大部分操作并不会真正影响地图的信息。故关卡编辑器中所做出的修改几乎都是在地图第一次打开时才会生效,此时预览窗里的只是一个预览场景的呈现,我们将关卡编辑器中的这个预览窗称为**场景**(**Scene**)。场景和左侧**舞台**(**Stage**)窗口中的信息往往是一一对应的。不过,值得注意的是,如果预设中存在实体,那么关卡编辑器的保存也会触发地图本身的保存。 + +### 预设编辑器 + +![预设编辑器](./images/3.1_preset_editor.png) + +**预设编辑器**是新版编辑器的重中之重,为开发提供了极大的便利。为了了解什么是预设编辑器,我们需要先了解什么是预设。 + +#### 预设 + +**预设**(**Preset**)是新版编辑器提供的一个全新的功能。预设类似于模板,开发者可以通过它来预先设置一定的方块结构、实体属性、特效以及世界和玩家的属性,还可以通过将**零件**(**Part**)挂接在预设上来实现一定的逻辑。然后,开发者可以将这个“模板”通过关卡编辑器放置在世界中。一旦预设被放置在世界中,它便会成为一个**实例**(**Instance**),零件中的逻辑也将开始起作用。所以,我们将“把预设通过关卡编辑器放置在世界中”这一过程称为“预设在场景中的实例化”。预设一旦实例化,预设中的方块地形将实打实地放置在世界上,预设中的实体也将在指定位置召唤并执行指定逻辑。如果预设绑定了世界子预设或玩家子预设,那么世界和玩家的部分属性和逻辑也将立即生效。 + +**预设编辑器**便是用来编辑预设的编辑器。通过中央的内嵌游戏预览窗、左侧的预设层级面板和右侧的预设属性面板,我们可以编辑、修改预设中的地形结构,添加实体,挂接特效。我们还可以在预设编辑器中快速添加各种素材和零件,修改预设的各种属性。通过和关卡编辑器的配合,制作出含有预设的各种玩法地图或玩法组件。 + +### 地图编辑器 + +![](./images/3.1_map_editor.png) + +**地图编辑器**是真正用于修改存档中的世界的编辑器,它具备了非常强大的地形制作和编辑功能,可以轻松地制作出复杂而高级的地形或结构。对于附加包组件来说,地图编辑所带来的效果可能微乎其微,因为附加包最终可能并不是应用到当前的这个地图中。但是对于一个地图组件,地图编辑的重要性便是不言而喻的。当前的地图编辑器中制作的地图都将应用到最终的地图发布中去。 + +### 界面编辑器 + +![界面编辑器](./images/3.1_interface_editor.png) + +**界面编辑器**是用来编辑基岩版UI的编辑器。如上一章中所述,基岩版的UI都是以JSON UI的方式存储、读取和显现的。但是,由于基岩版的JSON UI格式冗杂难懂,故如果仅仅是从JSON文件层面上编辑,将是对开发者的一个重大的挑战。界面编辑器便简化了这一流程。通过可视化和点击式的操作,我们将能动态地从中央的内嵌游戏预览窗中看到我们添加的UI以及所做的更改。有了界面编辑器,开发者便可以轻而易举地绘制出自己想要的UI,为自己的模组平添几分不一样的色彩。 + +### 特效编辑器 + +![特效编辑器](./images/3.1_effect_editor.png) + +**特效编辑器**是为了编辑中国版特效而诞生的。在特效编辑器中,你可以快速添加、修改特效,还可以通过简单的拖动来将特效挂接到模型上,以展示特效的效果。在特效编辑器中,我们还有一个时间轴功能。由于一些特效中拥有序列帧动画,所以我们可以通过时间轴来实现特效的播放和调试,让特效制作得尽善尽美。 + +### 逻辑编辑器 + +![3.1_logic_editor](./images/3.1_logic_editor.png) + +**逻辑编辑器**是用来编辑逻辑**蓝图**(**Blueprint**)的编辑器。蓝图是一个可以通过在逻辑编辑器中以节点连线的方式编写的逻辑文件,可以挂接到预设的蓝图零件中,通过简单的逻辑来实现类似于模组SDK脚本的功能。虽然通过逻辑编辑器制作蓝图所获得的功能上限并没有通过模组SDK编写Python脚本来得高,但是对于初学者来说,逻辑蓝图是一个非常友好的逻辑制作抓手,这远比直接编写Python代码要简单得多。 + +## 电脑开发版 + +**我的世界基岩版电脑开发版**(**Mod PC开发包**)是我的世界开发工作台中集成的一个我的世界的开发构建版,不过这个开发构建中包含了所有网易编写的模组功能,因此可以用来进行网易模组作品上架前的PC端自测。关于如何进行自测,我们在第一章已经有了一个简要的步骤说明。在最后一章,我们还会再次详细地介绍作品自测的步骤和要点,此处仅仅介绍电脑开发版的相关概念。 + +![电脑开发版](./images/3.1_modpc.png) + +我的世界电脑开发版作为一个开发构建,拥有很多开发测试用的相关功能。善用这些功能能够极大地丰富你的开发过程,极好地帮助你进行模组调试。 + +电脑开发版拥有许多开发者版本独占的**命令**(**Command**),这可以做到但不限于打开世界边界盒、生物AI意向和各种基本信息,调试分析器、相机,快速修改生命值、饥饿值,转储方块和物品的调试信息,改变各种世界状态等丰富的功能。善用开发者命令有助于你在自测过程中更好地把握你的模组状态、修复漏洞。 + +电脑开发版的设置菜单中还拥有一个调试选项卡,可以用来快速开关一些调试用的基本设置,打开或关闭一些内置的功能旗标。其中,打开“断言错误的模态窗口”的开关将非常有助于开发者发现和更正自己模组中漏洞与错误。将调试选项卡中的“调试屏幕”下拉菜单切换到“ImGui”以打开**ImGui调试屏幕**(**ImGui Debug Screen**),ImGui中丰富的功能也会有助于开发者更好地监视自己的模组在自测过程中出现的异常状况。 + +电脑开发版中还存在一个特殊的物品,那便是**调试棒**(**Debug Stick**)物品。调试棒在预编译时会默认编译进入开发版的游戏中,以方便开发者们对自己自定义的方块进行调试,完善自己的玩法和功能。 + +善于利用各种开发版中内置的工具将对开发者们开发模组大有裨益。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/2-社区工具介绍.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/2-社区工具介绍.md new file mode 100644 index 0000000..c10657b --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/2-社区工具介绍.md @@ -0,0 +1,60 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 10分钟 +--- + +# 社区工具介绍 + +学习完了官方的工具我的世界开发工作台,我们接下来一起认识一些社区开发的优秀工具。熟练掌握这些工具,有助于我们开发模组时更快更好地完成那些本来冗杂而繁复的各种工作。 + +## Blockbench + +![Blockbench](./images/3.2_blockbench.png) + +**Blockbench**是免费的一个不可多得的模型制作工具,在基岩版中它主要被用来制作实体模型。在制作实体模型的同时,它还可以制作模型对应的纹理贴图和动画,还可以将模型导出为OBJ格式文件。Blockbench支持加载插件,通过**Minecraft Entity Wizard**插件,开发者可以快速制作出一个带有完整模型、动画和AI的基岩版实体。Blockbench有网络应用(WebAPP)和终端程序两种版本,网络应用的存在极大方便了开发者通过多种设备进行模型制作的需求。BLockbench的工程文件的后缀为`.bbmodel`,可以在我的世界开发工作台中用于原版模型几何的导入。 + +- 下载地址:[https://www.blockbench.net/](https://www.blockbench.net/) + +- 网络应用:[https://web.blockbench.net/](https://web.blockbench.net/) + +## Blockception's Minecraft Bedrock Development + +![Blockception's Minecraft Bedrock Development](./images/3.2_blockceptions.png) + +**Blockception's Minecraft Bedrock Development**是一个Visual Studio Code扩展,由Blockception工作室开发。它能够为JSON文件(包括Molang代码)、MCFunction文件和本地化等文件提供代码高亮和自动补全支持。它支持这些文件的自动补全、模式验证、快速格式化和代码诊断等一系列功能。它还可以通过控制台命令一键生成资源包、行为包和世界模板的文件,可以方便地进行批量编程。 + +- 下载地址:[https://marketplace.visualstudio.com/items?itemName=BlockceptionLtd.blockceptionvscodeminecraftbedrockdevelopmentextension](https://marketplace.visualstudio.com/items?itemName=BlockceptionLtd.blockceptionvscodeminecraftbedrockdevelopmentextension) + +## bridge. + +![bridge.](./images/3.2_bridge.png) + +**bridge.** 是一个功能强大的附加包集成开发环境。它支持几乎全部附加包文件的代码高亮、自动补全,支持资源包、行为包、皮肤包和世界模板等一系列附加包格式。bridge.拥有树状JSON视图,可以方便开发者快速地定位和增删JSON文件节点,以便于快速开发出正确且内容充实的附加包文件。bridge.还支持一系列第三方扩展,它们可以更好更有力地驱动附加包的创作。在最新版中,bridge.只有网络应用(WebAPP)的形态,建议使用Chromium内核的浏览器来安装网络应用。 + +- 网络应用:[https://bridge-core.app/](https://bridge-core.app/) + +## Snowstorm + +![Snowstorm](./images/3.2_snowstorm.png) + +**Snowstorm**是一个功能强大的国际版粒子制作器,由Blockbench的开发人员制作。它拥有一个可视化界面和操作面板。通过操作面板,你可以添加和修改粒子的各种属性,它们会实时显示在右侧的预览界面中。这使得粒子的制作非常高效。Snowstorm既有网络应用(WebAPP)又有功能相同的Visual Studio Code扩展。 + +- 网络应用:[https://snowstorm.app/](https://snowstorm.app/) +- 扩展下载:[https://marketplace.visualstudio.com/items?itemName=JannisX11.snowstorm](https://marketplace.visualstudio.com/items?itemName=JannisX11.snowstorm) + +## Chunker + +![Chunker](./images/3.2_chunker.png) + +**Chunker**是一个用于转换Java版和基岩版存档的网络工具,同时可以通过它轻松地配置世界设置。Chunker支持存档设置、维度和区块数据的转换,这包括方块、生物群系、战利品表和地图信息等各类数据。你还可以在转换的过程中一键对区块和维度进行修剪,或在转换之后执行LevelDB的压缩方法以缩小存档体积等。Chunker是一个网络应用(WebAPP),需要在线打开。 + +- 网络应用:[https://chunker.app/](https://chunker.app/) + +## Mcblend + +![Mcblend](./images/3.2_mcblend.png) + +**Mcblend**是一个Blender插件,可以用来创作基岩版实体的模型和动画。通过Blender的强大动画功能,配合Mcblend,可以快速地制作基岩版实体的动画。Mcblend可以导入和导出我的世界基岩版的多边形网格模型和基于立方体的模型,生成模型的UV贴图和纹理,导出动画和姿态。更强大的功能是,Mcblend可以检测骨架中通过约束而移动的部分并将其自动添加到我的世界实体导出的动画中,它还可以一键将物理模拟添加到实体模型中。Mcblend目前只能从它的Github仓库的发布页中下载。 + +- 下载地址:[https://github.com/Nusiq/mcblend/releases](https://github.com/Nusiq/mcblend/releases) \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/README.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.1_effect_editor.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.1_effect_editor.png new file mode 100644 index 0000000..c4ef78a Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.1_effect_editor.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.1_interface_editor.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.1_interface_editor.png new file mode 100644 index 0000000..7bc890a Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.1_interface_editor.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.1_level_editor.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.1_level_editor.png new file mode 100644 index 0000000..4c0fda2 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.1_level_editor.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.1_level_editor_desc.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.1_level_editor_desc.png new file mode 100644 index 0000000..25754b7 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.1_level_editor_desc.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.1_logic_editor.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.1_logic_editor.png new file mode 100644 index 0000000..d6093a2 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.1_logic_editor.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.1_map_editor.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.1_map_editor.png new file mode 100644 index 0000000..3b71230 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.1_map_editor.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.1_mc_studio_new_screen.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.1_mc_studio_new_screen.png new file mode 100644 index 0000000..86e169d Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.1_mc_studio_new_screen.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.1_modpc.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.1_modpc.png new file mode 100644 index 0000000..c93a522 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.1_modpc.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.1_preset_editor.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.1_preset_editor.png new file mode 100644 index 0000000..c8495c8 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.1_preset_editor.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.2_blockbench.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.2_blockbench.png new file mode 100644 index 0000000..4c4ef0e Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.2_blockbench.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.2_blockceptions.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.2_blockceptions.png new file mode 100644 index 0000000..71f1f3c Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.2_blockceptions.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.2_bridge.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.2_bridge.png new file mode 100644 index 0000000..f614036 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.2_bridge.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.2_chunker.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.2_chunker.png new file mode 100644 index 0000000..c7b5756 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.2_chunker.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.2_mcblend.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.2_mcblend.png new file mode 100644 index 0000000..36bab69 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.2_mcblend.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.2_snowstorm.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.2_snowstorm.png new file mode 100644 index 0000000..b539616 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/2-官方与社区工具介绍/images/3.2_snowstorm.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/0-摘要.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/0-摘要.md new file mode 100644 index 0000000..e8deff4 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/0-摘要.md @@ -0,0 +1,18 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 5分钟 +--- + +# 摘要 + +在本章中,我们将学习我的世界中的命令,了解命令的用途,并学会使用各种基础的命令。 + +- 在第一节(*命令的用途*)中,我们将一起认识**命令**(**Command**)这一概念,认识到命令在开发过程中的重要性。 +- 第二节(*使用命令的方法*)中,我们一起学习命令的执行方法,了解命令不同的执行途径。 +- 在第三节(*理解命令参数*)中,我们了解命令的**参数**(**Argument**),学习什么是**坐标**(**Coordinate**),学习使用**目标选择器**(**Target Selector**)和**原始文本**(**Raw Text**)。 +- 在第四节(*命令列表和概述*)中,我们将列出一个命令的列表和概述供大家参考。 +- 在第五节(*常用命令及用法*)中,我们将一起学习一些常用命令。 +- 在最后一节(*挑战:实体叠叠乐*)中,我们将一起完成一个挑战,以此巩固我们学到的知识。 + +本章关键词:命令 函数 命令方块 目标选择器 坐标 原始文本 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/1-命令的用途.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/1-命令的用途.md new file mode 100644 index 0000000..25dd6f2 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/1-命令的用途.md @@ -0,0 +1,17 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 10分钟 +--- + +# 命令的用途 + +**命令**(**Command**)是我的世界中一个非常重要的系统。通过操作命令,你可以在世界中快速执行各式各样的操作,从简单的改变游戏模式或改变天气,到复杂的记分板和函数逻辑。 + +创作命令的初衷是为了通过这种系统让游戏世界得以动态运行。例如,开发者可以通过命令实时监测玩家周围的环境,然后对特定的环境做出特定的反应。通过这样的操作可以使游戏不再是一个纯粹的静态。虽然没有了命令,我们依旧有很多种形式为游戏添加各种逻辑,但是命令作为与游戏嵌合得最好且最简单的一种表示形式,不仅可以简化模组的开发,还可以使游戏运行地更加流畅。 + +![命令](./images/4.1_command.png) + +同时,命令被整合到了模组开发的方方面面中。我们可以用**函数**(**Function**)来批量执行命令,我们还可以在实体、方块和物品等逻辑中执行命令,甚至,我们还可以通过模组API执行命令来简化脚本的逻辑或和模组其他部分相兼容,比如通过统一的记分板来记录世界信息,调配游戏资源内容。 + +接下来,我们将通过简洁易懂的语言一起来学习命令的执行。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/2-使用命令的方法.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/2-使用命令的方法.md new file mode 100644 index 0000000..28fca2e --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/2-使用命令的方法.md @@ -0,0 +1,48 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 15分钟 +--- + +# 使用命令的方法 + +每一个命令都必须经过使用才能生效,我们将命令的使用的过程称作**执行**(**Execute**)命令。 + +为了使大部分命令顺利执行,我们需要在世界选项中打开“**作弊模式**”。这样,所有的“作弊命令”和“非作弊命令”就都可以使用了,我们也就无需担心命令的可用性问题。在我的世界开发工作台的开发测试中,我们确保“作弊模式”是开启的: + +![4.2_enable_cheat](./images/4.2_enable_cheat.png) + +同时,我们强烈建议在探索命令系统时将游戏模式更改为**创造模式**。因为玩家可以在创造模式下自由地使用最高权限的命令,同时玩家在创造模式下还可以操控命令方块,以实现更多高级功能。 + +接下来,我们了解一下多种执行命令的方式。 + +## 通过聊天栏执行命令 + +进入世界后,我们按下`T`键(移动端用户是按下顶部的聊天按钮),我们将看到一个**聊天栏**(**Chat Bar**)覆盖在我们的界面上,这边是我们与其他玩家聊天的场所。但是,值得注意的是,这个聊天栏的功能不止于聊天。我们还可以将其视为一个控制台来执行命令。在聊天栏的第一个字符处输入斜杠`/`,我们将进入“命令的世界”。此时,我们将看到屏幕上出现了很多命令的提示信息,这些是可以用来提示我们输入正确的命令的信息,同时我们还可以利用这个信息进行命令的自动补全。 + +![输入`/`后出现的命令信息](./images/4.2_command_auto_complete.png) + +当我们输入命令的前几个字母后,屏幕上的提示信息也将筛选减少以适应我们已经输入的部分。随着我们的输入,我们可以看到符合筛选的命令将会越来越少,最终定位到某一个最终的命令上。不过,在此之前,如果你在使用PC设备,并且在屏幕上还有一些候选命令的时候按下`Tab`键,你将体验到命令的自动补全功能。重复按下`Tab`键可以让补全的命令在当前屏幕上提示的所有命令中循环。这是一个快速输入某个命令的非常实用的功能。 + +## 通过命令方块执行命令 + +我们在创造模式开启作弊的聊天栏中输入以下命令:`/give @s command_block`。我们将得到一个**命令方块(Command Block)**。命令方块是一个可以以自己为执行者执行预先配置好的命令的方块,只有在创造模式下通过对其按下“使用键”(PC端是鼠标右键)才可以将其配置界面打开。打开后,我们将看到如下一系列选项。 + +![4.2_command_block_screen](./images/4.2_command_block_screen.png) + +- **命令输入**:命令输入是我们输入命令的地方,这里的格式几乎与聊天栏中输入命令相同。唯一的不同点是命令方块中的命令无需斜杠`/`前缀。 +- **方块类型**:我们放下命令方块时默认是**脉冲**模式。脉冲模式的命令方块时橙色的,每激活一次就执行一次命令。我们还可以将其改成绿色的**连锁**模式和紫色的**重复**模式。连锁模式的命令方块只在指向它的命令方块执行命令时才会执行命令,而重复模式的命令方块被激活时每一游戏刻都会执行一次命令。 +- **条件**:默认为**无条件**,即不受条件制约,无视周围的命令方块执行成功与否。**有条件**时受到条件制约,即只有当前一个指向自己的命令方块执行指令成功时,自己才会执行命令。 +- **红石**:脉冲模式和连锁模式默认**需要红石**,即必须由红石对其激活才能使其发挥作用,执行命令。但是如果调整成重复模式默认的**保持开启**,就可以使其重复模式的命令方块无需红石即可持续执行其中的命令;连锁模式是在感应后立即执行一次,脉冲模式将在设置完毕后立即执行一次,且此后将无法再次执行。 +- **执行第一个已选项**(**Execute on First Tick**,在首刻执行):在激活后第一个游戏刻立即执行,而非进入挂起状态等待延迟后再执行。 +- **已选项中的延迟**(**Delay in Ticks**,延迟刻数):对于脉冲模式命令方块和连锁模式命令方块,指定在被激活或触发后,执行命令之前延迟的游戏刻。对于重复模式命令方块,指定其重新执行命令所延迟的游戏刻。 + +## 通过函数文件执行命令 + +**函数**(**Function**)是一个允许开发者使用`.mcfunction`文件作为一个命令的集合来批量执行命令的功能,常常配合命令方块实现一些高级功能。 + +在函数文件中,一条命令单独占一行,没有斜杠`/`前缀。在文件中可以使用`#`来开启一个注释,`#`之后的文字都会属于注释内容而不被执行。 + +通过`/function`命令,我们可以执行一个函数。`/function`的语法是`/function `。比如,我们可以通过`/function test`来执行行为包根目录的`functions`文件夹下的`test.mcfunction`文件。 + +通过`/reload`命令,我们可以重新加载行为包中的函数文件。通过该命令,我们可以更快地调试函数而不重启游戏。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/3-理解命令参数.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/3-理解命令参数.md new file mode 100644 index 0000000..c37df2a --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/3-理解命令参数.md @@ -0,0 +1,253 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 15分钟 +--- + +# 理解命令参数 + +通过前期的探索,我们可以粗略地了解到,一个命令是由如下部分组成的: + +1. 如果是聊天栏输入的命令,则必须有一个`/`开头; +2. 紧跟着`/`之后是命令的**名称**(**Name**); +3. 在命令名称之后是一个空格` `; +4. 紧接着是各个命令参数,参数与参数之间以空格隔开。 + +所以可见,我们必须要理解何为命令的**参数**(**Argument**),才能正确地使用一个命令。 + +## 参数 + +参数是命令名之后跟着的一系列用于精确界定命令执行范围或执行对象的数据。大部分命令都有一个或多个参数。参数分为多种类型,分别有**必需参数**、**可选参数**和**字面参数**。 + +### 必需参数 + +**必需参数**(**Required Argument**)是一种通过尖括号包裹起来的参数,格式是``。尖括号代表该命令必须具有该参数才能正常运作,否则将会出现执行错误。例如,给予一个玩家管理员的命令: + +```shell +/op +``` + +冒号之前的部分称作该参数的**描述符**(**Descriptor**),通常又称为参数名,在这里是`player`,代表这里应该输入一个玩家。冒号之后的部分称作该参数的**类型**(**Type**),这里是`target`。`target`类型代表可以输入一个玩家名或者一个目标选择器。关于目标选择器,后面我们会一起重点学习。 + +如果该命令只输入`/op`,那么将会报错,提示缺少必要的参数。 + +### 可选参数 + +**可选参数**(**Optional Argument**)是一种通过方块号包裹起来的参数,是并非一定要输入的参数。这种参数往往具有一些默认值,如果不输入将自动调用其默认值。可选参数的格式为`[descriptor: type]`。例如,将一个玩家踢出世界的命令: + +```shell +/kick [reason: message] +``` + +这个命令有两个参数,第一个参数是一个必需参数,而第二个便是一个可选参数。如果只传入第一个玩家名,保留第二个为空,那么踢出玩家时将不会带有原因,而如果传入第二个可选参数,那么踢出玩家时将展示踢出原因。 + +### 字面参数 + +**字面参数**(**Literal Argument**)是一种和上面两种参数表达方式都不一样的参数,它没有描述符和类型,仅仅是一个字面意义的文本。也就是说,这个参数不允许玩家自定义传入的值,该写成什么样就写成什么样。往往,一个字面参数可以允许多个字面值,当有多个字面值时,各个值之间用`|`分隔开,此时如果一个字面参数是必需的,那么在外围将包裹尖括号;如果是可选的,将包裹方括号。对于只有一个字面值的字面参数,其外面没有括号,这种字面参数往往都是必需参数。它的格式一般为`arg`、``或`[arg1|arg2|...]`。例如,天气命令: + +```shell +/weather [duration: int] +/weather query +``` + +`/weather `用来更换天气,`/weather query`用来查询天气。此时``是一个必需的字面参数,`query`是一个必须的只有一种选择的字面参数。 + +### 参数类型列表 + +这里列出了常见的参数类型列表和它们的描述。 + +| 类型 | 描述 | +| :--------- | :----------------------------------------------------------- | +| Boolean | 布尔值,`true`或者`false`。 | +| int | 整数,比如`1`、`2`、`3`等。 | +| float | 浮点数,比如`1.3`、`3.14`等。 | +| string | 字符串,可以直接是一个单词/汉字或是一个由引号包裹的多个单词/带空格的汉字,比如`single`或`"multiple words"`。 | +| message | 消息文本,无需引号包括的字符串。 | +| x y z | 代表坐标的三个浮点数,中间用空格间隔,比如`16.5 128 32`代表x=16.5、y=128、z=32的位置。可以用`~`表示相对坐标,或用`^`表示局部坐标,具体详见[坐标](#坐标)。 | +| Block | 方块ID。 | +| Item | 物品ID。 | +| EntityType | 实体ID。 | +| json | JSON格式文本。 | +| target | 一个玩家名或一个目标选择器。 | +| …… | …… | + +## 坐标 + +我的世界中的**坐标**(**Coordinate**)是一个三维空间的欧拉右手直角坐标系,但是与典范的欧拉坐标系不同,我的世界的坐标系经过一定的旋转,旋转成了y轴正方向代表朝上,因此,我们在世界中的高度成为了y坐标。 + +![4.3_coords](./images/4.3_coords.png) + +具体来讲,xyz的三个轴代表如下方向: + +- x轴的正方向为东,其坐标反映了玩家距离原点在东(+)西(-)方向上的距离。 +- z轴的正方向为南,其坐标反映了玩家距离原点在南(+)北(-)方向上的距离。 +- y轴的正方向为上,其坐标反映了玩家位置的高低程度(其中海平面为62)。 + +一般来讲,如果需要给予坐标一个单位的话,我们认为世界中坐标的单位为米(m)。 + +### 相对坐标 + +在命令中,我们可以使用`~`来代表**相对坐标**(**Relative Coordinate**)。单独的`~`代表与执行原点距离为0的位置,`~`后面加一个数字(例如`~1`)代表相对于执行原点偏移了这个数字大小的距离的位置。后面所跟的数字可正可负,正代表沿着该轴正方向偏移,负则代表沿着该轴负方向偏移。 + +例如,`/tp ~1 ~5 ~-3`可以将玩家传送到往东1m,往上5m,同时往北3m的位置。 + +### 局部坐标 + +和相对坐标类似,**局部坐标**(**Local Coordinate**)也是一种从执行原点开始计算的坐标,不过其各个轴的方向和相对坐标有很大不同。局部坐标是以玩家视角面对的方向为轴的。准确地说: + +- x轴的正方向为左,其坐标反映了在以玩家视角为参考左(+)右(-)方向上的距离。 +- z轴的正方向为前,其坐标反映了在以玩家视角为参考前(+)后(-)方向上的距离。 +- y轴的正方向为上,其坐标反映了在以玩家视角为参考上(+)下(-)方向上的距离(注意,玩家的上不一定就是竖直方向,例如玩家“90°看天”时玩家的上方向和水平面平行)。 + +局部坐标使用`^`代替`~`,其余的用法和相对坐标完全相同。 + +## 目标选择器 + +对于带有`target`类型参数的命令,我们知道可以使用玩家的名字来执行。但是,有时候使用玩家名是非常不便的。比如,我们通常不知道使用我们的玩法组件的玩家究竟是何名,自然无法在开发过程中预先使用玩家的名字。这时候,我们就需要**目标选择器**(**Target Selector**)的登场了。 + +我们有5种基选择器来选择不同的目标实体,它们分别是: + +| 基选择器 | 功能 | +| :------- | :----------------------------------------------------------- | +| `@p` | 选择距离执行原点最近的玩家,执行原点通常是玩家或者命令方块。当`x`、`y`和`z`参数被指定时将选择距离该指定坐标最近的玩家。 | +| `@r` | 选择一个随机玩家。当`type`参数被指定时将选择其他类型的实体。 | +| `@a` | 选择所有玩家,无论存活与否。 | +| `@e` | 选择所有实体。 | +| `@s` | 选择命令的执行实体。 | + +上面所说的“参数”皆指**选择器参数**(**Selector Parameter**),而非命令参数。选择器参数是跟在基选择器后面用方括号包裹起来的一种变量,可以用来筛选和缩小选择器选择的范围。选择器参数通常的语法是: + +```shell +@[param1=value1,param2=value2,...] +``` + +我们可以看到,方括号内可以具有一个或多个选择器参数,每个选择器参数之间用英文半角逗号隔开。每个参数内部都会先出现一个参数名,参数名之后紧跟一个等号(`=`),等号后是该参数选择的值。 + +比如,我们想将所有冒险模式下的玩家更改为生存模式,而不影响创造模式或旁观者模式下的玩家,那么我应该使用如下命令和选择器参数:`/gamemode survival @a[m=adventure]`。 + +有一些选择器参数允许进行一种反选,这可以通过在参数的值之前添加英文半角感叹号`!`来实现,这称作对参数的值进行**否定**(**Negate**)。比如,`/gamemode survival @a[m=!adventure]`将实现将所有非冒险模式下的玩家的游戏摩米士全部更改为生存模式。 + +### 选择器参数列表 + +下面列出了所有的选择器参数及其功能: + +| 参数 | 功能 | +| :----------- | :----------------------------------------------------------- | +| `x, y, z` | 指定选择器的执行原点,不必同时指定全部三个轴,不指定时默认和命令的执行原点一致。 | +| `r, rm` | 指定距离执行原点一定范围内的实体,该参数的值代表范围的半径。`r`是radius的缩写,代表范围的最大半径;`rm`是radius minimum的缩写,代表范围的最小半径。两者都可不同时指定,`r`不指定时默认为无穷大,`rm`不指定时默认为0。 | +| `dx, dy, dz` | 指定在矩形长方体内的实体,执行原点将是该长方体的一个顶点。`d`是distance的缩写,三个参数分别代表距离该顶点(执行原点)的距离。若三者其中之一被指定,另外没有指定的参数默认值均为0。 | +| `c` | 指定最大可能选择的实体数,最终选择的实体依赖于基础目标选择器本来应选出的实体的排列顺序。比如,对于`@p`和`@r`,该参数默认为1,`c`可以使其增加选择最近或随机目标的数量。而`@a`或`@e`仅仅会限制选择的目标数量,限制的顺序依赖内部代码的排序。负的值可以使得选择顺序被反转,比如`@p[c=-5]`可以选中距离执行原点最远的5个目标。`c`是count的缩写。 | +| `type` | 指定实体ID,不可以被用于`@p`和`@a`,因为它们只能用于选择ID为`player`的实体,即玩家。该参数的值可以被否定,用于选择非某ID的实体。 | +| `m` | 指定玩家的游戏模式。`m`是mode的缩写。 | +| `tag` | 指定具有某种标签的实体,其中实体的标签可以`/tag`给予。 该参数的值可以被否定,用于选择不具有某个标签的实体。可以同时指定多个`tag`参数,来更严格地筛选出想要的目标。 | +| `name` | 指定目标的名字,玩家名或者实体的自定义名字皆可以用该参数指定。该参数的值可以被否定,用于选择不具有某个名字的实体。可以同时指定多个`name`参数,来更严格地筛选出想要的目标。 | +| `l, lm` | 指定具有一定经验等级的实体,该参数的值代表等级。`l`是level的缩写,代表最大经验等级;`lm`是level minimum的缩写,代表最小经验等级。两者都可不同时指定。 | +| `rx, rxm` | 指定具有一定的绕x轴旋转角的实体,即指定具有一定**俯仰角**(**Pitch**)的实体,该参数的值的单位为角度制。`rx`是rotation x的缩写,代表最大俯仰角;`rxm`是rotation x minimum的缩写,代表最小俯仰角。实体的俯仰角范围为从-90°(向正上看天)到90°(向整下看地)。两者都可不同时指定。 | +| `ry, rym` | 指定具有一定绕y轴旋转角的实体,即指定具有一定**偏航角**(**Yaw**)的实体,该参数的值的单位为角度制。`ry`是rotation y的缩写,代表最大偏航角;`rym`是rotation y minimum的缩写,代表最小偏航角。实体的偏航角范围为从-180°到180°。两者都可不同时指定。 | +| `scores` | 指定具有特定记分项的目标。该参数的值允许使用花括号来进一步指定记分项,各个记分项之间使用英文半角逗号分隔,记分项的值可以使用`..`来代表一个范围。比如,`1..5`代表可以接受从1到5这个区间内的值。记分项的值也可以被否定,用来筛选不满足指定分数值的目标。花括号内的语法示例:`[scores={objectiveA=valueA1..valueA2,objectiveB=!valueB,...}]`。 | +| `family` | 指定目标所属的**族**(**Family**,***科系***)类型。族可以在实体JSON文件的的`minecraft:type_family`组件中定义。该参数的值可以被否定,用于选择不属于某个族的实体。可以同时指定多个`family`参数,来更严格地筛选出想要的目标。 | + +## 原始文本 + +**原始文本**(**Raw Text**)是一种JSON格式的文本,可以为我的世界中的文本增添一些富文本特性和动态翻译特性。原始文本可以被用在很多地方,比如命令中、书中和自定义游戏内容的许多支持原始文本的对象里。原始文本会以JSON的格式写入,但是以富文本的形式输出。在阅读原始文本的格式之前,建议先初步自行了解一下JSON的基本格式。 + +我的世界基岩版的原始文本具有严格的格式,首先,它必须是一个用花括号包裹起来的对象,且其中只有一个字段,即`rawtext`数组。`rawtext`数组中包含许多对象,每个对象称作一个原始文本**内容对象**。大体的格式如下: + +```json +{ + "rawtext": [ + { /* object 1 */ }, + { /* object 2 */ }, + // other objects + ] +} +``` + +要使每个内容对象生效,每个内容对象都必须要含有以下**内容标签**字段之一:`translate`、`text`、`selector`和`score`。如果一个内容对象含有多个内容标签,也只会有一个内容标签生效。生效是按照内容标签的权重来计算的,而上面列出的内容标签的顺序也恰是它们的权重顺序,当两个同时出现时,靠前的内容标签会生效。 + +每个内容标签的格式都略微不同,分别如下。 + +### `translate` + +`translate`是用于翻译本地化文本的内容标签。格式如下: + +```json +{ + "rawtext": [ + { + "translate": "some.localization.key", // String类型,本地化键名,如果在语言文件中没有找到该本地化键名,或者填入的不是本地化键名而是普通的文本,则直接显示该文本。 + "with": [ /* some arguments for localization */ ] //可选,Array类型,用于填充到本地化文本中的参数,当本地化文本中带有一个或多个类似于“%s”的变量时用于替换变量所在的位置。数组中每一个元素都可以是一个单独的字符串文本或者另一个完整的原始文本对象。 + } + ] +} +``` + +### `text` + +`text`是最简单的内容标签,就是单纯地输出一段纯文本。格式如下: + +```json +{ + "rawtext": [ + { + "text": "Some Text." //String类型,纯文本。 + } + ] +} +``` + +### `selector` + +`selector`是一个单独的目标选择器,主要用于输出玩家或实体的名字。格式如下: + +```json +{ + "rawtext": [ + { + "selector": "@someSelector[someParams=someValues]" //String类型,其内容按照目标选择器的格式进行写入即可,会返回一个或多个实体的名字,返回多个实体名时会按照“AA、BB和CC”的格式。 + } + ] +} +``` + +### `score` + +`score`是用于显示一个记分项分数的标签。格式如下: + +```json +{ + "rawtext": [ + { + "score": { + "name": "Entity Name or @selector or *", // String类型,需要显示分数的实体名字或一个目标选择器。也可以使用“*”作为值,此时将显示该原始文本的“读者”的分数,如果该原始文本的潜在读者为多个人,则无法显示。 + "objective": "someObjective", //String类型,记分项的内部名称。 + "value": "someValue" //可选,String类型,如果设置了该字段,那么不管实际分数为多少,都会显示该字段所给的值。 + } + } + ] +} +``` + +### 追加 + +在介绍原始文本的最一开始我们便提到,一个原始文本数组里面可以有多个内容对象。向一个原始文本数组中添加多个内容对象的行为称为原始文本的**追加**(**Append**)。 + +```json +{ + "rawtext": [ + { /* object */ }, + { /* appended object */ }, + // other appended objects + ] +} +``` + +追加的原始文本内容对象最后解析的文本会和原来的文本的末尾直接连在一起。从输出结果来看和分别连着写了两个原始文本没有什么区别。但是,有些情况下只允许我们写一个原始文本,那么我们就需要用追加功能来依次排列我们所需要的内容标签,灵活运用四种标签组合出一个完整的预期的文本。 + +### 换行与参数顺序 + +你可以在原始文本中使用`\n`代表一个**换行**。换行有助于更好地进行文本排版。 + +你还可以使用`%%1`、`%%2`等来代替`%s`、`%d`这种不带顺序的参数占位符。这样即使`%%2`在文本出现在了`%%1`的前面,你依旧可以使`with`字段中的第二个元素成功替换到排在前面的`%%2`的位置。 + +本节的最后,简单地提示一点。如果你系统学习过JSON,你应该知道JSON文件不管是写成多行带有缩进的格式,还是全部写到一行中去,其功能是不受任何影响的。本节中的原始文本为了便于大家理解,所以使用了带缩进的排版,但是事实上,大部分使用原始文本的地方都是需要写到一行中的,还请各位开发者在使用时随机应变,正确地输入原始文本。 diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/4-命令列表和概述.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/4-命令列表和概述.md new file mode 100644 index 0000000..ec83f91 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/4-命令列表和概述.md @@ -0,0 +1,76 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 5分钟 +--- + +# 命令列表和概述 + +本节列出了当前所有可用的命令及其概述。 + +### 列表 + +| 命令 | 概述 | +| ----------------- | ------------------------------------------------------------ | +| `?` | 提供帮助或命令的列表。 | +| `alwaysday` | 锁定或解锁昼夜循环。 | +| `camerashake` | 对玩家的相机应用指定强度和时长的摇晃效果。 | +| `clear` | 清空玩家背包的物品。 | +| `clearspawnpoint` | 清除一个玩家的出生点。 | +| `clone` | 把方块从一个区域复制到另一个区域。 | +| `daylock` | 锁定或解锁昼夜循环。 | +| `deop` | 撤销一名玩家的管理员状态。 | +| `difficulty` | 设置难度等级。 | +| `effect` | 添加或移除状态效果。 | +| `enchant` | 向玩家所选择的物品添加一个魔咒。 | +| `event` | 触发指定对象的一个事件。 | +| `execute` | 代表一个或多个实体执行一个命令。 | +| `fill` | 用特定的方块填充全部或部分区域。 | +| `fog` | 添加或移除迷雾设置文件。 | +| `function` | 运行对应函数文件里的命令。 | +| `gamemode` | 设置玩家的游戏模式 | +| `gamerule` | 设置或查询一个游戏规则。 | +| `give` | 给予玩家一个物品。 | +| `help` | 提供帮助或命令的列表。 | +| `kick` | 从服务器踢出一名玩家。 | +| `kill` | 杀死实体(玩家、生物等)。 | +| `locate` | 显示距离最近的给定类型的结构的坐标。 | +| `me` | 显示一条关于你自己的消息。 | +| `mobevent` | 控制哪些生物事件允许运行。 | +| `msg` | 向一名或多名玩家发送私密消息。 | +| `music` | 允许你控制播放音乐音轨。 | +| `op` | 授权一名玩家草管理员状态。 | +| `particle` | 创建一个粒子发射器。 | +| `playanimation` | 使一个或多个实体播放一个一次性动画。假设所有变量都被正确设置。 | +| `playsound` | 播放一条声音。 | +| `reload` | 从全部行为包加载所有函数文件。 | +| `removeedunpc` | 删除地图内所有NPC。 | +| `replaceitem` | 替换物品栏中的物品。 | +| `ride` | 使实体骑乘其他实体,停止实体的骑乘,使坐骑赶下骑手,或召唤坐骑或骑手。 | +| `say` | 在聊天栏中向其他玩家发送一条消息。 | +| `schedule` | 计划在一个区域一旦加载时或加载特定长度的时间后便执行的操作 | +| `scoreboard` | 追踪并显示各个记分项的分数。 | +| `setblock` | 将一个方块更改为另一个方块。 | +| `setmaxplayers` | 设置该游戏绘画的最大玩家数量。 | +| `setworldspawn` | 设置世界出生点。 | +| `spawnpoint` | 为一个玩家设置出生点。 | +| `spreadplayers` | 将实体传送到随机的位置。 | +| `stopsound` | 停止播放一条声音。 | +| `structure` | 在世界中保存或加载一个结构。 | +| `summon` | 召唤一个实体。 | +| `tag` | 管理存储在实体中的标签。 | +| `teleport` | 传送实体(玩家、生物等)。 | +| `tell` | 向一名或多名玩家发送私密消息。 | +| `tellraw` | 向一个玩家发送一条JSON消息。 | +| `testfor` | 计数满足特定条件的实体(玩家、生物、物品等)。 | +| `testforblock` | 测试一个特定的方块是否在特定的位置。 | +| `testforblocks` | 测试两个区域中的方块是否匹配。 | +| `tickingarea` | 添加、移除或列出常加载区域。 | +| `time` | 更改或查询世界的游戏时间。 | +| `title` | 控制屏幕标题。 | +| `titleraw` | 通过JSON消息控制屏幕标题。 | +| `toggledownfall` | 切换天气。 | +| `tp` | 传送实体(玩家、生物等)。 | +| `w` | 向一名或多名玩家发送私密消息。 | +| `weather` | 设置天气。 | +| `xp` | 添加或移除玩家的经验。 | \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/5-常用命令及用法.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/5-常用命令及用法.md new file mode 100644 index 0000000..3228bba --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/5-常用命令及用法.md @@ -0,0 +1,214 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 20分钟 +--- + +# 常用命令及用法 + +本节我们介绍最常用的一些命令及其使用方法。我们从实体、世界、聊天、方块、音效和粒子等角度介绍常用的命令。 + +## 实体命令 + +### summon + +`/summon`命令可以用于在世界中召唤一系列实体。它的语法为: + +```shell +summon [spawnPos: x y z] [spawnEvent: string] [nameTag: string] +``` + +- `entityType`:EntityType类型,实体类型,可以为实体ID。 +- `spawnPos`:x y z类型,生成点,是你要召唤的实体的生成坐标。 +- `spawnEvent`:string类型,生成事件,生成时实体触发的事件。 +- `nameTag`:string类型,实体出生自带的名字。 + +### scoreboard + +`scoreboard`命令是一种可以在各个实体的基础上跟踪数值,以及执行数学运算的命令,其使用的容器被称作**记分板**(**Scoreboard**)。记分板非常复杂,如果想详细了解,可以参见[Minecraft Wiki的记分板页面](https://zh.minecraft.wiki/w/%E8%AE%B0%E5%88%86%E6%9D%BF)。 + +### tag + +`tag`命令允许你在各个实体的基础上跟踪标签。有如下几种子命令: + +为目标添加一个标签: + +```shell +tag add +``` + +移除目标的一个标签: + +```shell +tag remove +``` + +列出目标所拥有的所有标签: + +```shell +tag list +``` + +## 世界命令 + +### time + +`time`命令可以用于调整世界时间。它的语法如下: + +- `set`子命令: + +```shell +/time set +/time set +``` + +- `query`子命令: + +```shell +time query +``` + +- `add`子命令: + +```shell +time add +``` + +`set`子命令用于设置当前时间,后面紧跟的参数为要设置的时间数值或`TimeSpec`枚举值,枚举值可以为`day`、`night`、`noon`、`midnight`、`sunrise`和`sunset`。 + +`query`子命令用于查询当日时间、游戏时间和天数。 + +`add`子命令用于在当前时间的基础上添加一定量的时间。 + +### weather + +`weather`命令用于调整天气。它的语法如下: + +```shell +/weather [duration: int] +``` + +第一个字面参数是要调整的目标: + +- `clear`代表晴天。 +- `rain`代表雨雪。 +- `thunder`代表雷暴。 + +第二个可选参数代表该状态的持续时间,单位是游戏刻,其中20游戏刻等于1秒。 + +### gamemode + +`gamemode`命令可以用来更改一个玩家的游戏模式。它的语法如下: + +```shell +/gamemode [player: target] +/gamemode [player: target] +``` + +我们可以看到,第一个参数是必需参数,可以为一个代表游戏模式的整数,或是一个`GameMode`枚举值。枚举值可以为`survival`(`s`)生存模式、`creative`(`c`)创造模式、`adventure`(`a`)冒险模式和、`default`(`d`)默认的游戏模式。 + +第二个参数是可选参数,如果不指定则默认更改当前执行命令的玩家的游戏模式。如若指定玩家,则更改指定玩家的游戏模式,该参数可以为玩家名或一个目标选择器。 + +### gamerule + +`gamerule`命令是用来更新一个**游戏规则**(**Game Rule**)的。游戏中有大量的游戏规则,控制着游戏中的各种事件是否应该发生。我们可以通过如下语法使用该命令: + +```shell +/gamerule [value: Boolean] +/gamerule [value: int] +``` + +游戏规则有两种形态,一种是布尔游戏规则,其值只有`true`或`false`;一种是整数游戏规则,其值可以为整数。完整的游戏规则列表可以在[Minecraft Wiki的游戏规则页面](https://zh.minecraft.wiki/w/%E6%B8%B8%E6%88%8F%E8%A7%84%E5%88%99)找到。 + +## 聊天命令 + +### tell + +`tell`命令是用于向一个或多个玩家私发消息的命令。该命令有两个别名,分别是`msg`和`w`,这两个别名和本名有相同的效果。有如下格式: + +```shell +tell +msg +w +``` + +第一个参数可以为玩家名或目标选择器,通过目标选择器可以选择多个玩家。第二个参数是一个消息文本,即你要发送的消息。通过灵活使用该命令可以做到针对世界中不同玩家发送不同的独占消息,制作一些针对每个人都不同的成就、交易消息等非常有用。 + +## 方块命令 + +### setblock + +`setblock`命令可以在指定坐标处放置一个指定方块。 + +```shell +setblock [tileData: int] [destroy|keep|replace] +setblock [blockStates: block states] [destroy|keep|replace] +``` + +其中最后一个字面参数分别可以用于选择一下三种模式: + +- `destroy`:摧毁模式,原位置的方块将被模拟摧毁,正常掉落并播放破坏音效。该模式为默认值。 +- `keep`:保留模式,只有在原位置是空气时才会成功放置,否则都将保留原方块不变。 +- `replace`:替换模式,原方块直接消失并被替换成新方块,不会经历破坏和掉落。 + +### fill + +`fill`命令用于在某个矩形区域内批量填充方块,其语法如下: + +```shell +fill [tileData: int] [oldBlockHandling: FillMode] +fill [blockStates: block states] [oldBlockHandling: FillMode] +``` + +最后一个`oldBlockHandling`枚举参数除了上述三种模式外,还支持如下两种模式: + +- `hollow`:镂空模式,将区域的最外层方块更改为指定的方块,内部全部替换为空气。 +- `outline`:外框模式,将区域的最外层方块更改为指定的方块,内部保留原样。 + +该参数的默认值为`replace`。 + +### clone + +`clone`命令用于将一处地形复制(克隆)到另一处,其本质也是方块的复制。 + +```shell +clone [maskMode: MaskMode] [cloneMode: CloneMode] +clone filtered +clone filtered +``` + +其中`maskMode`枚举参数能够指定掩码模式,有以下几种类型: + +- `masked`:执行一种类似于计算机中“掩码取或”的过程,即使最终结果为仅复制非空气方块,保持目的区域中原本会被替换为空气的方块不变。 +- `replace`:执行纯粹的替换过程,用源区域的方块覆盖目标区域中的所有方块。该模式为默认值。 +- `filtered`:执行一种类似于筛选的过程,使命令只复制指定方块。其本质也是一种掩码模式,但是需要再跟一些参数额外制定一个方块。 + +`cloneMode`枚举参数能够指定克隆模式,有以下三种类型: + +- `force`:强制克隆,即使源区域与目标区域有重叠。 +- `move`:移动克隆,并将源区域替换为空气。如果掩码模式为`filtered`,只有被指定的克隆的方块位置才会替换为空气。 +- `noraml`:正常克隆,不强制或移动。该模式为默认值。 + +## 音效命令 + +### playsound + +`playsound`命令用于在游戏中播放一条声音。其语法如下: + +```shell +playsound [player: target] [position: x y z] [volume: float] [pitch: float] [minimumVolume: float] +``` + +其中`sound`参数必须指定一个`sound_definitions.json`中定义的声音事件,例如`mob.fox.ambient`。 + +## 粒子命令 + +### particle + +`particle`命令的主要作用是在游戏中生成一个国际版粒子发射器。 + +```shell +particle +``` + +`effect`参数是粒子发射器的ID,必须带有命名空间。`position`是粒子发射器生成的初坐标,之后发射器可能会根据其定义文件里的配置情况而改变位置。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/6-挑战:实体叠叠乐.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/6-挑战:实体叠叠乐.md new file mode 100644 index 0000000..512dc71 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/6-挑战:实体叠叠乐.md @@ -0,0 +1,51 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 10分钟 +--- + +# 挑战:实体叠叠乐 + +在本节中,我们一起来进行一个挑战——实体叠叠乐。我们将运用目标选择器和命令相配合,制作一组叠高高的实体。 + +## 召唤实体 + +为了制作叠高高的实体,我们先召唤出一个基础实体,比如一个**尸壳**(**Husk**)。尸壳在白天不会燃烧,所以我们可以放心将其召唤出来。我们使用尸壳的刷怪蛋召唤出一个尸壳。 + +![召唤尸壳](./images/4.6_husk.png) + +## 为尸壳命名 + +我们希望使用`/ride`命令来使一个尸壳骑乘另一个尸壳,然而尸壳不是玩家,所以不能使用玩家名作为`target`类型的参数值,我们必须使用目标选择器。为了使目标尸壳被精确选中,我们可以使用`name`选择器参数,因此我们需要给这个尸壳命名。使用铁砧和命名牌使尸壳获得一个名字。 + +![命名牌](./images/4.6_nametag_renaming.png) + +![尸壳命名](./images/4.6_named_husk.png) + +## 为尸壳添加一个骑手 + +我们先为该尸壳添加一个**骑手**(**Rider**),即让另一个尸壳骑乘在该尸壳的头上。我们使用`/ride`命令的`summon_rider`参数,这个参数可以召唤一个新的实体充当骑手。 + +```shell +/rider @e[name=叠叠乐演示实体] summon_rider husk +``` + +![summon_rider](./images/4.6_ride_summon_rider.png) + +可以看到,我们使用了`@e`选择器配合`name`参数精确指定到了该实体。执行成功后,我们可以看到这个尸壳头上已经出现了一个新的尸壳。 + +![两个尸壳](./images/4.6_husk_with_rider.png) + +## 为尸壳添加一个坐骑 + +我们还可以让这个尸壳再坐在另一个尸壳的身上,所以我们可以继续用`/ride`命令为其添加一个**坐骑**(**Ride**)。我们只需要将`summon_rider`参数改为`summon_ride`,即可成功添加一个坐骑。 + +```shell +/rider @e[name=叠叠乐演示实体] summon_ride husk +``` + +![summon_ride](./images/4.6_ride_summon_ride.png) + +成功之后,我们看到了三个尸壳坐在一起,颇有叠高高的风味。 + +![三个尸壳](./images/4.6_husk_with_rider_and_ride.png) \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/README.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.1_command.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.1_command.png new file mode 100644 index 0000000..02a926d Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.1_command.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.2_command_auto_complete.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.2_command_auto_complete.png new file mode 100644 index 0000000..f45b445 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.2_command_auto_complete.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.2_command_block_screen.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.2_command_block_screen.png new file mode 100644 index 0000000..d169560 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.2_command_block_screen.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.2_enable_cheat.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.2_enable_cheat.png new file mode 100644 index 0000000..f378685 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.2_enable_cheat.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.3_coords.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.3_coords.png new file mode 100644 index 0000000..29dcf91 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.3_coords.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.6_husk.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.6_husk.png new file mode 100644 index 0000000..a91d814 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.6_husk.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.6_husk_with_rider.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.6_husk_with_rider.png new file mode 100644 index 0000000..41d1666 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.6_husk_with_rider.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.6_husk_with_rider_and_ride.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.6_husk_with_rider_and_ride.png new file mode 100644 index 0000000..b48e9d6 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.6_husk_with_rider_and_ride.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.6_named_husk.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.6_named_husk.png new file mode 100644 index 0000000..e1fe250 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.6_named_husk.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.6_nametag_renaming.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.6_nametag_renaming.png new file mode 100644 index 0000000..35483b1 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.6_nametag_renaming.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.6_ride_summon_ride.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.6_ride_summon_ride.png new file mode 100644 index 0000000..4da29f7 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.6_ride_summon_ride.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.6_ride_summon_rider.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.6_ride_summon_rider.png new file mode 100644 index 0000000..2b38b2e Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/3-了解我的世界原版命令/images/4.6_ride_summon_rider.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/0-摘要.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/0-摘要.md new file mode 100644 index 0000000..795dd1c --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/0-摘要.md @@ -0,0 +1,17 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 5分钟 +--- + +# 摘要 + +在本章中,我们将一起学习我的世界开发工作台编辑器的基础知识。了解新版关卡编辑器的各个功能,最后亲手设计一个自己的地形。 + +- 在第一节(*适应新版关卡编辑器*)中,我们将初步了解**关卡编辑器**的布局,认识界面上的各个窗口。 +- 第二节(*创建资源与导入导出*)中,我们将一起操作资源的导入和导出。 +- 第三节(*建造并打磨地图*)中,我们将一起在**地图编辑器**中打磨一个地图。 +- 最后一节(*挑战:设计地形并和好友分享*),我们将亲手设计一个地形并与好友分享这份喜悦! + +本章关键词:关卡编辑器 地图编辑器 资源 资源包 预设 配置 笔刷 地形 + diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/1-适应新版关卡编辑器.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/1-适应新版关卡编辑器.md new file mode 100644 index 0000000..b4ee660 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/1-适应新版关卡编辑器.md @@ -0,0 +1,45 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 5分钟 +--- + +# 适应新版关卡编辑器 + +为了更好地探索新版关卡编辑器,我们创建一个新版地图组件。这样,我们每次所做的修改都可以跟随地图保留在一起,方便我们进行探索。现在,我们打开这个新版地图。 + +![新版关卡编辑器](./images/5.1_level_editor.png) + +## 什么是预览窗 + +![游戏预览窗](./images/5.1_preview_window.png) + +正如第三章中我们介绍的那样,我们可以看到在界面中央有一个**游戏预览窗**,这里便是我们进行预览更改的地方。我们所有的更改都可以通过这个预览窗具现化、可视化。通过这个预览窗,我们能够直观地感受到我们添加的内容,同时可以便捷地更改各种玩法。 + +## 什么是属性 + +![属性](./images/5.1_property_window.png) + +在屏幕的右侧,我们可以看到有一个“**属性**”窗格,这里是显示和修改我们自定义的配置或预设的属性的地方。**属性**(**Property**)是一个配置或预设的各种性质。同一个配置或预设,一旦更改了一些属性,其表现也就会变得略微不同甚至大不一样。 + +## 什么是舞台 + +![舞台](./images/5.1_stage_window.png) + +在屏幕的左上角,我们可以看到“**舞台**”窗格。**舞台**(**Stage**)是用于显示当前存档中放置的预设的窗口。我们可以在舞台中看到预设的名字和各种挂接状态,通过在舞台中拖动我们可以更改预设的顺序和挂接状态,更好地进行编辑。我们已经在第三章中初步介绍过预设,在下面的第三节中,我们还会一起使用预设进行各种具体的创作。 + +## 移动与旋转视角 + +在预览窗中,鼠标左键主要用于点击和选择预设,那么如果我们要移动和旋转视角,应该如何操作呢? + +### 移动 + +移动分为快速移动和慢速移动,当鼠标的焦点位于预览窗内部时,我们可以通过**W**、**S**、**A**、**D**来进行前后左右的快速移动,通过**空格键**与**Shift键**进行升高和下降。当鼠标的焦点位于预览窗外时,我们可以通过**W**、**S**、**A**、**D**来进行前后左右的慢速移动,此时空格键将不起作用,同时Shift键仅仅是将视角更改为潜行状态,但并不影响移速。 + +### 旋转 + +将鼠标焦点移动至预览窗内,按下**鼠标右键**,你将进入旋转模式,此时移动鼠标,视角将会随着鼠标移动而移动。 + +![坐标系](./images/5.1_frame.png) + +在预览窗的右上角,我们还可以看到一个小型的坐标系,那便是我们当前的视角所对应的坐标系。我们可以通过点击坐标系上的**三角形按钮**来快速更改我们当前的朝向。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/2-创建资源与导入导出.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/2-创建资源与导入导出.md new file mode 100644 index 0000000..930d374 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/2-创建资源与导入导出.md @@ -0,0 +1,87 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 10分钟 +--- + +# 创建资源与导入导出 + +本节中,我们将一起学习如何新建资源文件,以及如何导入和导出资源。 + +## 快捷新建资源文件 + +在关卡编辑器屏幕的下方,我们可以看到一个“**资源管理**”窗格。 + +![资源管理](./images/5.2_resource_manager_window.png) + +在“资源管理”窗格的最左侧,有三个快捷按钮。其中第一个便是“**新建**”按钮。我们可以通过“新建”按钮来快速创建我们想要的资源文件。 + +![新建按钮](./images/5.2_new_button.png) + +点击“新建”按钮后,我们来到“新建文件向导”对话框,这里列举了所有可以快速创建的资源类型。 + +![常用选项卡](./images/5.2_new_file_wizard_popular.png) + +下面我们一一进行介绍: + +![预设选项卡](./images/5.2_new_file_wizard_preset.png) + +**预设**选项卡是用来快速建立一个预设的。 + +- **空预设**:快速创建一个空的预设。 +- **实体预设**:快速创建一个实体预设。实体预设会同时创建一个实体配置,这意味着你在这里创建实体预设时将同时创建一个新的实体。如果想以现有的实体为基础创建预设。可以在左侧“配置”窗格中找到实体,右键并选择“**创建预设**”。 +- **特效预设**:快速创建一个特效预设。特效预设并不会创建新的特效,你需要手动创建特效并将特效挂接在特效预设上。 +- **玩家预设**:快速创建一个玩家预设。玩家预设会在每个玩家身上自动实例化,应用一些玩家特有的逻辑。 +- **方块预设**:快速创建一个方块预设。方块预设不会同时创建新的方块,如果想将自己的自定义方块应用在该预设上,需要在创建之后在“属性”窗格中手动输入或选择你的自定义方块。 +- **界面预设**:快速创建一个界面预设。你需要在创建之后手动绑定界面。 + +![代码选项卡](./images/5.2_new_file_wizard_code.png) + +**代码**选项卡用来快速创建一个代码逻辑。 + +- **EmptyPart**:快速创建一个空的零件。 +- **ModMain**:快速创建模组主脚本目录和相关文件。 +- **ClientSystem**:快速创建客户端控制中心。 +- **ServerSystem**:快速创建服务端控制中心。 +- **ScreenNode**:快速创建UI节点逻辑脚本。 + +![配置选项卡](./images/5.2_new_file_wizard_config.png) + +**配置**选项卡可以用来快速创建一个配置,即快速创建一种游戏玩法的*实体*(此实体是广义的内容实体,非游戏中的实体概念。这个广义的实体包括下面列出的所有内容)。 + +- **实体**:快速创建一个Minecraft中的实体。 +- **物品**:快速创建一个Minecraft中的物品。 +- **方块**:快速创建一个Minecraft中的方块。 +- **配方**:快速创建一个Minecraft中的配方。配方是一种用来合成物品的规则。 +- **交易表**:快速创建一个Minecraft中的**交易**(**Trade**)表。交易表可以用来配置类似于村民交易那样的一套交换规则。可以在自定义实体中通过相关的接口来实现交易表。 +- **掉落表**:快速创建一个Minecraft中的**战利品表**(**Loot Table**,***掉落表***)。战利品表可以用于生物抢夺的掉落物抓取、宝藏和箱子的内容生成等一系列需要带有一个随机性的物品产出过程。 +- **生成规则**:快速创建一个Minecraft中的**生成规则**(**Spawn Rule**)。生成规则可以用于自定义生物的**自然生成**(**Naturally Spawn**)。 +- **维度**:快速创建一个Minecraft中的**维度**(**Dimension**)。类似于主世界、下界和末路之地,我们也可以通过此处新建一个新的维度,为我们的模组添加一个“新的世界”。 +- **生物群系**:快速创建一个Minecraft中的**生物群系**(**Biome**)。生物群系是一种用于区分一个维度中各种地形地貌、生物植被、建筑结构的生成的一种看不到却能试试体验到的功能。善于自定义生物群系可以极大地提升模组可玩性。其中,**地形生成**(**Terrain Generation**)是一个生物群系最主要的部分。 +- **特征**:快速创建一个Minecraft中的**地物**(**Feature**,***特征***)。地物是一种可以用于特定种类的结构或方块集团快速生成而编写的功能。我们在世界中看到的树木、花草、矿石的矿脉乃至水晶洞、矿洞和各种遗迹都是地物的杰作。 +- **特征生成**:快速创建一个Minecraft中的**地物规则**(**Feature Rule**)。地物规则控制了地物如何**自然生成**(**Naturally Generate**)。 + +![蓝图零件选项卡](./images/5.2_new_file_wizard_blueprint_part.png) + +**蓝图零件**选项卡用来创建适用于逻辑编辑器蓝图的**蓝图零件**(**Blueprint Part**)。 + +- **BlueprintPart**:创建一个空的蓝图零件。 +- **BlueprintUIPart**:创建一个空的UI蓝图零件。 + +## 导出资源包 + +当我们制作好了一个资源包后,我们就希望将其导出以备他用。这里的**资源包**并非指我们通常用来存放纹理、模型等客户端资源的资源包(Resource Pack),而是指代“带有资源的包”,通常指我们所创建的预设、配置、特效和模型等资源的集合体,是一个`.mep`格式文件。通过“**资源管理**”窗格左侧的导出功能,我们可以将我们目前制作的一些资源或所有资源导出为一个资源包。 + +![导出按钮](./images/5.2_export_button.png) + +点击窗格左侧的“**导出**”按钮,我们可以看到弹出了一个“导出为资源包”的对话框。选择好我们想要导出的文件,点击“**导出**”,选择好位置后即可成功将资源导出备用,以便来日再选择导入或传给他人。 + +## 导入内置或外部资源 + +正如我们可以导出资源包,我们也可以导入资源。点击“**资源管理**”窗格左侧的“**导入**“按钮,我们可以看到有”**导入预设**“”**导入零件**“”**导入内置资源包**“和”**导入自定义资源包**“。 + +![导入按钮](./images/5.2_import_button.png) + +我的世界开发工作台内置了一系列资源包供我们导入,我们可以通过这些内置资源学习各种开发功能,丰富我们的开发技巧。点击”**导入内置资源包**“即可选择一个内置资源包来导入。 + +”**导入自定义资源包**“则用于和上述的导出资源包功能相配套,方便我们导入之前曾经导出的资源包。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/3-建造并打磨地图.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/3-建造并打磨地图.md new file mode 100644 index 0000000..8bdcd60 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/3-建造并打磨地图.md @@ -0,0 +1,139 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 15分钟 +--- + +# 建造并打磨地图 + +在本节中,我们将一起使用**地图编辑器**学习如何建造和打磨地图。我们使用和上文中同一个测试地图,在顶部将编辑器类型切换为“地图编辑器”即可打开我们的地图编辑功能。 + +和其他编辑器一样,地图编辑器也具备撤销和重做的功能,所以各位开发者们在摸索地图编辑器时不必担心会破坏地形,放心大胆进行探索即可。如果有操作失误,也可以及时使用快捷操作栏中的“撤销”按钮来撤回错误的操作。 + +![地图编辑器](./images/5.3_map_editor.png) + +## 选取、拖动与缩放区域 + +如果你是初次进入地图编辑器,我们可以看到顶部的快捷操作栏中的”**选取**“按钮已经被按下,此时我们处于**选择模式**。在选择模式下,我们可以在预览窗中按下鼠标并拖动以**选取**区域。如图,我们选取了一个区域: + +![选择了一个区域](./images/5.3_map_editor_region_selected.png) + +我们可以通过该区域的某个顶点(示例中是右下角顶点)的坐标轴来移动这个区域。按住某个轴再进行**拖动**可以使区域沿该轴移动。按下坐标轴原点的方块再移动可以使其自由移动。 + +我们在选择好一个区域之后依旧可以更改该区域的选择范围。只需将鼠标移动到对应的面上,按住该面进行拖动,就可以在该面垂直的方向上增大或缩小区域。 + +我们称如此选中了一个区域的边界盒为**包围盒**。我们单击选中包围盒,点击顶部快捷操作栏中的“**编辑**”按钮,可以使其进入**编辑模式**。 + +编辑模式下的包围盒如果再次进行拖动,将带领其内部的所有方块一起移动。如果拖动某个面进行缩放,将使其内部的方块一同“**缩放**”,此时缩放可能会导致内部的方块失真。同时,进入编辑模式后会呼出一个二级菜单,通过该二级菜单我们可以进行同样的缩放以及旋转操作。 + +![二级菜单](./images/5.3_edit_menu.png) + +## 撤销、重做与清除区域 + +在快捷操作栏的最左侧,我们有“**撤销**”和“**重做**”按钮。通过这两个按钮我们可以随时撤销我们的上一步操作,如果误撤了操作,还可以随时通过重做按钮进行恢复。 + +![撤销和重做](./images/5.3_undo_and_redo.png) + +如果我们想清除某片区域,比较快捷的方法是使用“**橡皮**”。我们在快捷操作栏中选中“橡皮”。 + +![选中橡皮](./images/5.3_erase.png) + +然后在预览窗中想要清楚的部分进行涂抹,即可快速清除方块。 + +当然,橡皮的清除效率还是有限。为了提高清除效率,一个更好地方法是先通过“选取”进行区域的选择,然后直接按下**Delete键**,即可将区域内的方块清空为空气。 + +## 使用方块作为素材 + +为了使地形制作更为便捷,我们可以使用**素材**来快速地在世界中放置一些结构。在左下角我们可以看到**素材库**窗格。我们可以通过我的世界开发工作台产生的素材文件(`.mld`文件)、地图文件(`.osm`)文件和各种模型文件(`.obj`、`.fbx`等)来导入一个素材。导入的素材便可以作为快捷途径一键设置在世界中。 + +下面我们演示使用方块作为素材。 + +### 保存素材 + +为了使用方块作为素材,首先我们需要在地图中将一些方块保存为素材。我们在此选中一棵桦树,点击上方的“**保存为素材**”按钮来保存素材。 + +![保存素材](./images/5.3_pumpkin_material.png) + +此时我们便可以看到我们左下角的“素材库”窗格中出现了我们刚刚保存的素材: + +![素材](./images/5.3_material_saved.png) + +我们还可以通过右键该素材选择“导出”来将其导出为`.mld`文件以备他用。 + +![导出素材](./images/5.3_material_export.png) + +### 放置素材 + +此时我们只需要鼠标左键单机该素材,便可以在世界中选择位置进行放置。 + +![放置素材](./images/5.3_pumpkin_set.png) + +## 利用笔刷预设改变空间 + +我们可以使用**笔刷**快速地在世界中以特定的形状“刷上”特定的方块。笔刷预设分为“单点笔刷”“方形笔刷”“球形笔刷”“圆柱笔刷”和“半球笔刷”。 + +![半球笔刷预设](./images/5.3_brush_preset.png) + +我们在“**材质预设**”窗格中添加自己想要的笔刷材质,然后在顶部的快捷工具栏中选中“**笔刷**”按钮。 + +![](./images/5.3_brush.png) + +即可开始在世界中以笔刷预设的形状放置方块。 + +![半球笔刷](./images/5.3_semisphere.png) + +## 使用地形预设设计地形 + +除了笔刷,我们还可以通过**地形预设**来改变地形。地形预设是一系列预先设定好的地形操作模式,分为“隆起”“侵蚀”“平滑”“填充”“顶层覆盖”和“植物”,其中除了“植物”外,其他的都是单纯的对地形进行操作,比如“隆起”就是让指定区域内的方块隆起1格。 + +![地形预设](./images/5.3_terrain_preset.png) + +选中想要的地形预设,配置好相关的参数,然后在顶部快捷工具栏中选择“**地形**”按钮,就可以开始改变地形了。 + +![隆起](./images/5.3_bump.png) + +## 改变时间、能见度和出生点 + +我们可以通过顶部快捷操作栏中的“时间“”能见度“和”出生点“按钮快速改变当前的能见度、时间和出生点。值得注意的是,能见度改变的只是当前编辑器内的能见度,但是时间和出生点的改变将会影响地图发布后的时间和出生点。 + +![时间、能见度和出生点](./images/5.3_spawn_point_time_and_render_distance.png) + +改变出生点: + +![改变出生点](./images/5.3_change_spawn_point.png) + +改变时间为午夜: + +![改变时间为午夜](./images/5.3_change_time.png) + +增加能见度: + +![增加能见度](./images/5.3_change_render_distance.png) + +## 进入游戏模式 + +我们在地图编辑器中可以通过进入**游戏模式**来真实地体验我们编辑的地形,同时也可以方便我们制作一些素材。点击右上角的“**游戏模式**”按钮来进入游戏模式。 + +![](./images/5.3_gaming_mode.png) + +进入游戏模式后,将模拟玩家在真实游戏中的控制。你将可以使用正常的键鼠控制来游玩你刚刚编辑的世界: + +![游戏模式](./images/5.3_in_gaming_mode.png) + +在游戏中按**Esc键**将返回普通的选择模式。 + +## 保存区域结构并跨存档分享 + +在选中一定区域后,除了保存为素材,我们还可以将其保存为结构,并在其他存档中通过结构方块加载它们。选择“保存为结构”,输入结构名即可将其保存为结构: + +![保存为结构](./images/5.3_save_as_structure.png) + +保存的结构将会出现在你的行为包的`structures`文件夹下的``文件夹中,比如我当前的命名空间为`demo_world`,则它会出现在`structures`文件夹下的`demo_world`文件夹中。 + +![保存的结构](./images/5.3_saved_structure.png) + +我保存的结构名为`some_grass_block`,因此结构文件的名称将是`some_grass_block.mcstructure`。在结构方块中,我们只需要调用`demo_world:some_grass_block`即可成功调用我们的结构! + +## 当前编辑器的限制 + +需要提醒的是,当前的关卡编辑器和地图编辑器还有一些限制的,比如无法进入其他维度进行调试等,需要各位开发者稍加注意。 diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/4-挑战:设计地形并和好友分享.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/4-挑战:设计地形并和好友分享.md new file mode 100644 index 0000000..71a1d1c --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/4-挑战:设计地形并和好友分享.md @@ -0,0 +1,123 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 15分钟 +--- + +# 挑战:设计地形并和好友分享 + +在本节中,我们将一起设计一个湖泊湿地并通过导出的方式与好友分享。可以点击 [对应的湿地链接](https://g79.gdl.netease.com/addonguide-6.zip)下载资源。 + +## 设计一处湖泊湿地 + +为了快速设计一处湖泊湿地,我们需要用到地形编辑器的笔刷和地形预设。为了大家快速上手,我们本次演示设计一个比较小的湖泊湿地,大家可以根据自己的喜好自行进行操作,充分发挥想象力,无需拘泥于复原本教程图示中的样子。 + +首先,为了做出一个湖泊,我们可以先将地表“挖”出一个坑,比较简单的方法便是通过选区功能选择并使用**`Delete`键**快速删除一部分方块。比如,我们可以如下这样选择并删除。我们采用“**多选模式**”: + +![](./images/5.4_select_region.png) + +![](./images/5.4_delete_region.png) + +接下来,我们将湖底填充为沙子来模仿真正的湖泊。我们打算使用沙子材质的**单点笔刷**,放置基点改为**居中**。小心地填充每一处区域。 + +![](./images/5.4_set_sands.png) + +当然,如果地块较大,我们便推荐另一种便捷的方式——**顶层替换**。哦们切换到“**顶层覆盖**”**地形预设**,然后点击“**顶层替换**”按钮。 + +![](./images/5.4_top_replace.png) + +这样我们便可以使用顶层替换功能批量替换我们的地形的最顶层方块。 + +![](./images/5.4_top_replace_effect.png) + +我们为了让湖泊更真实,需要让湖泊稍加凹陷。我们打算采用“**侵蚀**”**地形预设**。为了使侵蚀有效,我们先在湖泊中央删去一个方块。 + +![](./images/5.4_delete_rectangle.png) + +![](./images/5.4_deleted_rectangle.png) + +然后从中间这个方块处开始向四周“涂抹”侵蚀。 + +![](./images/5.4_erose.png) + +这样就形成了最中间为泥土,靠近岸处为沙子的湖泊结构。接下来我们填充水体。我们使用顶部功能区中的“替换”功能。我们先选中我们的整个湖泊,我们欲将选中区域内的空气方块替换为水。我们点击快捷功能区中的”**替换**“按钮。 + +![](./images/5.4_set_water.png) + +![](./images/5.4_water_replace.png) + +我们找到空气那一栏,我们希望将后面的方块替换成水。此时,我们选择静止的水来替换,所以我们不选择”流动水“,而是直接选择”水“。 + +![](./images/5.4_water_replaced.png) + +可以看到,我们已经成功将水体放置在湖泊中了! + +![](./images/5.4_water_set.png) + +现在,我们已经完整地制作了一个湖泊,但是,这个湖泊明显并不是很真实,所以我们继续制作它的“外饰”。比如,我们想制作一个假山。我们可以使用“**隆起**”**地形预设**,并稍微调小半径。 + +![](./images/5.4_make_rockery.png) + +我们还可以再假山上放置一个水源来模拟一个泉眼。我们使用单点笔刷放置一个水源。注意,此时我们应选择流动水,否则方块将无法更新,水将无法流下假山。 + +![](./images/5.4_make_foutain.png) + +![](./images/5.4_foutain.png) + +> **补充知识**:液体方块的更新原理 +> +> 液体方块一般都同时存在两种方块,分别是**静止形态**和**流动形态**。静止形态的液体在世界中不会时刻进行方块更新。只有当静止形态的液体的毗邻发生更新时它才会受到更新,然后转变为流动形态的液体。而流动形态的液体自存在起便会时刻进行更新,直到其方块状态相对固定,然后重新变为静止形态。所以当放置流动形态的液体方块时他们会立即开始流动,而静止形态的液体方块则会因为得不到更新而静止在空中。 +> +> 事实上,水源方块和水流方块在本质上并没有什么区别。不管是流动方块还是静止方块,只要其附加值为0,即是水源,可以与桶互动;其余的附加值皆为水流,不可被桶捞起。 + +在“生成”了一个假山后,我们还可以通过花草树木对湖泊进行点缀。目前新版编辑器中还没有一个相对较好的放置花草的方法。我们直接点击右上角的按钮**进入游戏模式**通过骨粉放置花草。 + +![](./images/5.4_gaming_mode.png) + +![](./images/5.4_use_bone_dust.png) + +同时也可以对地形上一些“漏洞”进行修补。 + +![](./images/5.4_make_smoother.png) + +回到正常模式,我们可以看到我们的湖泊已经被花花草草围绕了。 + +![](./images/5.4_grass_set.png) + +接下来我们放置一棵树,以作为收尾。我们可以通过“地形预设”中的“植物”预设来放置一棵橡树。通过放置、撤销、再放置的循环直到放出一棵你理想的“树型”,比如如下这种效果就很合适: + +![](./images/5.4_tree_set.png) + +至此,我们大功告成!我们成功地制作了一个湖泊湿地的地形。现在再看起来,这个地形已经“比较真实”了。我们可以用它当做素材来进行以后的创作了。 + +## 导出为素材和结构 + +为了接下来进一步使用该地形,也为了便于分享给他人,我们可以将其导出为素材或结构。首先,我们将我们制作的地形完整地选中。 + +![](./images/5.4_region_bounded.png) + +### 导出为素材 + +我们点击顶部的“**保存为素材**”,就可以将该地形保存为素材。务必取消“去除流体”的选项,否则将无法保存水。 + +![](./images/5.4_save_as_resource.png) + +保存之后,我们可以在素材库中看到我们的地形。我们可以右键它继续选择导出,即可导出到外部。 + +![](./images/5.4_export_waterland.png) + +![](./images/5.4_export_successfully.png) + +### 导出为结构 + +我们点击顶部的“**保存为结构**”,就可以将该地形保存为结构。此时结构便位于行为包的`structures`文件夹内,直接将其复制出来即可。 + +![](./images/5.4_save_as_structure.png) + +当然,如果你的结构相当大,已经不适用于使用一个单独的结构文件保存了。那么编辑器此时还提供了切分结构的功能。选中“切分结构”,此时我们可以将大结构切分成许多小结构。 + +![](./images/5.4_cut_structure.png) + +此时,编辑器还会生成一个对应的`.json`扩展名文件用于保存各个切分结构的位置信息。你可以后续通过模组API中的`PlaceStructure`接口通过脚本还原放置这个切分结构到你的世界中! + +至此,你已经成功地导出了素材和结构了,与其他开发者伙伴们分享你的成果吧! \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/README.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.1_frame.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.1_frame.png new file mode 100644 index 0000000..22d3990 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.1_frame.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.1_level_editor.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.1_level_editor.png new file mode 100644 index 0000000..40df4b8 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.1_level_editor.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.1_preview_window.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.1_preview_window.png new file mode 100644 index 0000000..48770ef Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.1_preview_window.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.1_property_window.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.1_property_window.png new file mode 100644 index 0000000..d1f1307 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.1_property_window.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.1_stage_window.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.1_stage_window.png new file mode 100644 index 0000000..bf99a9d Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.1_stage_window.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.2_export_button.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.2_export_button.png new file mode 100644 index 0000000..1986ee3 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.2_export_button.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.2_import_button.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.2_import_button.png new file mode 100644 index 0000000..856ee1b Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.2_import_button.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.2_new_button.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.2_new_button.png new file mode 100644 index 0000000..8446735 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.2_new_button.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.2_new_file_wizard_blueprint_part.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.2_new_file_wizard_blueprint_part.png new file mode 100644 index 0000000..e48eb73 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.2_new_file_wizard_blueprint_part.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.2_new_file_wizard_code.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.2_new_file_wizard_code.png new file mode 100644 index 0000000..2dfecdc Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.2_new_file_wizard_code.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.2_new_file_wizard_config.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.2_new_file_wizard_config.png new file mode 100644 index 0000000..830a6b6 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.2_new_file_wizard_config.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.2_new_file_wizard_popular.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.2_new_file_wizard_popular.png new file mode 100644 index 0000000..ef3bee0 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.2_new_file_wizard_popular.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.2_new_file_wizard_preset.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.2_new_file_wizard_preset.png new file mode 100644 index 0000000..6f99f5e Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.2_new_file_wizard_preset.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.2_resource_manager_window.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.2_resource_manager_window.png new file mode 100644 index 0000000..787cf61 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.2_resource_manager_window.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_brush.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_brush.png new file mode 100644 index 0000000..2baa2d8 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_brush.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_brush_preset.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_brush_preset.png new file mode 100644 index 0000000..badb67d Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_brush_preset.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_bump.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_bump.png new file mode 100644 index 0000000..f20c483 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_bump.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_change_render_distance.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_change_render_distance.png new file mode 100644 index 0000000..d9cb229 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_change_render_distance.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_change_spawn_point.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_change_spawn_point.png new file mode 100644 index 0000000..901b869 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_change_spawn_point.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_change_time.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_change_time.png new file mode 100644 index 0000000..e94f975 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_change_time.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_edit_menu.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_edit_menu.png new file mode 100644 index 0000000..ac88577 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_edit_menu.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_erase.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_erase.png new file mode 100644 index 0000000..9504862 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_erase.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_gaming_mode.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_gaming_mode.png new file mode 100644 index 0000000..d2f2663 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_gaming_mode.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_in_gaming_mode.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_in_gaming_mode.png new file mode 100644 index 0000000..461bfe2 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_in_gaming_mode.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_map_editor.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_map_editor.png new file mode 100644 index 0000000..299ef29 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_map_editor.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_map_editor_region_selected.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_map_editor_region_selected.png new file mode 100644 index 0000000..8516f8f Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_map_editor_region_selected.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_material_export.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_material_export.png new file mode 100644 index 0000000..1cad570 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_material_export.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_material_saved.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_material_saved.png new file mode 100644 index 0000000..3ff1cae Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_material_saved.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_pumpkin_material.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_pumpkin_material.png new file mode 100644 index 0000000..9ed1a0b Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_pumpkin_material.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_pumpkin_set.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_pumpkin_set.png new file mode 100644 index 0000000..ef9cd09 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_pumpkin_set.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_save_as_structure.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_save_as_structure.png new file mode 100644 index 0000000..35d3afe Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_save_as_structure.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_saved_structure.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_saved_structure.png new file mode 100644 index 0000000..8fe5cba Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_saved_structure.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_semisphere.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_semisphere.png new file mode 100644 index 0000000..3f2a19e Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_semisphere.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_spawn_point_time_and_render_distance.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_spawn_point_time_and_render_distance.png new file mode 100644 index 0000000..e6d9bda Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_spawn_point_time_and_render_distance.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_terrain_preset.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_terrain_preset.png new file mode 100644 index 0000000..87fd217 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_terrain_preset.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_undo_and_redo.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_undo_and_redo.png new file mode 100644 index 0000000..406913d Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.3_undo_and_redo.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_cut_structure.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_cut_structure.png new file mode 100644 index 0000000..786cd98 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_cut_structure.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_delete_rectangle.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_delete_rectangle.png new file mode 100644 index 0000000..38e022b Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_delete_rectangle.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_delete_region.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_delete_region.png new file mode 100644 index 0000000..4d19e5a Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_delete_region.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_deleted_rectangle.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_deleted_rectangle.png new file mode 100644 index 0000000..54de857 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_deleted_rectangle.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_erose.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_erose.png new file mode 100644 index 0000000..147af13 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_erose.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_export_successfully.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_export_successfully.png new file mode 100644 index 0000000..7e697dd Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_export_successfully.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_export_waterland.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_export_waterland.png new file mode 100644 index 0000000..ae5a818 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_export_waterland.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_foutain.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_foutain.png new file mode 100644 index 0000000..e0dde87 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_foutain.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_gaming_mode.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_gaming_mode.png new file mode 100644 index 0000000..a6d7383 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_gaming_mode.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_grass_set.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_grass_set.png new file mode 100644 index 0000000..81fb70f Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_grass_set.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_make_foutain.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_make_foutain.png new file mode 100644 index 0000000..0fde77f Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_make_foutain.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_make_rockery.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_make_rockery.png new file mode 100644 index 0000000..9613f92 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_make_rockery.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_make_smoother.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_make_smoother.png new file mode 100644 index 0000000..5c0c15a Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_make_smoother.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_region_bounded.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_region_bounded.png new file mode 100644 index 0000000..4562ebd Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_region_bounded.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_save_as_resource.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_save_as_resource.png new file mode 100644 index 0000000..8930eba Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_save_as_resource.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_save_as_structure.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_save_as_structure.png new file mode 100644 index 0000000..871d6fa Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_save_as_structure.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_select_region.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_select_region.png new file mode 100644 index 0000000..a092e2c Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_select_region.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_set_sands.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_set_sands.png new file mode 100644 index 0000000..f9d1085 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_set_sands.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_set_water.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_set_water.png new file mode 100644 index 0000000..3fa2815 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_set_water.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_top_replace.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_top_replace.png new file mode 100644 index 0000000..6bd261c Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_top_replace.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_top_replace_effect.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_top_replace_effect.png new file mode 100644 index 0000000..3595537 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_top_replace_effect.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_tree_set.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_tree_set.png new file mode 100644 index 0000000..59de406 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_tree_set.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_use_bone_dust.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_use_bone_dust.png new file mode 100644 index 0000000..58e1b4b Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_use_bone_dust.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_water_replace.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_water_replace.png new file mode 100644 index 0000000..8b8985d Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_water_replace.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_water_replaced.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_water_replaced.png new file mode 100644 index 0000000..e993687 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_water_replaced.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_water_set.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_water_set.png new file mode 100644 index 0000000..9e144bf Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/4-玩转我的世界开发工作台编辑器基础/images/5.4_water_set.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/0-摘要.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/0-摘要.md new file mode 100644 index 0000000..cc0ccac --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/0-摘要.md @@ -0,0 +1,22 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 5分钟 +--- + +# 摘要 + +在本章中我们将一起开始使用编辑器的**配置**(**Configuration**)功能来创建基础各种基础玩法。 + +- 在第一节(*初步了解实体功能配置*)中,我们将了解如何配置一个**实体**(**Entity**)。学习如何设置实体的AI与组件,自定义其生成规则。 +- 在第二节(*初步了解物品功能配置*)中,我们将一起了解**物品**(**Item**)的配置,制作武器、盔甲和食物消耗品。 +- 在第三节(*初步了解方块功能配置*)中,我们将初步了解如何配置**方块**(**Block**),自定义刷怪笼和光源。 +- 在第四节(*初步了解配方配置*)中,我们将一起再次学习**配方**(**Recipe**)的配置。 +- 在第五节(*初步了解交易表配置*)中,我们将学习如何配置**交易**(**Trade**)表。 +- 在第六节(*初步了解掉落物配置*)中,我们将一起学习如何配置**战利品表**(**Loot Table**,***掉落表***)。此时我们将结合方块配置来制作一个幸运方块。 +- 在第七节(*初步了解生成规则配置*)中,我们将了解**生成规则**(**Spawn Rule**)的配置。 +- 在第八节(*初步了解特征配置*)中,我们将初步了解**特征**(**Feature**,又译**地物**)的配置。 +- 在第九节(*初步了解特征生成配置*)中, 我们将一起了解**特征规则**(**Feature Rule**,又译**地物规则**)的配置。 +- 在最后一节(*挑战:DIY史莱姆实体*)中,我们将一起自定义一个史莱姆实体,完成本章的回顾。 + +本章关键词:配置 组件 实体 物品 武器 盔甲 食物 方块 光源 刷怪笼 配方 交易 战利品表 幸运方块 生成规则 地物 地物规则 史莱姆 游泳 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/1-初步了解实体功能配置.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/1-初步了解实体功能配置.md new file mode 100644 index 0000000..c71e594 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/1-初步了解实体功能配置.md @@ -0,0 +1,63 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 15分钟 +--- + +# 初步了解实体功能配置 + +在本节中,我们将一起初步了解实体功能配置。一起通过实体配置来制作一个**实体**(**Entity**)。 + +## 创造一个新的实体 + +我们已经在前面的章节了解到,要通过我的世界开发工作台中的编辑器创建一个实体,我们需要使用实体配置。而实体配置可以通过**关卡编辑器**的“**资源管理**”窗格的“**新建**”按钮快速进行创建。我们通过该功能来打开实体配置的向导窗口。 + +![实体向导窗口](./images/6.1_entity_wizard.png) + +我们可以看到我们有许多数据模板可以用来选择。如果我们选择了一个数据模板,我们将可以继承该数据模板的所有预先设置的配置,可以在这个配置的基础上进行进一步的修改。如果我们选择了“空“模板,我们便可以从头开始一个实体的配置。这里为了演示方便,我们选择”羊“的模板。创建完毕后,我们在左侧“**配置**”窗格中选中该实体,窗口右侧的”**属性**“窗格中将出现这个实体的属性。 + +![属性](./images/6.1_entity_property.png) + +## 设置实体AI与组件 + +在属性窗格中,我们可以看到有三个大分类,分别是**配套文件**、**基础属性**和**行为包组件**。 + +- **配套文件**:每种配置其实都是由多个文件构成的,比如实体的一个配置就是一个行为包JSON文件、一个资源包JSON文件和语言文件中的涉及到该实体的部分所构成的。这里列出了该配置的配套文件。 +- **基础属性**:基础属性是该实体的**描述**部分,通常由标识符、名称等基础属性构成,描述了一个实体的基本性质。你可以通过该分类标题右侧的“+”来添加一个属性。 +- **行为包组件**:行为包组件是一个实体的主体组成部分。实体的大部分内容、特性、行为都是在这里定义的。编辑器支持各种**组件**的树状修改,你可以通过该分类标题右侧的“+”来添加一个组件。 + +为了设置实体的AI和组件,我们需要再深入了解一下“**组件**(**Component**)”到底是什么。事实上,组件是微软对于实体定义JSON文件中一个对象字段的称呼,不同的组件可以用来定义实体不同的性质、功能。在编辑器中,我们无需接触到复杂的JSON修改,编辑器将JSON解析为了树状的可视化编辑供我们修改,同时将组件名进行了翻译。所以我们便可以通过编辑器便捷地增删和修改组件。其中能够控制生物AI的组件被冠以“**行为.**”的前缀。 + +为了不和“作品组件”中的组件概念冲突,在编辑器的一些地方组件字段及其子字段也被称为**特性**。我们点击“**+**”来添加组件,就可以看到“添加特性”窗口。 + +![](./images/6.1_add_feature.png) + +> **选读:实体的组件** +> +> 往深处看,组件也有不同的类型。大部分普通的组件都是用来定义实体所具备的一个“功能”的,比如实体的骑乘功能、攻击功能、呼吸功能等。有些组件可以定义一个实体的“AI行为”,比如何时闲游、何时看向玩家、何时游泳等,这种组件称为**AI意向**(**AI Goal**),一般有一个`behavior.`(“行为.”)前缀。有些组件定义了一些实体的固有性质,如生命值的高低,被称为**特性**(**Attribute**);而另一些定义了实体的“能够干什么”的性质,被称为**属性**(**Property**)。还有一些组件是用于定义实体处于特定的状态时将会触发何种**事件**(**Event**)的,这些组件被称为**触发器**(**Trigger**)。 +> +> 往高处看,我的世界的组件其实是属于一种被称作**ECS**(**实体-组件-系统**,**Entity-Component-System**)的架构,这种架构意味着游戏中每一个基本单元都是一个**实体**(此处为广义的*实体*,比如方块、物品、实体都是一种*实体*),每个实体又由一个或多个**组件**构成。组件中没有任何方法,只有代表实体特性的**数据**。**系统**则仅仅负责处理这些数据,仅仅关心拥有某个组件的实体的特性,通过组件提供的数据代入系统内置的相关方法,更新实体的信息。这种通过ECS系统的组件(数据)修改游戏的方法被称为**数据驱动**(**Data-Driven**)式的 + +我们向我们的“羊实体”中添加一个新的组件,比如`addrider`组件,这个组件可以为羊添加一个骑手。我们在“添加特性”窗口中选中这个组件,点击“**调整**”按钮。可以看到“属性”窗格中已经出现了“添加骑手”的组件。 + +![](./images/6.1_add_rider.png) + +我们点击“添加骑手”右侧的“**+**”按钮,可以看到又弹出了一个 **“添加特性"** 窗口。我们在此添加该组件的`entity_type`字段。该字段用于指定羊的骑手为何种生物。选定并点击“**调整**”按钮,我们可以看到“添加骑手”下面多出了一个“实体类型”。 + +![](./images/6.1_entity_type.png) + +我们可以随意输入一个实体的种类,比如僵尸(`zombie`)。 + +![](./images/6.1_zombie_rider.png) + +此时,我们的实体生成时头顶将骑着一个僵尸,让我们用这个实体配置创建一个实体预设,并放置在关卡编辑器中来查看效果。 + +![](./images/6.1_zombie_rides_sheep.png) + +成功了!我们成功为该实体添加了一个组件,这个组件可以让实体每次生成都附带一个僵尸骑手。 + +如果我们想为实体添加一个AI,我们也是有一样的操作过程。只不过我们要选择那些带有`behavior.`(“行为.”)前缀的组件来添加。这些组件可以让生物的行为(即AI)更加丰富! + +## 自定义实体的生成规则 + +我们的自定义实体目前还只是一个独立的实体,如果没有配置对应的生成规则将无法**自然生成**(**Naturally Spawn**)。所以,我们需要使用生成规则配置来进行生成规则的创建和修改。我们将在第七节中为我们的这只“羊”添加一个生成规则。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/10-挑战:DIY史莱姆实体.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/10-挑战:DIY史莱姆实体.md new file mode 100644 index 0000000..13d512d --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/10-挑战:DIY史莱姆实体.md @@ -0,0 +1,135 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 进阶 +time: 20分钟 +--- + +# 挑战:DIY史莱姆实体 + +在本节中,我们一起自定义一个史莱姆实体。我们都知道,史莱姆是不会在潜入水中自由地游泳的。所以一旦掉进水中,史莱姆只能漂浮在水面上。我们希望只做一种可以潜入水中游泳的史莱姆。 + +## 制作一个会潜水游泳的史莱姆 + +### 学会从原版模板包中导入原版资源 + +为了自定义一个新的史莱姆,我们需要使用原版史莱姆资源。遗憾的是,我们并没有在新建配置中找到史莱姆的数据模板。 + +![](./images/6.10_no_slime.png) + +所以我们需要在原版的资源包和行为包中手动找到史莱姆的资源和行为,并将其手动导入进编辑器中。我们使用下方的链接下载最新的原版资源包和行为包。 + +| 包类型 | 模板包链接 | +| ------ | ------------------------------------------------------------ | +| 资源包 | [https://aka.ms/resourcepacktemplate](https://aka.ms/resourcepacktemplate) | +| 行为包 | [https://aka.ms/behaviorpacktemplate](https://aka.ms/behaviorpacktemplate) | + +正如一个编辑器中的配置会创建一些配套文件一样,我们为了使实体生效,需要手动建立这一系列“配置”文件。一般来说,一个实体需要三个文件来支撑其渲染和行为,分别是**资源包定义文件**、**行为包定义文件**和**模型文件**。但是,由于我们想自定义一个和原版的史莱姆“长相一样”的实体,所以我们可以直接使用原版的史莱姆模型而无需修改。若无需修改,我们无需再在我们的资源包中导入原版的史莱姆几何模型。我们只需要找到并导入: + +| 文件 | 位置 | +| ---------- | ------------------------------------------------ | +| 资源包定义 | `Vanilla_Resource_Pack/entity/slime.entity.json` | +| 行为包定义 | `Vanilla_Behavior_Pack/entities/slime.json` | + +![](./images/6.10_slime_files.png) + +在导入前,我们用编辑器将其打开。我们需要修改他们的实体ID,否则我们导入的实体将和原版实体“撞车”。 + +我们将资源包定义文件中的`minecraft:client_entity/description`段落下的`identifier`中的命名空间(即冒号前面的部分)`minecraft`修改为我们自己的命名空间即可,比如此处我修改为了之前我们第一章中便提到过的`tutorial_demo`。 + +![](./images/6.10_client_file.png) + +同理我们将行为包定义文件中的`minecraft:entity/description`段落下的`identifier`中的命名空间`minecraft`也修改为同样的名称。 + +![](./images/6.10_server_file.png) + +将两个文件保存后便可以关闭了。不过,为了不和其他开发者制作的模组冲突,我们还建议更改这两个文件的文件名,比如在`slime`前面也加上命名空间。 + +![](./images/6.10_slime_files_edited.png) + +接下来我们将它们导入进编辑器。我们在“资源管理”窗格中找到对应的文件夹。 + +![](./images/6.10_resource_manager.png) + +我们对两个文件夹分别右击并点击“**导入文件**”。 + +![](./images/6.10_import.png) + +定位到我们刚才找到的原版文件,即可进行导入。导入成功后,我们便可以在“配置”窗格看到我们的自定义实体了。 + +![](./images/6.10_config.png) + +我们点击该实体,右侧的“属性”窗格便出现了史莱姆的相关属性和组件。 + +![](./images/6.10_slime_property.png) + +### 添加相关组件 + +#### 修改运行时ID + +为了使我们的史莱姆完全能够继承原版的硬编码行为,我们可以进一步将史莱姆的**运行时ID**(**Runtime Identifier**)修改为原版的史莱姆。 + +我们点击“**基础属性**”右侧的“**+**”按钮。选中“**继承生物**”特性。 + +![](./images/6.10_base_property.png) + +![](./images/6.10_runtime_id.png) + +选择“史莱姆”作为运行时ID继承的生物。 + +![](./images/6.10_slime_runtime.png) + +这样我们便将该生物没有进行数据驱动的行为也成功设置成了和原版是史莱姆完全一致了。 + +#### 添加游泳AI意向和游泳导航组件 + +点击“**行为包组件”**右侧的“**+**”按钮。搜索“swim”,即可看到我们想要的行为包组件。 + +![](./images/6.10_swim_components.png) + +我们选中 **`minecraft:behavior.random_swim`**的AI意向,该AI意向能够使史莱姆产生随机的“泳意”;再选中**`minecraft:navigation.swim`** 组件,该组件可以使史莱姆将水的内部纳入寻路算法。点击“调整”将组件添加到行为包中。 + +### 配置“行为.随机游泳” + +我们先配置游泳的AI意向。 + +![](./images/6.10_random_swim.png) + +![](./images/6.10_random_swim_fields.png) + +我们只需要设置其优先级即可,其余的保持默认状态。由于对于“随机游泳”我们可以认为是一种“休闲”状态,所以我们将其优先级设为最低“1”。这样,史莱姆触发其他状态,比如攻击玩家时,便可以优先进行攻击等行为。 + +![](./images/6.10_random_swim_priority.png) + +同时我们可以删除漂浮的行为,防止其继续漂浮而不潜入水中。 + +![](./images/6.10_delete_float.png) + +### 配置“导航.游泳” + +同理,我们配置导航组件。 + +![](./images/6.10_nav_swim.png) + +![](./images/6.10_nav_swim_fields.png) + +我们选中“可以涉水”和“可以下沉”。同时,由于 **“导航”相关组件只能存在一个** ,所以我们需要删去原有的“导航.行走”组件。 + +![](./images/6.10_delete_nav_walk.png) + +这样,我们的史莱姆就“只会游泳”了。 + +### 配置可呼吸性 + +我们需要使史莱姆可以在水中呼吸。所以我们为可呼吸组件添加在水中呼吸的特性。 + +![](./images/6.10_breathable.png) + +![](./images/6.10_breathable_water.png) + +![](./images/6.10_breathable_in_water.png) + +### 进入游戏自测 + +我们现在可以保存并进入游戏自测了,我们在水中放置一些我们自定义的史莱姆,看看史莱姆会不会游泳。不出我们所料,史莱姆顺利地在水中游了起来! + +![](./images/6.10_swim_in-game.gif) diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/2-初步了解物品功能配置.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/2-初步了解物品功能配置.md new file mode 100644 index 0000000..dc3167d --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/2-初步了解物品功能配置.md @@ -0,0 +1,54 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 10分钟 +--- + +# 初步了解物品功能配置 + +在本节中,我们将初步了解自定义物品。一起通过物品配置来制作多种**物品**(**Item**)。 + +## 创建一个空物品 + +和实体一样,我们可以通过在编辑器中新建配置的方式新建一个自定义物品。在创建新物品之后我们依旧可以通过添加特性的方式添加物品的组件。我们接下来先创建一个空物品以观察其属性结构。 + +![](./images/6.2_empty_item.png) + +![](./images/6.2_item_property.png) + +添加好新物品之后,我们可以看到物品的各种类别的属性。除了**配套文件**、**基础属性**和**行为包组件**外,我们可以看到还有**盔甲穿戴属性**和**资源包组件**。 + +- **盔甲穿戴属性**:盔甲穿戴属性能够定义盔甲相关的渲染属性。由于盔甲的自定义本质上是将一个**附着物**(**Attachable**,***挂件***)挂接到物品上,所以这里实际定义的便是附着物的客户端属性。 +- **资源包组件**:物品的资源包定义文件中也有一些资源包独占的组件,这里定义资源包的组件。 + +## 制作武器 + +我们在创建物品时选择“自定义武器”的数据模板,我们便可以快速创建一个武器。 + +![](./images/6.2_weapon_item.png) + +![](./images/6.2_weapon_property.png) + +我们可以看到“属性”窗格丰富了起来,其中比较重要的是“**武器属性**”组件。这个组件是武器物品的核心,它定义了武器的伤害、附魔能力、类型等基础属性。我们可以在这里对这些属性进行修改,直到将其修改为我们满意的样子。 + +## 制作盔甲 + +我们在创建物品时选择“自定义盔甲”的数据模板,我们便可以快速创建一个盔甲。 + +![](./images/6.2_armor_item.png) + +![](./images/6.2_armor_property.png) + +由于自定义盔甲是通过附着物实现的,所以此处出现了盔甲穿戴属性。盔甲穿戴属性决定了我们穿上该盔甲后的外观。**模型**是该盔甲使用的**模型**(**Model**)。**材质**是决定该盔甲的渲染质感和模式的**材质**(**Material**)。**脚本**是盔甲初始化时执行的**Molang**脚本。**纹理贴图**是该盔甲的**纹理**(**Texture**)。**渲染器**是该盔甲的**渲染控制器**(**Render Controller**)。 + +行为包组件中的“**盔甲属性**”组件决定了该盔甲的护甲值和附魔能力。 + +## 制作消耗品 + +最后,我们再尝试制作一个食物消耗品。 + +![](./images/6.2_food_item.png) + +![](./images/6.2_food_property.png) + +我们可以看到“**食物属性**”组件决定了该食物能够补充的饥饿值。我们还可以利用该组件自定义食用后产生的状态效果和食用的使用时间。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/3-初步了解方块功能配置.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/3-初步了解方块功能配置.md new file mode 100644 index 0000000..0f108e2 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/3-初步了解方块功能配置.md @@ -0,0 +1,39 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 15分钟 +--- + +# 初步了解方块功能配置 + +在本节中,我们将初步了解自定义方块。一起通过方块配置来制作多种**方块**(**Block**)。 + +## 自定义刷怪笼 + +在本段中,我们想要自定义一个刷怪笼。我们可以从“**刷牛方块**”数据模板开始。 + +![](./images/6.3_spawner.png) + +![](./images/6.3_spawner_property.png) + +我们可以看到,方块也分为**配套文件**、**基础属性**和**行为包组件**。决定了这个方块是刷牛方块的核心属性和组件便是“基础属性”中的“**继承方块**”和“行为包组件”中的“**刷怪箱属性**”。刷怪箱属性的“怪物类型”中便是该刷怪箱要刷的怪物类型。我们不妨将其改为我们第一节中自定义的头顶一个僵尸的“羊”。 + +![](./images/6.3_spawner_change_to_sheep.png) + +这样,我们便得到了一个可以刷僵尸羊骑手的方块,我们可以将其更改纹理后放在地形中实现其他功能。当然,这里除了国际版自定义生物之外也支持原版的生物,因此你可以灵活运用该功能。 + +![](./images/6.3_spawner_in-game.png) + +## 自定义光源方块 + +我们还可以通过“**普通的发光方块**”数据模板来创建一个光源。 + +![](./images/6.3_emission_block.png) + +![](./images/6.3_emission_property.png) + +我么可以看到,决定是否发光的属性是“亮度”组件。1.0的发光强度有点太亮了,我们希望将其调低,比如,我们可以调到0.6。 + +![](./images/6.3_emission_change.png) + +稍加更换纹理,我们便完成了一个光源方块的制作! \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/4-初步了解配方配置.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/4-初步了解配方配置.md new file mode 100644 index 0000000..1ef5bd2 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/4-初步了解配方配置.md @@ -0,0 +1,33 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 10分钟 +--- + +# 初步了解配方配置 + +在本节中,我们将初步了解自定义配方。我们还记得,在第一章中,我们便亲手制作过一个有序**配方**(**Recipe**),接下来,我们详细介绍一下不同的配方配置数据模板。 + +## 自定义无序配方 + +我们使用`NeExample:recipe_1`数据模板,可以快速创建一个无序配方。 + +![](./images/6.4_shapeless_recipe.png) + +![](./images/6.4_shapeless_property.png) + +我们可以看到,这和有序配方的属性界面还是有很多不同的,最明显的便是**适用方块**和**配方成分**。有序配方只能适用于工作台,但是无序配方却可以适用于很多其他方块,比如**制图台**和**切石机**。 + +配方成分也不再是网格的形状,而是一个线性的列表,分别列出了各个物品和所需的数量。 + +## 自定义熔炉配方 + +我们还可以使用`NeExample:recipe_2`数据模板快速创建一个熔炉配方。 + +![](./images/6.4_smelt_recipe.png) + +![](./images/6.4_smelt_property.png) + +我们可以看到,输入输出都变得单一了起来,因为这是一种用于烧炼的配方,所以输入输出都变成了一对一的关系。 + +通过更改输入输出,增删适用方块,我们便可以轻松制作一个新的配方。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/5-初步了解交易表配置.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/5-初步了解交易表配置.md new file mode 100644 index 0000000..c2c0761 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/5-初步了解交易表配置.md @@ -0,0 +1,61 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 10分钟 +--- + +# 初步了解交易表配置 + +在本节中,我们将初步了解自定义交易。一起通过交易表配置来制作一个类似于村民交易的自定义**交易**(**Trade**)。 + +## 创建交易表 + +我们希望能通过数据模板更好地观察交易表的结构,所以我们创建一个“多路交易表”。 + +![](./images/6.5_trade.png) + +![](./images/6.5_trade_property.png) + +事实上,此时我们依旧不能看清楚交易表的结构,所以我们先将交易表的顶级对象折叠。 + +![](./images/6.5_trade_property_shrink.png) + +现在我们便能够观察到交易表的顶级结构了。事实上,一张交易表是由一个或多个**品质**(**Tier**)的交易组成的,而不同的品质是由交易不同的经验花费区分开的。当生物生成时,可以选择从不同品质的交易池中抽取一个或多个来作为自己的交易。 + +![](./images/6.5_trade_property_expand_1.png) + +每个品质的交易中又存在一个“**交易列表**”,列表里存在多个交易,分别是该品质具有的交易项。每个交易都是“交易列表”的一个元素。在实体生成时,会从列表里随机选取一个作为该品质的交易。 + +![](./images/6.5_trade_detail.png) + +有的交易是一换一的,有的交易是一换多的。不管怎么样,交易所交付的物品必须只有一个,而交易返回的物品则可以有许多。这便是一整张交易表的结构。 + +## 挂接交易表 + +交易表可以挂接在实体上,我们不妨继续使用我们在第一节中定义的实体“羊”,将我们刚刚定义的交易表挂接在羊实体上。 + +我们在左侧的“配置”窗格中选择我们的自定义“羊”。 + +![](./images/6.5_select_entity.png) + +此时右侧的“属性”窗格将更换为羊的属性。我们添加一个行为包组件。找到`minecraft:economy_trade_table`组件。这个组件用于给一个实体添加一个交易表。 + +![](./images/6.5_trade_add_to_entity.png) + +在属性中找到“**经济交易表**”,点击“**+**”按钮。 + +![](./images/6.5_add_trade.png) + +简单地选中`display_name`、`new_screen`和`table`。并点击“**调整**”。 + +![](./images/6.5_select_trade_feature.png) + +选择我们的交易表并将其配置如下,其中“显示名称”完全可以根据个人喜好来进行配置。 + +![](./images/6.5_select_table.png) + +![](./images/6.5_config_component.png) + +点击右上角的“运行”进入游戏自测,使用我们的刷怪笼刷出我们的羊,右键我们的羊进行互动,我们将看到我们的交易自定义成功了! + +![](./images/6.5_trade_demo.png) \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/6-初步了解掉落表配置.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/6-初步了解掉落表配置.md new file mode 100644 index 0000000..9104fc1 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/6-初步了解掉落表配置.md @@ -0,0 +1,47 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 10分钟 +--- + +# 初步了解掉落表配置 + +在本节中,我们将初步了解自定义战利品表。**战利品表**(**Loot Table**,***掉落表***)常用于生物或方块破坏、箱子和宝藏的战利品抓取。 + +## 创建战利品表 + +和交易表一样,我们选择一个“多路战利品表”。 + +![](./images/6.6_loot.png) + +![](./images/6.6_loot_property.png) + +和交易表类似,战利品表也是从池子中**掷取**(**Roll**)一个或多个物品。战利品表的根节点的每一个元素都称为一个**池**(**Pool**)。如下所示,我们创建的战利品表有2个池。 + +![](./images/6.6_pools_expand_2.png) + +每次掷取战利品表,每个池都会进行一次或多次掷取,掷取由”**随机次数**“决定。而掷取过程则是根据”**随机权重**“随机地从”**随机池**“中抽出一个物品出来。多次掷取时就抽出多次,最后每个池中抽出的所有物品加起来便是此次该战利品表掷取的所有物品。 + +## 挂接战利品表 + +战利品表可以挂接在多个地方。此次我们以制作一个幸运方块为例,演示如何将战利品表挂接在方块上。 + +### 制作幸运方块 + +我们先配置一个“与蜘蛛掉落相同的方块”,该方块自带一个“掉落属性”组件,可以用来挂接战利品表。 + +![](./images/6.6_lucky_block.png) + +![](./images/6.6_drop_property.png) + +我们选择我们的战利品表,此时便算作挂接成功了。 + +![](./images/6.6_loot_table_select.png) + +此时我们可以进入游戏来观察一下效果。 + +![](./images/6.6_lucky_destroying.png) + +![](./images/6.6_lucky_destroyed.png) + +可以看到,我们的战利品表挂接成功了!我们可以向战利品表中添加更多有趣的物品,来完善我们的“‘内容庞大”的幸运方块了! \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/7-初步了解生成规则配置.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/7-初步了解生成规则配置.md new file mode 100644 index 0000000..46685e3 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/7-初步了解生成规则配置.md @@ -0,0 +1,108 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 15分钟 +--- + +# 初步了解生成规则配置 + +在本节中,我们将初步了解自定义生成规则。**生成规则**(**Spawn Rule**)是用于使生物在世界中**自然生成**(**Naturally Spawn**)的规则。 + +## 创建生成规则 + +我们通过生成规则配置来创建一个空的生成规则。 + +![](./images/6.7_spawn_rule.png) + +![](./images/6.7_spawn_rule_property.png) + +我们可以看到,生成规则的配置还是十分简单易懂的,大多都是字面意思。我们直接开始尝试为我们的自定义实体“羊”配置生成规则。 + +### 将实体挂接在生成规则上 + +我们找到“**生物**”,选择我们的羊。 + +![](./images/6.7_spawn_rule_sheep.png) + +我们希望羊只生成在草方块上,所以在“**可生成于方块**”处添加一个草方块。 + +![](./images/6.7_spawn_rule_on_grass.png) + +我们希望每次羊都有概率成群结队地生成,所以我们选中“**以群落方式生成**”并适当修改其值。 + +![](./images/6.7_spawn_rule_in_group.png) + +我们希望这种羊生成的多一些,所以增大“**生成权重**”为20(原版羊是12)。 + +![](./images/6.7_spawn_rule_weight.png) + +我们希望其只在主世界生成,所以我们指定代表主世界的两个“**生物群系标签**”。 + +![](./images/6.7_spawn_rule_biome_tag.png) + +事实上,我的世界开发工作台的编辑器中默认只能形成`all_of`的过滤器逻辑,即只有同时具有标签`overworld`和`overworld_generation`的生物群系才能生成,但是事实上任何生物群系只可能具有上述的其中一种,所以我们需要手动将其更改为`any_of`。我们在“资源管理”窗格的行为包下的`spawn_rules`文件夹中找到我们的生成规则,双击将其打开。打开后文件内容如下文所示: + +```json +{ + "format_version": "1.8.0", + "minecraft:spawn_rules": { + "conditions": [ + { + "minecraft:biome_filter": { + "all_of": [ // 更改此处! + { + "operator": "==", + "test": "has_biome_tag", + "value": "overworld" + }, + { + "operator": "==", + "test": "has_biome_tag", + "value": "overworld_generation" + } + ] + }, + "minecraft:herd": { + "max_size": 4, + "min_size": 1 + }, + "minecraft:spawns_on_block_filter": [ + "minecraft:grass" + ], + "minecraft:spawns_on_surface": { + + }, + "minecraft:weight": { + "default": 20 + } + } + ], + "description": { + "identifier": "tutorial_demo:demo_sheep", + "population_control": "animal" + } + } +} +``` + +我们将`minecraft:biome_filter`组件下的`all_of`修改为`any_of`后保存。更改完成后的`minecraft:biome_filter`组件节选如下: + +```json +"minecraft:biome_filter": { + "any_of": [ // 更改完成 + { + "operator": "==", + "test": "has_biome_tag", + "value": "overworld" + }, + { + "operator": "==", + "test": "has_biome_tag", + } + ] +} +``` + +![](./images/6.7_spawn_rule_in-game.png) + +现在打开游戏进行自测,很轻松便能够找到我们想要的生成结果了! \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/8-初步了解特征配置.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/8-初步了解特征配置.md new file mode 100644 index 0000000..e91373a --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/8-初步了解特征配置.md @@ -0,0 +1,25 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 10分钟 +--- + +# 初步了解特征配置 + +在本节中,我们将初步了解自定义特征。一起通过特征配置来制作用于“点缀“世界的**特征**(**Feature**,又译**地物**)。 + +## 创建特征 + +我们通过特征配置来创建一个特征。 + +![](./images/6.8_feature.png) + +![](./images/6.8_feature_property.png) + +我们可以看到,特征属性十分简单。这是因为目前如果我们选择在编辑器内创建特征,我们目前只能创建一种特征,被称为**结构特征**(**Structure Feature**,又译**结构地物**)。结构特征支持一个结构挂接其上,然后该特征生成时就会自动放置这个结构,从而形成类似于“自然遗迹”之类的地形或建筑。 + +我们可以将我们上一章中一起制作的湖泊湿地结构挂接于此。 + +![](./images/6.8_feature_structure.png) + +然后,一个结构模板特征就制作完成了。如果需要其自然生成,我们需要下一节讲到的特征规则。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/9-初步了解特征生成配置.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/9-初步了解特征生成配置.md new file mode 100644 index 0000000..20d9c64 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/9-初步了解特征生成配置.md @@ -0,0 +1,43 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 10分钟 +--- + +# 初步了解特征生成配置 + +在本节中,我们将初步了解自定义特征规则。一起通过特征生成配置来制作能够使特征**自然生成**(**Naturally Generate**)的**特征规则**(**Feature Rule**,又译**地物规则**)。 + +## 创建特征规则 + +我们通过特征生成配置来创建一个特征规则。 + +![](./images/6.9_feature_rules.png) + +![](./images/6.9_feature_rules_property.png) + +我们可以将特征挂接在“**特征**”属性一栏。 + +![](./images/6.9_feature_rules_attach.png) + +然后根据自己的意愿对其他属性稍加改动,比如,我们可以通过“**生成时机**”属性修改特征的**放置阶段**(**Placement Pass**),但是此处我们无需修改,因为我们的池塘放置在“表层”阶段即可。 + +![](./images/6.9_feature_rules_pass.png) + +由于我们的池塘结构是高出地表3格的,所以我们将Y坐标偏移改成-3。 + +![](./images/6.9_feature_rules_y-offset.png) + +为了避免生成过多,将每个区块特征数目减少。 + +![](./images/6.9_feature_rules_distribution.png) + +这样,我们就完成了一个特征规则的创建。 + +![](./images/6.9_feature_in-game_1.png) + +![](./images/6.9_feature_in-game_2.png) + +我们进入游戏自测,很容易就能找到我们的特征。但是,我们也发现了我们的特征放置出来之后和原本的结构稍有不同,那是因为在我们的特征放置之后,又有其他特征被放置,他们的放置阶段在“表层”阶段之后,比如图中的冰、顶层雪和水边自然生成的砂砾和沙子。 + +这样我们就成功制作了一个特征规则!有关特征的更多详细内容我们将在第十六章中共同学习。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/README.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_base_property.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_base_property.png new file mode 100644 index 0000000..13e5b87 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_base_property.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_breathable.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_breathable.png new file mode 100644 index 0000000..ab94d0f Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_breathable.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_breathable_in_water.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_breathable_in_water.png new file mode 100644 index 0000000..8ce9e51 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_breathable_in_water.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_breathable_water.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_breathable_water.png new file mode 100644 index 0000000..e5043ee Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_breathable_water.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_client_file.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_client_file.png new file mode 100644 index 0000000..1c0af7b Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_client_file.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_config.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_config.png new file mode 100644 index 0000000..2e901c3 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_config.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_delete_float.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_delete_float.png new file mode 100644 index 0000000..6b8aea9 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_delete_float.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_delete_nav_walk.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_delete_nav_walk.png new file mode 100644 index 0000000..e4fe096 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_delete_nav_walk.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_import.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_import.png new file mode 100644 index 0000000..4759d8c Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_import.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_nav_swim.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_nav_swim.png new file mode 100644 index 0000000..b0ad600 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_nav_swim.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_nav_swim_fields.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_nav_swim_fields.png new file mode 100644 index 0000000..6d55d49 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_nav_swim_fields.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_no_slime.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_no_slime.png new file mode 100644 index 0000000..28aa9f5 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_no_slime.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_random_swim.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_random_swim.png new file mode 100644 index 0000000..b97ffd4 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_random_swim.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_random_swim_fields.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_random_swim_fields.png new file mode 100644 index 0000000..ba34348 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_random_swim_fields.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_random_swim_priority.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_random_swim_priority.png new file mode 100644 index 0000000..8ebecc5 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_random_swim_priority.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_resource_manager.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_resource_manager.png new file mode 100644 index 0000000..e246dc2 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_resource_manager.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_runtime_id.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_runtime_id.png new file mode 100644 index 0000000..6f978c3 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_runtime_id.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_server_file.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_server_file.png new file mode 100644 index 0000000..e5ed696 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_server_file.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_slime_files.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_slime_files.png new file mode 100644 index 0000000..dc82a0a Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_slime_files.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_slime_files_edited.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_slime_files_edited.png new file mode 100644 index 0000000..127728b Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_slime_files_edited.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_slime_property.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_slime_property.png new file mode 100644 index 0000000..76bd628 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_slime_property.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_slime_runtime.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_slime_runtime.png new file mode 100644 index 0000000..bd878a5 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_slime_runtime.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_swim_components.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_swim_components.png new file mode 100644 index 0000000..7a51093 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_swim_components.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_swim_in-game.gif b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_swim_in-game.gif new file mode 100644 index 0000000..73f2961 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.10_swim_in-game.gif differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.1_add_feature.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.1_add_feature.png new file mode 100644 index 0000000..0e48f41 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.1_add_feature.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.1_add_rider.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.1_add_rider.png new file mode 100644 index 0000000..ec1f8d7 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.1_add_rider.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.1_entity_property.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.1_entity_property.png new file mode 100644 index 0000000..bd8284f Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.1_entity_property.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.1_entity_type.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.1_entity_type.png new file mode 100644 index 0000000..5b11417 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.1_entity_type.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.1_entity_wizard.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.1_entity_wizard.png new file mode 100644 index 0000000..034184c Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.1_entity_wizard.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.1_zombie_rider.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.1_zombie_rider.png new file mode 100644 index 0000000..d3d5801 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.1_zombie_rider.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.1_zombie_rides_sheep.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.1_zombie_rides_sheep.png new file mode 100644 index 0000000..31b08af Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.1_zombie_rides_sheep.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.2_armor_item.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.2_armor_item.png new file mode 100644 index 0000000..65ad7d6 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.2_armor_item.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.2_armor_property.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.2_armor_property.png new file mode 100644 index 0000000..0ea00a2 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.2_armor_property.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.2_empty_item.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.2_empty_item.png new file mode 100644 index 0000000..db4b838 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.2_empty_item.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.2_food_item.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.2_food_item.png new file mode 100644 index 0000000..690d9fc Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.2_food_item.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.2_food_property.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.2_food_property.png new file mode 100644 index 0000000..28f1458 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.2_food_property.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.2_item_property.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.2_item_property.png new file mode 100644 index 0000000..4a4d201 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.2_item_property.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.2_weapon_item.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.2_weapon_item.png new file mode 100644 index 0000000..96f599a Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.2_weapon_item.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.2_weapon_property.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.2_weapon_property.png new file mode 100644 index 0000000..b5f964d Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.2_weapon_property.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.3_emission_block.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.3_emission_block.png new file mode 100644 index 0000000..35f45e5 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.3_emission_block.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.3_emission_change.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.3_emission_change.png new file mode 100644 index 0000000..81317a4 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.3_emission_change.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.3_emission_property.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.3_emission_property.png new file mode 100644 index 0000000..deaa03f Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.3_emission_property.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.3_spawner.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.3_spawner.png new file mode 100644 index 0000000..0976f55 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.3_spawner.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.3_spawner_change_to_sheep.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.3_spawner_change_to_sheep.png new file mode 100644 index 0000000..29483a9 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.3_spawner_change_to_sheep.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.3_spawner_in-game.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.3_spawner_in-game.png new file mode 100644 index 0000000..70ce376 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.3_spawner_in-game.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.3_spawner_property.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.3_spawner_property.png new file mode 100644 index 0000000..97e633f Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.3_spawner_property.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.4_shapeless_property.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.4_shapeless_property.png new file mode 100644 index 0000000..af7a900 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.4_shapeless_property.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.4_shapeless_recipe.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.4_shapeless_recipe.png new file mode 100644 index 0000000..eb4d887 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.4_shapeless_recipe.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.4_smelt_property.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.4_smelt_property.png new file mode 100644 index 0000000..bf230d0 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.4_smelt_property.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.4_smelt_recipe.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.4_smelt_recipe.png new file mode 100644 index 0000000..0551654 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.4_smelt_recipe.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_add_trade.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_add_trade.png new file mode 100644 index 0000000..97061b8 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_add_trade.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_config_component.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_config_component.png new file mode 100644 index 0000000..95e476a Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_config_component.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_select_entity.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_select_entity.png new file mode 100644 index 0000000..8809c8b Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_select_entity.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_select_table.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_select_table.png new file mode 100644 index 0000000..1a211ab Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_select_table.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_select_trade_feature.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_select_trade_feature.png new file mode 100644 index 0000000..bce7612 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_select_trade_feature.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_trade.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_trade.png new file mode 100644 index 0000000..416935f Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_trade.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_trade_add_to_entity.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_trade_add_to_entity.png new file mode 100644 index 0000000..9eb0102 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_trade_add_to_entity.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_trade_demo.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_trade_demo.png new file mode 100644 index 0000000..b26c329 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_trade_demo.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_trade_detail.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_trade_detail.png new file mode 100644 index 0000000..653a9bb Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_trade_detail.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_trade_property.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_trade_property.png new file mode 100644 index 0000000..4f7ad87 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_trade_property.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_trade_property_expand_1.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_trade_property_expand_1.png new file mode 100644 index 0000000..b085863 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_trade_property_expand_1.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_trade_property_shrink.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_trade_property_shrink.png new file mode 100644 index 0000000..7b0fca6 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.5_trade_property_shrink.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.6_drop_property.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.6_drop_property.png new file mode 100644 index 0000000..dbc42a0 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.6_drop_property.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.6_loot.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.6_loot.png new file mode 100644 index 0000000..edcc4e0 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.6_loot.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.6_loot_property.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.6_loot_property.png new file mode 100644 index 0000000..3d9a02c Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.6_loot_property.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.6_loot_table_select.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.6_loot_table_select.png new file mode 100644 index 0000000..120c0bd Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.6_loot_table_select.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.6_lucky_block.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.6_lucky_block.png new file mode 100644 index 0000000..ffbd211 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.6_lucky_block.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.6_lucky_destroyed.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.6_lucky_destroyed.png new file mode 100644 index 0000000..00f2633 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.6_lucky_destroyed.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.6_lucky_destroying.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.6_lucky_destroying.png new file mode 100644 index 0000000..d283bb3 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.6_lucky_destroying.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.6_pools_expand_2.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.6_pools_expand_2.png new file mode 100644 index 0000000..110d8b6 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.6_pools_expand_2.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.7_spawn_rule.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.7_spawn_rule.png new file mode 100644 index 0000000..1961b0e Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.7_spawn_rule.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.7_spawn_rule_biome_tag.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.7_spawn_rule_biome_tag.png new file mode 100644 index 0000000..dc60996 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.7_spawn_rule_biome_tag.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.7_spawn_rule_in-game.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.7_spawn_rule_in-game.png new file mode 100644 index 0000000..551ebed Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.7_spawn_rule_in-game.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.7_spawn_rule_in_group.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.7_spawn_rule_in_group.png new file mode 100644 index 0000000..370c6c2 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.7_spawn_rule_in_group.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.7_spawn_rule_on_grass.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.7_spawn_rule_on_grass.png new file mode 100644 index 0000000..26ad57f Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.7_spawn_rule_on_grass.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.7_spawn_rule_property.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.7_spawn_rule_property.png new file mode 100644 index 0000000..a17ab7e Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.7_spawn_rule_property.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.7_spawn_rule_sheep.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.7_spawn_rule_sheep.png new file mode 100644 index 0000000..1b372e9 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.7_spawn_rule_sheep.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.7_spawn_rule_weight.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.7_spawn_rule_weight.png new file mode 100644 index 0000000..933da26 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.7_spawn_rule_weight.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.8_feature.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.8_feature.png new file mode 100644 index 0000000..62a05f2 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.8_feature.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.8_feature_property.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.8_feature_property.png new file mode 100644 index 0000000..87991c8 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.8_feature_property.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.8_feature_structure.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.8_feature_structure.png new file mode 100644 index 0000000..95f3041 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.8_feature_structure.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.9_feature_in-game_1.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.9_feature_in-game_1.png new file mode 100644 index 0000000..f10f8cd Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.9_feature_in-game_1.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.9_feature_in-game_2.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.9_feature_in-game_2.png new file mode 100644 index 0000000..ece5a1c Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.9_feature_in-game_2.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.9_feature_rules.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.9_feature_rules.png new file mode 100644 index 0000000..b28195f Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.9_feature_rules.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.9_feature_rules_attach.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.9_feature_rules_attach.png new file mode 100644 index 0000000..07ad65a Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.9_feature_rules_attach.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.9_feature_rules_distribution.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.9_feature_rules_distribution.png new file mode 100644 index 0000000..0cab5b5 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.9_feature_rules_distribution.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.9_feature_rules_pass.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.9_feature_rules_pass.png new file mode 100644 index 0000000..874f542 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.9_feature_rules_pass.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.9_feature_rules_property.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.9_feature_rules_property.png new file mode 100644 index 0000000..7e32491 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.9_feature_rules_property.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.9_feature_rules_y-offset.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.9_feature_rules_y-offset.png new file mode 100644 index 0000000..b48e236 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/5-开始配置基础玩法功能/images/6.9_feature_rules_y-offset.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/6-认识附加包/0-摘要.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/6-认识附加包/0-摘要.md new file mode 100644 index 0000000..2b34dec --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/6-认识附加包/0-摘要.md @@ -0,0 +1,15 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 5分钟 +--- + +# 摘要 + +在本章中,我们将一起认识**附加包**(**Add-on**). + +- 在第一节(*理解附加包的结构*)中,我们将一起了解附加包的结构,认识**资源包**(**Resource Pack**)和**行为包**(**Behavior Pack**),以及它们之间的区别。 +- 在第二节(*了解附加包的功能*)中,我们将一起学习附加包的功能,分别从资源包和行为包的角度了解附加包都有哪些功能。 +- 在最后一节(*挑战:亲手新建一个附加包*)中,我们将通过一个挑战,亲手创建一个附加包并导入我的世界开发工作台。 + +本章关键词:附加包 资源包 行为包 基岩引擎 清单文件 定义文件 脚本文件 纹理包 光影包 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/6-认识附加包/1-理解附加包的结构.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/6-认识附加包/1-理解附加包的结构.md new file mode 100644 index 0000000..29be3e9 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/6-认识附加包/1-理解附加包的结构.md @@ -0,0 +1,108 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 10分钟 +--- + +# 理解附加包的结构 + +接下来在本节中,我们将着重且正式介绍前文中已经出现过多次的**附加包**(**Add-on**)的概念。 + +## 附加包的概念 + +附件包这一概念由是微软引入我的世界开发,在我的世界中国版中,网易沿用了这一概念同样作为中国版作品的代名词。从本质上看,国际版和中国版的附加包性质是一样的。**附加包**就是一个带有一个**清单文件**(**Manifest File**)的能够通过**基岩引擎**(**Bedrock Engine**)对我的世界做出修改的**模组**(**Mod**),一般是以一个文件夹、一个压缩包或者某种特殊格式的加密文件出现。 + +基岩引擎就是我的世界游戏读取并应用附加包的一套接口和程序。我们只要按照规定的格式编写附加包,引擎就不会报错。而清单文件则是一个被称作`pack_manifest.json`或者`manifest.json`的文件,用于引领整个附加包的内容,同时方便基岩引擎了解附加包的类型,使其能够知道应选择何种接口来对接这个附加包。 + +从上述定义可以看出,附加包根据清单文件中的规定可以分为不同的类型。在中国版中,我们主要使用的便是**资源包**(**Resource Pack**)和**行为包**(**Behavior Pack**)两种附加包。 + +- **资源包**:资源包是一种用来存放客户端资源的附加包,其中存放的大部分内容都是用于客户端显示、渲染、计算的内容。比如,资源包中常放着方块、物品、实体等物体的纹理,有些资源包中还存放着渲染用的自定义的材质和着色器。对于和行为包一起制作用于特定玩法的一些资源包,还存放着各种方块、物品、实体等元素的客户端定义文件。 +- **行为包**:顾名思义,行为包主要用来存放各种元素、物体的行为。同时行为包还存放着各种用于逻辑计算的表、脚本,决定了游戏的服务端如何运作。比如,行为包中一般用来存放玩法的方块、物品、实体等元素的服务端定义文件,以及交易表、战利品表、维度、群系、地物等用于游戏服务端的数据或逻辑。模组API编写的脚本也存在于行为包中。 + +国际版和中国版的附加包的格式有相同之处,也有不同之处,这里我们着重介绍中国版的附加包格式。 + +## 资源包结构 + +```shell +RESOURCE_PACK +│ biomes_client.json # 客户端生物群系配色定义文件 +│ blocks.json # 客户端方块属性定义文件,包括纹理、各向异性和方块形状等 +│ pack_icon.jpg # 资源包的图标,也可以为.png或.tag格式 +│ pack_manifest.json # 资源包的清单文件,也可以单纯地为manifest.json +│ sounds.json # 声音定义文件,将声音事件链接到对应的系统音效类型上 +│ +├─animations # 客户端动画定义 +├─animation_controllers # 客户端动画控制器定义,主要用于实体骨骼动画 +├─attachables # 附着物定义 +├─effects # 中国版特效定义 +├─entity # 国际版客户端实体定义 +├─fogs # 迷雾定义 +├─font # 字体图片及其定义 +├─items # 国际版客户端物品定义 +├─materials # 材质定义 +├─models # 模型定义,包括了几何、骨架和网格等 +├─netease_items_res # 中国版客户端物品定义 +├─particles # 国际版粒子定义 +├─render_controllers # 渲染控制器定义 +├─shaders # 着色器 +├─sounds # 声音,包含了声音文件和声音事件的定义文件 +├─texts # 本地化 +├─textures # 纹理(资源包必须存在的文件夹) +└─ui # JSON UI定义 +``` + +## 行为包结构 + +```shell +BEHAVIOR_PACK +│ pack_icon.jpg # 行为包的图标,也可以为.png或.tag格式 +│ pack_manifest.json # 行为包的清单文件,也可以单纯地为manifest.json +│ +├─animations # 服务端动画定义 +├─animation_controllers # 服务端动画控制器定义,主要用于实体命令动画 +├─biomes # 国际版服务端生物群系定义 +├─BoxData # 中国版素材 +├─config # 中国版模组配置文件 +├─customBook # 中国版自定义书 +├─blocks # 国际版服务端方块定义 +├─entities # 国际版服务端实体定义(行为包必须存在的文件夹) +├─features # 国际版地物定义 +├─feature_rules # 国际版地物规则定义 +├─functions # 函数 +├─Galaxy # 中国版逻辑编辑器的宏和模板 +├─items # 国际版服务端物品定义 +├─loot_tables # 战利品表定义 +├─netease_biomes # 中国版服务端生物群系定义 +├─netease_blocks # 中国版服务端方块定义 +├─netease_dimension # 中国版维度定义 +├─netease_effects # 中国版状态效果定义 +├─netease_enchants # 中国版魔咒定义 +├─netease_features # 中国版特征定义 +├─netease_feature_rules # 中国版特征规则定义 +├─netease_group # 中国版物品分组定义 +├─netease_items_beh # 中国版服务端物品定义 +├─netease_micro_blocks # 中国版微缩方块定义 +├─netease_recipes # 中国版配方定义 +├─netease_tab # 中国版物品分类/物品栏分页定义 +├─Parts # 中国版零件定义及其脚本 +├─Presets # 中国版预设定义 +├─recipes # 国际版配方定义 +├─Script_xxx # 中国版的各类Python脚本 +├─spawn_rules # 生成规则定义 +├─storyline # 中国版逻辑编辑器的逻辑文件 +├─structures # 结构 +├─texts # 本地化 +└─trading # 交易定义 +``` + +以上为完整的资源包和行为包的文件夹模板框架。大部分资源包和行为包无需拥有以上全部的文件夹,只须在需要的时候建立对应的文件夹和相关的文件即可。在上述所有文件中,只有**清单文件**(`pack_manifest.json`或`manifest.json`)是必须正确存在的,这关系到一个附加包是否能被游戏正常识别。 + +## 资源包、纹理包和光影包的区别 + +为了避免大家对各种概念的混淆,这里着重强调一下**资源包**、**纹理包**(**Texture Pack**,旧译**材质包**)和**光影包**(**Shader Pack**)的区别。 + +前面我们已经介绍了,资源包就是一种附加包的类型,一般包含着纹理文件、声音文件、着色器文件、各种客户端的实体、物品、方块定义文件中的全部或者一部分。这种包我们统称为资源包。 + +而纹理包(旧译材质包)则是指那些基本上只有**纹理**(**Texture**)贴图文件的资源包。一般的纹理包都主要用于修改原版纹理,更改原版游戏的外观,使游戏画风焕然一新。 + +光影包则是指主要包含**着色器**(**Shader**)文件的资源包,有些光影包还包含一些自定义材质(Material),或暴露出一些纹理(Texture)接口供其他纹理包使用(比如这段时间很火的四合一PBR贴图规则接口)。光影包主要用于改变游戏的渲染效果,同样的纹理在不同的渲染效果下显示的样貌和状态是不同的。阴影效果、水面反射、体积云雾都是光影包能够实现的特效。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/6-认识附加包/2-了解附加包的功能.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/6-认识附加包/2-了解附加包的功能.md new file mode 100644 index 0000000..0756672 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/6-认识附加包/2-了解附加包的功能.md @@ -0,0 +1,85 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 10分钟 +--- + +# 了解附加包的功能 + +通过附加包,我们可以实现对我的世界各种功能的修改。而前面我们知道,我的世界开发工作台里的编辑器也可以可视化地支持我们对我的世界进行一键式修改。那么,这两者又有什么不同呢?事实上,在编辑器的“AddOn组件”中操作各种功能修改的过程的本质就是一个制作和修改附加包,从而进一步对我的世界游戏进行修改的过程。只不过,编辑器中的修改更加可视化,也更加简便。但是同时相对地,在编辑器中修改的扩展性、灵活性和结果的复杂性都比直接操作文件进行修改低很多。因此,如果我们要制作一些大型的、复杂的、功能全面的附加包,我们还是需要学会如何通过直接操作文件的方式进行内容与功能的修改。 + +## 定义文件 + +我们介绍一类在附加包中需要经常接触到的文件,那便是**定义文件**(**Definition File**)。定义文件是一种能够通过其中包含的数据来定义游戏中各种内容的文件类型,通常都是JSON文件,也就是我们所见到的`.json`扩展名的文件。 + +这些JSON格式的定义文件都采用了一种可带注释的JSON格式。所以如果要看懂并且正确书写定义文件,我们需要先了解**JSON**(**JavaScript Object Notation**,**JavaScript对象表示法**)的格式。这一块知识各位开发者可以在网络上自行了解。 + +事实上,在上一章的挑战节中,我们在小节开头接触到原版的`Vanilla_Resource_Pack/entity/slime.entity.json`和`Vanilla_Behavior_Pack/entities/slime.json`便是实体的两个定义文件。`slime.entity.json`定义了客户端实体的渲染、模型、纹理等属性,而`slime.json`定义了同一个实体的服务端组件、事件等内容。 + +定义文件决定了大部分游戏玩法的“初始状态”和少部分“伪逻辑”(比如实体的动画)。也正因如此,定义文件中的内容往往仅仅被称为是一种静态的**数据**(**Data**),而通过这种数据新增或更改的内容、为这种数据提供的接口和组件都被称为是**数据驱动**(**Data-Driven**)的。 + +## 脚本文件 + +**脚本文件**(**Script File**)是仅次于定义文件的最重要的文件类型。脚本文件指那些可以被我的世界基岩引擎加载的脚本。在我的世界国际版中,开发者们通常会使用JavaScript脚本,但是在我的世界中国版中,我们可以使用另一种中国版独占且接口功能强大的脚本——**Python**脚本。我的世界中国版中的Python脚本的接口又被称作**模组API**(**Mod API**)。这是中国版开发组精心打造的我的世界模组接口,富有非常强大的功能,并且和数据驱动接口紧密结合,在充分发挥数据驱动接口的功能的同时为其添加”真正“的逻辑,通过“真正”的事件系统和组件工厂实现更加强大的游戏玩法。 + +脚本文件的内容一般都被称为**脚本**(**Script**),而通过脚本新增或更改的内容、为脚本提供的接口和组件都被称为是**脚本使能**(**Script-Enabled**)的。 + +数据驱动内容和脚本使能内容紧密结合,可以使附加包作为一个游戏模组拥有相当高的功能上限。 + +## 依赖行为包的功能 + +下面我们一起了解主要有哪些功能是依赖于行为包的。 + +### 实体、物品和方块的服务端内容 + +实体、物品和方块都是由两个定义文件定义的,一个位于资源包,负责客户端内容,一个位于行为包,负责服务端内容。其中,位于服务端的定义文件往往更加重要,因为服务端定义文件占据了实体、物品和方块的主要游戏表现形式。我们在游戏中看到的各种功能、行为、游戏玩法其实大部分都源自他们的服务端行为。 + +同时,如果我们要给他们加上脚本逻辑,那么这些脚本也全部位于服务端。所以,实体、物品和方块在行为包的部分尤为重要。 + +### 维度、生物群系和特征 + +维度、生物群系和地物的定义也全部位于行为包,因为他们决定了世界生成,而世界生成全部都是在服务端完成的。**维度**(**Dimension**)是一种类似于主世界、下界和末路之地的世界概念;**生物群系**(**Biome**)是决定了类似于哪里是草原、森林、海洋,哪里生成各种生物,以及哪里生成各种地物的的世界概念;而**特征**(**Feature**,又译**地物**)则是一种类似于大到遗迹建筑、小到矿石矿脉的在世界中零散分布的实物。 + +### 预设和零件 + +**预设**(**Preset**)是一种预先设定好的可以是方块、实体、玩家或世界的抽象结构,但是可以通过实例化变为世界中真实存在的事物或逻辑。**零件**(**Part**)是一种可以挂接在预设上的逻辑脚本,可以跟随预设发挥作用。这两类的文件全部都应存储在行为包中。 + +### 模组API脚本 + +前面我们介绍了中国版独占的一种脚本——模组API的Python脚本。所有的模组API脚本全应位于行为包中。 + +### 其他内容 + +配方、交易、战利品表和逻辑编辑器中的宏、逻辑蓝图也全都是由行为包来定义和存储的。 + +## 依赖资源包的功能 + +下面我们一起了解主要有哪些功能是依赖于资源包的。 + +### 纹理贴图 + +所有的纹理贴图,不论是覆盖原版纹理的贴图文件,还是为了自定义新的玩法而加入的文件,全都应存储在资源包对应的文件夹中。只有这样,客户端才能找到对应的纹理并将其渲染。 + +### 着色器(光影) + +所有的**着色器**(**Shader**,俗称**光影**)都是在资源包中定义和实现的。这是因为着色器是用来提供给渲染器进行渲染的,而渲染器的渲染是仅发生于客户端的内容。 + +### UI + +虽然微软正在研发我的世界的新型UI,但是目前在游戏中广泛采用的依旧是一种沿用了很多年的由JSON驱动的UI格式,我们通常称其为**JSON UI**。JSON UI的定义文件全部位于资源包内,通过基岩引擎解析来发挥作用。 + +但是,在资源包内定义的UI事实上几乎没有什么逻辑。原版的JSON UI空间大部分都需要绑定硬编码的功能来实现逻辑,但是我们可以使用模组API来为我们自定义的UI添加逻辑。不过,由于使用了模组API,UI逻辑相关的脚本文件全部都应位于行为包内,还请各位开发者谨记。 + +### 实体、物品和方块的客户端内容 + +这部分内容往往是实体、物品和方块用于绑定纹理(Texture)、模型和材质(Material)的,而材质会进一步将它们关联到着色器上,方便游戏内的渲染。 + +当然,物品中也有一些组件是客户端独占的组件,它们被写在物品的客户端定义文件中。 + +### 模型 + +显然,模型是用来渲染的。游戏中实体、方块真正的碰撞箱往往与模型不同,大部分时候都比模型要简陋一些,大多是一个大的或一些小立方体的组合,这可以方便服务端的计算。而更为精细的模型则是用来输出在玩家的屏幕上的,服务端无需得知此类精细的信息。所以模型也被安置在了资源包中。 + +### 其他内容 + +粒子和特效定义、字体文件及其定义、声音文件(音效和音乐)等内容都是用于在客户端计算和输出的内容,他们都应位于资源包中。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/6-认识附加包/3-挑战:亲手新建一个附加包.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/6-认识附加包/3-挑战:亲手新建一个附加包.md new file mode 100644 index 0000000..44b522f --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/6-认识附加包/3-挑战:亲手新建一个附加包.md @@ -0,0 +1,256 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 10分钟 +--- + +# 挑战:亲手新建一个附加包 + +在本节中,我们一起来完成一个挑战,亲手通过文件级操作的形式制作一个附加包! + +## 新建一个完整的附加包 + +事实上,要制作一个没有功能的附加包格外简单。因为附加包中唯一必须的文件是清单文件(`pack_manifest.json`或`manifest.json`),所以当一个文件夹只包含一个清单文件的时候,我们就认为这个附加包已经具备正常的基本功能了。事实上,一个只含有清单文件的附加包是可以被游戏正常世界并加载的,只是其中任何额外的功能都没有罢了。 + +不过也正因如此,我们首先便需要为附加包制作正确的清单文件。 + +### 新建工作区和清单文件 + +为了使我们的附加包今后可以同时兼容我的世界开发工作的编辑器,我们在创建附加包的文件夹结构时也应稍加注意。 + +![主文件夹](./images/7.3_main_folder.png) + +由于我们希望我们的附加包同时具备资源包部分和行为包部分,所以我们新建一个大的文件夹用于容纳一个资源包和一个行为包。同时,如果我们后续将该附加包导入我的世界开发工作台,那么这个文件夹将用于作为我们的“工作区”文件夹。因此,我们也不要在这个文件夹中再额外放置任何除了资源包和行为包的东西了。在这里,我们使用了`DemoAddon`作为我们的大的主文件夹的名字。需要注意的是,为了后续能够在我的世界开发工作台中导入,我们所有文件夹的名字都需要使用**全英文书写**。 + +接下来,我们应该建立资源包文件夹和行为包文件夹。在上图中,我们可以看到我已经建立了这两个文件夹。但是,我们还需要注意一点,那便是,**资源包和行为包文件夹的名字不应太过简单**,比如,不应如上述仅仅是用“资源包”和“行为包”的英文名直接来命名。因为在资源包和行为包导入游戏后,不同开发者开发的不同模组的资源包和行为包都会放在一起。所以如果有一个玩家加载了大量的模组,那么就极易可能出现其中两个模组的文件夹名称“撞车”的现象,这将导致出现各种的潜在错误。因此,我们建议将这两个文件夹的名字尽可能修改成“只有你可能写得出来的样子”,或者使用随机生成的数字或字母来命名。这样可以在最大程度上避免重名而导致的游戏加载失败。 + +![](./images/7.3_main_folder_random.png) + +如图所示,我们使用了随机生成的两串字母和数字来重命名了文件夹。有人会问,咱们给这些文件夹重命名成了不同的样子,那么之后编辑器或者游戏怎么还能知道哪个是资源包,哪个是行为包呢?因为,不管是编辑器还是游戏本身,他们都会通过读取清单文件的方式来区分各种附加包。所以,接下来我们为资源包和行为包建立清单文件。 + +资源包和行为包都分别需要建立一个清单文件。我们首先在资源包中建立。我们打开我们的资源包文件夹。新建一个名字叫做`pack_manifest.json`的文件。 + +![](./images/7.3_manifest.png) + +用任何一种文本编辑器打开文件,输入我们的清单文件的内容。此时,我们了解一下清单文件的格式。 + +#### 清单文件格式 + +以下是一个清单文件的格式: + +```json +{ + "format_version": 1, // Number类型,清单文件的格式版本(Format Version),在目前的中国版附加包中请填写1。 + "header": { // Object类型,以下这部分是你的附加包的头(Header),这里是该附加包最主要的信息,所有类型的附加包的清单都必须包含如此的头。 + "name": "Name", // String类型,该附加包的名字。 + "description": "Main Description", // String类型,该附加包的简介。 + "uuid": "f61b3faf-e3f3-448d-b19a-0445f504263b", // String类型,该附加包的UUID,格式为Version 4 UUID:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx,其中x可以为任意数字或a-f的任意字母,不能和其他UUID相同。UUID可以用UUID生成器:https://www.uuidgenerator.net/ 获取。 + "version": [0, 0, 1], // Array类型,该附加包的版本,格式为“语义化版本(Semver)”格式,即[Major, Minor, Patch],分别为该附加包的主版本号、次版本号和修订版本号,这里代表0.0.1版本。 + "min_engine_version": [1, 2, 0], // 格式版本为“1”时为可选,Array类型,该附加包适配基岩引擎的最低版本,格式为“语义化版本(Semver)”格式。若我的世界低于该版本,则该附加包不会工作。该值如若填写,将会影响到游戏中的版本化变更(Versioned Change)接口,比如影响到引擎使用哪种版本的命令和Molang的语法。 + }, + "modules": [ // Array类型,以下是该附加包具有的模块(Module),一个模块对应一个附加包所具有的功能,可以有一个模块,也可以由多个模块,但不论有几个,这里都必须是一个数组的形式,而里面的每个模块分别以一个对象的形式存在,并且一个附加包至少要求有一个模块。 + { + "description": "Module Description", // String类型,该模块的简介,不在游戏内显示。 + "uuid": "9b75733a-cf3b-48fe-9df0-91fb4fc81dfe", // String类型,该模块的UUID,不能和其他UUID相同。 + "version": [ 0, 0, 1 ], // Array类型,该模块的版本,格式为“语义化版本(Semver)”。 + "type": "resources" // String类型,该模块的类型,代表了该包拥有哪些功能,在中国版附加包中可以为“resources”或“data”,分别代表资源包和数据包。 + } // Object类型,第一个模块。 + // ... + ], + "dependencies": [ // 可选,Array,该附加包的依赖(Dependency)包,只有当该数组内所有附加包都加载时,该包才会正常加载。其中UUID和版本需要完全对应。当你在游戏里将该附加包加入当前运行的包列表时,如果该附加包有依赖,则其依赖也会自动加入对应列表。建议附加包中的资源包和行为包互为依赖,这样可以最大可能避免“玩家只加载了其中一个包”的情况出现。 + { + "uuid": "a0490260-2b0f-40a2-b31e-bd992ed0a14d", + "version": [ 0, 0, 1 ] + } // Object类型,第一个依赖项。 + // ... + ], + "capabilities": [ // 可选,Array类型,该附加包的权能(Capability,又称固有功能),目前中国版附加包中只有下述两者可用。 + "chemistry", // 允许使用化学。 + "raytraced" // 允许使用光线追踪。 + ] +} +``` + +我们可以看到,一个清单文件是一个JSON对象,其中该对象有如下字段: + +- **格式版本**(**Format Version**,`format_version`):在我的世界附加包中,几乎大部分JSON文件都具有一个格式版本,格式版本代表了该JSON文件使用的**模式**(**Schema**)为哪个版本。换言之,格式版本决定了游戏应该用“什么格式”来读取这个JSON文件。 +- **头**(**Header**,`header`):头是一个附加包最主要的信息,包括了名称、描述和UUID。 +- **模块**(**Module**,`modules`):模块是决定一个附加包类型的地方。目前我们可以在其类型字段中使用`resources`来代表资源包,`data`来代表行为包。 +- **依赖**(**Dependency**,`dependencies`):一个附加包的依赖往往也是一个附加包,此时依赖里的UUID就是那个附加包的UUID。资源包和行为包互为依赖是制作附加包时最方便且可信的填写形式。 +- **权能**(**Capability**,又称**固有能力**,`capabilities`):该附加包能够使用的固有能力。 + +例如,我们在我们的资源包的清单文件中如下书写: + +```json +{ + "format_version": 1, + "header": { + "name": "Resource Pack", + "description": "A demo resource pack", + "uuid": "f61b3faf-e3f3-448d-b19a-0445f504263b", + "version": [0, 0, 1] + }, + "modules": [ + { + "description": "A demo resource module", + "uuid": "9b75733a-cf3b-48fe-9df0-91fb4fc81dfe", + "version": [ 0, 0, 1 ], + "type": "resources" + } + ], + "dependencies": [ + { + "uuid": "a0490260-2b0f-40a2-b31e-bd992ed0a14d", + "version": [ 0, 0, 1 ] + } // 依赖行为包 + ] +} +``` + +我们在我们的行为包的清单文件中如下书写: + +```json +{ + "format_version": 1, + "header": { + "name": "Behavior Pack", + "description": "A demo behavior pack", + "uuid": "a0490260-2b0f-40a2-b31e-bd992ed0a14d", + "version": [0, 0, 1] + }, + "modules": [ + { + "description": "A demo behavior module", + "uuid": "426f64e6-f008-4965-8a80-ce1bf4184321", + "version": [ 0, 0, 1 ], + "type": "data" + } + ], + "dependencies": [ + { + "uuid": "f61b3faf-e3f3-448d-b19a-0445f504263b", + "version": [ 0, 0, 1 ] + } // 依赖资源包 + ] +} +``` + +这样,我们的两个清单文件就完成了! + +### 补充其他文件夹 + +事实上,当具备清单文件之后,一个附加包就可以正常运作了。但是,我们依旧可以再补充一些我们需要的文件夹,比如,我们需要接下来创建实体,那么我们就可以在资源包中创建`entity`文件夹,同时在行为包中创建`entities`文件夹。 + +在这里,为了演示需要,我们尝试创建了所有的文件和文件夹。各位开发者可以根据自己的需要自行选择需要创建的文件夹。我们创建好文件夹之后如下: + +```shell +DEMOADDON +├─behavior_pack_bf114e04 +│ │ pack_icon.jpg +│ │ pack_manifest.json +│ │ +│ ├─animations +│ ├─animation_controllers +│ ├─biomes +│ ├─blocks +│ ├─config +│ ├─customBook +│ ├─entities +│ ├─features +│ ├─feature_rules +│ ├─functions +│ ├─Galaxy +│ │ ├─Macro +│ │ └─Template +│ ├─items +│ ├─loot_tables +│ ├─netease_biomes +│ ├─netease_blocks +│ ├─netease_dimension +│ ├─netease_effects +│ ├─netease_enchants +│ ├─netease_features +│ ├─netease_feature_rules +│ ├─netease_group +│ ├─netease_items_beh +│ ├─netease_micro_blocks +│ ├─netease_recipes +│ ├─netease_tab +│ ├─Parts +│ ├─Presets +│ ├─recipes +│ ├─Script_xxx +│ ├─spawn_rules +│ ├─storyline +│ │ └─level +│ ├─structures +│ ├─texts +│ └─trading +└─resource_pack_8b514eca + │ biomes_client.json + │ blocks.json + │ pack_icon.jpg + │ pack_manifest.json + │ sounds.json + │ + ├─animations + ├─animation_controllers + ├─attachables + ├─effects + ├─entity + ├─font + ├─items + ├─materials + ├─models + │ ├─animation + │ ├─editor_materials + │ ├─effect + │ ├─geometry + │ ├─mesh + │ ├─netease_block + │ └─skeleton + │ + ├─netease_items_res + ├─particles + ├─render_controllers + ├─shaders + │ ├─glsl + │ └─hlsl + ├─sounds + ├─texts + ├─textures + │ │ terrain_texture.json + │ │ + │ ├─blocks + │ ├─entity + │ ├─items + │ ├─models + │ ├─particle + │ ├─sfxs + │ └─ui + └─ui +``` + +## 尝试修改方块纹理 + +我们尝试将一个方块的纹理修改为其他的贴图,然后导入游戏来验证我们的包是否能够成功运作。 + +我们得知,原版的泥土方块的纹理位于资源包的`textures\blocks`文件夹中的`dirt.png`文件,我们不妨将其替换为基岩的纹理。我们从原版的模板包中找到基岩的纹理,将其重命名为`dirt.png`然后复制到我们资源包的`textures\blocks`文件夹内。如图所示: + +![](./images/7.3_block_texture.png) + +这样,我们便等于是将原版的某个纹理进行了替换。接下来我们就需要进入游戏验证了。 + +## 导入我的世界开发工作台并应验效果 + +为了验证效果,我们需要先将我们的附加包导入我的世界开发工作台。 + +我们打开我的世界开发工作台,切换到“**作品库**”选项卡,点击右上角的“**本地导入**”按钮,即可弹出一个“导入”对话框。我们输入“作品名称”,在“作品分类”处选择基岩版“附加包”。然后将我们最一开始的那个附加包工作区文件夹输入或“选择”到最下面的地址栏中。点击“**导入**”按钮即可成功导入作品。 + +![](./images/7.3_import.png) + +然后,我们便可以像往常一样,打开编辑器或者直接进入开发测试进行自测了。 + +![](./images/7.3_dirt_in-game.png) + +我们可以看到,我们的泥土的纹理已经全部变成基岩的纹理了!这代表我们的附加包制作取得了成功!接下来,各位开发者们可以进一步根据自己的意愿进行其他附加包内容的文件级创作了。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/6-认识附加包/README.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/6-认识附加包/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/6-认识附加包/images/7.3_block_texture.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/6-认识附加包/images/7.3_block_texture.png new file mode 100644 index 0000000..6a02c88 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/6-认识附加包/images/7.3_block_texture.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/6-认识附加包/images/7.3_dirt_in-game.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/6-认识附加包/images/7.3_dirt_in-game.png new file mode 100644 index 0000000..90b4988 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/6-认识附加包/images/7.3_dirt_in-game.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/6-认识附加包/images/7.3_import.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/6-认识附加包/images/7.3_import.png new file mode 100644 index 0000000..8ea3666 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/6-认识附加包/images/7.3_import.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/6-认识附加包/images/7.3_main_folder.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/6-认识附加包/images/7.3_main_folder.png new file mode 100644 index 0000000..7d7db9e Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/6-认识附加包/images/7.3_main_folder.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/6-认识附加包/images/7.3_main_folder_random.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/6-认识附加包/images/7.3_main_folder_random.png new file mode 100644 index 0000000..79adf39 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/6-认识附加包/images/7.3_main_folder_random.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/6-认识附加包/images/7.3_manifest.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/6-认识附加包/images/7.3_manifest.png new file mode 100644 index 0000000..4dfca5c Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/6-认识附加包/images/7.3_manifest.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/0-摘要.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/0-摘要.md new file mode 100644 index 0000000..4371977 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/0-摘要.md @@ -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 水鸭 模型 几何 纹理 资源 材质 动画 动画控制器 渲染控制器 粒子 音效 格式版本 最低引擎版本段 短名称 挂接 骨骼 位置 旋转 尺度 通道 关键帧 状态机 状态 转移 附着物 组件 组件组 事件 运行时标识符 系统声音 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/1-使用Blockbench修改鸡的几何模型.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/1-使用Blockbench修改鸡的几何模型.md new file mode 100644 index 0000000..b8b2d16 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/1-使用Blockbench修改鸡的几何模型.md @@ -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**)的格式,即`:`的格式。这里我们沿用`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`保存几何文件即可。但是,可以看到,我们的纹理由于我们的移动和缩放发生了奇怪的改变。不用担心,这是因为我们移动和缩放并不会自动改变纹理而导致的,所以下一节中,我们将一起来重新绘制新的纹理。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/2-在Blockbench上为模型绘制鸭子贴图.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/2-在Blockbench上为模型绘制鸭子贴图.md new file mode 100644 index 0000000..0662cd7 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/2-在Blockbench上为模型绘制鸭子贴图.md @@ -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) \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/3-探索实体的资源控制.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/3-探索实体的资源控制.md new file mode 100644 index 0000000..51f93bf --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/3-探索实体的资源控制.md @@ -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`:字符串,该实体的赋命名空间标识符,格式为`:`,需要和行为包中的服务端实体中的标识符相同。 +- `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.": [ /* some resource elements */ ]`格式。比如`geometry`的数组,我们在示例中看到其定义了`array.crossbow_geo_frames`数组,数组中的元素的格式都为`geometry.`,其中``是我们在实体的客户端定义文件中定义的短名称。其他的数组也是类似。在示例中,我们看到其定义了`textures`和`geometry`数组,唯独没有定义`materials`数组,这是因为该附着物使用的材质非常简单,只有两种,无需使用数组大费周章。 + +下面便是几何、材质和纹理。对于**几何**,每个渲染控制器只能定义一个,或者说,每一帧每个渲染控制器只能存在一个几何。因为几何代表着实体的模型,而一个实体不可能同时具备多个模型。我们不存在“薛定谔的实体”。不过,我们可以通过Molang表达式来做到动态切换模型,比如上述例子中`array.crossbow_geo_frames[query.get_animation_frame]`便可以做到根据纹理动画的帧来切换对应的模型。 + +**材质**则不限制个数,因为一个模型具有不同的部位,而不同的部位则可能需要有多种渲染方式,自然就需要有多种材质。材质数组中每一个元素又都是一个对象,对象中只有一个键值对,那便是`"": `格式的键值对。其中``可以填写一个骨骼的名称,也可以填写一个骨骼的部分名称,其余部分使用通配符`*`来补充。比如`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)。在下一节,我们一起来探索实体的行为文件,了解实体行为的作用机理。 diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/4-探索实体的行为文件.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/4-探索实体的行为文件.md new file mode 100644 index 0000000..f0a4595 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/4-探索实体的行为文件.md @@ -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 :`的格式。 + +有一些组件是其他组件触发的,比如`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`时才会生效。这会使掷出的鸡蛋打碎时有概率生成该实体。 + +合理利用事件,可以使你的实体更加具有机动性,是你能够更好地丰富和完善其功能。 diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/5-为实体添加音效.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/5-为实体添加音效.md new file mode 100644 index 0000000..9971b97 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/5-为实体添加音效.md @@ -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`命令来播放声音。 diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/6-让实体在水中悬浮.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/6-让实体在水中悬浮.md new file mode 100644 index 0000000..2333da4 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/6-让实体在水中悬浮.md @@ -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意向! \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/7-为实体添加粒子.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/7-为实体添加粒子.md new file mode 100644 index 0000000..d90f48f --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/7-为实体添加粒子.md @@ -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) + +我们进入游戏自测,可以看到,果不其然,鸭子脖颈处的栓绳点在水下时便会发射出大量的气泡粒子! \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/8-挑战:自定义水上坐骑.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/8-挑战:自定义水上坐骑.md new file mode 100644 index 0000000..70fb82c --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/8-挑战:自定义水上坐骑.md @@ -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"] + } + } + } + } +} +``` diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/README.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_addon.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_addon.png new file mode 100644 index 0000000..d1479ed Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_addon.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_chicken.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_chicken.png new file mode 100644 index 0000000..7760a80 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_chicken.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_chicken_with_block_grid.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_chicken_with_block_grid.png new file mode 100644 index 0000000..31b8981 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_chicken_with_block_grid.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_copy_address.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_copy_address.png new file mode 100644 index 0000000..df1b5d5 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_copy_address.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_file_addon.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_file_addon.png new file mode 100644 index 0000000..cda67ce Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_file_addon.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_file_addon_complete.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_file_addon_complete.png new file mode 100644 index 0000000..4730d03 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_file_addon_complete.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_grid.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_grid.png new file mode 100644 index 0000000..2960010 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_grid.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_import.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_import.png new file mode 100644 index 0000000..ec25a36 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_import.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_let_s_go.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_let_s_go.png new file mode 100644 index 0000000..5cd0b86 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_let_s_go.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_mc_entity_wizard.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_mc_entity_wizard.png new file mode 100644 index 0000000..5974508 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_mc_entity_wizard.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_mc_entity_wizard_appearance.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_mc_entity_wizard_appearance.png new file mode 100644 index 0000000..1d6fe9c Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_mc_entity_wizard_appearance.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_mc_entity_wizard_behavior.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_mc_entity_wizard_behavior.png new file mode 100644 index 0000000..2cabb2b Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_mc_entity_wizard_behavior.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_mc_entity_wizard_discard.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_mc_entity_wizard_discard.png new file mode 100644 index 0000000..2add615 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_mc_entity_wizard_discard.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_mc_entity_wizard_edit_model.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_mc_entity_wizard_edit_model.png new file mode 100644 index 0000000..24f2493 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_mc_entity_wizard_edit_model.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_mc_entity_wizard_export.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_mc_entity_wizard_export.png new file mode 100644 index 0000000..c973d19 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_mc_entity_wizard_export.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_mc_entity_wizard_name.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_mc_entity_wizard_name.png new file mode 100644 index 0000000..45be89f Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_mc_entity_wizard_name.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_mc_entity_wizard_next_steps.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_mc_entity_wizard_next_steps.png new file mode 100644 index 0000000..7fa3fff Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_mc_entity_wizard_next_steps.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_mc_entity_wizard_spawn_egg.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_mc_entity_wizard_spawn_egg.png new file mode 100644 index 0000000..bfc9d69 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_mc_entity_wizard_spawn_egg.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_move.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_move.png new file mode 100644 index 0000000..42bc7e9 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_move.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_move_eg.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_move_eg.png new file mode 100644 index 0000000..5b93837 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_move_eg.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_open_directory.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_open_directory.png new file mode 100644 index 0000000..0f3f66a Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_open_directory.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_open_model.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_open_model.png new file mode 100644 index 0000000..7585109 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_open_model.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_resize.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_resize.png new file mode 100644 index 0000000..3a00cac Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_resize.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_resize_eg.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_resize_eg.png new file mode 100644 index 0000000..896ac22 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_resize_eg.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_rotate.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_rotate.png new file mode 100644 index 0000000..5d457ce Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_rotate.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_rotate_eg.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_rotate_eg.png new file mode 100644 index 0000000..0e4b588 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_rotate_eg.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_settings.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_settings.png new file mode 100644 index 0000000..f553446 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_settings.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_teal_without_texture.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_teal_without_texture.png new file mode 100644 index 0000000..1791c36 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.1_teal_without_texture.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_color_picker.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_color_picker.png new file mode 100644 index 0000000..a28ded3 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_color_picker.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_color_picker_tool.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_color_picker_tool.png new file mode 100644 index 0000000..f0b3d42 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_color_picker_tool.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_create_texture.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_create_texture.png new file mode 100644 index 0000000..b9c729d Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_create_texture.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_delete_texture.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_delete_texture.png new file mode 100644 index 0000000..30b06ab Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_delete_texture.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_eraser.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_eraser.png new file mode 100644 index 0000000..db1cb11 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_eraser.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_mirror_paint.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_mirror_paint.png new file mode 100644 index 0000000..175ef06 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_mirror_paint.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_paint_mode.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_paint_mode.png new file mode 100644 index 0000000..9fec175 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_paint_mode.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_reopen.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_reopen.png new file mode 100644 index 0000000..9e085a8 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_reopen.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_texture.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_texture.png new file mode 100644 index 0000000..bc8ef96 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_texture.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_texture_complete.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_texture_complete.png new file mode 100644 index 0000000..e1d332d Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_texture_complete.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_texture_save.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_texture_save.png new file mode 100644 index 0000000..4342a5d Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_texture_save.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_texture_saving.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_texture_saving.png new file mode 100644 index 0000000..865702c Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_texture_saving.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_texture_template.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_texture_template.png new file mode 100644 index 0000000..77d7445 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.2_texture_template.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.3_finite_state_machine_example_with_comments.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.3_finite_state_machine_example_with_comments.png new file mode 100644 index 0000000..6f69438 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.3_finite_state_machine_example_with_comments.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.5_husk_sounds.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.5_husk_sounds.png new file mode 100644 index 0000000..cffd7f4 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.5_husk_sounds.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.5_import.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.5_import.png new file mode 100644 index 0000000..a5c4170 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.5_import.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.5_imported.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.5_imported.png new file mode 100644 index 0000000..06af2a9 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.5_imported.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.5_imported_modified.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.5_imported_modified.png new file mode 100644 index 0000000..8191366 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.5_imported_modified.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.6_in-game.gif b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.6_in-game.gif new file mode 100644 index 0000000..3281510 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.6_in-game.gif differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.7_in-game.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.7_in-game.png new file mode 100644 index 0000000..9665268 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.7_in-game.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.7_locator.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.7_locator.png new file mode 100644 index 0000000..dacf870 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.7_locator.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.8_in-game.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.8_in-game.png new file mode 100644 index 0000000..636f4c5 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/7-自定义实体/images/8.8_in-game.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/0-摘要.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/0-摘要.md new file mode 100644 index 0000000..dade30e --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/0-摘要.md @@ -0,0 +1,17 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 5分钟 +--- + +# 摘要 + +在本章中,我们将一起深入学习自定义物品。 + +- 在第一节(*认识自定义物品*)中,我们将认识自定义物品的基本结构和形式,以及我们可继承的原版物品功能。 +- 在第二节(*制作一个新药水*)中,我们将一起学习自定义**药水**(**Potion**)。 +- 在第三节(*制作一个新武器*)中,我们将制作一个自定义的**武器**(**Weapon**)。 +- 在第四节(*制作一个新盔甲*)中,我们将一起来做一个自定义**盔甲**(**Armor**)。 +- 在最后一节(*挑战:制作一个3D盔甲*)的挑战中,我们将一起只做一个3D的盔甲,我们将使用**附着物**(**Attachable**,***挂件***)来实现这一功能。 + +本章关键词:物品 附着物 格式版本 描述 组件 事件 图集 药水 武器 盔甲 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/1-认识自定义物品.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/1-认识自定义物品.md new file mode 100644 index 0000000..9c7bda4 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/1-认识自定义物品.md @@ -0,0 +1,144 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 20分钟 +--- + +# 认识自定义物品 + +在本节中,我们一起来接触自定义物品。 + +物品和实体一样,也是可以通过JSON文件数据驱动来定义的。为了演示,我们使用在第七章中我们一起创建的拥有完整文件夹的附加包。我们记得,我们已经将这个附加包导入游戏。我们在我的世界开发工作台中将其打开。 + +![](./images/9.1_item_config.png) + +我们希望使用配置功能新建一个物品,然后在“**资源管理**”窗格中找到物品的定义文件并研究这些文件。 + +我们可以看到,向导将提示我们建立如下配套文件: + +```shell +./behavior_pack/netease_items_beh/tutorial_demo_item_demo.json +./resource_pack/netease_items_res/tutorial_demo_item_demo.json +./resource_pack/attachables/tutorial_demo_item_demo.json +``` + +这分别是物品的行为包定义文件、资源包定义文件和附作物文件。**附着物**(**Attachable**,***挂件***)作为一种可选的类实体物体,在物品定义中往往起到配合盔甲作为盔甲的模型或配合一般物品作为其自定义模型的作用。一般的物品是不需要这个文件的,所以我们如果选取了普通的物品或者“空”物品,那么这个文件是不会建立的。 + +## 行为包定义 + +我们先来看物品的行为包定义文件,这里以我们新创建的`./behavior_pack/netease_items_beh/tutorial_demo_item_demo.json`为例。 + +```json +{ + "format_version": "1.10", + "minecraft:item": { + "description": { + "identifier": "tutorial_demo:item_demo", + "category": "Construction", + "register_to_create_menu": true, + "custom_item_type": "none", + "is_experimental": false + }, + "components": { + + }/*, + "events": { + // no use at 1.10 format + }*/ + } +} +``` + +- `format_version`:这是该物体文件的**格式版本**(**Format Version**)。物品稳定的格式版本有`1.10`、`1.14`和`1.16`,这三种格式版本均为不需要开启实验性玩法的稳定格式版本,也是我们这里推荐的格式版本,我们尤为推荐格式版本`1.10`。`1.16.100`及以上的格式版本意味着新版物品,这需要开启实验性玩法,我们不推荐使用。不过需要注意的是,如果使用`1.16.100`及以上的新版物品,则只需创建行为包定义文件,客户端需要的数据将从服务端通过来自数据包的形式接收。 +- `minecraft:item`:物品的模式标识符,从标识符上来看并不区分客户端和服务端,引擎会根据其文件放置在行为包还是资源包来判断其为服务端还是客户端。其下有`description`、`components`和`events`对象。 + +`description`代表该物品的**描述**(**Description**)。 + +- `identifier`:字符串,该物品的赋命名空间标识符,格式为`:`,客户端和服务端的标识符要保持一致。 +- `category`:字符串,该物品的物品栏分类,`Construction`为第一个分类。可以使用自定义的分类,自定义分类可以详见自定义物品分页。 +- `register_to_create_menu`:可选,布尔值,是否要将物品注册到创造物品栏。默认为`false`。 +- `custom_item_type`:可选,字符串,中国版特有的描述属性,可以继承原版的一些种类的物品,从而定义具有原版硬编码特性的物品。目前可以填写`weapon`、`armor`、`egg`、`ranged_weapon`、`bucket`、`projectile_item`等。 +- `is_experimental`:可选,布尔值,该物品是否是否为实验性物品,即是否需要打开实验性玩法才可以得到。 + +`components`代表该物品的服务端**组件**(**Component**)。物品的大部分组件都是服务端的。但是,在资源包中(即客户端中),也存在部分可用的物品组件。并且其中一些组件有着重要的作用,例如添加物品的图标。 + +`events`代表该物品的服务端**事件**(**Event**),**并且只有在格式版本为`1.16.100`及以上时才有效**。与实体类似,物品服务端定义文件也允许定义一些事件,不过,目前可定义的事件只能由少数组件触发。也就是说目前定义事件的功能是比较有限的。我们可以看一个原版物品附魔金苹果的事件段落的节选。 + +```json +"events" : { + "consumed" : { + "sequence" : [ + { + "add_mob_effect" : { + "effect" : "regeneration", + "duration" : 5, + "amplifier" : 1, + "target" : "holder" + } + }, + { + "add_mob_effect" : { + "effect" : "absorption", + "duration" : 120, + "amplifier" : 0, + "target" : "holder" + } + } + ] + } +} +``` + +这个`consumed`事件由`minecraft:food`组件触发,可以给予食用者生命恢复和生命吸收状态效果。 + +## 资源包定义 + +我们再来看物品的资源包定义文件,我们依旧以通过配置新创建的`./resource_pack/netease_items_res/tutorial_demo_item_demo.json`为例。 + +```json +{ + "format_version": "1.10", + "minecraft:item": { + "description": { + "identifier": "tutorial_demo:item_demo" + }, + "components": { + "minecraft:icon": "tutorial_demo:item_demo" + } + } +} +``` + +因为和行为包定义文件具有相同的模式标识符`minecraft:item`,所以理应具有相同的文件模式。但是,资源包定义中各个段落的支持情况却和行为包中的有所不同。资源包定义中只支持`description`和`components`,而且`description`只用来定义标识符,且`components`中支持的组件数目也没有行为包中的多。 + +但是,我们看到了一个极为重要的组件:`minecraft:icon`。该组件可以用于定义我们的物品图标。我们可以看到该组件的值已经自动为我们填写了`"tutorial_demo:item_demo"`。事实上,这里的值无需带有命名空间,但是为了不和其他模组冲突,我们还是可以为其加上命名空间的。 + +`minecraft:icon`的值是一个纹理的**短名称**。该短名称的定义并不像实体那样,它不是被定义在客户端定义文件中的。该短名称定义在一种被称为**图集定义文件**(**Atlas Definition File**)的文件中。 + +### 图集 + +**图集**(**Atlas**)是我的世界中一种只存在于内存中的纹理文件的称呼。我的世界每次启动游戏时,都会在游戏内依据资源包中的纹理创建图集。比如,物品会根据资源包中的各个小的物品贴图,以图集定义文件为索引创建物品图集。下面展示了一个原版的我的世界创建的**物品图集**。 + +![](./images/9.1_item_atlas.png) + +我们的模组中的物品想要拥有图标,就必须引用一个物品图集中的图标。但是,如果我们不像物品图集中定义新的纹理,那么我们就无法引用自定义的图标,只能引用原版图标。不过,好在我们拥有自定义图集的能力,那便是修改图集定义文件。 + +我们可以以我们编辑器自动为我们生成的图集文件为例。为了讲解方便,我已经在其中手动添加了我们新物品的图标纹理。物品图集定义文件位于资源包`textures`文件夹根目录下,名称为`item_texture.json`。 + +```json +{ + "resource_pack_name": "tutorial_demo", + "texture_name": "atlas.items", + "texture_data": { + "tutorial_demo:item_demo": { + "textures": "textures/items/tutorial_demo/item_demo" + } + } +} +``` + +- `resource_pack_name`:字符串,我们资源包的名称,这可能影响到图集中精灵图的排序情况。不过普通开发者并不用担心这一点,只有某些特殊的着色器开发者需要注意图集的UV情况。 +- `texture_name`:字符串,在物品图集中必须为`atlas.items`,它是代表物品图集的标识符。 +- `texture_data`:对象,这里定义了所有的短名称。所有的字段都是`"": { /* texture definition */ }`的形式。一般来说,纹理定义对象中只会有一个`textures`字段,代表该纹理的相对根目录的路径,不带有扩展名。扩展名将按照`tga`、`png`、`jpg`的格式顺序依次检查是否存在,并且读取第一个检查到的纹理。皆不存在时将会报出错误。 + +这样,我们就定义好了该物品的图标,从而完成了一个基础的空白物品的定义。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/2-制作一个新药水.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/2-制作一个新药水.md new file mode 100644 index 0000000..77e15a8 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/2-制作一个新药水.md @@ -0,0 +1,98 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 15分钟 +--- + +# 制作一个新药水 + +在本节中,我们一起来制作一个新的药水物品。 + +## 创建配置 + +我们打开我的世界开发工作台的编辑器,通过创建**配置**的方式快速创建一个新物品。 + +![](./images/9.1_item_config.png) + +![](./images/9.2_potion_create.png) + +我们希望创建的物品可以像食品那样被吃掉或喝掉,所以这里我们选择“**可食用物品**”作为数据模板。 + +![](./images/9.2_potion_created.png) + +创建完成后,我们便可以在“属性”窗格中看到我们新物品的属性了。 + +## 更改属性 + +和普通的空物品相比,可食用物品多了一些组件。其中尤为重要的便是“**食物属性**”组件。 + +这个组件其实就是JSON文件中的`minecraft:food`组件。`minecraft:food`主要用于定义一个物品食用后恢复的**饥饿值**(**Hunger Value**)和**饱和度**(**Saturation**),同时可以定义食用时触发的事件、产生的状态效果等。我们就需要用到产生状态效果这一功能。 + +我们保持“**饥饿值满后是否可以**”食用这一栏为选中状态,把“**补充饥饿值**”改为0。由于下面已经自动生成了一个“**附加效果**”,所以我们只需要更改“**效果类型**”等属性为我们需要的值即可。 + +这样,我们就制作了一个和药水功能基本一致的物品了。不过还有一点我们需要注意。普通食品和液体食品的食用动画和播放的音效是不同的。不过,这一点我们也可以进行更改。 + +![](./images/9.2_potion_modded.png) + +我们在“**资源包组件**”中加入“**使用动画**”组件。将使用动画的`eat`更改为`drink`即可。只不过,由于编辑器的限制,我们需要在资源包中手动更改该组件的值。我们在资源包中更改如下: + +```json +{ + "format_version": "1.10", + "minecraft:item": { + "description": { + "identifier": "tutorial_demo:potion" + }, + "components": { + "minecraft:icon": "tutorial_demo:potion", + "minecraft:use_animation": "drink" // 更改此处 + } + } +} +``` + +同时,我们也可以打开行为包来查看我们的行为包定义文件。通过编辑器修改,然后再打开文件亲自学习JSON的写法是一种非常好的附加包学习方式。行为包中的文件如下: + +```json +{ + "format_version": "1.10", + "minecraft:item": { + "description": { + "identifier": "tutorial_demo:potion", + "category": "Nature", + "register_to_create_menu": true + }, + "components": { + "minecraft:foil": true, + "minecraft:food": { + "can_always_eat": true, + "effects": [ + { + "amplifier": 3, + "chance": 1.0, + "duration": 20, + "name": "health_boost" + } + ], + "nutrition": 0 + }, + "minecraft:max_stack_size": 64, + "minecraft:use_duration": 20.0 + } + } +} +``` + +这样,我们的药水就创建完成了!我们打开游戏来验证效果。 + +![](./images/9.2_potion_in-game.png) + +可以看到药水正确添加到了“自然”(Nature)分类中。 + +![](./images/9.2_potion_drunk.png) + +喝药水的声音也是液体食用声音。 + +![](./images/9.2_potion_effect.png) + +并且在食用后正确给予了我们效果。这代表我们成功添加了一个新的药水物品! \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/3-制作一个新武器.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/3-制作一个新武器.md new file mode 100644 index 0000000..32c5eac --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/3-制作一个新武器.md @@ -0,0 +1,77 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 10分钟 +--- + +# 制作一个新武器 + +在本节中,我们将一起新建一个武器,使其具备攻击特性。 + +## 创建配置 + +我们打开我的世界开发工作台的编辑器,通过创建**配置**的方式快速创建一个新物品。 + +![](./images/9.3_weapon_create.png) + +我们以“**自定义武器**”为基础,这样便可以创建一个武器。 + +![](./images/9.3_weapon_created.png) + +## 更改属性 + +我们一起来考察这个物品的组件。我们可以看到,决定了这个物品为武器的组件为“**武器属性**”组件。该组件为中国版自制的组件`minecraft:weapon`。这个组件决定了一个武器的伤害、附魔能力、挖掘等级、基础挖掘速度等属性。我们可以根绝自己的意愿对该组件进行自定义。比如,我们把武器的伤害改为20。 + +![](./images/9.3_weapon_modded.png) + +我们这里提供一对稍加修改之后的武器物品对应的定义文件。行为包定义文件如下: + +```json +{ + "format_version": "1.10", + "minecraft:item": { + "description": { + "identifier": "tutorial_demo:weapon", + "category": "Equipment", + "custom_item_type": "weapon", + "register_to_create_menu": true + }, + "components": { + "minecraft:max_damage": 10, + "netease:weapon": { + "attack_damage": 20, + "enchantment": 10, + "level": 3, + "speed": 5, + "type": "sword" + } + } + } +} +``` + +资源包定义文件如下: + +```json +{ + "format_version": "1.10", + "minecraft:item": { + "description": { + "identifier": "tutorial_demo:weapon" + }, + "components": { + "minecraft:icon": "tutorial_demo:weapon" + } + } +} +``` + +值得注意的是,这里的“**耐久**”组件对应的是`minecraft:max_damage`,即最大的**损坏值**(**Damage Value**,简称**DV**),而非攻击能够产生的**伤害**(**Damage**)。该武器能够造成的攻击伤害由`netease:weapon/attack_damage`定义。这里的损坏值即我们在其他语境下经常提到的特殊值、数据值或附加值。之所以称为损坏值,是因为该物品每用一次便会一定程度上地损坏一次,损坏值也会逐渐增大。当损坏值达到最大损坏值时,物品便会因为损坏殆尽而破碎。所以最大损坏值也就是物品的**耐久**(**Durability**)。 + +我们进入游戏测试我们的武器。 + +![](./images/9.3_weapon_in-game.png) + +![image-20211217180955089](./images/9.3_weapon_kill.png) + +可以看到,我们的武器确实如我们修改的那样,多出了一个+20的攻击伤害。这代表我们的武器自定义成功了! \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/4-制作一个新盔甲.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/4-制作一个新盔甲.md new file mode 100644 index 0000000..e6f7d8e --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/4-制作一个新盔甲.md @@ -0,0 +1,99 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 15分钟 +--- + +# 制作一个新盔甲 + +在本节中,我们将一起新建一个盔甲,使其具备一定的防御能力。 + +## 创建配置 + +我们打开我的世界开发工作台的编辑器,通过创建**配置**的方式快速创建一个新物品。 + +![](./images/9.4_armor_create.png) + +我们以“**自定义盔甲**”为基础,这样便可以创建一个盔甲。 + +![](./images/9.4_armor_created.png) + +我们可以看到,盔甲相比于其他物品,多出了一个盔甲穿戴属性栏。这里便是盔甲的**附着物**(**Attachable**,***挂件***)定义的位置。 + +## 更改属性 + +我们先着眼于行为包组件。我们可以看到,盔甲具有一个“**盔甲属性**”组件,其本质是JSON中的`netease:armor`组件。该组件可以做到修改盔甲的**护甲值**(**Armor Value**)和附魔能力等。 + +对于盔甲的附着物,其JSON文件存放在资源包的`attachables`文件夹中。它的定义文件与实体的资源包定义文件几乎一致,只不过它只有那么一个资源包定义。附着物的作用是单纯显示一个没有行为逻辑的模型,比如玩家身上穿着的盔甲模型。因此它只需要一个资源包定义文件。 + +我们将该物品对应的JSON文件的示例放出供大家参考。首先是行为包定义文件。 + +```json +{ + "format_version": "1.10", + "minecraft:item": { + "description": { + "category": "Equipment", + "identifier": "tutorial_demo:armor", + "custom_item_type": "armor", + "register_to_create_menu": true + }, + "components": { + "minecraft:max_damage": 10, + "netease:armor": { + "armor_slot": 1, + "defense": 20, + "enchantment": 10 + } + } + } +} +``` + +然后是资源包。 + +```json +{ + "format_version": "1.10", + "minecraft:item": { + "description": { + "identifier": "tutorial_demo:armor" + }, + "components": { + "minecraft:icon": "tutorial_demo:armor" + } + } +} +``` + +附着物的定义文件也非常简单,因为这种格式我们之前在第八章中就已经充分学习过了。 + +```json +{ + "format_version": "1.8.0", + "minecraft:attachable": { + "description": { + "identifier": "tutorial_demo:armor", + "geometry": { + "default": "geometry.humanoid.armor.chestplate" + }, + "materials": { + "default": "armor", + "enchanted": "armor_enchanted" + }, + "textures": { + "default": "textures/models/armor/diamond_1", + "enchanted": "textures/misc/enchanted_item_glint" + }, + "render_controllers": [ + "controller.render.armor" + ], + "scripts": { + "parent_setup": "variable.chest_layer_visible = 0.0;" + } + } + } +} +``` + +在下一节的挑战中,我们将为这个物品添加一个3D模型,这将用到我们的附着物功能。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/5-挑战:制作一个3D盔甲.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/5-挑战:制作一个3D盔甲.md new file mode 100644 index 0000000..217bab1 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/5-挑战:制作一个3D盔甲.md @@ -0,0 +1,65 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 20分钟 +--- + +# 挑战:制作一个3D盔甲 + +接下来,我们为我们上一节中添加的盔甲挂接一个模型。所使用的模型可在[3D盔甲资源包](https://g79.gdl.netease.com/addonguide-9.zip)下载。 + +## 准备3D盔甲模型 + +3D盔甲模型本质上就是一个实体模型,所以我们可以用Blockbench来制作这样的模型。在玩法地图教程中,我们一起制作了很多模型,这些模型都可以在玩法地图教程的示例包中下载找到。我们拿出一个T恤衫模型,将`.bbmodel`文件在Blockbench中打开。 + +![](./images/9.5_export.png) + +我们通过Blockbench的导出功能将模型的**几何**(**Geometry**)导出为JSON文件。然后将这个文件和纹理文件一起准备好,将其导入到编辑器中。 + +![](./images/9.5_import_geo.png) + +将几何文件放置在资源包的`models/entity`下。 + +![](./images/9.5_import_texture.png) + +将纹理文件放在`textures/models/armor`下。事实上,纹理文件不像几何文件那么严格,因此放在其他文件夹中也是可以的。但是,为了和原包保持一致,且避免和其他附加包冲突,我们还是作此安排。 + +## 编辑附着物 + +我们打开我们的附着物JSON定义文件。我们需要将附着物的几何和纹理修改为我们自己的资源。 + +```json +{ + "format_version": "1.8.0", + "minecraft:attachable": { + "description": { + "identifier": "tutorial_demo:armor", + "geometry": { + "default": "geometry.mc_t-shirt" // 修改此处 + }, + "materials": { + "default": "armor", + "enchanted": "armor_enchanted" + }, + "textures": { + "default": "textures/models/armor/mc_t-shirt", // 修改此处 + "enchanted": "textures/misc/enchanted_item_glint" + }, + "render_controllers": [ + "controller.render.armor" + ], + "scripts": { + "parent_setup": "variable.chest_layer_visible = 0.0;" + } + } + } +} +``` + +我们按照上述写法进行修改,这样,我们的几何和默认纹理就修改成功了。 + +![](./images/9.5_t-shirt_in-game.png) + +![](./images/9.5_t-shirt_preview.png) + +打开游戏自测观察效果,我们可以看到,盔甲穿着到身上之后确实显示出了应有的模型和纹理! \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/README.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.1_item_atlas.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.1_item_atlas.png new file mode 100644 index 0000000..227f76f Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.1_item_atlas.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.1_item_config.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.1_item_config.png new file mode 100644 index 0000000..9afce30 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.1_item_config.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.2_potion_create.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.2_potion_create.png new file mode 100644 index 0000000..ef9576e Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.2_potion_create.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.2_potion_created.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.2_potion_created.png new file mode 100644 index 0000000..98ca60e Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.2_potion_created.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.2_potion_drunk.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.2_potion_drunk.png new file mode 100644 index 0000000..0250d40 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.2_potion_drunk.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.2_potion_effect.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.2_potion_effect.png new file mode 100644 index 0000000..236b7e5 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.2_potion_effect.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.2_potion_in-game.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.2_potion_in-game.png new file mode 100644 index 0000000..d2d80bd Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.2_potion_in-game.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.2_potion_modded.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.2_potion_modded.png new file mode 100644 index 0000000..05889fa Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.2_potion_modded.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.3_weapon_create.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.3_weapon_create.png new file mode 100644 index 0000000..426566f Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.3_weapon_create.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.3_weapon_created.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.3_weapon_created.png new file mode 100644 index 0000000..227668b Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.3_weapon_created.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.3_weapon_in-game.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.3_weapon_in-game.png new file mode 100644 index 0000000..cad6bbc Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.3_weapon_in-game.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.3_weapon_kill.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.3_weapon_kill.png new file mode 100644 index 0000000..bab00fd Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.3_weapon_kill.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.3_weapon_modded.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.3_weapon_modded.png new file mode 100644 index 0000000..28b299c Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.3_weapon_modded.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.4_armor_create.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.4_armor_create.png new file mode 100644 index 0000000..2ed1e40 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.4_armor_create.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.4_armor_created.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.4_armor_created.png new file mode 100644 index 0000000..a91cfe4 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.4_armor_created.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.5_export.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.5_export.png new file mode 100644 index 0000000..ed5e27e Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.5_export.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.5_import_geo.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.5_import_geo.png new file mode 100644 index 0000000..ca667cc Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.5_import_geo.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.5_import_texture.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.5_import_texture.png new file mode 100644 index 0000000..cd78c1b Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.5_import_texture.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.5_t-shirt_in-game.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.5_t-shirt_in-game.png new file mode 100644 index 0000000..0dd1e58 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.5_t-shirt_in-game.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.5_t-shirt_preview.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.5_t-shirt_preview.png new file mode 100644 index 0000000..85c7590 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/8-自定义物品/images/9.5_t-shirt_preview.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/0-摘要.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/0-摘要.md new file mode 100644 index 0000000..5e21372 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/0-摘要.md @@ -0,0 +1,15 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 入门 +time: 5分钟 +--- + +# 摘要 + +在学习完自定义物品之后,我们来学习如何自定义方块。 + +- 在第一节(*认识自定义方块*)中,我们将学习自定义方块的基本格式。 +- 在第二节(*制作一个苹果方块*)中,我们将一起用Blockbench制作一个方块模型并将其挂接在方块中。 +- 在最后一节(*挑战:制作一个发光地灯*)中,我们将一起进行一个发光地灯的挑战方块制作。 + +本章关键词:方块 格式版本 描述 组件 图集 方块模型 苹果 地灯 diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/1-认识自定义方块.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/1-认识自定义方块.md new file mode 100644 index 0000000..6b58061 --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/1-认识自定义方块.md @@ -0,0 +1,179 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 15分钟 +--- + +# 认识自定义方块 + +在本节中,我们一起考察自定义方块的格式。依据惯例,我们先使用编辑器快速创建一个自定义方块,以便于我们查看它的基本结构。 + +![](./images/10.1_block_config.png) + +我们选择“方块配置”,然后创建一个空的自定义方块。 + +![](./images/10.1_block_create.png) + +我们可以看到,编辑器提示我们将创建以下配套文件: + +```shell +./behavior_pack/netease_blocks/tutorial_demo_block_demo.json +``` + +![](./images/10.1_block_created.png) + +从文件的结构可以看到,我们的自定义方块只有一个行为包定义文件。但是事实上,方块也是需要客户端定义的,不过这个客户端定义比较特殊,不像物品和实体那样是单独的一个文件。方块的客户端定义文件是一个单独的文件,其中包含了所有需要定义的方块的客户端资源,那便是资源包根目录下的`blocks.json`。 + +不过,在此之前,我们先一起来看行为包定义文件。 + +## 行为包定义 + +```json +{ + "format_version": "1.10.0", + "minecraft:block": { + "description": { + "identifier": "tutorial_demo:block_demo", + "register_to_creative_menu": true, + "base_block": "none", + "is_experimental": false + }, + "components": { + + } + } +} +``` + +- `format_version`:这是这个方块的**格式版本**(**Format Version**)。方块稳定的格式版本有`1.10.0`和`1.16.0`,它们均不需要开启实验性玩法,我们依旧推荐格式版本`1.10.0`。`1.16.100`及以上的格式版本意味着新版方块,这需要开启实验性玩法,我们不推荐使用。同样,如果使用`1.16.100`及以上的新版方块,则只需创建行为包定义文件,客户端需要的数据将从服务端通过来自数据包的形式接收。 +- `minecraft:block`:方块的模式标识符。其下有`description`和`components`对象。 + +我们再依次查看`description`对象下的属性。 + +- `identifier`:字符串,该方块的赋命名空间标识符,格式为`:`。需要注意的是,由于方块的内部代码限制,就算我们设定了命名空间,但是依旧无法避免重名冲突现象。这是因为方块作为方块物品存在时可以使用带有命名空间的标识符作为唯一标识符,但是方块作为自身或世界中的实例时只能够使用上述格式中的``作为标识符。所以我们依旧要保证上述格式中的``的唯一性,比如,我们可以使用类似`:_`的格式来保证其唯一性。 +- `register_to_create_menu`:可选,布尔值,是否要将物品注册到创造物品栏。默认为`false`。 +- `base_block`:可选,字符串,中国版特有的描述属性,可以继承原版的一些种类的方块,从而定义具有原版硬编码特性的方块。目前可以填写`mob_spawner`、`portal`、`custom_crop_block`、`liquid`、`custom_heavy_block`等。 +- `is_experimental`:可选,布尔值,该物品是否是否为实验性物品,即是否需要打开实验性玩法才可以得到。 + +## 资源包定义 + +资源包定义文件在资源包根目录下的`blocks.json`,所有的方块的资源包定义皆集中在这一个方块中。 + +```json +{ + "format_version": [ 1, 1, 0 ], + "tutorial_demo:block_demo": { + "textures": "tutorial_demo:dirt" + } +} +``` + +方块的资源包定义文件的格式版本为非常旧的`1.1.0`,且需要用语义化版本数组的形式写入。下面每一个字段的键名是方块的赋命名空间标识符,值是一个对象,其中`textures`字段是该方块的纹理文件的短名称。方块纹理的短名称和物品的一样,依旧是在一个图集文件中定义的,我们稍后会一起来看方块的图集文件。 + +我们下面展示一段原版方块的更详细的资源包定义。 + +```json +{ + "format_version": [ 1, 1, 0 ], + "air": {}, + "stone": { + "textures": "stone", + "sound": "stone" + }, + "grass": { + "isotropic": { + "up": true, + "down": true + }, + "textures": { + "up": "grass_top", + "down": "grass_bottom", + "side": "grass_side" + }, + "carried_textures": { + "up": "grass_carried_top", + "down": "grass_carried_bottom", + "side": "grass_carried" + }, + "sound": "grass" + }, + "dirt": { + "isotropic": true, + "textures": "dirt", + "sound": "gravel" + }, + // ... + "sand": { + "isotropic": true, + "textures": "sand", + "brightness_gamma": 0.55, + "sound": "sand" + }, + // ... + "leaves": { + "isotropic": { + "up": true, + "down": true + }, + "textures": "leaves", + "carried_textures": "leaves_carried", + "brightness_gamma": 0.80, + "sound": "grass" + }, + // ... + "dispenser": { + "textures": { + "up": "dispenser_top", + "down": "dispenser_top", + "north": "dispenser_side", + "south": "dispenser_front_horizontal", + "west": "dispenser_side", + "east": "dispenser_front_vertical" + }, + "carried_textures": { + "up": "dispenser_top", + "down": "dispenser_top", + "north": "dispenser_side", + "south": "dispenser_front_horizontal", + "west": "dispenser_side", + "east": "dispenser_side" + }, + "sound": "stone" + }, + // ... +} +``` + +我们可以看到,除了`textures`之外,我们还可以设置声音(`sounds`)、手持纹理(`carried_textures`)、各向异性(`isotropic`)和亮度伽马值(`brightness_gamma`)和等,并且每个面可以单独设置这些值。事实上,我们还可以通过`blockshape`设置方块形状。 + +声音是用于链接一个系统声音事件(亦称存档声音事件)的集合的。在资源包根目录的`sounds.json`文件的`block_sounds`对象中,我们为每种方块大类设置了一个系统声音事件集,用于方块相关音效的“自动”播放,比如`normal`、`gravel`、`wood`等。这里便需要填写对应的系统声音事件集名称。其实实体的音效播放也是如此,只不过实体的系统声音事件集名称和实体标识符是一致的,所以他们可以自动地链接在一起。 + +各向异性是用于开启纹理的随机旋转的。开启了各向异性的面将采用伪随机的形式通过世界种子进行纹理的随机旋转。 + +方块形状是一种类似于方块模型的属性。开发者可以用方块形状继承一个原版的方块模型,同时继承所有该模型的渲染方法和渲染属性,比如是否进行背面消隐、环境光遮蔽等。 + +### 图集 + +与物品的图集称为物品图集不同,方块的图集称为**地形图集**。 + +![](./images/10.1_terrain_atlas.png) + +我们可以在`textures/terrain_texture.json`处找到方块的图集定义。 + +```json +{ + "resource_pack_name": "tutorial_demo", + "texture_name": "atlas.terrain", + "padding": 8, + "num_mip_levels": 4, + "texture_data": { + "tutorial_demo:dirt": { + "textures": "textures/blocks/dirt" + } + } +} +``` + +`atlas.terrain`是地形图集的图集标识符,不可以改变为其他的字段。`padding`是每个纹理文件整合为一个大的图集文件时的内边距。我们可以看到上面的示例大图集文件中每个纹理周围都存在一段延伸的边距,这便是由该字段定义的,这是为了避免MIPMAP过程中不同方块的贴图之间像素融合的问题。`num_mip_levels`是该图集文件进行的MIPMAP次数,MIPMAP次数越多远处的方块的噪点越少。在`texture_data`中,便是短名称的定义了。这一点和物品是一致的。 + +在定义好了我们的方块纹理后,我们的方块便算是初步完成了! diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/2-制作一个苹果方块.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/2-制作一个苹果方块.md new file mode 100644 index 0000000..2ab07fd --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/2-制作一个苹果方块.md @@ -0,0 +1,211 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 15分钟 +--- + +# 制作一个苹果方块 + +我们学会了如何自定义一个基本的方块,下面,我们学习如何自定义方块模型。可通过[方块模型资源](https://g79.gdl.netease.com/addonguide-10.zip)将模型资源下载到本地。 + +## 准备苹果模型 + +虽然目前最新版的国际版接口已经支持了方块的自定义模型,但是该接口需要`1.16.100`以上的实验性玩法。我们使用中国版单独支持的中国版方块模型。 + +中国版方块模型也是使用Blockbench支持的模型,只不过方块的(0, 0, 0)在模型的(8, 0, 8)处,也就是说整体方块需要以基面中央为西北下角进行制作,并且需要使用“**自由模型**”模式。 + +![](./images/10.2_apple_bb.png) + +我们准备好了一个苹果模型,用来模仿树上挂着的苹果。我们将其保存为`.bbmodel`文件。 + +## 在编辑器中导入模型 + +中国版的方块模型使用特殊的文件模式,为了快速将其转换为中国版方块模型格式,我们需要使用编辑器导入模型。我们在“文件管理”窗格中找到导入按钮。 + +![](./images/10.2_import.png) + +我们选择Blockbench模型,即可将其导入为方块模型。 + +![](./images/10.2_importing.png) + +输入好模型的标识符,选该模型对应的方块物品贴图文件,点击确定即可成功导入。之后,该文件将被导入至资源包的`models/netease_block`文件夹中。 + +我们可以来查看该模型文件。可以看到此时这个文件已经被转换成了一个JSON文件。 + +```json +{ + "format_version": "1.13.0", + "netease:block_geometry": { + "description": { + "identifier": "tutorial_demo:apple", + //"item_texture": "tutorial_demo:apple_icon", + "textures": ["tutorial_demo:apple"], + "use_ao": false + }, + "bones": [{ + "cubes": [{ + "origin": [8, 12, 5], + "pivot": [11, 17, 10], + "rotation": [0, 0, 0], + "size": [0, 4, 6], + "uv": { + "down": { + "texture": 0, + "uv": [0, 0], + "uv_size": [0, 3] + }, + "east": { + "texture": 0, + "uv": [3, 1], + "uv_size": [3, 2] + }, + "north": { + "texture": 0, + "uv": [0, 0], + "uv_size": [0, 3] + }, + "south": { + "texture": 0, + "uv": [0, 0], + "uv_size": [0, 3] + }, + "up": { + "texture": 0, + "uv": [0, 0], + "uv_size": [0, 3] + }, + "west": { + "texture": 0, + "uv": [6, 1], + "uv_size": [-3, 2] + } + } + }, { + "origin": [5, 6, 5], + "pivot": [11, 6, 5], + "rotation": [0, 0, 0], + "size": [6, 6, 6], + "uv": { + "down": { + "texture": 0, + "uv": [6, 3], + "uv_size": [-3, 3] + }, + "east": { + "texture": 0, + "uv": [0, 0], + "uv_size": [3, 3] + }, + "north": { + "texture": 0, + "uv": [0, 0], + "uv_size": [3, 3] + }, + "south": { + "texture": 0, + "uv": [0, 0], + "uv_size": [3, 3] + }, + "up": { + "texture": 0, + "uv": [0, 3], + "uv_size": [3, 3] + }, + "west": { + "texture": 0, + "uv": [0, 0], + "uv_size": [3, 3] + } + } + }], + "name": "bone", + "pivot": [0, 0, 0], + "rotation": [0, -90, 0] + }] + } +} +``` + +我们可以看到格式版本为`1.13.0`,模式标识符为`netease:block_geometry`。在`description`中,`identifier`为你刚刚输入的标识符带上了命名空间,`item_texture`为方块物品的纹理贴图的短名称,`textures`是用于本模型UV映射的纹理贴图。`use_ao`为是否为本模型启用**环境光遮蔽**(**Ambient Occlusion**)。环境光遮蔽是一种描绘物体和物体相交或靠近的时候遮挡周围漫反射光线的效果。 + +## 使用编辑器配置苹果方块 + +我们在编辑器中打开方块,找到”**属性**“窗格中的”**基础属性**“,点击”**+**“。 + +![](./images/10.2_model.png) + +我们选择`model`来在方块的描述中添加一个模型字段。 + +![](./images/10.2_apple_model.png) + +我们在下拉菜单中选择刚刚导入的模型,即可将模型挂接在方块上。接下来,我们只需要进一步配置苹果方块的其他相关属性即可完成制作。 + +![](./images/10.2_components.png) + +我们一起通过对应的JSON文件来考察各个组件的含义。 + +```json +{ + "format_version": "1.10.0", + "minecraft:block": { + "description": { + "identifier": "tutorial_demo:apple", + "register_to_creative_menu": true + }, + "components": { + "minecraft:block_light_absorption": { + "value": 0 + }, + "minecraft:destroy_time": { + "value": 1.0 + }, + "minecraft:loot": { + "table": "loot_tables/custom_apple.json" + }, + "netease:aabb": { + "clip": { + "max": [0.6875, 1.0, 0.6875], + "min": [0.3125, 0.375, 0.3125] + }, + "collision": { + "max": [0.6875, 1.0, 0.6875], + "min": [0.3125, 0.375, 0.3125] + } + }, + "netease:pathable": { + "value": true + }, + "netease:render_layer": { + "value": "alpha" + }, + "netease:solid": { + "value": false + } + } + } +} +``` + +`minecraft:block_light_absorption`是该方块的**不透明度**(**Opacity**),即光线吸收程度。该值越大光线在穿过时将减少得越多。我们希望这个方块不影响光线传播,所以将其改成和空气的不透明度保持一致。 + +`minecraft:destroy_time`是该方块的**硬度**(**Hardness**),即方块的破坏基时间,单位为秒(s)。实际的破坏时间将根据该基时间乘以对应的速度修饰符而计算产生。 + +`minecraft:loot`为该方块掉落的战利品表,我们将其设定为可以掉落苹果物品的战利品表。 + +![](./images/10.2_loot_table.png) + +`netease:aabb`是只运行在中国版的组件,用于设定一个方块的**轴对其边界框**(**Axis-Aligned Bounding Box**,或译**轴对其包围盒**,简称**AABB**),即该方块的**碰撞**(**Collision**)体积和**裁剪**(**Clip**)体积。碰撞体积是该方块在世界中与实体相碰撞的体积,通常称为**碰撞箱**(**Collision Box**);裁剪体积则是用于设定方块的裁剪面,与游戏内的射线相交互的体积,比如用于玩家相机的射线和弹射物消失的轨道检测,通常称为**击中箱**(**Hitbox**)。 + +`netease:pathable`同样是中国版的组件,用于设定一个方块是否为可寻路方块。可寻路方块允许自己在生物寻路算法计算时被视作可践踏的方块。 + +`netease:render_layer`是中国版的组件,决定该方块的**渲染图层**(**Render Layer**)。方块的渲染图层是一系列预置的渲染方法,分别于不同的材质相绑定,用于决定一个方块是否透明、半透明、双面渲染等。 + +`netease:solid`是中国版的组件,决定该方块是否为**固体**(**Solid**)。在基岩版中固体属性决定着生物在方块中是否会受到窒息伤害。 + +我们将该方块设置为不吸光的非固体,边界框设定为和模型一致,渲染图层设置为透明,即可完成一个常规的非固体方块的设置。 + +![](./images/10.2_apple_in-game_1.png) + +![](./images/10.2_apple_in-game_2.png) + +我们进入游戏自测,可以看到苹果的渲染和模型都非常正常,正符合我们的预期! \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/3-挑战:制作一个发光地灯.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/3-挑战:制作一个发光地灯.md new file mode 100644 index 0000000..da5ca6e --- /dev/null +++ b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/3-挑战:制作一个发光地灯.md @@ -0,0 +1,231 @@ +--- +front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg +hard: 高级 +time: 15分钟 +--- + +# 挑战:制作一个发光地灯 + +在本节中,我们完成一个挑战,制作一个发光地灯。可点击[方块模型资源](https://g79.gdl.netease.com/addonguide-10.zip)链接将模型资源下载到本地,地灯方块模型在压缩包内的bbmodel文件夹里。 + +## 准备模型 + +我们在Blockbench中绘制一个发光地灯模型。 + +![](./images/10.3_model.png) + +我们使用编辑器中的导入方块模型的功能导入该模型。 + +![](./images/10.3_model_import.png) + +## 创建并修改配置 + +![](./images/10.3_block_config.png) + +我们创建一个方块配置,然后通过在“**基础属性**”中添加`model`和`sound`的方式加入其地灯的模型和玻璃的声音。在“**行为包组件**”中重点设置“亮度”为大于0的值,“吸光度”为0,“渲染材质”为“全透明”。如果设置了非零吸光度,则地灯将不能完全发出亮度等价的光,而渲染材质如果没有设置为透明,将出现黑色的阴影。 + +我们同时展示对应的JSON文件。行为包定义文件: + +```json +{ + "format_version": "1.10.0", + "minecraft:block": { + "description": { + "identifier": "design:lamp", + "register_to_creative_menu": true + }, + "components": { + "minecraft:block_light_absorption": { + "value": 0 + }, + "minecraft:block_light_emission": { + "emission": 1.0 + }, + "minecraft:destroy_time": { + "value": 0.0 + }, + "minecraft:map_color": { + "color": "#ffffff" + }, + "netease:render_layer": { + "value": "alpha" + }, + "netease:tier": { + "destroy_special": false, + "digger": "pickaxe", + "level": 0 + } + } + } +} +``` + +资源包定义文件: + +```json +{ + "format_version": [1, 1, 0], + "design:lamp": { + "netease_model": "design:lamp", + "sound": "glass" + } +} +``` + +纹理图集文件: + +```json +{ + "resource_pack_name": "vanilla", + "texture_name": "atlas.terrain", + "texture_data": { + "design:lamp": { + "textures": "textures/blocks/design/lamp" + } + } +} +``` + +方块模型几何: + +```json +{ + "format_version": "1.13.0", + "netease:block_geometry": { + "description": { + "identifier": "design:lamp", + "textures": ["design:lamp"], + "use_ao": false + }, + "bones": [{ + "name": "bone", + "pivot": [0, 0, 0], + "rotation": [0, 0, 0], + "cubes": [{ + "origin": [-12.5, 1.5, 3.5], + "pivot": [0, 0, 0], + "rotation": [0, 0, 0], + "size": [9, 11, 9], + "uv": { + "down": { + "texture": 0, + "uv": [5.75, 6.25], + "uv_size": [-2.25, 2.25] + }, + "east": { + "texture": 0, + "uv": [3.5, 0], + "uv_size": [2.25, 2.75] + }, + "north": { + "texture": 0, + "uv": [3.5, 3.5], + "uv_size": [2.25, 2.75] + }, + "south": { + "texture": 0, + "uv": [5.75, 5.75], + "uv_size": [2.25, 2.75] + }, + "up": { + "texture": 0, + "uv": [5.75, 2.75], + "uv_size": [2.25, 2.25] + }, + "west": { + "texture": 0, + "uv": [5.75, 0], + "uv_size": [2.25, 2.75] + } + } + }, { + "origin": [-12, 2, 4], + "pivot": [0, 0, 0], + "rotation": [0, 0, 0], + "size": [8, 10, 8], + "uv": { + "down": { + "texture": 0, + "uv": [4, 8.5], + "uv_size": [-2, 2] + }, + "east": { + "texture": 0, + "uv": [8, 8], + "uv_size": [2, 2.5] + }, + "north": { + "texture": 0, + "uv": [0, 7], + "uv_size": [2, 2.5] + }, + "south": { + "texture": 0, + "uv": [8, 0], + "uv_size": [2, 2.5] + }, + "up": { + "texture": 0, + "uv": [8, 5], + "uv_size": [2, 2] + }, + "west": { + "texture": 0, + "uv": [8, 2.5], + "uv_size": [2, 2.5] + } + } + }, { + "origin": [-15, 0, 1], + "pivot": [0, 0, 0], + "rotation": [0, 0, 0], + "size": [14, 2, 14], + "uv": { + "down": { + "texture": 0, + "uv": [3.5, 3.5], + "uv_size": [-3.5, 3.5] + }, + "east": { + "texture": 0, + "uv": [8, 7.5], + "uv_size": [3.5, 0.5] + }, + "north": { + "texture": 0, + "uv": [8, 7], + "uv_size": [3.5, 0.5] + }, + "south": { + "texture": 0, + "uv": [4, 8.5], + "uv_size": [3.5, 0.5] + }, + "up": { + "texture": 0, + "uv": [0, 0], + "uv_size": [3.5, 3.5] + }, + "west": { + "texture": 0, + "uv": [4, 9], + "uv_size": [3.5, 0.5] + } + } + }] + }] + } +} +``` + +语言本地化文件`zh_CN.lang`: + +```lang +tile.design:lamp.name=地灯 +``` + +我们进入游戏查看效果。 + +![](./images/10.3_in-game.png) + +可以看到,地灯的模型与预期一致,而且如期发出了最高亮度的光。 \ No newline at end of file diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/README.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.1_block_config.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.1_block_config.png new file mode 100644 index 0000000..55bf56c Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.1_block_config.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.1_block_create.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.1_block_create.png new file mode 100644 index 0000000..038a68e Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.1_block_create.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.1_block_created.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.1_block_created.png new file mode 100644 index 0000000..98d4962 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.1_block_created.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.1_terrain_atlas.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.1_terrain_atlas.png new file mode 100644 index 0000000..41f5327 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.1_terrain_atlas.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.2_apple_bb.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.2_apple_bb.png new file mode 100644 index 0000000..bdf6e55 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.2_apple_bb.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.2_apple_in-game_1.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.2_apple_in-game_1.png new file mode 100644 index 0000000..7d4f0dc Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.2_apple_in-game_1.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.2_apple_in-game_2.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.2_apple_in-game_2.png new file mode 100644 index 0000000..2925c68 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.2_apple_in-game_2.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.2_apple_model.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.2_apple_model.png new file mode 100644 index 0000000..d6ddd30 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.2_apple_model.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.2_components.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.2_components.png new file mode 100644 index 0000000..3071b61 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.2_components.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.2_import.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.2_import.png new file mode 100644 index 0000000..d61237f Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.2_import.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.2_importing.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.2_importing.png new file mode 100644 index 0000000..1f8a46f Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.2_importing.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.2_loot_table.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.2_loot_table.png new file mode 100644 index 0000000..6269285 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.2_loot_table.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.2_model.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.2_model.png new file mode 100644 index 0000000..52c14e5 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.2_model.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.3_block_config.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.3_block_config.png new file mode 100644 index 0000000..5a3de9b Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.3_block_config.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.3_in-game.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.3_in-game.png new file mode 100644 index 0000000..3b515ad Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.3_in-game.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.3_model.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.3_model.png new file mode 100644 index 0000000..dab5ef9 Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.3_model.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.3_model_import.png b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.3_model_import.png new file mode 100644 index 0000000..e91488f Binary files /dev/null and b/mconline/100-历史归档教程/15-玩法组件教程【新版】/9-自定义方块/images/10.3_model_import.png differ diff --git a/mconline/100-历史归档教程/15-玩法组件教程【新版】/README.md b/mconline/100-历史归档教程/15-玩法组件教程【新版】/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/20-玩法地图教程/第00章:示例下载/README.md b/mconline/100-历史归档教程/20-玩法地图教程/第00章:示例下载/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/20-玩法地图教程/第00章:示例下载/示例下载.md b/mconline/100-历史归档教程/20-玩法地图教程/第00章:示例下载/示例下载.md new file mode 100644 index 0000000..0264105 --- /dev/null +++ b/mconline/100-历史归档教程/20-玩法地图教程/第00章:示例下载/示例下载.md @@ -0,0 +1,47 @@ +--- +front: https://nie.res.netease.com/r/pic/20210730/ee109f39-8987-46e0-9fe7-40ebb23060fa.png +hard: 入门 +time: 5分钟 +--- +# 示例下载地址 + +有关玩法地图教程所需的资源素材与Demo可点击 [下载链接](https://g79.gdl.netease.com/demopack.zip) 下载到本地。 + + + +## MapDemo + +该文件夹内主要放置了玩法地图开发相关的示例,是已经完工的玩法地图。可供开发者们研究学习。 + + + +## 家具 + +该文件夹内主要放置了玩法地图需要的家具方块美术素材。 + + + +## 农作物 + +该文件夹内主要放置了玩法地图需要的农作物方块美术素材。 + + + +## 实体 + +该文件夹内主要放置了商人NPC与船的美术素材。 + + + +## 装备 + +该文件夹内主要放置了穿着装备与道具的美术素材。 + + + +## 空地图 + +该文件夹内主要放置了玩法地图需要用到的地形地图与建筑模板地图。 + + + diff --git a/mconline/100-历史归档教程/20-玩法地图教程/第01章:摘要/README.md b/mconline/100-历史归档教程/20-玩法地图教程/第01章:摘要/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mconline/100-历史归档教程/20-玩法地图教程/第01章:摘要/什么是玩法地图.md b/mconline/100-历史归档教程/20-玩法地图教程/第01章:摘要/什么是玩法地图.md new file mode 100644 index 0000000..7d3979b --- /dev/null +++ b/mconline/100-历史归档教程/20-玩法地图教程/第01章:摘要/什么是玩法地图.md @@ -0,0 +1,15 @@ +--- +front: https://nie.res.netease.com/r/pic/20210730/ee109f39-8987-46e0-9fe7-40ebb23060fa.png +hard: 入门 +time: 5分钟 +selection: true +--- +# 什么是玩法地图 + +玩法地图是由地图作者创建的自定义世界。通过一种到多种的元素组合达到有别于原版生存的游戏体验。如建筑地形、命令方块、粒子特效、自定义实体、自定义UI等。它们可以是让玩家沉浸在史诗故事中的精彩角色扮演冒险,也可以是考验玩家解决问题能力的具有挑战性的解密地图。 + +制作地图最佳的技巧是同理心。您必须对玩家所见、所感与所想建立一个游戏模型。同理心让您可以设计通关难度曲线,并知道何时让玩家休息,何时添加场景让玩家感到惊喜。同理心是最能对玩家产生心灵“暴击”的一种思维。 + +使用我的世界创作地图的可能性是无限的。唯一限制地图作者的只有想象力。任何人都可以在我的世界创建自己的玩法地图,并通过我的世界中国版开发者平台,将您的手作分享给所有玩家! + +