feat:上传mcguide-开发指南部份

This commit is contained in:
Othniel su
2024-12-23 10:57:59 +08:00
parent 7292166c88
commit 0dc59fa4f0
3297 changed files with 63375 additions and 0 deletions

View File

@@ -0,0 +1,345 @@
---
front: https://nie.res.netease.com/r/pic/20220408/3e2bb79b-32b3-4b95-a408-83556c00f775.jpg
hard: 入门
time: 20分钟
selection: true
---
# 自定义基础物品
## 概述
该功能不需要开启实验性玩法。
开发者可以通过在addon中配置json来添加自定义物品。添加的自定义物品支持“MOD SDK文档”中与物品相关的所有事件及接口。
## 物品相关组件事件流程
**组件事件流程**
<img src="./picture/item_event_flow.png" alt="流程" style="zoom:75%;" />
**注意ActorUseItemClientEventActorUseItemServerEvent与ClientItemTryUseEventServerItemTryUseEvent的先后顺序与使用的物品有关。**
## 注册
以demo [CustomItemsMod](../../13-模组SDK编程/60-Demo示例.md#CustomItemsMod)为例
1. 在behavior中新建`netease_items_beh`目录
![avatar](./picture/customitem/base1.png)
2. 在目录下新建一个json编写物品的定义。json的格式可参考[官方wiki](https://minecraft.gamepedia.com/Bedrock_Edition_beta_item_documentation)。<a name="item_reg_2"></a>
* json中至少有一个component
* identifier分为命名空间及物品名命名空间推荐与mod名称一致identifier必须全局唯一。mod中其他地方都是用这个identifier与这个自定义物品对应上。**identifier由小写字母以及下划线组成请勿使用大写字母。**
![avatar](./picture/customitem/base2.png)
![avatar](./picture/customitem/base3.png)
3. 下面开始操作resource包。
将物品的贴图放到`textures\items`
![avatar](./picture/customitem/base4.png)
4. 在textures中新建`item_texture.json`编写资源名与贴图的对应关系。资源名的命名必须满足全局唯一。json格式可参考“Mod PC开发包”的`data\resource_packs\vanilla\textures\item_texture.json`
![avatar](./picture/customitem/base5.png)
![avatar](./picture/customitem/base6.png)
5. 在resource中新建一个`netease_items_res`文件夹在文件夹中添加json用于配置物品的表现属性例如贴图。category字段需要与behavior包中的设置一致或者不填否则可能出现错误。<a name="item_reg_5"></a>
这个json的identifier需要与behavior中的一致贴图的值需要与上一步`item_texture.json`中配置的资源名对应。json格式可参考“Mod PC开发包”的`data\resource_packs\vanilla\items`
![avatar](./picture/customitem/base7.png)
![avatar](./picture/customitem/base8.png)
6.`texts\zh_CN.lang`中配置物品的中文名称。
键的格式为item.物品identifier.name
![avatar](./picture/customitem/base9.png)
![avatar](./picture/customitem/base10.png)
7. 重复1-6编写其他自定义物品
## JSON组件
### format_version
请填写1.10
### description
| 键 | 类型 | 默认值 | 解释 |
| ----------------------- | ---- | ------ | ------------------------------------------------------------ |
| identifier | str | | 包括命名空间及物品名。需要全局唯一。<br>建议使用mod名称作为命名空间 |
| register_to_create_menu | bool | false | 是否注册到创造栏 |
| category | str | Nature | 注册到创造栏的分类,可选的值有:<br>Construction<br>Nature<br>Equipment<br>Items |
| custom_item_type | str | | 自定义物品类别,可选值有:<br/>weapon<br/>armor<br/>egg<br/>ranged_weapon<br/>projectile_item |
### 原版components
支持的微软原版component包括参数及解释见[官方wiki](https://minecraft.gamepedia.com/Bedrock_Edition_beta_item_documentation)
* minecraft:foil
可以使物品拥有类似附魔的闪烁效果
* minecraft:max_damage
可以使物品拥有耐久度,范围为[0, 32767]
> 若物品堆叠数量大于1时耐久度的变更对整一叠的物品生效。并且耐久度为0后每次消耗耐久度的行为会使数量减一
* minecraft:max_stack_size
设置物品最大堆叠数量,`注意该值不能超过64`
* minecraft:seed<span id="minecraft_seed"></span>
设置种子的属性如crop_result、plant_at
> plant_at列表中需要保持格式一致如["minecraft:grass", "minecraft:dirt"]或["grass", "dirt"],否则在相邻自定义农作物种植新的农作物会导致上一个农作物被破坏
* minecraft:use_animation
使用物品时播放的动画,如原版使用`apple`时会播放`eat`动画,需在材质包中定义,如在`resource_packs/vanilla/items/apple.json`中就有`"minecraft:use_animation": "eat"`配置。
* minecraft:stacked_by_data
值为bool类型不同aux值的物品是否能够堆叠例如AUX值为1的燃料无法和aux值为2的燃料堆叠在一起此外该组件也会导致不同aux的物品在合成配方中被识别为不同的合成原料。
> 1. 不同aux的物品可以使用<a href="../../../../mcdocs/1-ModAPI/接口/玩家/背包.html#spawnitemtoplayerinv" rel="noopenner">SpawnItemToPlayerInv</a>接口生成到玩家背包。
> 2. 该组件在物品无法和耐久度(minecraft:max_damage)组件共存。
配置示例:
``` json
{
"format_version": "1.10",
"minecraft:item": {
"description": {
"identifier": "wiki:gem"
},
"components": {
"minecraft:stacked_by_data": true,
"minecraft:max_stack_size": 64
}
}
}
```
### 网易components
#### netease:compostable
设置物品是否可以堆肥
使用形式如:"netease:compostable" : 48 。其中48表示堆肥成功概率 48%。
详细例子如下:
``` json
//这是一个自定义物品的配置
{
"format_version": "1.10",
"minecraft:item": {
"description": {
"identifier": "customitems:test1",
"register_to_create_menu":true,
"category": "Nature"
},
"components": {
"minecraft:max_stack_size": 1,
"minecraft:max_damage": 10,
"netease:compostable": 100,
"netease:show_in_hand": {
"value": true
}
}
}
}
```
#### netease:show_in_hand
设置物品拿在手上时是否显示
| 键 | 类型 | 默认值 | 解释 |
| ----- | ---- | ------ | -------------- |
| value | bool | | 手持时是否显示 |
- 备注格式和微软的component不一样里面多了一个“value"
- 可以参考[CustomItemsMod](../../13-模组SDK编程/60-Demo示例.md#CustomItemsMod)行为包的`netease_items_beh\customitems_test1.json`
<span id="netease_fire_resistant"></span>
#### netease:fire_resistant
设置物品是否防火,防火的物品会与下界合金一样,不会被火烧毁,掉进岩浆时会弹走
| 键 | 类型 | 默认值 | 解释 |
| ----- | ---- | ------ | -------- |
| value | bool | | 是否防火 |
- 备注格式和微软的component不一样里面多了一个“value"
- 可以参考CustomItemsMod示例行为包的`netease_items_beh\customitems_test_sword.json`
#### netease:allow_offhand
设置物品是否可以放在副手
| 键 | 类型 | 默认值 | 解释 |
| ----- | ---- | ------ | ---------------- |
| value | bool | | 是否可以放在副手 |
- 备注格式和微软的component不一样里面多了一个“value"
- 可以参考CustomItemsMod示例行为包的`netease_items_beh\customitems_test_offhand.json`
- 目前基岩版原版副手功能较弱大多数时候都无法使用物品主要用于装饰和在mod中判断副手中是否持有某物品来执行特定逻辑
- 副手不支持一些其他组件,如`minecraft:foil``netease:render_offsets`
#### netease:enchant_material
设置物品是否可以当附魔材料
| 键 | 类型 | 默认值 | 解释 |
| ----- | ---- | ------ | ------------------ |
| value | bool | false | 是否可以当附魔材料 |
- 备注格式和微软的component不一样里面多了一个“value"
- 可以参考CustomItemsMod示例行为包的`netease_items_beh\customitems_test_enchantmaterial.json`
#### netease:fuel
可燃类物品组件。允许该物品作为燃料在熔炉中燃烧
| 键 | 类型 | 默认值 | 解释 |
| ------- | ------ | ------ | ------------------------------------------------------------ |
| duration| int | 0 | 可填, 该物品可提供的熔炉燃烧时长(秒)。 |
#### netease:cooldown
定义物品冷却时间
| 键 | 类型 | 默认值 | 解释 |
| ------- | ------ | ------ | ------------------------------------------------------------ |
| category | string | "item" | 必填,物品的冷却类型,同种类型冷却会互相影响。 |
| duration| int | 0 | 可填,这个物品能够再次使用前的冷却时间。 |
- 备注:自定义食品(minecraft:food)的冷却时间需定义在minecraft:food中
#### netease:customtips
定义物品描述信息
| 键 | 类型 | 默认值 | 解释 |
| ------- | ------ | ------ | ------------------------------------------------------------ |
| value | string | "" | 物品的描述信息。 |
## 附属功能
### python事件及接口
支持“MOD SDK文档”中所有与物品相关的事件及组件包括旧版自定义物品支持的全部功能。与旧版自定义物品不同现在的事件及接口使用“itemName”传递物品identifier来区分不同的物品。
### 自定义配方
见“自定义配方”文档材料及结果都支持填写自定义物品的identifier。可参考[CustomItemsMod](../../13-模组SDK编程/60-Demo示例.md#CustomItemsMod)行为包的`netease_recipes`
### 自定义食品
原版apple json结构
```python
{
"format_version": "1.10",
"minecraft:item": {
"description": {
"identifier": "minecraft:apple"
},
"components": {
"minecraft:use_duration": 32,
"minecraft:food": {
"nutrition": 4,
"saturation_modifier": "low"
}
}
}
}
```
食品类的json结构需要包含在minecraft:item -> components中包含minecraft:food节点下面对minecraft:food节点进行解释说明。
| json字段 | 举例 | 含义 |
| ------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| nutrition | "nutrition": 2 | 营养值 |
| saturation_modifier | "saturation_modifier": "low" | 饱和度等级有poorlownormalgoodmaxsupernatural六个等级系数分别是0.10.30.60.81.01.2 |
| using_converts_to | "using_converts_to": "bowl" | 使用后转化为的Item如使用甜菜汤后留下碗 |
| on_use_action | "on_use_action": "chorus_teleport" | 使用食品产生的动作,目前仅支持传送到其他位置 |
| on_use_range | "on_use_range": [0, 10, 4] | 传送位置x,y,z的偏移值 |
| cooldown_type | "cooldown_type": "chorusfruit" | 冷却类型 |
| cooldown_time | "cooldown_time": 1 | 冷却时间 |
| can_always_eat | "can_always_eat": true | 是否可以一直使用 |
| effects | "effects": [<br/> {<br/> "name": "regeneration",<br/> "chance": 1.0,<br/> "duration": 30,<br/> "amplifier": 4<br/> },<br/> {<br/> "name": "absorption",<br/> "chance": 1.0,<br/> "duration": 120, // 2 * 60<br/> "amplifier": 3<br/> }] | 使用后的效果 |
### 村民交易
可将行为包的交易配置中的item修改为自定义物品的identifier。可参考[CustomItemsMod](../../13-模组SDK编程/60-Demo示例.md#CustomItemsMod)行为包的`trading\economy_trades\butcher_trades.json`里面将新手级屠夫的其中一个交易项替换为绿宝石兑换customitems:test0物品
### loot_table
自定义物品的identifier可用作loot_table配置掉落物。可参考[CustomItemsMod](../../13-模组SDK编程/60-Demo示例.md#CustomItemsMod)行为包的`loot_tables\entities\zombie.json`里面将僵尸的掉落物由腐肉改为customitems:test0物品
## demo解释
[CustomItemsMod](../../13-模组SDK编程/60-Demo示例.md#CustomItemsMod)中定义了2个自定义物品
* customitems:test0
平平无奇的只替换了贴图的自定义物品。
演示了"村民交易" “loot_table”的功能
* customitems:test1
无法堆叠,拥有耐久度,手持时不渲染的自定义物品
演示了“自定义配方”的功能
## Mod卸载
若使用了自定义物品的存档卸载mod后再进入时
2. 对玩家背包中的自定义物品:
物品会消失。若重新加载mod对卸载期间登录过的玩家物品不会恢复没登录过的玩家物品可以保留
3. 对地图上容器内的自定义物品:
物品会消失。若重新加载mod对卸载期间进行探索过的区域内的容器物品不会恢复未探索区域的容器物品可以保留
4. 对地图上未捡起的掉落物:
掉落物会消失。若重新加载mod对卸载期间进行探索过的区域掉落物不会恢复除非subchunk内没有其他任何entity未探索区域的掉落物可以保留。
## 常见报错
* JSON: xxx has an error
一般为json格式有问题检查是否漏写或多写逗号括号是否对应等
![avatar](./picture/customitem/base11.png)

View File

@@ -0,0 +1,94 @@
---
front:
hard: 入门
time: 分钟
---
# 自定义武器及工具
## 概述
属于特殊的自定义物品,在支持自定义物品所有特性的基础上,还具有武器或工具相关的功能。
## 注册
1. 与自定义基础物品的注册1-6步相同
2. 在behavior/netease_items_beh的json中添加武器/工具相关的定义,包括:
custom_item_type为weapon
一个netease:weapon组件必填。组件的参数见[json组件](#json组件)
设置最大堆叠数量,可选
设置耐久度,可选
![avatar](./picture/customitem/tool1.png)
```python
{
"format_version": "1.10",
"minecraft:item": {
"description": {
"identifier": "customitems:test_sword",
"register_to_create_menu":true,
"custom_item_type": "weapon"
},
"components": {
"minecraft:max_stack_size":1,
"minecraft:max_damage":153,
"netease:weapon":{
"type":"sword",
"level":3,
"speed":8,
"attack_damage":7,
"enchantment":10
}
}
}
}
```
## JSON组件
### description
| 键 | 类型 | 默认值 | 解释 |
| -------- | ---- | --------- | ---------------------------------------------- |
| category | str | Equipment | 与普通物品不同,武器/工具的默认分类是Equipment |
### 原版components
* minecraft:max_stack_size
自定义武器/工具只支持设置为1
### 网易components
* netease:weapon
| 键 | 类型 | 默认值 | 解释 |
| ------------- | ---- | ------ | ------------------------------------------------------------ |
| type | str | | 武器/工具的类型,目前支持类型有:<br>sword<br>shovel<br>pickaxe<br>hatchet<br>hoe锄头 |
| level | int | | 武器/工具的level说明及修复信息下面以镐举例<br>1. 对于镐,对应挖掘等级<br>即当使用镐挖掘某些方块时,镐子的挖掘等级大于等于方块的挖掘等级,才会产生掉落物。<br>对于原版镐子木镐与金镐是0石镐是1铁镐是2钻石镐是3<br>2. 并且该值与铁砧的修复材料有关。<br>level为0当速度为2对应木板,否则对应金锭 <br>level为1对应石头<br>level为2对应铁锭<br>level为3对应钻石<br>level大于3无法使用铁砧修复 |
| speed | int | 0 | 对采集工具生效,表示挖掘方块时的基础速度 |
| attack_damage | int | 0 | 攻击伤害 |
| enchantment | int | 0 | 附魔能力。该值的解释见[官方wiki](https://minecraft-zh.gamepedia.com/index.php?title=%E6%95%99%E7%A8%8B/%E9%99%84%E9%AD%94%E6%9C%BA%E5%88%B6&variant=zh#.E9.AD.94.E5.92.92.E6.98.AF.E5.A6.82.E4.BD.95.E9.80.89.E6.8B.A9.E5.87.BA.E6.9D.A5.E7.9A.84) |
## demo解释
[CustomItemsMod](../../13-模组SDK编程/60-Demo示例.md#CustomItemsMod)中定义了一个自定义武器:
* customitems:test_sword
剑类型的自定义武器

View File

@@ -0,0 +1,253 @@
---
front:
hard: 入门
time: 分钟
---
# 自定义盔甲
## 概述
属于特殊的自定义物品,在支持自定义物品所有特性的基础上,还具有盔甲相关的功能。
## 注册
1. 与自定义基础物品的注册1-6步相同
2. custom_item_type为amor
3.`behavior/netease_items_beh`的json中添加netease:armor组件参数详见[json组件](#json组件)
```json
{
"format_version": "1.10",
"minecraft:item": {
"description": {
"identifier": "customitems:modarmor1",
"register_to_create_menu":true,
"custom_item_type": "armor"
},
"components": {
"netease:armor":{
"defense": 10,
"enchantment":4,
"armor_slot":0
}
}
}
}
```
4. 添加模型贴图到资源包的`textures\models`
与微软内置盔甲贴图一致,自定义盔甲的贴图也需要设置贴图。
5. 在`resource/netease_item_res`目录下创建新json中添加盔甲物品的贴图参数详见自定义物品注册中贴图的设置解释
```json
{
"format_version": "1.10",
"minecraft:item": {
"description": {
"identifier": "customitems:modarmor1",
"category": "Equipment"
},
"components": {
"minecraft:icon": "customitems:modarmor1"
}
}
}
```
6. 在resource目录下创建`attachables`目录创建新json描述文件其中可配置的参数详见[json组件](#json组件)
```json
{
"format_version": "1.8.0",
"minecraft:attachable": {
"description": {
"identifier": "customitems:modarmor1",
"materials": {
"default": "armor",
"enchanted": "armor_enchanted"
},
"textures": {
"default": "textures/models/diamond_1",
"enchanted": "textures/misc/enchanted_item_glint"
},
"geometry": {
"default": "geometry.customitems_modarmor1"
},
"scripts": {
"parent_setup": "variable.chest_layer_visible = 0.0;"
},
"render_controllers": [ "controller.render.armor" ]
}
}
}
```
7. 在`resource/models`目录下增加`entity`目录增加对应的geometry描述文件
可配置的参数主要为根节点描述符`geometry.modid_modarmor1`这个值与上一步中的geometry文件值对应。
```json
{
"geometry.customitems_modarmor1": {
"texturewidth": 64,
"textureheight": 32,
"visible_bounds_width": 2,
"visible_bounds_height": 2,
"visible_bounds_offset": [ 0, 1, 0 ],
"bones": [
{
"cubes": [
{
"origin": [ -4, 24, -4],
"uv": [ 0, 0 ],
"size": [ 8, 8, 8 ],
"inflate": 1.0
},
{
"origin": [ 4, 30, -1],
"size": [ 2, 2, 2],
"uv": [47, 0]
},
{
"origin": [-6, 30, -1],
"size": [2, 2, 2],
"uv": [47, 0]
},
{
"origin": [6, 31, -1],
"size": [2, 2, 2],
"uv": [42, 8]
},
{
"origin": [-8, 31, -1],
"size": [2, 2, 2],
"uv": [42, 8]
},
{
"origin": [8, 32, -1],
"size": [2, 4, 2],
"uv": [41, 0]
},
{
"origin": [-10, 32, -1],
"size": [2, 4, 2],
"uv": [41, 0]
},
{
"origin": [-8, 36, -1],
"size": [16, 2, 2],
"uv": [28, 28]
}
],
"name": "head",
"parent": null,
"pivot": [
0.0,
24.0,
0.0
]
}
]
}
}
```
## JSON组件
### `netease_item_beh`文件
**description**
| 键 | 类型 | 默认值 | 解释 |
| -------- | ---- | --------- | ----------------------------------------- |
| category | str | Equipment | 与普通物品不同盔甲的默认分类是Equipment |
**网易components**
* netease:armor行为包
| 键 | 类型 | 默认值 | 解释 |
| -------------------- | ----- | ------ | ------------------------------------------------------------ |
| defense | int | 0 | 盔甲的防御值 |
| enchantment | int | 0 | 盔甲的附魔能力。该值的解释见[官方wiki](https://zh.minecraft.wiki/w/%E9%99%84%E9%AD%94%EF%BC%88%E7%89%A9%E5%93%81%E4%BF%AE%E9%A5%B0%EF%BC%89) |
| armor_slot | int | | 盔甲槽位,详见<a href="../../../../mcdocs/1-ModAPI/枚举值/ArmorSlotType.html" rel="noopenner"> ArmorSlotType </a> |
| toughness | int | 0 | 盔甲韧性范围0~20详见[盔甲机制](https://zh.minecraft.wiki/w/%E7%9B%94%E7%94%B2%E6%9C%BA%E5%88%B6) |
| knockback_resistance | float | 0 | 击退抗性范围0~1详见[伤害-击退抗性](https://zh.minecraft.wiki/w/%E4%BC%A4%E5%AE%B3#%E5%87%BB%E9%80%80%E6%8A%97%E6%80%A7) |
### attachables文件
| 键 | 类型 | 描述 |
| -------------------- | ---- | ------------------------------------------------------ |
| identifier | str | 盔甲的唯一描述符,与之前注册流程中的物品描述符保持一致 |
| textures/default | str | 盔甲对应的贴图路径设置 |
| geometry/default | str | 盔甲对应的模型描述文件路径设置 |
| scripts/parent_setup | str | 盔甲对应部位影响玩家渲染的设置参数 |
**parent_setup**
微软内置的盔甲对应设置的值为以下几种:
> variable.helmet_layer_visible 玩家是否显示
>
> variable.chest_layer_visible 玩家上半身是否显示
>
> variable.leg_layer_visible 玩家腿部是否显示
>
> variable.boot_layer_visible 玩家脚部是否显示
一般而言,当设置对应的部位的盔甲,需要设置在`scripts/parent_setup ` 设置对应的变量值为0
**geometry/default**
需要设置成盔甲对应位置的geometry例如`geometry.humanoid.armor.boots`。
| 键 | 说明 |
| ---------------------------------- | ---- |
| geometry.humanoid.armor.boots | 靴子 |
| geometry.humanoid.armor.chestplate | 护胸 |
| geometry.humanoid.armor.helmet | 头盔 |
| geometry.humanoid.armor.leggings | 脚部 |
## 附属功能
除了支持自定义物品的所有功能外还支持盔甲的python事件及接口包括OnNewArmorExchangeServerEvent事件及armorslot组件
## demo解释
[CustomItemsMod](../../13-模组SDK编程/60-Demo示例.md#CustomItemsMod)中定义了一个自定义装备:
* customitems:modarmor1
头盔类型的自定义装备
## 自定义模型设置建议
* 盔甲模型的基础参考文件为:`data\resource_packs\vanilla\models`目录下的`mobs.json`文件中的**geometry.humanoid**。
* 模型制作工具推荐使用BlockBench导出的模型JSON文件格式与要求的格式相近。
* 使用BlockBench制作模型并导出为JSON文件后按照我们提供的装备物品Demo的JSON样式进行调整
需注意的是请尽量使用JSON范例中key的顺序避免出现比较奇怪的Bug。
* 如果Cube和已有的模型或者Cube之间有重叠的面重叠部分可能会无法显示或者闪烁可通过调整每个Cube的`inflate`可为负数来避免这种情况。inflate的作用是让Cube向各个方向膨胀一定数值同时不会影响贴图坐标。
* **注意**:对于溺尸,不能使用`SetArmorNew`接口装备自定义盔甲,该行为会造成游戏闪退。

View File

@@ -0,0 +1,64 @@
---
front:
hard: 入门
time: 分钟
---
# 自定义生物蛋
## 概述
属于特殊的自定义物品,在支持自定义物品所有特性的基础上,还具有右键可以生成[自定义生物](../3-自定义生物/01-自定义基础生物.md)的功能。
支持刷怪箱和发射器。
## 注册
1. 与自定义基础物品的注册1-6步相同
2. 在behavior/netease_items_beh的json中设置custom_item_type为egg
3. 添加生物蛋相关的定义,包括一个必填的**netease:egg组件**,组件的参数见[json组件](#json组件)
![avatar](./picture/customitem/egg1.png)
```json
{
"format_version": "1.10",
"minecraft:item": {
"description": {
"identifier": "customitems:test_egg",
"register_to_create_menu":true,
"custom_item_type": "egg"
},
"components": {
"minecraft:max_stack_size":64,
"netease:egg":{
"entity":"minecraft:mod{customitems:chicken}"
}
}
}
}
```
## JSON组件
### 网易components
* netease:egg
| 键 | 类型 | 默认值 | 解释 |
| ------ | ---- | ------ | -------------------- |
| entity | str | | 生成生物的identifier |
## demo解释
[CustomItemsMod](../../13-模组SDK编程/60-Demo示例.md#CustomItemsMod)中定义了一个自定义生物蛋:
* customitems:test_egg
右键时会生成微软自定义生物"customitems:chicken"

View File

@@ -0,0 +1,126 @@
---
front:
hard: 入门
time: 分钟
---
# 自定义蓄力物品
## 概述
属于特殊的自定义物品,在支持自定义物品所有特性的基础上,还具有右键按下开始蓄力过程,右键抬起触发释放事件。
与弓minecraft:bow、弩minecraft:crossbow类似在物品使用过程支持蓄力操作支持序列帧动画。
详细的物品定义以及物品使用详见示例mod [CustomRangedWeaponMod](../../13-模组SDK编程/60-Demo示例.md#CustomRangedWeaponMod)。
## 注册
1. 与自定义物品的注册1-6步相同
2. 在behavior创建的json文件
```python
{
"format_version": "1.10",
"minecraft:item": {
"description": {
"identifier": "customitems:ranged_weapon",
"register_to_create_menu":true,
"custom_item_type": "ranged_weapon"
},
"components": {
"minecraft:use_duration": 72000
}
}
}
```
*注意use_duration的大小应该与序列帧帧数相匹配*
3. 自定义序列帧
3.1 将物品的序列帧贴图放到`textures\items`中,如:
![avatar](./picture/customitem/custom_ranged_weapon01.png)
3.2 在textures/item_texture.json中增加序列帧图片的定义
```python
"customitems:ranged_weapon": {
"textures": [
"textures/items/customitems_ranged_weapon_0",
"textures/items/customitems_ranged_weapon_1",
"textures/items/customitems_ranged_weapon_2"
]
}
```
3.3 在netease_items_res中增加json文件
```python
{
"format_version": "1.10",
"minecraft:item": {
"description": {
"identifier": "customrangedweapon:bow",
"category": "Equipment"
},
"components": {
"minecraft:icon": "customrangedweapon:bow",
"minecraft:use_animation": "bow",
"netease:frame_animation": {
"frame_count": 3,
"texture_name": "customrangedweapon:bow_frame",
"animate_in_toolbar": true
}
}
}
}
```
## 网易components
* netease:frame_animation
| 键 | 类型 | 默认值 | 解释 |
| ------------------ | ---- | ------ | ----------------------------------- |
| frame_count | int | 1 | 序列帧帧数 |
| texture_name | str | | item_texture.json中定义的序列帧数组 |
| animate_in_toolbar | bool | true | 在物品栏中是否支持动画 |
* netease:render_offsets
手中物品渲染配置
右手坐标系x是拇指方向y是食指方向z是中指方向
| 键 | 类型 | 默认值 | 解释 |
| -------------------------- | ----- | ------------- | ------------ |
| controller_position_adjust | array | [0.0,0.0,0.0] | 物品位置调整 |
| controller_rotation_adjust | array | [0.0,0.0,0.0] | 物品旋转调整 |
| controller_scale | float | 1.0 | 物品大小调整 |
## 物品耐久
自定义蓄力物品可以通过minecraft:max_damage组件设置其耐久值
然后在在物品使用事件ItemReleaseUsingServerEvent中获取/设置耐久值
```python
slotIndex = 0
comp = serverApi.CreateComponent(playerId,'Minecraft','item')
val = comp.GetInvItemDurability(slotIndex)
if val == 0:
# 销毁物品
comp.SetInvItemNum(slotIndex, 0)
else:
comp.SetInvItemDurability(slotIndex, val - 1)
```

View File

@@ -0,0 +1,63 @@
---
front:
hard: 入门
time: 分钟
---
# 自定义物品贴图使用序列帧动画
## 概述
开发者按规范制作资源及json配置可实现物品贴图使用序列帧动画。
物品贴图使用序列帧的修改分为两部分一种是手持及地图掉落贴图修改另一种是UI界面上的修改。实际中一般同时修改但也允许开发者单独配置其中一项。
## 物品手持及地图掉落贴图使用序列帧动画
实现分为两个步骤:
1) 资源制作:
应以由上往下平铺的方式,贴图高度必须为宽度的整数倍,第一帧在最上方位置,最后一帧在最下方位置, 贴图分辨率越大性能消耗越大建议贴图宽度不要超过32:
<img src="./picture/customitem/frames/frames1.png" style="zoom: 100%;" />
2) 添加json组件
修改netease_items_res相应自定义物品的json添加netease:frame_anim_in_scene组件这里以自定义的斧头为例:
<img src="./picture/customitem/frames/frames2.png" style="zoom: 80%;" />
| 键 | 类型 | 解释 |
| ------------------- | -------- | ---------------------------------------------------- |
| ticks_per_frame | int |代表多少帧切换一次贴图按1秒20帧算设置20的话即为1秒切换一帧贴图 |
| texture_path | str |序列帧贴图的路径 |
最终效果:
<img src="./picture/customitem/frames/frames_scene.gif" style="zoom: 100%;" />
## 物品UI界面上贴图使用序列帧动画
实现分为三个步骤:
1) 在图集中声明资源。因为UI界面显示的贴图取自于图集所以需要先声明。
在textures/item_texture.json声明我们上述使用的这张序列帧贴图hatchet_frames
<img src="./picture/customitem/frames/frames_ui1.png" style="zoom: 100%;" />
2) 修改netease_items_res自定义物品的json修改minecraft:icon字段为我们上面图集中声明的字段
<img src="./picture/customitem/frames/frames_ui2.png" style="zoom: 100%;" />
3) 新建textures/flipbook_textures_items.json(类似于微软原版的flipbook_textures.json文件),并在其中进行配置:
<img src="./picture/customitem/frames/frames_ui3.png" style="zoom: 100%;" />
| 键 | 类型 | 默认值 | 解释 |
| ----------------- | ---- | ---------- | -------- |
| flipbook_texture | str | | 序列帧资源的路径 |
| atlas_tile | str | |在图集中声明的名称 |
| ticks_per_frame | int | | 代表多少帧切换一次贴图按1秒20帧算设置20的话即为1秒切换一帧贴图 |
| blend_frames | bool | True | 切换贴图的时候是否混合上一帧 |
最终效果:
<img src="./picture/customitem/frames/frames_ui.gif" style="zoom: 140%;" />

View File

@@ -0,0 +1,59 @@
---
front:
hard: 入门
time: 分钟
---
# 自定义桶
## 概述
自定义桶主要用于装载自定义流体,一般需配合自定义流体使用,使用后可以倒出指定的流体方块
## custom_item_type设置
- 自定义桶的**custom_item_type**需要设为**bucket**
## 注册
1. 与自定义基础物品的注册1-6步相同
2. 在behavior/netease_items_beh的json中设置custom_item_type为**bucket**
3. 添加自定义桶相关的定义,包括一个必填的**netease:bucket组件**,组件的参数见[json组件](#json组件)。注自定义桶的最大堆叠数为1无法通过max_stack_size进行修改
```json
{
"format_version": "1.10",
"minecraft:item": {
"description": {
"identifier": "customBucket:green_bucket",
"custom_item_type": "bucket",
"register_to_create_menu": true
},
"components": {
"netease:bucket": {
"fill_liquid": "flowing_green_water"
}
}
},
}
```
## JSON组件
### 网易components
* netease:bucket
| 键 | 类型 | 默认值 | 解释 |
| ----------- | ------ | --------------- | ---------------------------- |
| fill_liquid | string | "flowing_water" | 可选使用时倒出的流体方块id |
**注:该流体方块需为自定义流体的动态流体**

View File

@@ -0,0 +1,334 @@
---
front:
hard: 入门
time: 分钟
---
# 自定义盾牌
## 概述
属于特殊的自定义物品,在支持自定义物品所有特性的基础上,还具有盾牌相关的功能,支持自定义盾牌的动画和模型。
## 注册
1. 与自定义基础物品的注册1-6步相同
**物品名即identifier冒号后的部分不可以叫shield否则会和原版盾牌冲突**
1. 如果resource中的icon填写为空则盾牌的物品形态会渲染为模型使用default模型与default贴图渲染不会播放动作
如果配置了贴图icon则物品形态渲染为贴图
```python
{
"minecraft:item": {
"description": {
"identifier": "customshield:test_shield"
},
"components": {
"minecraft:icon": ""
}
},
"format_version": "1.10"
}
```
1. 在behavior/netease_items_beh的json中添加武器/工具相关的定义,包括:
custom_item_type为shield
一个netease:shield组件必填。组件的参数见[json组件](#json组件)
```python
{
"minecraft:item": {
"description": {
"category": "Custom",
"identifier": "customshield:test_shield",
"custom_item_type": "shield",
"register_to_create_menu": true
},
"components": {
"netease:shield":{
"defence_damage_source_list":["drowning"],//防御的伤害类型,如果不配或者配置为空则走原生的格挡伤害逻辑
"undefence_damage_source_list":["entity_attack"],//不防御的伤害类型,和上面这个不能有相同元素,如果有相同则优先防御该伤害类型
"is_consume_damage":false//是否消耗耐久度false则不消耗
}
}
},
"format_version": "1.10"
}
```
1. 在resource/netease_item_res中增加json文件
```python
{
"minecraft:item": {
"description": {
"identifier": "customshield:test_shield"
},
"components": {
"minecraft:icon": "shield"
}
},
"format_version": "1.10"
}
```
1. 在resource/attachables中增加json文件
```python
{
"format_version": "1.10.0",
"minecraft:attachable": {
"description": {
"identifier": "customshield:test_shield",
"materials": {
"default": "entity_alphatest",
"enchanted": "entity_alphatest_glint"
},
"textures": {
"default": "textures/entity/test_shield", //模型表面的贴图
"enchanted": "textures/misc/enchanted_item_glint"
},
"geometry": {
"default": "geometry.test_shield" //模型
},
"animations": {
//我们提供了一个通用的animation controller他位于vanilla_netease/animation_controllers/shield.animation_controllers.json
"wield": "controller.animation.default_custom_shield.wield",
//普通的拿盾状态使用了自定义动作
//您也可以复用原版的动作animation.shield.xxx
"wield_main_hand_first_person": "animation.test_shield.wield_main_hand_first_person",
"wield_off_hand_first_person": "animation.test_shield.wield_off_hand_first_person",
"wield_third_person": "animation.test_shield.wield_third_person",
// 如果防御状态的动作复用了原版的动作, 则在举盾状态时盾牌的动画部分将会失效
// "wield_main_hand_first_person_block": "animation.shield.wield_main_hand_first_person_blocking",
// "wield_off_hand_first_person_block": "animation.shield.wield_off_hand_first_person_blocking"
// 为了使举盾状态时盾牌的动画部分也能正常运作,防御状态的动作使用了了自定义的动作
"wield_main_hand_first_person_block": "animation.test_shield.wield_main_hand_first_person_blocking",
"wield_off_hand_first_person_block": "animation.test_shield.wield_off_hand_first_person_blocking"
},
"scripts": {
"animate": [
"wield"
],
// 如果复用了原版动作,则需要有以下数值定义
"initialize": [
"variable.main_hand_first_person_pos_x = 5.3;",
"variable.main_hand_first_person_pos_y = 26.0;",
"variable.main_hand_first_person_pos_z = 0.4;",
"variable.main_hand_first_person_rot_x = 91.0;",
"variable.main_hand_first_person_rot_y = 65.0;",
"variable.main_hand_first_person_rot_z = -43.0;",
"variable.off_hand_first_person_pos_x = -13.5;",
"variable.off_hand_first_person_pos_y = -5.8;",
"variable.off_hand_first_person_pos_z = 5.1;",
"variable.off_hand_first_person_with_bow_pos_z = -25.0;",
"variable.off_hand_first_person_rot_x = 1.0;",
"variable.off_hand_first_person_rot_y = 176.0;",
"variable.off_hand_first_person_rot_z = -2.5;"
],
// 使用弓时的动作修正
"pre_animation": [
"variable.is_using_bow = (query.get_equipped_item_name == 'bow') && (query.main_hand_item_use_duration > 0.0f);"
]
},
"render_controllers": [ "controller.render.item_default" ]
}
}
}
```
1. resource/models/entity中自定义自己的盾牌模型
1. 在resource/animations以及resource/animation_controllers放置自定义的动作与动作控制器如果有的话
1. 在python绑定玩家的动画控制器同时这个给玩家绑定动画控制器的操作是在一个新增事件的回调函数里完成的
事件名称为<a href="../../../../mcdocs/1-ModAPI/事件/世界.html#addplayercreatedclientevent">"AddPlayerCreatedClientEvent"</a>触发此事件时会传递一个playerId参数此事件触发表明该玩家渲染相关资源已加载完毕。
```python
animaComp = clientApi.GetEngineCompFactory().CreateActorRender(playerId)
# 添加激活盾牌时玩家的动作
# animationKey需要唯一
# animationName复用了原版的
# 如果需要用自定义动作也可以参考player.animation.json编写新的动作
animaComp.AddPlayerAnimation("shield_block_main_hand_test", "animation.player.shield_block_main_hand")
animaComp.AddPlayerAnimation("shield_block_off_hand_test", "animation.player.shield_block_off_hand")
# blocking状态并且左手上不是原版盾牌与自定义盾牌并且主手是我们的自定义盾牌
animaComp.AddPlayerAnimationIntoState("root", "third_person", "shield_block_main_hand_test", "query.blocking && query.get_equipped_item_name('off_hand') != 'shield' && !query.get_equipped_item_is_netease_shield('off_hand') && query.get_equipped_item_full_name == 'customshield:test_shield'")
# blocking状态并且左手是我们的自定义盾牌
animaComp.AddPlayerAnimationIntoState("root", "third_person", "shield_block_off_hand_test", "query.blocking && query.get_equipped_item_full_name('off_hand') == 'customshield:test_shield'")
```
## Molang表达式
### 新增molang表达式
- query.get_equipped_item_is_netease_shield
* 描述
获取主手/副手是否自定义盾牌(原版盾牌不算)
* 参数:
| 键 | 类型 | 默认值 | 解释 |
| -------- | ---- | --------- | ---------------------------------------------- |
| hand | str | 'main_hand' | 获取主手(main_hand)/副手('off_hand')是否盾牌 |
* 返回值:
| 说明 |
| ---- |
| 主手(main_hand)/副手('off_hand')是否网易自定义盾牌 |
- query.get_equipped_item_full_name
* 描述
获取主手/副手物品的identifier仅对自定义物品使用原版物品请使用query.get_equipped_item_name。
* 参数:
| 键 | 类型 | 默认值 | 解释 |
| -------- | ---- | --------- | ---------------------------------------------- |
| hand | str | 'main_hand' | 获取主手(main_hand)/副手('off_hand')是否盾牌 |
* 返回值:
| 说明 |
| ---- |
| 主手/副手物品的identifier |
### 原版molang表达式
* c.item_slot
本物品所在的位置
**返回值:**
| 说明 |
| ---- |
| "main_hand":主手;"off_hand":副手;"head":头;"torso":躯干 |
* q.item_slot_to_bone_name
**返回值:**
| 说明 |
| ---- |
| "rightitem":主手;"leftitem":副手; |
## JSON组件
### 网易components
* netease:shield
| 键 | 类型 | 默认值 | 解释 |
| -------- | ---- | --------- | ---------------------------------------------- |
| defence_damage_source_list | list | [] | 防御的伤害类型,如果不配或者配置为空则走原生的格挡伤害逻辑。暂时不支持抵御火烧(fire,fire_tick),自杀(suicide),凋零(wither)的伤害 |
| undefence_damage_source_list | list | [] | 不防御的伤害类型,和上面这个不能有相同元素,如果有相同则优先防御该伤害类型 |
| is_consume_damage | bool | true | 是否消耗耐久度false则不消耗 |
### 原版components
* minecraft:icon
贴图名字
* minecraft:attachable
minecraft:attachable主要健是description下面介绍description的值
| 键 | 类型 | 默认值 | 解释 |
| ---------- | ---- | ------ | ---------------------------------------------------- |
| identifier | str | | 盾牌的id |
| textures | dict | | "default":默认的表面贴图;"enchanted":附魔的表面贴图 |
| geometry | dict | | "default":物品模型 |
| animations | dict | | 物品动画控制器和动画相关,参考示例的解释 |
| scripts | dict | | "animate": [物品动画控制器名],参考示例 |
| materials | dict | | "default":默认的表面贴图;"enchanted":附魔的表面贴图 ,参考示例 |
| render_controllers | list | | 参考示例 |
## 新增的事件和接口
### 事件
* <a href="../../../../mcdocs/1-ModAPI/事件/物品.html#onplayerblockedbyshieldbeforeserverevent">OnPlayerBlockedByShieldBeforeServerEvent</a>
玩家使用盾牌抵挡伤害之前触发
* <a href="../../../../mcdocs/1-ModAPI/事件/物品.html#onplayerblockedbyshieldafterserverevent">OnPlayerBlockedByShieldAfterServerEvent</a>
玩家使用盾牌抵挡伤害之后触发
* <a href="../../../../mcdocs/1-ModAPI/事件/物品.html#onplayeractiveshieldserverevent">OnPlayerActiveShieldServerEvent</a>
玩家激活/取消激活盾牌触发的事件
### 接口
* <a href="../../../../mcdocs/1-ModAPI/接口/物品.html#getitemdefenceangle">GetItemDefenceAngle</a>
服务端接口。获取盾牌物品的抵挡角度范围
* <a href="../../../../mcdocs/1-ModAPI/接口/物品.html#setitemdefenceangle">SetItemDefenceAngle</a>
服务端接口。设置盾牌物品的抵挡角度范围,参考下面抵挡角度范围的解释
* <a href="../../../../mcdocs/1-ModAPI/接口/玩家/行为.html#setusingshield">setUsingShield</a>
客户端接口。设置是否激活盾牌
* <a href="../../../../mcdocs/1-ModAPI/接口/玩家/行为.html#getisblocking">GetIsBlocking</a>
服务端接口。获取玩家是否处于抵挡状态,即盾牌是否被激活
## 新增物品字典参数
**抵挡范围**
| 键 | 类型 | 默认值 | 解释 |
| -------- | ---- | ------ | ---------------------- |
| shieldDefenceAngleLeft | float | -90 | 左边的范围 ,默认是-90取值范围是[-180,180] |
| shieldDefenceAngleRight | float | 90 | 右边的范围,默认是90,取值范围是[-180,180],且shieldDefenceAngleLeft<shieldDefenceAngleRight |
```python
itemDict1 = {
'itemName': 'customshield:test_shield',
'count': 1,
'shieldDefenceAngleLeft':-45,
'shieldDefenceAngleRight':30
}
comp = serverApi.GetEngineCompFactory().CreateItem(playerId)
comp.SpawnItemToPlayerInv(itemDict1, playerId)
```
### 关于抵挡范围的解释
如示意图所示,如果一个抵挡范围是[-45°,30°]的盾牌即shieldDefenceAngleLeft=-45shieldDefenceAngleRight=30那么当玩家举起盾牌时如果伤害来源是一个实体那么在玩家前方-45°到30°范围内的伤害可以抵挡否则不能抵挡。注:原版的盾牌范围是[-90°,90°]
![示意图](./picture/customitem/shield1.png)
## demo解释
[CustomShieldItemMod](../../13-模组SDK编程/60-Demo示例.md#CustomShieldItemMod)中定义了一个自定义盾牌:
* customshield:test_shield
自定义盾牌

View File

@@ -0,0 +1,179 @@
# Json配置自定义3D物品
在本节教程中,我们将围绕**如何自定义一个3D物品**的话题教给大家如何仅使用JSON配置和附加包开发知识来快速创建你的第一个3D物品。
> 温馨提示开始读起这篇指南前我们希望你对《我的世界》基岩版附加包有一定了解有能力撰写JSON数据格式并能够独立阅读《我的世界》开发者官网-开发指南或其他技术引用文档。
**通过这节教程,你将学会:**
•如何将一个现有的微软基岩版格式物品模型变为自定义物品的3D模型。
•校对模型在模型空间的相对位置,以匹配并固定在玩家第三人称的手持位置。
## 背景介绍
模组API 2.0 (基岩版版本 1.17.2开始在资源包定义物品的附着物Attachable文件可以绑定微软原版模型至物品上。
## 制作模型规范
让人型生物(如 溺尸、尸壳、僵尸 或 玩家手持3D物品模型时我们推荐使模型骨骼遵守玩家模型骨骼布局。以便模型能优先保证在玩家手持的情况下能够跟随肢体动画进行正常的幅度摆动。
原版玩家模型使用一条root根骨骼作为基骨骼子骨骼内使用waist骨骼控制上半身rightLeg骨骼和leftLeg骨骼控制左腿和右腿。
基岩版标识符推荐以小写数字母搭配下划线因为在游戏中使用rightLeg或rightleg效果是一致的。以下是玩家模型的骨骼树状图锚点即模型格式内的pivot。
```
-root锚点[0, 0, 0]
--waist锚点[0, 12, 0]
---jacket用于persona
---cape披风
---body锚点[0, 24, 0]
----leftArm锚点[5, 22, 0]
-----leftSleeve用于persona
-----leftItem锚点[6, 15, 1]
----rightArm锚点[-5, 22, 0]
-----rightSleeve用于persona
-----rightItem锚点[-6, 15, 1]
--leftLeg锚点[-1.9, 12, 0]
---leftPants用于persona
--rightLeg锚点[1.9, 12, 0]
---rightPants用于persona
```
## 校正模型在空间下对应第三人称手持的位置
先在模型工程内点击 **【文件】** - **【保存项目】** 。
![img](./images/1_0.png)
新建一个人型模型工程,并根据骨骼树状图拉出人型模型格式。
![img](./images/2_0.png)
选择 **【文件】** - **【导入】** 导入前面保存的3D模型项目。
![img](./images/3_0.png)
此时可以看到物品模型的贴图完全丢失。但不用担心它默认援引了人型模型的材质只要它的UV在前面的工程内已经分好这边就无需多管。
![img](./images/4_0.png)
将物品模型基骨骼放入 **【root】** - **【waist】** - **【body】** - **【rightArm】** - **【rightItem】** 骨骼下。并删除除右手方块以外的其他模型方块体。
![img](./images/5_0.png)
继续调整3D物品模型直至调整到一个满意的手持角度。此时删去右手模型方块体。这样一个有效的基础第三人称右手手持模型就做好了。
![img](./images/6_0.png)
## 将3D模型实装
在附加包的自定义资源包内新建attachables文件夹新建一个JSON文件。
![img](./images/7_0.png)
- 渲染控制器:默认可填 **"controller.render.item_default"** ,这是原版提供的一个物品渲染控制器。
- 材质:填写 **"controller.render.item_default"** 后,必须新增 **"default"** 和 **"enchanted"** 键,其中前者对应物品模型默认的材质,后者对应附魔时物品携带的材质。 **"default"** 无特殊情况默认填写 **"entity_alphatest"** "enchanted"默认填写 **"entity_alphatest_glint"** 。
- 贴图纹理:填写 **"controller.render.item_default"** 后,必须 **"default"** 和 **"enchanted"** 键, 其中前者对应自定义物品模型贴图,后者对应物品附魔时的附魔效果贴图(一般默认填写 **""textures/misc/enchanted_item_glint"** )。
- 动画附着物与实体一样可以添加动画和动画控制器也可以添加粒子但目前粒子默认会以实体脚底坐标发射粒子暂时不能在粒子挂点locator上发射。
- 几何体附着物的模型以自定义3D模型的geometry标识符为准。
- 物品标识符:与行为包的自定义物品标识符一致即可。在这里预先填写一个自定义物品标识符 **"design:custom_golden_sword"** ,稍后将用一个自定义物品行为与之呼应。
```json
{
"format_version":"1.10.0",
"minecraft:attachable":{
"description":{
"identifier":"design:custom_golden_sword",
"materials":{
"default":"entity_alphatest",
"enchanted":"entity_alphatest_glint"
},
"textures":{
"default":"textures/entity/custom_golden_sword",
"enchanted":"textures/misc/enchanted_item_glint"
},
"geometry":{
"default":"geometry.custom_golden_sword"
},
"render_controllers":[
"controller.render.item_default"
]
}
}
}
```
接着在附加包的自定义行为包里创建netease_items_beh文件夹并创建子文件custom_golden_sword.json。有关更多自定义物品行为请查看此链接[自定义武器及工具](../2-自定义武器及工具.md)
```json
{
"format_version":"1.10",
"minecraft:item":{
"description":{
"identifier":"design:custom_golden_sword",
"register_to_create_menu":true,
"custom_item_type":"weapon",
"category":"Equipment"
},
"components":{
"minecraft:max_damage":700,
"minecraft:max_stack_size":1,
"netease:weapon":{
"type":"sword",
"level":0,
"attack_damage":7,
"enchantment":10
}
}
}
}
```
我们还可以继续在资源包内的netease_items_res文件夹为自定义物品添上2D的物品图标。有关更多自定义物品的基础内容请查看此链接[自定义基础物品](../1-自定义基础物品.md)
```json
{
"format_version":"1.10",
"minecraft:item":{
"description":{
"identifier":"design:custom_golden_sword",
"category":"Equipment"
},
"components":{
"minecraft:icon":"design:custom_golden_sword",
"minecraft:render_offsets":"tools"
}
}
}
```
现在你就可以在游戏加载附加包完成后使用这个自定义3D物品了
![img](./images/8_0.png)
可以使用快速格式化Json等在线工具定位到可能在撰写过程中出现的错误语法或结构。

View File

@@ -0,0 +1,194 @@
# 自定义3D物品第一人称动画
在上节教程中我们学会了如何使用微软的Attachable功能实现自定义3D物品并使用社区工具Blockbench校正模型在第三人称的位置最后在游戏内实装上一把自定义3D金剑。
**本节课程Demo[戳这里下载](https://g79.gdl.netease.com/CustomWeapon3D.zip)**
**目前我们已经掌握:**
- 如何将一个现有的微软基岩版格式物品模型变为自定义物品的3D模型。第一节内容
- 校对模型在模型空间的相对位置,以匹配并固定在玩家第三人称的手持位置。(第一节内容)✔
- 借助《我的世界》基岩版电脑开发版,使用开发者工具制作模型的第一人称动画。(本节内容)❌
所以接下来将带领大家借助《我的世界》基岩版电脑开发版,使用开发者工具制作模型的第一人称动画。
![img](./images/0_0.png)
我的世界基岩版中,第一人称和第三人称的摄像机存在空间角度区别。因此需要单独为物品模型设计第一人称的手持动画。(下图为第三人称手持正确,但在第一人称显示意外的图例)
<img src="./images/0_1.png" alt="image-20220608133313000" style="zoom:150%;" />
启动Blockbench选择之前预制好的自定义3D物品模型选择【动画模式】-【添加动画创建一个用于控制第一人称手持角度位置的动画一个用于控制第一人称手持3D物品缩放的动画。我的世界基岩版动画可以叠加叠加后会对同一时刻下对位移、缩放、旋转的值进行加总
![image-20220608133346326](./images/0_2.png)
需要将动画设置为循环播放并将动画时长长度设置为0。
在对应的第一人称手持缩放动画(以下简称"first_scale"插入一帧缩放关键帧任意填入关键帧的缩放参数这里我们填写XYZ为1.1并保存。
![image-20220608133438507](./images/0_3.png)
在对应的第一人称手持位置角度动画以下简称“first_hold”插入一帧关键帧同时调整位置和旋转值这里我们任意填写XYZ的位置和旋转值为3并保存。
![image-20220608133501905](./images/0_4.png)
接着回到附着物的定义文件,将前面的"first_hold"和"first_scale"动画进行挂载并加入进scripts/animate根动画中。
```json
{
"format_version": "1.10.0",
"minecraft:attachable": {
"description": {
"identifier": "design:custom_golden_sword",
"materials": {
"default": "entity_alphatest",
"enchanted": "entity_alphatest_glint"
},
"textures": {
"default": "textures/entity/custom_golden_sword",
"enchanted": "textures/misc/enchanted_item_glint"
},
"animations": {
"first_hold": "animation.custom_golden_sword.first_hold", //挂载自定义动画“第一人称手持”
"first_scale": "animation.custom_golden_sword.first_scale" //挂载自定义动画“第一人称剑缩放”
},
"geometry": {
"default": "geometry.custom_golden_sword"
},
"render_controllers": [ "controller.render.item_default" ]
}
}
}
```
再次回到《我的世界》电脑版基岩开发版在游戏界面中按F3按键3次或14次呼出开发者控制台。接着按下F11键呼出鼠标。
![image-20220608133527259](./images/0_11.png)
打开Animation动画列表点击Open Editor打开编辑器
![image-20220608133539709](./images/0_10.png)
默认情况下,编辑器窗口会很小。此时点击右下角的伸缩区域进行拉伸。
![image-20220608133552800](./images/0_9.png)
拉到足够看清楚的大小后,直接往下拉到最底,这里展示着附着物挂载的动画状态。
![image-20220608133606005](./images/0_8.png)
打开“first_hold”的Details动画细节再点击Bone Animation骨骼动画就可以看到前面用Blockbench制作的动画的各项值并且支持直接在编辑器内动态修改最后直观的体现在游戏内。
<img src="./images/0_7.png" alt="image-20220608133619036" style="zoom:150%;" />
这里我们对"first_hold"的position设置值为X为-8Y为5Z为-6。再对rotation设置X为-45Y为15Z为-135。对"first_scale"的scale设置为0.35。
![image-20220608133639677](./images/0_6.png)
再按F3按键直到界面消失就可以在游戏内看到基本合格的第一人称自定义3D物品手持效果了。
![image-20220608133650381](./images/0_5.png)
请格外注意在这里调整的动画并不会最终保存进动画Json文件里因此我们还需要将值背下来并写入动画文件中。
```json
{
"format_version": "1.8.0",
"animations": {
"animation.custom_golden_sword.first_hold": {
"loop": true,
"bones": {
"sword": {
"rotation": [-45, 15, 135],
"position": [-8, 5, -6]
}
}
},
"animation.custom_golden_sword.first_scale": {
"loop": true,
"bones": {
"sword": {
"scale": 0.35
}
}
}
}
}
```
最后,对"first_hold"和"first_scale"的加载模式添加条件,使用"c.is_first_person"来判定动画是否运行在第一人称模式下,是则运行。
```json
{
"format_version": "1.10.0",
"minecraft:attachable": {
"description": {
"identifier": "design:custom_golden_sword",
"materials": {
"default": "entity_alphatest",
"enchanted": "entity_alphatest_glint"
},
"textures": {
"default": "textures/entity/custom_golden_sword",
"enchanted": "textures/misc/enchanted_item_glint"
},
"animations": {
"first_hold": "animation.custom_golden_sword.first_hold", //挂载自定义动画“第一人称手持”
"first_scale": "animation.custom_golden_sword.first_scale" //挂载自定义动画“第一人称剑缩放”
},
"scripts": {
"animate": [ // 挂载动画至根动画中
{
"first_hold": "c.is_first_person" // 保证“第一人称手持”动画只在第一人称下显示
},
{
"first_scale": "c.is_first_person"// 保证“第一人称剑缩放”动画只在第一人称下显示
}
]
},
"geometry": {
"default": "geometry.custom_golden_sword"
},
"render_controllers": [ "controller.render.item_default" ]
}
}
}
```
c是context的缩写就像可以使用q来代表query一样query是查询某个活动对象特定状态的查询函数。c.is_first_person将返回附着物是否处于第一人称视角下在前面加上!运算符即可代表是否处于非第一人称视角则可能是第三人称前视角或第三人称后视角下。其他的已知context函数还有
- **context.owning_entity**返回附着物穿戴的实体可以使用Molang的->运算符获取该实体身上的属性假设已知自定义3D物品由玩家手持则可以使用"c.owning_entity -> q.is_sneaking"返回玩家是否此时手持该附着物并下蹲。
- **context.item_slot**附着物手持的位置,返回"main_hand"(主手)或者"off_hand"(副手)。

View File

@@ -0,0 +1,551 @@
# 自定义3D武器第一人称攻击效果
在前两节自定义3D物品教程结束后我们学会了如何使用微软的Attachable功能完全实现自定义3D物品功能。
许多武器动作模组在《我的世界》模组体验上占有非常高的地位自定义3D物品可以在武器方面着重凸显组件的特点。本节课将会带领开发者学习如何使用自定义金剑制作独特的攻击形式配合模组SDK让作品特色更上一层楼。
**假设你看过前两节自定义3D物品教程你应该已经学会**
- 如何将一个现有的微软基岩版格式物品模型变为自定义物品的3D模型。✔
- 校对模型在模型空间的相对位置,以匹配并固定在玩家第三人称的手持位置。✔
- 借助《我的世界》基岩版电脑开发版使用开发者工具制作3D模型的第一人称动画。✔
**在本节课程你将学会:**
- 使用Blockbench为附着物设计手臂模型并重新调整金剑建模。
- 使用Blockbench和基岩版动画叠加效果制作第一人称手持金剑攻击动画。
- 用额外的渲染控制器挂载手臂模型至附着物。
- 借助模组SDK和自定义Query节点完成第一人称手持金剑攻击效果。
**本节课程的[Demo请戳这里下载](https://g79.gdl.netease.com/CustomWeaponAttackFP.zip)**
![动画](./images/attack0_0.gif)
## 设计手臂模型并重新调整金剑建模
打开前一节[自定义3D物品的Demo](https://g79.gdl.netease.com/CustomWeapon3D.zip),目前金剑的骨骼会按照下列排序:
```
-root玩家根骨骼
--waist玩家上半身骨骼
---body玩家身体骨骼
----rightArm玩家右手骨骼
-----rightItem玩家右手手持物品骨骼
------sword金剑骨骼
```
![image-20220612014504234](./images/attack0_0.png)
**我们设法在玩家手持金剑时,会在第一人称下显示两个手臂紧握着金剑。则此时两只手臂是跟随着金剑模型,而不牵扯到玩家模型本身。这样的设计意图给予了一些新的启发与可预料到的挑战。**
![任务计划甘特图 (2)](./images/attack0_1.png)
首先金剑的模型直接挂载在右手手持物品骨骼下。为了在之后的动画可以让金剑跟随着挥舞的手臂需要用arm骨骼取代sword骨骼用来存放握住金剑把手的手臂并将原先sword骨骼的体块纳入一个新的骨骼组item并将它作为arm骨骼的子骨骼。 **为之后做第一人称动画提供便利** 直接调整arm骨骼的动画参数即可金剑会跟随arm旋转、偏移或缩放。
![任务计划甘特图 (2)](./images/attack0_2.png)
除此之外我们并不希望让arm骨骼会出现在第三人称视角。 **这是因为当玩家手持物品时,玩家模型的手臂会自动消失。但切换回第三人称将重新出现。玩家模型本身的手臂会与金剑的新双臂同时出现,影响观感。**
![任务计划甘特图 (2)](./images/attack0_3.png)
**最终得出的初步方案里我们需要使用一个新的渲染控制器挂接arm骨骼至附着物上。一个附着物是可以满足挂载多个渲染控制器同时在骨骼层级不变的情况下可以在默认渲染控制器下挂载剑模型在arm骨骼渲染控制器下挂载arm模型。接着设置渲染控制器只会在第一人称下显示。**
**最终剑模型的骨骼层级如下Tips右键对应骨骼点击Resolve Group可在不移动子骨骼的前提下展开父骨骼**
```
-root玩家根骨骼
--waist玩家上半身骨骼
---body玩家身体骨骼
----rightArm玩家右手骨骼
-----rightItem玩家右手手持物品骨骼
------arm金剑双臂骨骼-不放置体块)
------item金剑骨骼
```
![image-20220612025314595](./images/attack0_4.png)
**最终剑手臂模型的骨骼层级如下Tips将原版资源包/models/mobs.json内的humanoid模型的两只手臂直接复制到这里可以继承原版steve模型的UV展开并快速对手臂进行建模**
```
-root玩家根骨骼
--waist玩家上半身骨骼
---body玩家身体骨骼
----rightArm玩家右手骨骼
-----rightItem玩家右手手持物品骨骼
------arm金剑双臂骨骼
------item金剑骨骼-不放置体块)
```
![image-20220612025523344](./images/attack0_5.png)
## 制作第一人称手持金剑攻击动画
切换至动画模式新建用以表现第一人称攻击动画sword_attack。
![image-20220612094740384](./images/attack0_6.png)
选中sword_attack动画同时勾选first_hold动画在手持第一人称动画生效的同时以此视角基础设计挥舞动画。
![image-20220612095303937](./images/attack0_7.png)
这里设计了一个0.75秒的攻击动画,在游戏里将实际从右往左挥舞金剑。其他形式可自行创作。
![动画1](./images/attack0_1.gif)
## 挂载手臂模型至附着物
在资源包/render_controllers文件夹下新建一个自定义的剑渲染控制器用以显示手臂模型。
```json
{
"format_version": "1.8.0",
"render_controllers": {
"controller.render.sword_controller": {
"geometry": "Geometry.arm",
"materials": [
{
"*": "Material.default"
}
],
"textures": [
"Texture.arm"
]
}
}
}
```
接着将手臂模型、手臂贴图、渲染控制器和第一人称动画挂载于附着物上。
```json
{
"format_version": "1.10.0",
"minecraft:attachable": {
"description": {
"identifier": "design:custom_golden_sword",
"materials": {
"default": "entity_alphatest",
"enchanted": "entity_alphatest_glint"
},
"textures": {
"default": "textures/entity/custom_golden_sword",
"enchanted": "textures/misc/enchanted_item_glint",
"arm": "textures/entity/steve" // arm键对应渲染控制器内的Texture.arm贴图路径则是原版内置的steve粗手臂贴图
},
"animations": {
"first_hold": "animation.custom_golden_sword.first_hold",
"first_scale": "animation.custom_golden_sword.first_scale",
"attack_rotation_sword": "animation.custom_golden_sword.sword_attack" // 自定义第一人称剑攻击动画
},
"scripts": {
"animate": [
{
"first_hold": "c.is_first_person"
},
{
"first_scale": "c.is_first_person"
}
]
},
"geometry": {
"default": "geometry.custom_golden_sword",
"arm": "geometry.custom_golden_sword_arm" // arm键对应渲染控制器内的Geometry.arm
},
"render_controllers": [ "controller.render.item_default",
{
"controller.render.sword_controller": "c.is_first_person" // 只有在第一人称下显示手臂模型和手臂贴图
}
]
}
}
}
```
进入游戏后,可以看到拿出金剑时,显示了两支手臂握住剑柄的效果。
![image-20220612102522220](./images/attack0_9.png)
## 完成第一人称手持金剑攻击效果
使用自定义Query节点接口可以最大限度地减少组件冲突的影响。自定义Query接口可以在客户端线程上自定义新的Query查询函数可以内置在动画或动画控制器中作为条件改变动画和动画控制器的效果。
![任务计划甘特图 (2)](./images/attack0_10.png)
在行为包内创建Python脚本目录取名CustomSwordScripts并创建服务端系统和客户端系统。有关模组SDK目录创建和系统注册的模块可以查看[此官网教程链接](https://g.126.fm/00FchC4)。
```
-CustomSwordScripts文件夹
--client.py客户端Python文件
---AnimationClient客户端类
--modMain.pymod入口
--server.py服务端Python文件
---AnimationServer服务端类
```
```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="CustomSwordMod", version="0.1")
class CustomSwordMod(object):
def __init__(self):
pass
@Mod.InitClient()
def init_client(self):
clientApi.RegisterSystem("CustomSwordMod", "AnimationClient",
"CustomSwordScripts.client.AnimationClient")
@Mod.InitServer()
def init_server(self):
serverApi.RegisterSystem("CustomSwordMod", "AnimationServer",
"CustomSwordScripts.server.AnimationServer")
@Mod.DestroyClient()
def destroy_client(self):
pass
@Mod.DestroyServer()
def destroy_server(self):
pass
```
在客户端类的\__init__函数内监听OnLocalPlayerStopLoading事件当玩家客户端加载完毕后调用Query节点接口并注册query.mod.sword_attack。
```python
# -*- coding: UTF-8 -*-
import mod.client.extraClientApi as clientApi
import time
compFactory = clientApi.GetEngineCompFactory()
ClientSystem = clientApi.GetClientSystemCls()
class AnimationClient(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,
'OnLocalPlayerStopLoading', self, self.client_init) # 监听事件
def client_init(self, event):
player_id = clientApi.GetLocalPlayerId()
query_comp = compFactory.CreateQueryVariable(clientApi.GetLevelId()) # 创建基于世界的query组件
query_comp.Register('query.mod.sword_attack_time', 0.0) # 注册自定义Query节点必须以query.mod开头
query_comp = compFactory.CreateQueryVariable(player_id)
query_comp.Set('query.mod.sword_attack_time', 0.0) # 对本地玩家再次设置此节点的默认值
```
新增第一人称剑攻击动画控制器controller.animation.sword.first_attack。
```json
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.sword.first_attack": {
"initial_state" : "default",
"states" : {
"default": {
"transitions" : [
{
"first_person" : "c.is_first_person"
}
]
},
"first_person": {
"transitions" : [
{
"default" : "!c.is_first_person" // 若为非第一人称视角,则返回至默认状态
},
{
"first_person_attack": "query.mod.sword_attack_time" // 当query.mod.sword_attack_time为真时切换至播放攻击动画的状态
}
]
},
"first_person_attack": {
"animations": [
"attack_rotation_sword" // 攻击动画
],
"transitions" : [
{
"default" : "!c.is_first_person" // 若为非第一人称视角,则返回至默认状态
},
{
"first_person": "!query.mod.sword_attack_time"// 当query.mod.sword_attack_time为假时切换至默认状态
}
]
}
}
}
}
}
```
将第一人称剑攻击动画控制器置入附着物定义文件内。
```json
{
"format_version": "1.10.0",
"minecraft:attachable": {
"description": {
//...
"animations": {
"first_hold": "animation.custom_golden_sword.first_hold",
"first_scale": "animation.custom_golden_sword.first_scale",
"attack_rotation_sword": "animation.custom_golden_sword.sword_attack",
"controller.attack": "controller.animation.sword.first_attack"
},
"scripts": {
"animate": [
"controller.attack",
{
"first_hold": "c.is_first_person"
},
{
"first_scale": "c.is_first_person"
}
]
}
// ...
}
}
}
```
紧接着监听LeftClickBeforeClientEvent与TapBeforeClientEvent分别对应在电脑启动器上使用左键与在手机上点击屏幕的事件事件回调可指向同一个函数地址代码解析如下
1. 在客户端系统上贮存点击的时间戳。
2. 在事件响应时计算当前时间扣去过去时间戳是否大于攻击动画的时间我们的自定义攻击动画时间为0.75秒因此判断结果需要大于0.75。
3. 满足条件2时继续判断是否满足是金剑是则重新计算时间戳。并执行函数send_attacked_packet。
4. send_attacked_packet函数内的业务逻辑主要是创建定时器任务用来发送数据至服务端再依次通过服务端向其他玩家客户端通知本地玩家播放挥舞动画。并在本地客户端设置query.mod.sword_attack的值帮助动画控制器播放动画效果。
```python
# -*- coding: UTF-8 -*-
import mod.client.extraClientApi as clientApi
import time
compFactory = clientApi.GetEngineCompFactory()
ClientSystem = clientApi.GetClientSystemCls()
class AnimationClient(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,
'OnLocalPlayerStopLoading', self, self.client_init)
self.ListenForEvent(namespace, system_name,
'LeftClickBeforeClientEvent', self, self.attack_click)
self.ListenForEvent(namespace, system_name,
'TapBeforeClientEvent', self, self.attack_click)
self.click_cooldown = time.time()
def client_init(self, event):
# ...
pass
def attack_click(self, event):
current_time = time.time()
carried_item = compFactory.CreateItem(clientApi.GetLocalPlayerId()).GetCarriedItem()
if current_time - self.click_cooldown > 0.75 and carried_item and carried_item['newItemName'] == 'design:custom_golden_sword':
self.click_cooldown = time.time()
self.send_attacked_packet(0.0, {# 动画开始的时间点
'playerId': clientApi.GetLocalPlayerId(),
'type': 'start'
})
self.send_attacked_packet(0.16, {# 动画打击出伤害的时间点,具体以自定义动画的设计形式为准
'playerId': clientApi.GetLocalPlayerId(),
'type': 'will_hit'
})
self.send_attacked_packet(0.75, { # 动画结束时间点
'playerId': clientApi.GetLocalPlayerId(),
'type': 'end'
})
else:
# 正在播放动画时,若重复点击,则取消点击
if carried_item and carried_item['newItemName'] == 'design:custom_golden_sword':
event['cancel'] = True
def send_attacked_packet(self, _time, data):
game_comp = compFactory.CreateGame(clientApi.GetLevelId())
game_comp.AddTimer(
_time,
self.NotifyToServer, # 发送数据至服务端,用来告知其他客户端当前玩家的播放动画状态,以便在其他客户端上也能看到玩家播放动画的效果
'AttackedPacket',
data
)
game_comp.AddTimer(
_time,
compFactory.CreateQueryVariable(clientApi.GetLocalPlayerId()).Set, # 设置本地玩家客户端播放动画所需的query节点的值
'query.mod.sword_attack_time',
1.0 if data['type'] == 'start' or data['type'] == 'will_hit' else 0.0
)
```
在服务端系统代码上监听来自客户端的事件AttackedPacket。代码解析如下
1. 若玩家点击左键播放动画的自定义客户端事件判断状态为will_hit动画打击出伤害获取周围的实体执行扇形攻击函数sector_attack。
2. sector_attack函数内计算了某个实体是否在另一个实体的扇形视角范围内。通过计算攻击者和受害者的坐标距离与攻击者和受害者的视角差度判断视角差度在一个固定的扇形角度时则创建伤害组件对受害者创造伤害。
3. 使用GetRelevantPlayer接口获取周围的玩家ID并使用NotifyToMultiClients接口向其他玩家传送事件AttackSync告知此名玩家开始播放动画其他玩家需在自己的本地客户端同样设置该名玩家的query.mod.sword_attack为真以实现动画效果同步。
```python
# -*- coding: UTF-8 -*-
import mod.server.extraServerApi as serverApi
import math
from mod.common.utils.mcmath import Vector3
compFactory = serverApi.GetEngineCompFactory()
ServerSystem = serverApi.GetServerSystemCls()
class AnimationServer(ServerSystem):
def __init__(self, namespace, system_name):
ServerSystem.__init__(self, namespace, system_name)
namespace = serverApi.GetEngineNamespace()
system_name = serverApi.GetEngineSystemName()
self.ListenForEvent('CustomSwordMod', 'AnimationClient',
'AttackedPacket', self, self.attacked)
def attacked(self, event):
_type = event['type']
player_id = event['playerId']
if _type == 'will_hit':
entities = compFactory.CreateGame(player_id).GetEntitiesAround(player_id, 6,
{
'any_of': {
'test': 'is_family',
'subject': 'other',
'operator': 'not',
'value': 'instabuild'
}
})
for entity in entities:
self.sector_attack(player_id, entity, 65.0, 6.0, 7, attacker_id=player_id)
elif _type == 'start':
players = compFactory.CreatePlayer(player_id).GetRelevantPlayer([player_id])
self.NotifyToMultiClients(players, 'AttackSync', {
'playerId': player_id,
'value': 1.0
})
else:
players = compFactory.CreatePlayer(player_id).GetRelevantPlayer([player_id])
self.NotifyToMultiClients(players, 'AttackSync', {
'playerId': player_id,
'value': 0.0
})
def sector_attack(self, attacker, victim, between_angle=0.0, radius=0.0, damage=0, cause=serverApi.GetMinecraftEnum().ActorDamageCause.EntityAttack, attacker_id=None, child_attacker_id=None, knock=True):
attacker_foot_pos = compFactory.CreatePos(attacker).GetFootPos()
victim_foot_pos = compFactory.CreatePos(victim).GetFootPos()
delta = Vector3(victim_foot_pos) - Vector3(attacker_foot_pos)
forward_vector = serverApi.GetDirFromRot(compFactory.CreateRot(attacker).GetRot())
angle = math.degrees(math.acos(Vector3.Dot(delta.Normalized(), Vector3(forward_vector).Normalized())))
if angle < between_angle and delta.Length() < radius:
compFactory.CreateHurt(victim).Hurt(damage, cause, attacker_id, child_attacker_id, knock)
```
最后在客户端上监听来自自定义服务端的AttackSync事件。在本地客户端设置其他玩家播放动画的进度。
```python
# -*- coding: UTF-8 -*-
import mod.client.extraClientApi as clientApi
import time
compFactory = clientApi.GetEngineCompFactory()
ClientSystem = clientApi.GetClientSystemCls()
class AnimationClient(ClientSystem):
def __init__(self, namespace, system_name):
ClientSystem.__init__(self, namespace, system_name)
namespace = clientApi.GetEngineNamespace()
system_name = clientApi.GetEngineSystemName()
# ....
self.ListenForEvent('CustomSwordMod', 'AnimationServer',
'AttackedSync', self, self.attacked_sync)
# ....
def client_init(self, event):
# ....
pass
def attack_click(self, event):
# ....
pass
def attacked_sync(self, event):
player_id = event['playerId']
value = event['value']
compFactory.CreateQueryVariable(player_id).Set(
'query.mod.sword_attack_time',
value
)
def send_attacked_packet(self, _time, data):
# ....
pass
```
可以看到最后实际效果如预期所示,对羊群造成了范围伤害并播放自定义攻击动画。有条件联机的开发者,也可以验证多人联机下攻击动画的播放效果。
![动画2](./images/attack0_2.gif)

View File

@@ -0,0 +1,250 @@
# 自定义3D武器第三人称攻击效果
在上节中我们初步掌握如何自定义3D武器在第一人称视角下的攻击效果。由于玩家存在第一人称和第三人称适配第三人称是必不可少的。
第三人称下的自定义3D武器攻击效果需要依赖Python代码这里使用传统的注册自定义系统的模组开发方式为大家讲解。
**在本章你将学会:**
- 使用Blockbench和基岩版动画叠加效果制作第三人称手持金剑动画与对应的攻击动画。
- 使用模组SDK挂载新的动画资源并重构第三人称动画控制器。
本节课程所使用到的[Demo请戳这里](https://g79.gdl.netease.com/customweaponnew.zip),以下是最终效果呈现:**
![444](./images/attack21_0.gif)
## 创建第三人称所需的动画资源
启动Blockbench打开custom_golden_sword_arm.geo.json并切换至动画模式新建表现第三人称手持动画的third_hold。并将手臂向X轴朝上方旋转。
![image-20220702104920739](./images/attack21_0.png)
新建表现第三人称挥舞动画的third_arm_attack选择third_arm_attack动画并勾选third_hold动画自定义一段0.75秒的攻击动画。并将关键帧设置为平滑,让动画效果更丝滑。
![0_0](./images/attack21_1.gif)
由于手臂和剑分成两个模型手臂的动画通道无法影响武器为了让手臂挥舞时剑能与之轨迹同步我们需要在剑模型上再多做一个动画。打开custom_golden_sword.geo.json切换至动画模式新增third_sword_attack动画在选中后勾选third_hold动画也自定义一段和手挥舞时长一样的0.75秒攻击动画这里可以将third_arm_attack的arm骨骼关键帧拷贝一份过来并对third_sword_attack的item骨骼打上动画关键帧。最后选择third_sword_attack动画并勾选third_hold和third_arm_attack动画可以看到完整的第三人称攻击动画效果。
![0_1](./images/attack21_2.gif)
## 挂载新的动画资源并创建新的动画控制器
新建controller.animation.player.third_person_attack_fixed动画控制器下设default和third_person_attack两个状态当满足query.mod.sword_attack_time不等于0时切换至播放第三人称攻击动画的状态动画播放结束时且对应的query.mod.sword_attack_time为0时切换回default状态以下是代码示例。
```json
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.player.third_person_attack_fixed" : {
"states" : {
"default": {
"transitions": [
{
"third_person_attack": "query.mod.sword_attack_time"
}
]
},
"third_person_attack": {
"animations": [
"third_arm_attack"
],
"transitions": [
{
"default": "!query.mod.sword_attack_time"
}
]
}
}
}
}
}
```
接着在CustomSwordScripts/client.py内的client_init函数下添加ActorRender组件在游戏运行时动态添加美术资源至玩家资源定义内。由于第三人称的手臂动画涉及到玩家的资源这是不得不采取的措施为了尽量减少冲突的可能性我们不选择直接修改玩家资源定义内容而是使用Python代码导入。
```python
class AnimationClient(ClientSystem):
# ...
def client_init(self, event):
# ...
actor_comp.AddPlayerAnimationController( # 第三人称攻击动画控制器
'controller.third_person_attack_fixed',
'controller.animation.player.third_person_attack_fixed'
)
actor_comp.AddPlayerAnimation(
'sword_third_hold',
'animation.custom_golden_sword.third_hold' # 第三人称手持动画
)
actor_comp.AddPlayerAnimation(
'third_arm_attack',
'animation.custom_golden_sword.third_arm_attack' # 第三人称手臂动画
)
# 添加第三人称手持动画进玩家root动画控制器下的third_person状态里这里存放的都是玩家在第三人称时会使用到的动画
# 动画需满足主手手持必须是自定义金剑才会生效
actor_comp.AddPlayerAnimationIntoState(
'root', 'third_person', 'sword_third_hold', "query.get_equipped_item_full_name('main_hand') == 'design:custom_golden_sword'"
)
# 添加第三人称手持动画控制器进玩家root动画控制器下的third_person状态里动画控制器同样可以被嵌套在其他动画控制器内
# 动画控制器需满足主手手持必须是自定义金剑才会生效
actor_comp.AddPlayerAnimationIntoState(
'root', 'third_person', 'controller.third_person_attack_fixed', "query.get_equipped_item_full_name('main_hand') == 'design:custom_golden_sword'"
)
actor_comp.RebuildPlayerRender() #导入资源后必须重新构建玩家渲染调用RebuildPlayerRender方法
```
在剑的attachable定义文件内挂接third_sword_attack动画。这是因为剑挥舞的效果可以转移给剑本身因此third_sword_attack应该挂在剑的资源定义文件内。
```json
{
"format_version": "1.10.0",
"minecraft:attachable": {
"description": {
"identifier": "design:custom_golden_sword",
//materials
//textures
"animations": {
//...
"attack_rotation_sword": "animation.custom_golden_sword.sword_attack",
"third_attack_rotation_sword": "animation.custom_golden_sword.third_sword_attack",
"controller.attack": "controller.animation.sword.attack"
},
"scripts": {
"animate": [
"controller.attack",
{
"first_hold": "c.is_first_person"
},
{
"first_scale": "c.is_first_person"
}
]
}
//geometry
//render_controlls
}
}
}
```
重写controller.attack动画控制器新增third_person和third_person_attack两个状态。要求在第三人称下状态切换为third_person攻击开始时状态切换至third_person_attack并播放第三人称的剑挥舞动画在query.mod.sword_attack_time为0时切换回third_person状态。在有关第三人称的所有状态下提供切换至default状态的条件即满足c.is_first_person手持实体处于第一人称时。
```json
{
"format_version": "1.10.0",
"animation_controllers": {
//...
"controller.animation.sword.attack": {
"initial_state" : "default",
"states" : {
"default": {
"transitions" : [
// first_person
{
"third_person": "!c.is_first_person"
}
]
},
// first_person
"third_person": {
"transitions" : [
{
"default" : "c.is_first_person"
},
{
"third_person_attack": "query.mod.sword_attack_time"
}
]
},
"third_person_attack": {
"animations": [
"third_attack_rotation_sword"
],
"transitions" : [
{
"default" : "c.is_first_person"
},
{
"third_person": "!query.mod.sword_attack_time"
}
]
}
// first_person_attack
}
}
}
}
```
最后进入游戏,可以看到第三人称效果已经生效。
![444](./images/attack21_0.gif)
## 一些其他优化
由于我们并没有屏蔽玩家在挥舞时点触方块的逻辑造成挥舞的区域若离方块较近会叠加出较为难看的挖掘动画。为了避免这个问题可以在client.py和server.py文件分别监听StartDestroyBlockClientEvent和StartDestroyBlockServerEvent根据实际情况判断结果对挖掘进行取消。
以下是代码示例:
```python
# client.py
class AnimationClient(ClientSystem):
def __init__(self, namespace, system_name):
#....
namespace = clientApi.GetEngineNamespace()
system_name = clientApi.GetEngineSystemName()
self.ListenForEvent(namespace, system_name,
'StartDestroyBlockClientEvent', self, self.attack_click_block)
#....
def attack_click_block(self, event):
player_id = event['playerId']
if player_id != clientApi.GetLocalPlayerId():
return
current_time = time.time()
if current_time - self.click_cooldown < 0.75:
event['cancel'] = True
```
```python
# server.py
class AnimationServer(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,
'StartDestroyBlockServerEvent', self, self.attack_click_block)
self.player_attacked_cache = {}
self.attack_type = ['start', 'will_hit', 'end']
#...
def attack_click_block(self, event):
player_id = event['playerId']
if self.player_attacked_cache.get(player_id, '') != 'end':
event['cancel'] = True
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 750 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 736 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 624 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB