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

View File

@@ -0,0 +1,118 @@
# 中国版特效的使用
<iframe src="https://cc.163.com/act/m/daily/iframeplayer/?id=64818d8ec31a9c0f360dc5de" width="800" height="600" allow="fullscreen"/>
在我的世界中国版中的特效,有两种:
- 微软特效
- 中国版特效
微软特效是游戏原生支持的特效,使用`/particle`原版指令来生成。
中国版特效基于特效编辑器来制作,可以实现更多效果的特效。
中国版特效主要有两种形式,它们应用的场景各不相同
- 序列帧特效:通过在场景中绘制一张单面片的贴图,在这个贴图上不断的更换新的图片,这个过程形成完整的序列帧特效。
- 粒子特效:通过在场景中生成大量粒子图像来产生视觉效果,每个粒子都代表着效果中的单个元素,所有的粒子组合起来就形成了完整的粒子特效。
特效编辑器的使用教程,可以参考[官方文档](https://mc.163.com/dev/mcmanual/mc-dev/mcguide/16-%E7%BE%8E%E6%9C%AF/9-%E7%89%B9%E6%95%88/00-%E7%89%B9%E6%95%88%E7%BC%96%E8%BE%91%E5%99%A8%E7%AE%80%E4%BB%8B.html?catalog=1)。本教程将主要介绍如何使用官方内容库中的现成的特效将其使用到开服工具2.0制作的网络游戏中。
## 素材下载和导入
开发者工作台的内容库中,包含了众多已经编辑好的特效包,可以供开发者自由使用。
本教程中,下载`代号羲和特效包`,并以该特效包为例,介绍如何在游戏中使用这些特效。
![](./images/01.png)
点击下载按钮后,我们可以新建一个空白基岩版附加包,用来导入、调试这个特效。
打开编辑器后,在左上角选择`特效`,切换到特效编辑器。
![](./images/02.png)
在编辑器打开的情况下,切换到开发者工作台的内容库,点击导入按钮,即可导入特效包。
![](./images/03.png)
弹出的对话框,全选导入即可。
![](./images/04.png)
这时在资源管理窗口中,切换到中国版特效分类,就可以看到所有我们导入的特效。
![](./images/05.png)
如果需要预览特效,可以将特效拖动到模型挂接的窗口中,然后点击播放按钮进行播放。
比如将Attack_2这个特效拖动到`head`上,可以看到它是粒子特效。点击时间轴的播放按钮,就可以看到特效在玩家模型头部被播放。
![](./images/06.png)
选中Attack_2这个特效后右侧属性窗口可以看到粒子特效的相关属性设置。
主要包括粒子的尺寸、速度、旋转、发射器的设置等等参数。
![](./images/32.png)
除此之外,资源包中还有部分序列帧特效,例如`test_xuanyun`这个特效。挂接播放后可以看到,它就是一个不断变化的图片。
属性窗口中也只有贴图相关选项,没有粒子的发射器等参数。
![](./images/31.png)
## 使用代码控制播放
我们可以首先新建一个插件,比如这里名为`testEffects`,删除其`developer_mods`的内容后复制到Mod目录。
然后对刚刚创建的空白附加包右键,打开目录
![](./images/07.png)
将资源包目录中的`effects`,`particles`,`textures`文件夹,复制到插件的资源包目录。
这三个文件夹 分别存放了`中国版特效``原版特效``材质`,如果需要完整的使用特效包内的特效,缺一不可。
![](./images/08.png)
### 粒子
接下来就是代码编写的部分模组SDK内提供了丰富的接口来控制特效的播放参考[链接](https://mc.163.com/dev/mcmanual/mc-dev/mcdocs/1-ModAPI/%E6%8E%A5%E5%8F%A3/%E7%89%B9%E6%95%88/%E7%B2%92%E5%AD%90.html?catalog=1#createengineparticle)。
例如我们可以将播放特效的代码封装到一个函数内,然后另外编写监听来自服务器的事件的代码,通过服务器控制粒子特效的播放。
```python
def PlayEffect(self, effectName, pos):
particleEntityId = self.CreateEngineParticle(effectName, pos)
particleControlComp = clientApi.GetEngineCompFactory().CreateParticleControl(particleEntityId)
particleControlComp.Play()
return particleEntityId
```
例如监听来自服务端的PlayEffectEvent播放特效服务端的部分大同小异这里不再介绍。
```python
def __init__(self, namespace, systemName):
ClientSystem.__init__(self, namespace, systemName)
self.ListenForEvent(EffectsConst.ModName, EffectsConst.ServerSystemName, "PlayEffectEvent", self, self.OnPlayEffect)
def OnPlayEffect(self, args):
name = args["name"]
pos = tuple(args["pos"])
particleId = self.PlayEffect(name, pos)
print "播放了特效实体id", particleId
```
### 序列帧
序列帧的播放控制代码和粒子略有不同,参考[文档](https://mc.163.com/dev/mcmanual/mc-dev/mcdocs/1-ModAPI/%E6%8E%A5%E5%8F%A3/%E7%89%B9%E6%95%88/%E5%BA%8F%E5%88%97%E5%B8%A7.html?catalog=1)。
主要通过两个接口创建序列帧特效:
- CreateEngineSfx
- CreateEngineSfxFromEditor
推荐使用`CreateEngineSfxFromEditor`,可以按照编辑器中编辑好的参数创建序列帧。支持环状序列帧。
在完成创建后,也可以使用其他接口对序列帧特效进行控制,使用上和粒子特效大同小异。

View File

@@ -0,0 +1,121 @@
# BlockBench模型的使用
<iframe src="https://cc.163.com/act/m/daily/iframeplayer/?id=64818e09ef3bb6958baf3886" width="800" height="600" allow="fullscreen"/>
在制作游戏玩法时经常会遇到需要修改玩家模型或者实体模型的场景。我们可以使用Blockbench制作的模型配合模组SDK来轻松实现修改实体模型的功能。
## 本地玩家模型的修改
在开始之前,首先前往内容库,找到`Spigot示例Demo`下载并打开。本节课将使用官方Demo中提供的模型素材来进行演示。
接下来创建一个插件,这里命名为`testModel`复制到Mod部署目录。
打开Demo的目录找到`SpigotDemo\CustomHumanModelDemo\DemoClientMod\resource_packs\resource_pack_geyser_demo_mod`为Demo中的资源包文件夹将其中的`models``textures`复制到`testModel`的资源包文件夹中。
![](./images/10.png)
通过IDE打开这个目录可以看到目录结构中主要是复制了2个`.geo.json`后缀的模型文件和1个贴图。
接下来我们尝试,将玩家的模型替换为`player.geo.json`所存储的模型,贴图替换为`player.png`的贴图。
![](./images/11.png)
首先要替换模型我们要知道模型的identifier打开`player.geo.json`可以看到identifier字段的值为`geometry.player`,这个值就是模型的识别符,需要保证它在所有资源包中是唯一的。
![](./images/12.png)
接下来我们还需要知道模型所对应的贴图的路径。在资源包中对应路径`textures/entity/player`
![](./images/13.png)
接下来我们就可以调用在[OnLocalPlayerStopLoading](https://mc.163.com/dev/mcmanual/mc-dev/mcdocs/1-ModAPI/%E4%BA%8B%E4%BB%B6/%E4%B8%96%E7%95%8C.html?key=OnLocalPlayerStopLoading&docindex=3&type=0)事件触发时,为本地玩家[重建数据渲染器](https://mc.163.com/dev/mcmanual/mc-dev/mcdocs/1-ModAPI/%E6%8E%A5%E5%8F%A3/%E7%8E%A9%E5%AE%B6/%E6%B8%B2%E6%9F%93.html?key=AddPlayerGeometry&docindex=1&type=0#rebuildplayerrender),修改其参数。
```python
def __init__(self, namespace, systemName):
ClientSystem.__init__(self, namespace, systemName)
self.ListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(), "OnLocalPlayerStopLoading", self, self.OnStopLoading)
def OnStopLoading(self, args):
playerId = args.get("playerId")
# 更换模型贴图
actorRenderComp = clientApi.GetEngineCompFactory().CreateActorRender(playerId)
actorRenderComp.AddPlayerGeometry('default', "geometry.player")
actorRenderComp.AddPlayerTexture('default', "textures/entity/player")
actorRenderComp.RebuildPlayerRender()
def Destroy(self):
self.UnListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(), "OnLocalPlayerStopLoading", self, self.OnStopLoading)
```
> 在这里所调用的接口实际上是为单独的实体修改了它的客户端实体json并重建。自定义模型以及json详细参数详见 [自定义生物](https://mc.163.com/dev/mcmanual/mc-dev/mcguide/20-玩法开发/15-自定义游戏内容/3-自定义生物/01-自定义基础生物.html)
>
> 因此只能客户端本地看到这个修改,如果需要让**所有玩家都看到**新模型,那么需要借助服务器**广播给所有客户端**,让其他客户端也为这个实体修改参数并重建数据渲染器。
>
> 同理,如果需要为不同玩家设置不同模型,也可以由服务端来控制,什么玩家显示什么模型。并在新玩家加入时给所有在线客户端广播,通知其他客户端修改新加入的玩家的模型。
进入游戏后,可以看到本地玩家的模型被修改。
![](./images/14.png)
## 本地实体模型的修改
仍继续使用官方Demo中的模型资源复制下方资源文件到testModel插件的对应资源包目录
- `SpigotDemo\CustomPigModelDemo\CustomPigModelClientMod\resource_packs\resource_pack_pig_model\textures\entity\pig` 贴图文件
- `SpigotDemo\CustomPigModelDemo\CustomPigModelClientMod\resource_packs\resource_pack_pig_model\render_controllers` 渲染控制器
- `SpigotDemo\CustomPigModelDemo\CustomPigModelClientMod\resource_packs\resource_pack_pig_model\entity` 客户端实体定义
修改的思路如下:
1. 修改客户端实体定义,添加贴图文件引用。修改渲染控制器
```json
"textures": {
"default": "textures/entity/pig/pig",
"saddled": "textures/entity/pig/pig_saddle",
// 黄猪贴图引用
"default_yellow": "textures/entity/pig/yellow_pig",
// 黄猪上鞍贴图引用
"saddled_yellow": "textures/entity/pig/yellow_pig_saddle"
},
```
```json
// 替换成自定义的猪渲染控制器,可变成黄猪
"render_controllers": [ "controller.render.pig_custom" ],
```
2. 修改渲染控制器根据molang选择贴图的使用。
> molang的解释参考[Wiki](https://zh.minecraft.wiki/w/Molang)
```json
// 自定义猪渲染器,增加黄猪皮肤
"controller.render.pig_custom": {
"arrays": {
"textures": {
"Array.skins": [ "Texture.default", "Texture.saddled" ],
"Array.skins_custom": ["Texture.default_yellow", "Texture.saddled_yellow"]
}
},
"geometry": "Geometry.default",
"materials": [ { "*": "Material.default" } ],
// 在Python脚本内注册query.mod.custom_pig节点默认值为0.0。该处表达式意思为当某只猪的实例的query自定义节点不为1时则皮肤组切换至黄猪组。
"textures": [ "query.mod.custom_pig == 0 ? Array.skins[query.is_saddled] : Array.skins_custom[query.is_saddled]" ]
}
```
3. Python脚本注册molang节点通过脚本控制molang的值来显示不同的模型或贴图。
在修改玩家模型的客户端的代码基础上做修改,框选部分为新增部分:
![](./images/15.png)
同样 如果需要指定某个实体ID为某个模型则需要通过服务端存储实体ID和对应模型之间的映射关系并广播给所有客户端对指定实体进行molang值的修改。
进入游戏生成猪,可以看到随机的猪的颜色。
![](./images/16.png)

View File

@@ -0,0 +1,201 @@
# 自定义物品制作
<iframe src="https://cc.163.com/act/m/daily/iframeplayer/?id=64818e58c31a9c0f360dc5f2" width="800" height="600" allow="fullscreen"/>
基于目前Spigot服相关接口、自定义物品流程Spigot服的自定义物品实际上是原生Java物品的换皮物品客户端Mod利用字段**java_identifier**来标识
在玩家通过Geyser转发之后利用**java_identifier**实现基岩版自定义物品和Java版物品的映射。
因此要实现自定义物品,我们不仅需要在服务端编写相关逻辑,还需要定义客户端行为包和资源包文件。
## 客户端定义物品
首先提前准备好一个服务端插件,这里命名为`testItem`。除此之外为了更加方便地编辑客户端物品我们可以另外再创建一个空白Addon用来完成物品之后复制到插件中。
编辑新创建的空白附加包,在编辑器中,首先在右上角作品菜单中,修改命名空间。教程中命名空间修改为`testitem`
![](./images/18.png)
找到资源管理的新建按钮->配置->物品。
![](./images/17.png)
接下来填写文件命名,同时它也是物品名,例如这里填写`example`。模板选择自定义武器。
**那么这个物品的标识符就是`命名空间:物品名`,即`testitem:example`。**
![](./images/19.png)
接下来我们可以给这个物品设置贴图贴图我们可以使用Spigot示例Demo中的武器贴图。
在资源管理中,点击导入,选择贴图,物品贴图。
![](./images/20.png)
找到目录`SpigotDemo\CustomItemDemo\CustomItemClientMod\resource_packs\CustomItemsMod_resource\textures\items`,导入`customitems_test_sword`,作为自定义武器的贴图。
如果你足够熟悉附加包的目录结构,也可以直接打开作品文件夹,将贴图复制到`资源包/textures/items`目录中,也可以不使用编辑器的贴图导入功能。
接下来在右侧属性窗口中,选择刚刚导入的贴图,也可以根据自己的需要,修改`游戏内名字`,它将会作为物品的默认物品名。
![](./images/21.png)
编辑完成后,点击`配套文件`中的`物品行为文件`后面的`打开文件`按钮。
![](./images/24.png)
在文件中添加`java_identifier`字段。这个字段用来表示在Java服中它所对应的实际的物品的标识符。
```json
{
"format_version": "1.10",
"minecraft:item": {
"components": {
"minecraft:max_damage": 10,
"netease:weapon": {
"attack_damage": 12,
"enchantment": 10,
"level": 3,
"speed": 5,
"type": "sword"
}
},
"description": {
"category": "Equipment",
"identifier": "testitem:example"
},
"java_identifier": "wooden_sword"
}
}
```
例如这里填写`wooden_sword`那么在Java服中木剑将尝试换皮为我们创建的自定义物品。
在编辑器的属性窗口中还包含了一些行为包组件。需要注意的是因为自定义物品本质上是Java版物品换皮这里的大多数属性都只是用来给客户端设置物品的表现的很多属性都无法生效。
### **需要注意的点**
- 字段工具挖掘速度、挖掘等级需要和Java物品对应不然会出现方块破坏速率不一致导致的卡方块现象。对应物品json中 `"netease:weapon":{ "type":"shovel", "level":0, "speed":2 }`
具体对应关系如下:
| 键 | 类型 | 默认值 | 解释 |
| ----- | ---- | ------ | ------------------------------------------------------------ |
| type | str | | 武器/工具的类型,目前支持类型有: sword剑 shovel铲 pickaxe镐 hatchet斧 hoe锄头 |
| level | int | | level为0当速度为2对应木板,否则对应金锭 level为1对应石头 level为2对应铁锭 level为3对应钻石 level大于3无法使用铁砧修复 |
| speed | int | 0 | 对采集工具生效,表示挖掘方块时的基础速度 木头2 石头4 铁6 钻石8 金12 |
使用编辑器,可以直接在`行为包组件中`进行修改,在`武器属性`中,选择对应的`类型``工具等级``挖掘基础速度`
- 同理盔甲字段json中需要和Java物品对应 `"netease:armor":{ "armor_slot":2 }` 盔甲槽位,详见[ ArmorSlotType](https://mc.163.com/dev/mcmanual/mc-dev/mcdocs/1-ModAPI/枚举值/ArmorSlotType.html)。使用编辑器,可以直接在`盔甲穿戴属性`类别中就可以选择槽位。
也有部分行为包组件不会生效,目前已知的有
````
- 基岩版自定义物品中用于物品防火的组件
```
设置物品是否防火
"netease:fire_resistant"{ "value" : true}
```
- 基岩版自定义物品中用于物品是否可做燃料的组件
```
设置物品是否可作为燃料
"netease:fuel" { "value" : true}
```
- 基岩版自定义物品中用于物品的使用间隔
```
设置物品使用间隔
"netease:cooldown" : { "duration" : 5}
```
````
完成了客户端物品的配置,我们就可以关闭编辑器。
打开附加包文件夹,将资源包和行为包目录中的部分涉及到自定义物品的内容,复制到`testItem`插件中的对应位置。
### 行为包
- netease_items_beh
![](./images/22.png)
### 资源包
- netease_items_res
- texts
- textures
![](./images/23.png)
至此,我们就完成了客户端物品的定义。
## 服务端逻辑
首先新建一个项目依赖SpigotMaster。
### 创建物品
```java
ItemStack itemStack = new ItemStack(Material.WOOD_SWORD);
itemStack = spigotMaster.setCustomItemIdentifier(itemStack, "testitem:example");
```
像正常Spigot插件一样直接实例化ItemStack来新建一个物品物品的Material为`java_identifier`字段中的值,即木剑。
然后调用spigotMaster的`setCustomItemIdentifier`方法,为这个物品设置自定义物品标识符,即`testitem:example`。
### 获取物品
```java
String customIdentifier = spigotMaster.getCustomItemIdentifier(itemStack);
```
调用spigotMaster的`getCustomItemIdentifier`方法来获取自定义物品标识符。如果是自定义物品返回标识符否则返回null。
我们可以编写测试一些测试用监听器,查看自定义物品如何生效。
```java
public class Listeners implements Listener {
private final SpigotMaster spigotMaster = TutorialItem.getInstance().getSpigotMaster();
@EventHandler
public void onJoin(PlayerJoinEvent e) {
Player player = e.getPlayer();
ItemStack itemStack = new ItemStack(Material.WOOD_SWORD);
itemStack = spigotMaster.setCustomItemIdentifier(itemStack, "testitem:example");
player.getInventory().addItem(itemStack);
}
@EventHandler
public void onInteract(PlayerInteractEvent e) {
Player player = e.getPlayer();
ItemStack itemStack = player.getInventory().getItemInMainHand();
if (itemStack == null) {
return;
}
String customIdentifier = spigotMaster.getCustomItemIdentifier(itemStack);
if (customIdentifier == null) {
player.sendMessage("你手持的物品不是自定义物品");
return;
}
player.sendMessage("你手持的物品的自定义ID为: " + customIdentifier);
}
}
```
安装插件到服务器,然后勾选`testItem`,重新部署。
进入游戏后,可以看到背包发送了一个我们的自定义物品。
![](./images/25.png)
对物品交互可以看到正常输出成功获取到了自定义物品的identifier。
![](./images/26.png)

View File

@@ -0,0 +1,161 @@
# 作业
<iframe src="https://cc.163.com/act/m/daily/iframeplayer/?id=64818ea7c31a9c0f360dc5f4" width="800" height="600" allow="fullscreen"/>
制作2个自定义物品可以长按空白处使用有不同的效果
- 物品1播放一个中国版特效
- 物品2修改玩家模型
## 客户端实现
客户端的部分将基于之前所制作的`testEffect``testModel``testItem`客户端模组来实现。制作过程见[中国版特效的配置](./0-中国版特效的配置.md)、[BlockBench模型的使用](./1-BlockBench模型的使用.md)、[自定义物品的制作](./2-自定义物品制作.md)。
其中`testEffect`模组已经实现了监听服务端的事件来播放特效的功能。
### testModel
`testModel`模组 需要让其监听服务端事件再做出修改即可。
修改的部分:
- OnLocalPlayerStopLoading 后通知服务端
- 监听服务端事件OnChangeLocalModelEvent事件触发后再更改模型
修改后的代码:
```python
class ModelClientSystem(ClientSystem):
"""
该mod的客户端类
根据服务端推送下来的数据显示通用显示界面
"""
def __init__(self, namespace, systemName):
ClientSystem.__init__(self, namespace, systemName)
self.ListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(), "OnLocalPlayerStopLoading", self, self.OnStopLoading)
self.ListenForEvent(ModelConst.ModName, ModelConst.ServerSystemName, "OnChangeLocalModelEvent", self, self.OnChangeLocalModel)
self.ListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(), "AddEntityClientEvent", self, self.OnAddEntity)
def OnChangeLocalModel(self, args):
playerId = clientApi.GetLocalPlayerId()
# 更换模型贴图
actorRenderComp = clientApi.GetEngineCompFactory().CreateActorRender(playerId)
actorRenderComp.AddPlayerGeometry('default', "geometry.player")
actorRenderComp.AddPlayerTexture('default', "textures/entity/player")
actorRenderComp.RebuildPlayerRender()
def OnStopLoading(self, args):
# query节点注册时为全局属性创建的QueryComp应传入世界参数
query_comp = clientApi.GetEngineCompFactory().CreateQueryVariable(clientApi.GetLevelId())
query_comp.Register('query.mod.custom_pig', 0.0)
self.NotifyToServer("ClientStopLoadingEvent", {})
def OnAddEntity(self, args):
entity_id = args['id']
identifier = args['engineTypeStr']
if identifier != 'minecraft:pig':
return
# query节点在某个实体实例被设置是创建的QueryComp应传入实体ID参数
query_comp = clientApi.GetEngineCompFactory().CreateQueryVariable(entity_id)
# 50%概率创建一个黄猪。若需要全部玩家看到该实体都为黄猪,需在服务端做好同步处理,并广播至每个客户端。
if random.randint(0, 100) < 50:
query_comp.Set('query.mod.custom_pig', 1.0)
def Destroy(self):
self.UnListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(), "OnLocalPlayerStopLoading", self, self.OnStopLoading)
self.UnListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(), "AddEntityClientEvent", self, self.OnAddEntity)
self.UnListenForEvent(ModelConst.ModName, ModelConst.ServerSystemName, "OnChangeLocalModelEvent", self, self.OnChangeLocalModel)
```
### testItem
使用编辑器打开之前所编辑的附加包,继续添加新物品。方便起见,直接使用自定义剑的配置,进行复制。
![](./images/27.png)
复制两个新物品,识别符分别为`item1``item2`,物品名分别为`物品1``物品2`
![](./images/28.png)
因为编辑器可能会覆盖字段,对**所有**的物品,打开`物品行为文件`,添加`java_identifier`,都为`wooden_sword`
> 需要注意的是:
>
> 添加完`java_identifier`后,再次使用编辑器打开,可能会将字段覆盖。
>
> 建议编辑完成后将文件进行备份,防止字段丢失。
完成后打开文件夹将行为包和资源包对应内容复制到testItem插件中。
## 服务端实现
新建一个项目,命名为`TutorialItemDemo`并导入SpigotMaster作为依赖。
- 创建监听器,监听玩家长按空白处(鼠标右键点击)
- 获取物品基岩版标识符,根据不同的标识符执行不同的逻辑
```java
@EventHandler
public void onInteract(PlayerInteractEvent e) {
Action action = e.getAction();
if (action != Action.RIGHT_CLICK_BLOCK && action != Action.RIGHT_CLICK_AIR) {
return;
}
ItemStack itemStack = e.getItem();
String identifier = spigotMaster.getCustomItemIdentifier(itemStack);
Player player = e.getPlayer();
if (identifier.equalsIgnoreCase("testitem:item1")) {
sendEffect(player);
} else if (identifier.equalsIgnoreCase("testitem:item2")) {
changeModel(player);
}
}
```
### 特效播放
实现特效播放的事件信息发送。
需要注意的是,根据之前所编写的客户端模组,命名空间为`testEffects`,系统名为`testEffectsDev`,事件为`PlayEffectEvent`
```java
private void sendEffect(Player player) {
Map<String, Object> data = new HashMap<>();
data.put("name", "effects/Attack_2.json");
Location location = player.getLocation();
data.put("pos", Arrays.asList(location.getX(), location.getY(), location.getZ()));
spigotMaster.notifyToClient(player, "testEffects", "testEffectsDev", "PlayEffectEvent", data);
}
```
### 模型更改
发送`OnChangeLocalModelEvent`事件给客户端,命名空间为`testModel`,系统名为`testModelDev`
```java
private void changeModel(Player player) {
spigotMaster.notifyToClient(player, "testModel", "testModelDev", "OnChangeLocalModelEvent", new HashMap<>());
}
```
## 效果测试
进入游戏后打开创造物品栏拿出物品1、物品2。分别手持物品右键。
- 物品1播放特效
![](./images/29.png)
- 物品2切换模型
![](./images/30.png)
## 参考插件下载
Java插件下载 [点我](https://g79.gdl.netease.com/TutorialItemDemo.zip)
Python模组下载 [点我](https://g79.gdl.netease.com/TutorialItemDemo-Python.zip)