Files
netease-bedrock-wiki/mconline/60-我的世界创造营教程/SDK大师教程/2-武器和装备制作/5-投掷类武器.md
2025-08-25 18:36:29 +08:00

459 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 投掷类武器
> 温馨提示:开始阅读这篇指南之前,我们希望你对《我的世界》基岩版附加包有一定了解,有能力撰写 JSON 数据格式,并能够独立阅读《我的世界》开发者官网-开发指南或其他技术引用文档。
本文将帮助你添加一个类似于原版三叉戟的可投掷武器。(强烈建议阅读之前先阅读前面一节课的内容,因为思路一样)
本文假定你熟悉 Molang、渲染控制器、动画和实体定义有基本的了解。本文不涉及美术资源的相关教程如果对此感兴趣的同学可以自行学习和了解。
在本教程中,您将学习以下内容。
- ✅类似于原版三叉戟的投掷类武器。
## 成果展示
类似于原版的三叉戟的自定义投掷类武器:
![投掷武器演示](./assets/5_1.gif)
## 投掷类武器制作
还是一样的思路,先去官方的内容库中「偷」一个模型文件:
![image-20231126203148298](./assets/image-20231126203148298.png)
![](./assets/image-20231126203237062.png)
### 原版三叉戟
我们没有制作投掷类武器的经验,所以我们直接去原版查看三叉戟的 `attachable` 文件(位于 `definitionsattachables\trident.entity.json`
```json
{
"format_version": "1.10",
"minecraft:attachable": {
"description": {
"identifier": "minecraft:trident",
"materials": {
"default": "entity_alphatest",
"enchanted": "entity_alphatest_glint"
},
"textures": {
"default": "textures/entity/trident",
"enchanted": "textures/misc/enchanted_item_glint"
},
"geometry": {
"default": "geometry.trident"
},
"animations": {
"wield": "controller.animation.trident.wield",
"wield_first_person": "animation.trident.wield_first_person",
"wield_first_person_raise": "animation.trident.wield_first_person_raise",
"wield_first_person_raise_shake": "animation.trident.wield_first_person_raise_shake",
"wield_first_person_riptide": "animation.trident.wield_first_person_riptide",
"wield_third_person": "animation.trident.wield_third_person",
"wield_third_person_raise": "animation.trident.wield_third_person_raise"
},
"scripts": {
"pre_animation": [
"variable.charge_amount = math.clamp((query.main_hand_item_max_duration - (query.main_hand_item_use_duration - query.frame_alpha + 1.0)) / 10.0, 0.0, 1.0f);"
],
"animate": [
"wield"
]
},
"render_controllers": [ "controller.render.item_default" ]
}
}
}
```
### 模型文件
那思路就很简单了,直接仿制一个三叉戟的模型文件,其余的东西都是可以通用的。所以我们把模型稍微改一下:
![](./assets/image-20231126203544339.png)
这里原版三叉戟的 `pivot` 影响了动画的对齐和命中效果,我们要保持**绝对一致**(也就是说这个 24 不能改):
![](./assets/image-20231126203731758.png)
我们稍微观察一下三叉戟就大概明白了这个 `pivot` 是在什么位置:
![](./assets/image-20231126205321013.png)
如果打开「调试」中的「能见度边界框」的话,也能够发现这个锚点实际上就是命中的位置:
![](./assets/image-20231126205445572.png)
所以我们稍微更改一下我们的模型:
![](./assets/image-20231126203946097.png)
上面是用于手持的物品模型,对于处理用于**投掷出去的抛射物模型**,我们这里有两种方式处理:
- 不添加额外的模型,通过动画来修正投掷出去的动画。好处是不需要增加额外的动画,但。
- 添加额外的抛射物模型。好处是动画简单,而且能够复用,坏处就是要多处理一遍模型。
如果我们想要采用第一种方式的话,就需要把模型往下移,把锚点移动到矛的尖上:
![](./assets/image-20231126205828881.png)
但此时游戏中的握持方式就会很奇怪,因为我们自己的模型长度跟原版的三叉戟不一致:
![](./assets/image-20231126205928159.png)
所以我们这里不采用第一种方式,而是额外增加一个单独的用于投掷物实体的模型。
不过这里还是先放一下采用第一种方式时的动画,提供给需要的同学:
```json
{
"format_version": "1.8.0",
"animations": {
"animation.tutorial_thrown_custom_throw_weapon.move": {
"loop": true,
"bones": {
"pole": {
// 下列是采用第一种方式时采用的动画
"rotation": [
"-query.target_x_rotation + 90", // 这里需要旋转 90 正对 N 方向
"-query.target_y_rotation",
0.0
],
"position": [
// 这里的 -24 对应了 pivot 偏移的 24
0, -24, 0
]
}
}
}
}
}
```
OK那我们采用第二种方式只需要新增一个锚点在原点并且朝向 N 方向的模型就可以:
![](./assets/image-20231126210304161.png)
此时的动画文件,可以通用,就是让实体朝向运动方向:
```json
{
"format_version": "1.8.0",
"animations": {
"animation.tutorial_thrown_custom_throw_weapon.move": {
"loop": true,
"bones": {
"pole": {
// 下列是教程中采用的第二种方法的动画
"rotation": [
"-query.target_x_rotation",
"-query.target_y_rotation",
0.0
]
}
}
}
}
}
```
### 基础物品定义
行为包下的物品定义文件:
```json
{
"format_version": "1.10",
"minecraft:item": {
"description": {
"category": "Equipment",
"identifier": "tutorial:custom_throw_weapon",
"custom_item_type": "ranged_weapon"
},
"components": {
"netease:show_in_hand": {
"value": false
},
"minecraft:max_damage": 10,
// 保证使用足够长,否则动画和视角会重新开始
"minecraft:use_duration": 99999
}
}
}
```
当我们把 `custom_item_type` 定义为 `ranged_weapon` 时,并且组件中拥有 `minecraft:use_duration` 时,我们在手持物品的情况下右键(手机是长按),就自动会有镜头缩放的效果:
![缩放效果](./assets/5_2.gif)
### 抛射物实体文件
我们还需要额外创建一个抛射物实体,直接复制粘贴原版的三叉戟就好,只不过需要额外添加两个组件:
```json
{
"format_version": "1.12.0",
"minecraft:entity": {
"description": {
"identifier": "tutorial:thrown_custom_throw_weapon",
"is_spawnable": false,
"is_summonable": false,
"is_experimental": false
},
"components": {
"minecraft:collision_box": {
"width": 0.25,
"height": 0.35
},
"minecraft:projectile": {
"on_hit": {
"impact_damage": {
"damage": 8,
"knockback": true,
"semi_random_diff_damage": false,
"destroy_on_hit": false
},
"stick_in_ground": {
"shake_time": 0
}
},
"liquid_inertia": 0.99,
"hit_sound": "item.trident.hit",
"hit_ground_sound": "item.trident.hit_ground",
"power": 4,
"gravity": 0.10,
"uncertainty_base": 1,
"uncertainty_multiplier": 0,
"stop_on_hurt": true,
"anchor": 1,
"should_bounce": true,
"multiple_targets": false,
"offset": [0, -0.1, 0]
},
"minecraft:physics": {
},
"minecraft:pushable": {
"is_pushable": true,
"is_pushable_by_piston": true
},
"netease:custom_entity_type": {
"value": "projectile_entity"
},
"netease:pick_up": {
"item_name": "tutorial:custom_throw_weapon"
}
}
}
}
```
我们需要额外添加 `netease:custom_entity_type` 来标识这个实体是抛射物,以及 `netease:pick_up` 组件,用来在玩家接触时拾取变成物品。
行为包下 `\entity` 文件:
```json
{
"format_version": "1.10.0",
"minecraft:client_entity": {
"description": {
"identifier": "tutorial:thrown_custom_throw_weapon",
"materials": {
"default": "entity_alphatest"
},
"textures": {
"default": "textures/models/tutorial_custom_throw_weapon"
},
"geometry": {
"default": "geometry.tutorial_thrown_custom_throw_weapon"
},
"animations": {
"move": "animation.tutorial_thrown_custom_throw_weapon.move"
},
"scripts": {
"animate": [
"move"
]
},
"render_controllers": [
"controller.render.default"
]
}
}
}
```
### 代码注入第三人称动画
当我们把这些文件都准备好之后,你会发现第三人称并不会把手抬起来:
![第三人称并不会举手](./assets/5_3.gif)
这就是我们在「自定义枪械」那一节课中说的attachable 中的动画,只会影响武器,而不会反作用于玩家。
所以我们还需要在玩家手持投掷武器时,播放原版的投掷动画。
问题是我们并不知道原版的投掷动画是哪一个,我们要么去原版的文件中找(还是挺好找的),要么,就按 F3 直到出现下列的界面:
![](./assets/image-20231126211648095.png)
然后打开动画编辑器:
![](./assets/image-20231126211714774.png)
然后我们就可以在手持三叉戟的情况下,通过不断右键触发动画,来找到到底播放的是哪一个动画:
![如何找到播放的哪一个动画](./assets/5_4.gif)
很快,我们就找到了播放的 `brandish_spear` 动画,一番搜索,就定位到了动画名称:
```json
animation.humanoid.brandish_spear
```
我们使用代码注入到玩家的控制器中:
```python
# -*- coding: utf-8 -*-
import mod.client.extraClientApi as clientApi
CompFactory = clientApi.GetEngineCompFactory()
class TutorialClientSystem(clientApi.GetClientSystemCls()):
def __init__(self, namespace, name):
super(TutorialClientSystem, self).__init__(namespace, name)
self.ListenEvent()
def ListenEvent(self):
self.ListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(), "AddPlayerCreatedClientEvent",
self, self.OnAddPlayerCreatedClientEvent)
def OnAddPlayerCreatedClientEvent(self, args):
playerId = args['playerId']
self.InitRender(playerId) # 包括其他玩家也需要被初始化
# 初始化绑定
def InitRender(self, playerId):
# 投掷武器
self._InitToThrowWeapon(playerId)
actorRenderComp = CompFactory.CreateActorRender(playerId)
actorRenderComp.RebuildPlayerRender()
# 投掷武器的渲染
def _InitToThrowWeapon(self, playerId):
actorRenderComp = CompFactory.CreateActorRender(playerId)
# 使用原版的动画
actorRenderComp.AddPlayerAnimation('custom_throw_weapon_third_person_raise', 'animation.humanoid.brandish_spear')
actorRenderComp.AddPlayerScriptAnimate(
'custom_throw_weapon_third_person_raise',
"!variable.is_first_person && query.get_equipped_item_name('main_hand') == 'custom_throw_weapon' && query.main_hand_item_use_duration > 0"
)
```
这样,我们在第三人称的情况下,就可以播放原版的抬手动作了:
![抬手动作演示](./assets/5_5.gif)
### 处理投掷事件
客户端代码:
```python
# -*- coding: utf-8 -*-
import mod.client.extraClientApi as clientApi
CompFactory = clientApi.GetEngineCompFactory()
class TutorialClientSystem(clientApi.GetClientSystemCls()):
def __init__(self, namespace, name):
super(TutorialClientSystem, self).__init__(namespace, name)
self.ListenEvent()
self.mTimeCounter = 0
#
self.mIsUsingItem = False # 是否正在使用投掷类武器
self.mStartUsingFrame = 0 # 开始使用物品的时间
def ListenEvent(self):
# 投掷武器相关的事件
self.ListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(), "ClientItemTryUseEvent", self,
self.OnClientItemTryUseEvent)
self.ListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(), "ItemReleaseUsingClientEvent", self,
self.OnItemReleaseUsingClientEvent)
def Update(self):
self.mTimeCounter += 1
def OnClientItemTryUseEvent(self, args):
if args['itemName'] == 'tutorial:custom_throw_weapon':
self.mIsUsingItem = True
self.mStartUsingFrame = self.mTimeCounter
def OnItemReleaseUsingClientEvent(self, args):
itemDict = args['itemDict']
if itemDict and itemDict['itemName'] == 'tutorial:custom_throw_weapon':
# 最大蓄力 2s也就是说威力最多为 2 倍
power = min(2.0, (self.mTimeCounter - self.mStartUsingFrame) / 30.0)
self.NotifyToServer('ThrowCustomWeapon', {'playerId': args['playerId'], 'power': power}
```
服务端代码:
```python
# -*- coding: utf-8 -*-
import mod.server.extraServerApi as serverApi
CompFactory = serverApi.GetEngineCompFactory()
class TutorialServerSystem(serverApi.GetServerSystemCls()):
def __init__(self, namespace, name):
super(TutorialServerSystem, self).__init__(namespace, name)
def ListenEvent(self):
# 自定义事件
self.ListenForEvent('tutorialMod', 'tutorialClientSystem', "SyncCustomGunAttackStateEvent", self,
self.OnSyncCustomGunAttackStateEvent)
self.ListenForEvent('tutorialMod', 'tutorialClientSystem', "ThrowCustomWeapon", self, self.OnThrowCustomWeapon)
def OnThrowCustomWeapon(self, args):
playerId = args['playerId']
power = args['power']
param = {
'power' : 4 * power,
'damage' : 3 + power * 3, # 最高伤害为 3+2*3=9 点伤害
'direction': serverApi.GetDirFromRot(CompFactory.CreateRot(playerId).GetRot())
}
projectileComp = CompFactory.CreateProjectile(playerId)
projectileEntityId = projectileComp.CreateProjectileEntity(playerId, 'tutorial:thrown_custom_throw_weapon', param)
if projectileEntityId != '-1':
self._ReduceCarriedItemNum(playerId, 1)
def _ReduceCarriedItemNum(self, playerId, reduceNum):
itemComp = CompFactory.CreateItem(playerId)
selectSlotId = itemComp.GetSelectSlotId()
itemDict = itemComp.GetPlayerItem(serverApi.GetMinecraftEnum().ItemPosType.INVENTORY, selectSlotId)
return itemComp.SetInvItemNum(selectSlotId, itemDict['count'] - reduceNum)
```
### 进入游戏测试
一切准备好之后,就可以进入游戏测试了。然后你就得到了一个自定义的投掷类 3D 武器。
## 小结
我们这一节课是高度利用了原版的三叉戟物品的动画,如果你想要完全自定义的投掷动画,也可以参照自定义枪械那样,完全定制化动画。
## 课后作业
本次课后作业,内容如下:
- 实现自己的类似于原版三叉戟的投掷类 3D 武器;