Files
2025-08-25 18:36:29 +08:00

1170 lines
35 KiB
Markdown
Raw Permalink 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.

---
front:
hard: 进阶
time: 60分钟
---
# 为玩法设计内购商品
## 导航
本章技术向实战分为 **Python实现路线****逻辑编辑器实现路线** 。你可根据选择需要跳转:
- [设计demo商品策划](#策划向实战-设计demo商品)
- [制作新职业-Python](#Python程序向实战-制作新职业)
- [制作新职业-逻辑编辑器](#逻辑编辑器程序向实战-制作新职业)
- [制作会员特效-Python](#Python程序向实战-制作会员特效)
- [制作会员特效-逻辑编辑器](#逻辑编辑器程序向实战-制作会员特效)
## 策划向实战-设计demo商品
有了上面的理论支撑,让我们为这个来自[第一期创造营的玩法地图教程](../玩法地图基础教程/1-玩法地图是追求完整游戏体验的不二选择.html)的职业战争demo设计和实现如下几个内购商品。
职业战争KitPVP的玩法很简单选择一个职业然后尽可能多击杀其他玩家。这个demo提供了三个职业坦克、弓箭手、战士。
![6-13](./image/1_4.png)
这三个互相制衡的基础职业是较为经典合理的,不适合作为内购商品。所以我们新设一个职业:治疗师,模型选用原版的女巫作为商品。设计信息如下:
- 治疗师Healer
- 定位:辅助
- 护甲:铁胸甲,铁护腿,铁靴子
- 武器:木剑
- 物品喷溅型伤害药水x3喷溅型治疗药水x3喷溅型生命恢复x1
- 售价100钻石不可重复购买
- 类型:购买后持久化,永久生效
<img src="./image/1_5.png" alt="image-20220903093522098" style="zoom:50%;" />
仅有增值职业还不够我们还可以售卖一些单局消耗品最简单的当然是原版的物品这里使用demo自带的一个补给类物品吧
- 美味鲜菇testmap:testitem1
- 售价10绿宝石可重复购买。
- 类型:单局消耗品。
![image-20220906160311234](./image/1_6.png)
有了永久商品、单局消耗品我们再设计一个带有效期的定时商品。特效是个不错的选择因为特效不会影响PVP中的平衡性维持良好的作品评分又可以为付费玩家提供很好的游戏体验。
- 脚底光圈
- 售价30钻石/1天可重复购买以叠加时长
- 类型:持久化,定期商品。
<img src="./image/1_7.png" alt="image-20220906160208593" style="zoom:50%;" />
## Python程序向实战-制作新职业
由于该demo原本没有治疗师职业所要简单制作一下首先创建一个实体预设。
![image-20220904073700649](./image/1_8.png)
选择女巫模板因为要借用它的模型遵循原作者命名规则命名为testPresetEntity4。
![image-20220904073750181](./image/1_9.png)
在属性栏勾选免疫伤害取消勾选保留AI再清理掉不需要的行为组件这样让女巫成为一个NPC站着不会动作为摆设也不会攻击玩家。
<img src="./image/1_10.png" alt="image-20220904073908374" style="zoom:50%;" />
<img src="./image/1_11.png" alt="image-20220904073933268" style="zoom:50%;" />
我们看到testPresetEntity1到3的头顶都有粒子特效所以入乡随俗将粒子预设挂载到testPresetEntity4预设下并调整位置让女巫模型头顶有一个粒子特效。
![image-20220904074357478](./image/1_12.png)
进入地图编辑器造一个放模型的方块原本demo地图里只有三个放置第四个。
![image-20220904092641585](./image/1_13.png)
在关卡编辑器里把testPresetEntity4预设放置到舞台并调整位置和旋转。
![image-20220904074055475](./image/1_14.png)
好了,到这里外观就已经完成,进入游戏就可以看到治疗师的模型正确的出现在第四个职业的位置。
![image-20220904073627091](./image/1_15.png)
接下来要开始实现功能,原本三个职业是用命令方块来实现,走进职业的模型就会被传送到游戏区域内、给予缓降特效和职业物品。
由于内购逻辑需要涉及modsdk编码这里使用预设零件来实现。首先创建一个新的空零件命名为Healer继承触发器TriggerPart
<img src="./image/1_16.png" alt="image-20220904092051635" style="zoom:50%;" />
**触发器不难理解,就是框定一个区域,有任何实体进入、停留、退出这个区域都会被触发器检测到。**
点击新建的零件,属性面板,修改下面的参数:
- **区域** :参数如图,触发器挂载在实体预设下时,坐标将会使用相对于预设的坐标。
- **监听** :由于只需要检测玩家进入(即为玩家意图选择此职业),离开和停留都不需要,所以只勾选进入。
- **监听间隔** 由于原demo使用命令方块做出来的效果是反应非常迅速的为了不造成割裂感设置此参数为1。
<img src="./image/1_17.png" alt="image-20220904092202193" style="zoom:50%;" />
将Healer零件挂载到testPresetEntity4预设下。
![image-20220904094123366](./image/1_18.png)
点击 **属性面板** - **定位按钮** ,浏览我们刚才设置的框框,就可以看到效果——玩家走进这个框框将会被检测到。
![image-20220904094247658](./image/1_19.png)
点击 **属性面板** - **配套文件** - **脚本** - **编辑文件** ,打开零件的代码文件,编写接受检测结果的代码:
```python
def InitServer(self):
TriggerPart.InitServer(self)
self.ListenSelfEvent('OnTriggerEntityEnter', self, self.OnTriggerEntityEnter)
def OnTriggerEntityEnter(self, e):
for entityId in e['EnterEntityIds']:
if entityId in self.GetLoadedPlayers():
self.NotifyOneMessage(entityId, '你尝试选择治疗师职业')
```
进入游戏,测试效果:
![enter](./image/enter.gif)
继续编写传送和给予buff、物品的逻辑
```python
def OnTriggerEntityEnter(self, e):
for entityId in e['EnterEntityIds']:
if entityId in self.GetLoadedPlayers():
self.NotifyOneMessage(entityId, '你尝试选择治疗师职业')
self.TurnHealer(entityId)
def TurnHealer(self, playerId):
self.NotifyOneMessage(playerId, '你变成了治疗师')
for itemDict in self.healerItem:
self.SpawnItemToPlayerInv(itemDict, playerId)
dim = self.GetEntityDimensionId(playerId)
self.ChangePlayerDimension(playerId, dim, (352, 83, 442))
self.AddEffectToEntity(playerId, 'slow_falling', 5, 0, False)
```
进入游戏,测试效果:
![turn](./image/turn.gif)
那么整个零件源码是这样:
```python
# -*- coding: utf-8 -*-
from Preset.Parts.TriggerPart import TriggerPart
from Preset.Model.GameObject import registerGenericClass
@registerGenericClass("HealerPart")
class HealerPart(TriggerPart):
def __init__(self):
TriggerPart.__init__(self)
# 零件名称
self.name = "治疗师零件"
self.area = {'min': (-1.0, -1.0, -1.0), 'max': (1.0, 3.0, 1.0), 'dimensionId': 0}
self.isTriggerExit = False
self.healerItem = [
{
'newItemName': 'minecraft:iron_leggings',
'newAuxValue': 0,
'count': 1
},
{
'newItemName': 'minecraft:iron_boots',
'newAuxValue': 0,
'count': 1
},
{
'newItemName': 'minecraft:iron_chestplate',
'newAuxValue': 0,
'count': 1
},
{
'newItemName': 'minecraft:wooden_sword',
'newAuxValue': 0,
'count': 1
},
{
'newItemName': 'minecraft:splash_potion',
'newAuxValue': 21,
'count': 3
},
{
'newItemName': 'minecraft:splash_potion',
'newAuxValue': 23,
'count': 3
},
{
'newItemName': 'minecraft:splash_potion',
'newAuxValue': 28,
'count': 1
}
]
self.intervalTick = 1
def InitServer(self):
TriggerPart.InitServer(self)
self.ListenSelfEvent('OnTriggerEntityEnter', self, self.OnTriggerEntityEnter)
def OnTriggerEntityEnter(self, e):
for entityId in e['EnterEntityIds']:
if entityId in self.GetLoadedPlayers():
self.NotifyOneMessage(entityId, '你尝试选择治疗师职业')
self.TurnHealer(entityId)
def TurnHealer(self, playerId):
self.NotifyOneMessage(playerId, '你变成了治疗师')
for itemDict in self.healerItem:
self.SpawnItemToPlayerInv(itemDict, playerId)
dim = self.GetEntityDimensionId(playerId)
self.ChangePlayerDimension(playerId, dim, (352, 83, 442))
self.AddEffectToEntity(playerId, 'slow_falling', 5, 0, False)
```
但是目前将坐标、buff、给予物品等内容硬编码在了代码里现在零件的自定义属性功能让我们有了更好的做法。想要让这个零件有更多可扩展性编辑零件元数据文件
```python
# -*- coding: utf-8 -*-
from Meta.ClassMetaManager import sunshine_class_meta
from Meta.TypeMeta import PBool, PStr, PInt, PCustom, PVector3, PVector3TF, PEnum, PDict, PFloat, PArray, PVector2, \
PColor
from Preset.Parts.TriggerPart import TriggerPartMeta
@sunshine_class_meta
class HealerPartMeta(TriggerPartMeta):
CLASS_NAME = "HealerPart"
PROPERTIES = {
"gamePos": PVector3(text="传送到", sort=12, group="职业设置"),
"effectList": PArray(sort=13, text="给予状态效果列表", group="职业设置", childAttribute=PDict(children={
"effectName": PStr(text="状态原版名称", sort=1, default="speed"),
"duration": PInt(text="持续时间", sort=2, default=1),
"amplifier": PInt(text="状态等级", sort=3, default=0),
"showParticles": PBool(text="显示粒子效果", sort=4, default=True)
})),
"itemList": PArray(text="给予物品列表", group="职业设置", sort=14, childAttribute=PDict(children={
"itemDict": PCustom(
sort=0,
text="物品选择",
editAttribute="MCItems",
default=("minecraft:wooden_sword", 0),
withNamespace=True,
withAuxValue=True,
isBlock=None,
),
"count": PInt(sort=1, text="物品数量", default=1)
})),
}
```
这样,治疗师零件的属性面板就出现了如下设置选项:
<img src="./image/1_20.png" alt="image-20220904103005406" style="zoom:50%;" />
把我们刚才硬编码在代码里的配置应用上去:
<img src="./image/1_21.png" alt="image-20220904104017464" style="zoom:50%;" />
这样将属性用mcs可视化面板暴露出来无论是后续要制作更多其他职业或是其他团队成员想要修改都会更加方便。
最后,简单修改刚才的硬编码,让属性面板的配置实际生效。
```python
def TurnHealer(self, playerId):
self.NotifyOneMessage(playerId, '你变成了治疗师')
for item in self.itemList:
self.SpawnItemToPlayerInv({
'newItemName': item['itemDict'][0],
'newAuxValue': item['itemDict'][1],
'count': item['count']
}, playerId)
dim = self.GetEntityDimensionId(playerId)
self.ChangePlayerDimension(playerId, dim, tuple(self.gamePos))
for effect in self.effectList:
self.AddEffectToEntity(playerId, effect['effectName'], effect['duration'], effect['amplifier'], effect['showParticles'])
```
## 逻辑编辑器程序向实战-制作新职业
由于该demo原本没有治疗师职业所以要简单制作一下首先创建一个实体预设。
![image-20220904073700649](./image/1_8.png)
选择女巫模板因为要借用它的模型遵循原作者命名规则命名为testPresetEntity4。
![image-20220904073750181](./image/1_9.png)
在属性栏勾选免疫伤害取消勾选保留AI再清理掉不需要的行为组件这样让女巫成为一个NPC站着不会动作为摆设也不会攻击玩家。
<img src="./image/1_10.png" alt="image-20220904073908374" style="zoom:50%;" />
<img src="./image/1_11.png" alt="image-20220904073933268" style="zoom:50%;" />
我们看到testPresetEntity1到3的头顶都有粒子特效所以入乡随俗将粒子预设挂载到testPresetEntity4预设下并调整位置让女巫模型头顶有一个粒子特效。
![image-20220904074357478](./image/1_12.png)
进入地图编辑器造一个放模型的方块原本demo地图里只有三个放置第四个。
![image-20220904092641585](./image/1_13.png)
在关卡编辑器里把testPresetEntity4预设放置到舞台并调整位置和旋转。
![image-20220904074055475](./image/1_14.png)
好了,到这里外观就已经完成,进入游戏就可以看到治疗师的模型正确的出现在第四个职业的位置。
![image-20220904073627091](./image/1_15.png)
接下来要开始实现功能,原本三个职业是用命令方块来实现,走进职业的模型就会被传送到游戏区域内、给予缓降特效和职业物品。
首先创建一个**新的蓝图零件**命名为Healer治疗师实现零件
![image-20220904073627091](./image/3_0.png)
创建好后在下方资源管理点击“蓝图”找到对应的Healer.bp双击进入逻辑编辑器编辑
![image-20220904073627091](./image/3_1.png)
首先要 **检测玩家靠近实体** 然后才会执行传送、给予物品、buff等逻辑。要实现 **检测玩家靠近实体** ,需使用到 **NewOnEntityAreaEvent** 事件,此事件在你注册一个感应区域后,当有实体进入或离开感应区域时会触发。在空白处右键,输入 **有实体** ,即可找到此事件,点击创建节点。
![image-20220904073627091](./image/3_2.png)
在空白处右键,输入“有实体”,即可找到此事件,点击创建节点。
![image-20220904073627091](./image/3_3.png)
可以看到这个事件要求注册感应区域我们需要注册一个实体附近1x3x1的区域它才会工作。右键空白区域输入“注册感应区域”可调出注册接口节点。
![image-20220904073627091](./image/3_4.png)
点击创建节点。
![image-20220904073627091](./image/3_5.png)
你会看到,注册接口需要传入一些参数,用以描述 **在哪个维度从哪个点到哪个点的一块区域当哪些类型的实体进入或离开时触发NewOnEntityAreaEvent事件**
由于这些参数需要较为复杂的构建,所以建议创建一个单独的接口来做这件事,在左侧自定义接口处点击 **+** 号,创建一个新的自定义接口。
![image-20220904073627091](./image/3_6.png)
命名为 **f_InitArea**
![image-20220904073627091](./image/3_7.png)
备注为 **构建更换职业触发区域**,分组填写 **初始化变量接口**
![image-20220904073627091](./image/3_8.png)
![image-20220904073627091](./image/3_9.png)
现在双击 **f_InitArea** ,你会进入到子接口编辑:
![image-20220904073627091](./image/3_10.png)
右键输入 **注册实体感应区域** 调出刚才的节点,创建节点。
![image-20220904073627091](./image/3_11.png)
将节点连接起来:
![image-20220904073627091](./image/3_12.png)
接下来开始编辑参数因为地图实体位于主世界维度ID类型选择int值输入0。
![image-20220904073627091](./image/3_13.png)
感应区域名为自取名,类型选择 **str** ,输入 **PlayerChoiceHealerArea** ,则事件通知时将传此名称。
![image-20220904073627091](./image/3_14.png)
实体类型,需要传入欲监听的实体类型,即你希望哪些实体走进/走出此区域时收到事件通知,此情景下我们是想监听玩家靠近实体,所以需要传入玩家枚举。有两个办法,一是查阅<a href="../../../mcdocs/1-ModAPI/枚举值/EntityType.html?catalog=1">EntityType枚举文档</a>,二是使用构造枚举节点传入。在空白处右键,输入 **构造 实体类型** ,点击创建节点。
![image-20220904073627091](./image/3_15.png)
点击节点,在右侧的属性面板找到 **实体类型** ,选择 **玩家**
![image-20220904073627091](./image/3_16.png)
**构造节点的输出值****注册接口节点的实体类型输入端口** 连接。
![image-20220904073627091](./image/3_17.png)
最后比较复杂的一个参数 **区域范围** ,通过<a href="../../../mcguide/20-玩法开发/14-预设玩法编程/13-PresetAPI/预设对象/通用/SDK接口封装SdkInterface.html#registerentityaoievent">查阅注册感应区域接口文档</a>可知此参数通过AABB两个坐标描述一个正方形区域依次为minX, minY, minZ, maxX, maxY, maxZ
![image-20220904073627091](./image/3_18.png)
将这6个数字依次排列为一个列表即可组成此参数。所以右键调出 **构造列表** 节点:
![image-20220904073627091](./image/3_19.png)
在右侧的节点属性面板中长度设置为6
![image-20220904073627091](./image/3_20.png)
**构造列表的输出** 连接到 **区域范围** - **参数输入** 就得到了一个往区域范围传入由6个数字构成的列表作为参数的节点如下
![image-20220904073627091](./image/3_21.png)
理论上,若你按顺序填写绝对坐标值(例如你到游戏中记录女巫实体的左上角和右下角两个实际坐标),此注册接口即可生效。
但那样做略显不优雅,且我们希望此零件无论挂到哪个实体预设下都可以正常工作,而不需要再额外改动一些东西。
所以可以获取父预设实体在游戏中的实际位置,复制两份,一份均+1作为max值一份均-1作为min值这样就等于为实体划出一个框框区域。
![image-20220904073627091](./image/3_18.png)
具体怎么做呢?首先调出 **获取世界位置** 接口节点:
![image-20220904073627091](./image/3_22.png)
获取到坐标后坐标是一个参数需要拆成x、y、z三个数字调出 **拆分三维坐标** 节点:
![image-20220904073627091](./image/3_23.png)
右键删除原先的输入连接线。
![image-20220904073627091](./image/3_24.png)
连接新的连接线。
![image-20220904073627091](./image/3_25.png)
连接 **获取世界位置输出的XYZ轴坐标****拆分三维坐标节点的坐标输入** 。你会发现获取到的世界位置坐标拆分为x、y、z后距离传入构造列表仅一步之遥。
![image-20220904073627091](./image/3_26.png)
还需要将一组变为两组依次为minX, minY, minZ, maxX, maxY, maxZ进行加减运算min组-1max组+1。右键 **+** 调出多个运算节点。
![image-20220904073627091](./image/3_27.png)
将拆分后的X、Y、Z坐标连接到运算节点的变量A输入端口。
![image-20220904073627091](./image/3_28.png)
然后在变量B端口输入三个-1负一即可为X、Y、Z坐标分别减一。
![image-20220904073627091](./image/3_29.png)
将运算结果输出端口与构造节点输入端口按下图依次相连:
![image-20220904073627091](./image/3_30.png)
现在完成了min组如法炮制 创建三个新的 **+** 运算节点运算值输入正1。考虑到女巫模型身高Y值可由1改为3增加判断准确率。
![image-20220904073627091](./image/3_31.png)
将拆分三维坐标输出的X、Y、Z再拉一次到第二组运算节点输入处。
![image-20220904073627091](./image/3_32.png)
**运算节点的结果输出** 拉到 **构造列表max组输入端口**
![image-20220904073627091](./image/3_33.png)
最后结果如下图。
![image-20220904073627091](./image/3_34.png)
至此,你获得了一个完美的注册感应区域接口。
点击左上角的Graph返回蓝图入口。
![image-20220904073627091](./image/3_35.png)
你需要在入口调用 **f_InitArea** ,使之生效。右键输入 **f_** 创建节点。
![image-20220904073627091](./image/3_36.png)
按照下图连接,此接口将在服务端初始化时被调用,然后执行上面制作的一连串逻辑,对感应区域进行注册。
![image-20220904073627091](./image/3_37.png)
注册好后,我们可以正式开始检测玩家走进感应区域了。在 **Graph** 空白处右键输入 **NewOnEntityAreaEvent** ,调出节点。
![image-20220904073627091](./image/3_38.png)
![image-20220904073627091](./image/3_39.png)
可见,此事件提供了 **逻辑入口****注册感应区域名称****进入感应区域的实体列表****离开感应区域的实体列表** 这三个参数。
-**注册感应区域名称** 和上面注册时拟定的 **PlayerChoiceHealerArea** 一致,则说明玩家触发的是刚才注册的那个区域,要进行处理,否则则不需要处理。
- **进入感应区域的实体列表** 将会传进一个由玩家/实体id构成的列表以便你对这些玩家做出操作。
- **离开感应区域的实体列表** 参数不需要用到,玩家离开区域不是我们所关心的。
首先要判断第一点, **注册感应区域名称** 和上面注册时拟定的 **PlayerChoiceHealerArea** 是否一致。右键输入 **=** 调出判断节点。
![image-20220904073627091](./image/3_40.png)
按照下图连接参数1连接 **注册感应区域名称** 参数2选择**str**类型,输入 **PlayerChoiceHealerArea**
![image-20220904073627091](./image/3_41.png)
判断节点将判断参数1和参数2是否相等并在结果端口输出一个布尔值是/否)。右键输入 **布尔值比较** 调出节点:
![image-20220904073627091](./image/3_42.png)
![image-20220904073627091](./image/3_43.png)
要判断是否相等节点输出的结果是否为 **是** ,若为 **是** ,则继续执行下面的流程。按照下图连接:
![image-20220904073627091](./image/3_44.png)
变量B选择bool布尔值类型并打勾。
![image-20220904073627091](./image/3_45.png)
此时,若 **注册感应区域名称** 等于 **PlayerChoiceHealerArea** ,则 **=** 后面的流程会被执行。
![image-20220904073627091](./image/3_46.png)
给靠近实体的玩家打印一句话, **你尝试选择治疗师职业** 。右键输入 **NotifyOneMessage** 调出节点。
![image-20220904073627091](./image/3_47.png)
可见此接口有两个主要参数来执行功能很好理解玩家id用来指定给哪个玩家发送消息内容更是字面意思。
![image-20220904073627091](./image/3_48.png)
可是事件给来的是玩家列表接口需要的是玩家id并不能直接连接。
![image-20220904073627091](./image/3_49.png)
此时需要用到循环,例如有三个玩家同时靠近实体,列表内容为[玩家A玩家B玩家C],则循环会为这三个玩家同时发送聊天框消息。
右键调出条件循环遍历节点。
![image-20220904073627091](./image/3_50.png)
![image-20220904073627091](./image/3_51.png)
按照下图连接。
![image-20220904073627091](./image/3_52.png)
消息内容选择 **str** 类型,输入 **你尝试选择治疗师职业** ,最后得到:
![image-20220904073627091](./image/3_53.png)
将制作好的这个蓝图零件挂接到治疗师实体预设下,功能即可生效。
![image-20220904073627091](./image/3_54.png)
现在测试一下,当玩家靠近实体时,将会收到消息通知。
![image-20220904073627091](./image/3_55.gif)
接下来要为靠近实体的玩家不仅发送消息通知继续添加tp到战斗场地、给予物品、给予状态效果的功能。
为了方便整理逻辑,像刚才一样在左侧自定义接口栏创建 **f_TurnHealer** 接口用于执行tp、给予物品、给予状态效果。
![image-20220904073627091](./image/3_56.png)
备注 **更换玩家职业** ,分组 **逻辑接口** ,点击输入参数右边的 **+** 号,创建 **playerId** 输入参数,表示为哪个玩家更换职业。
![image-20220904073627091](./image/3_57.png)
参数名输入 **playerId** ,类型选择 **Any**
![image-20220904073627091](./image/3_58.png)
双击进入接口,你会看到此接口在 **In** 逻辑入口时,接受一个 **playerId** 参数。
![image-20220904073627091](./image/3_59.png)
右键空白处,输入 **ChangePlayerDimension** ,调出传送玩家接口节点。
![image-20220904073627091](./image/3_60.png)
可以看到,此接口需要 **玩家Id****维度Id****传送坐标** 三个参数,才能实现功能。
**玩家Id** 就是传入的 **playerId** ,直接连接即可。
**维度Id** 选择 **int** 输入0指向主世界的战斗区域。
![image-20220904073627091](./image/3_61.png)
传送坐标需要一个三维坐标传入,战斗区域在地图上的坐标为(352, 83, 442)
右键输入 **合并三维坐标** ,调出节点。
![image-20220904073627091](./image/3_62.png)
类型选择int输入 **(352, 83, 442)** 。
![image-20220904073627091](./image/3_63.png)
**合并节点输出的坐标** 连接到 **传送接口节点的输入坐标**
![image-20220904073627091](./image/3_64.png)
创建执行连线。
![image-20220904073627091](./image/3_65.png)
传送玩家到战斗场地后,还要给予职业相关的物品。右键输入 **SpawnItemToPlayerInv** 调出生成物品到玩家背包接口节点。
![image-20220904073627091](./image/3_66.png)
将此节点插入到传送玩家逻辑后面。
![image-20220904073627091](./image/3_67.png)
![image-20220904073627091](./image/3_68.png)
可以看到,至少需要 **物品信息字典****玩家Id** 参数。玩家Id从最左边输入 **playerId** 连接,<a href="../../../mcguide/20-玩法开发/10-基本概念/1-我的世界基础概念.html#物品信息字典">物品信息字典</a>需要构造。右键输入 **构造物品信息字典** ,调出节点。
![image-20220904073627091](./image/3_69.png)
创建节点后,在右侧属性栏可看到,有物品一项可视化选择。点击文件夹即可选择物品。
![image-20220904073627091](./image/3_70.png)
![image-20220904073627091](./image/3_71.png)
搜索选择 **木剑** ,并按确定。
![image-20220904073627091](./image/3_72.png)
按照下图创建数据连线,即可实现传送玩家后,往玩家背包发放一把木剑。
![image-20220904073627091](./image/3_73.png)
按照 **构造物品信息字典** + **调用生成物品到背包接口** 的组合,为以下职业设计物品创建节点,并插入到接口逻辑尾部:
- 护甲:铁胸甲,铁护腿,铁靴子。
- 武器:木剑。
- 物品喷溅型伤害药水x3喷溅型治疗药水x3喷溅型生命恢复x1。
![image-20220904073627091](./image/3_74.png)
需要注意的是, **一定** 要在 **构造物品字典** 节点的属性面板设置 **物品数量** 它默认是0必须要设成1或更多数量。
![image-20220904073627091](./image/3_75.png)
创建连接线。
![image-20220904073627091](./image/3_76.png)
接下来,调出 **为实体添加状态效果** 节点。
![image-20220904073627091](./image/3_77.png)
编辑参数,名称选择 **str** ,输入 **slow_falling** (缓降),持续时间选择 **int** 输入5秒粒子效果看心情。 **实体Id** 从接口 **In****playerId** 拉。
![image-20220904073627091](./image/3_78.png)
创建连接线,将 **添加状态效果** 节点插入接口逻辑尾部。
![image-20220904073627091](./image/3_79.png)
最后,调出发送消息节点。
![image-20220904073627091](./image/3_80.png)
告诉玩家 **【你变成了治疗师!】** ,同上,插入逻辑尾部。
![image-20220904073627091](./image/3_81.png)
![image-20220904073627091](./image/3_82.png)
最后,回到 **Graph** ,为靠近实体的玩家调用 **f_TrunHealer** 接口。右键调出 **f_TrunHealer** 接口。
![image-20220904073627091](./image/3_83.png)
在发送 **你尝试选择治疗师** 节点后,连接 **f_TrunHealer** 接口。注意要传入 **playerId** 参数,从前面的循环遍历节点输出值处连接。
你可能会疑惑,为什么要先告诉玩家尝试选择,然后再调用切换职业接口。不要忘了,我们在制作内购商品,并不是所有玩家都购买了此付费职业。在发货章节,我们将在这里插入权限判断,若玩家没有购买商品,就不执行 **f_TrunHealer** ,而是告诉玩家你没有购买。
![image-20220904073627091](./image/3_84.png)
保存蓝图,运行游戏。
可以看到靠近女巫实体后玩家被传送到战斗区域并被赋予装备、buff。
![image-20220904073627091](./image/3_85.png)
## Python程序向实战-制作会员特效
除了购买职业权限,我们当然还可以售卖一些特殊外观服务,下面简单做一个绑定在玩家骨骼上的特效。为了减少工作量,进入**开发者工具台**的内容库,下载一个特效包。
<img src="./image/1_22.png" alt="image-20220906132847402" style="zoom:50%;" />
在编辑器打开时,点击导入此特效包。
![image-20220906132906385](./image/1_23.png)
进入特效编辑器。
![image-20220906134149945](./image/1_24.png)
在资源管理,点击中国版特效,挑选一个特效文件:
<img src="./image/1_25.png" alt="image-20220906152305916" style="zoom:50%;" />
我们要浏览这个特效将其拖拽至模型挂接栏挂接到bottom骨骼下
![image-20220906152333000](./image/1_26.png)
挂接好后,点击时间轴的播放按钮。
<img src="./image/1_27.png" alt="image-20220906134505434" style="zoom:50%;" />
可以看到特效播放出来了。
![effect1](./image/effect1.gif)
但是我们希望这个光环在玩家脚底下,所以在 **右侧属性栏** 找到 **渲染** - **粒子朝向模式** ,选择 **水平**
<img src="./image/1_29.png" alt="image-20220906152615676" style="zoom:50%;" />
可以看到特效变成了正确的脚底光环。
![image-20220906152649324](./image/1_30.png)
好了,准备好特效后,返回预设编辑器,创建新的特效预设。
![image-20220906135142712](./image/1_31.png)
![image-20220906135149553](./image/1_32.png)
在属性栏选择特效文件,也就是我们刚才挂载的特效文件。
<img src="./image/1_33.png" alt="image-20220906152929383" style="zoom:50%;" />
由于这是商品,需要选择性对已购买的玩家生效,所以我们取消勾选属性栏的自动播放。
<img src="./image/1_34.png" alt="image-20220906153421773" style="zoom:50%;" />
**特效预设** 挂接到 **玩家预设** 下。
![image-20220906153028935](./image/1_35.png)
可以看到玩家的脚底有了光环,这里有播放是因为默认勾选了浏览,游戏里是不会播放的。
![image-20220906153113282](./image/1_36.png)
由于脚底光环很容易和地面重合导致深度检测抽风所以最好这类特效增加一些y轴坐标变换。
<img src="./image/1_37.png" alt="image-20220906154556955" style="zoom:50%;" />
那么上面取消勾选了自动播放现在特效预设即使挂载也不会播放就需要一个零件来根据玩家是否有权限控制特效的播放。创建一个VipEffect空零件命名为会员特效零件编写代码
```python
def InitClient(self):
"""
@description 客户端的零件对象初始化入口
"""
PartBase.InitClient(self)
self.ListenForEngineEvent(ClientEvent.UiInitFinished, self, self.COnUIInitFinished)
def COnUIInitFinished(self, e):
# 这里就可以判断权限,播放特效
self.GetParent().ToEffectPreset().Play()
```
将会员特效零件挂载到特效预设下:
![image-20220906154144389](./image/1_38.png)
返回玩家特效,可以看到玩家预设下有特效预设,特效预设下有一个控制播放的零件:
<img src="./image/1_39.png" alt="image-20220906154236264" style="zoom:50%;" />
进入游戏,测试效果:
![image-20220906154726362](./image/1_40.png)
## 逻辑编辑器程序向实战-制作会员特效
除了购买职业权限,我们当然还可以售卖一些特殊外观服务,下面简单做一个绑定在玩家骨骼上的特效。为了减少工作量,进入**开发者工具台**的内容库,下载一个特效包。
<img src="./image/1_22.png" alt="image-20220906132847402" style="zoom:50%;" />
在编辑器打开时,点击导入此特效包。
![image-20220906132906385](./image/1_23.png)
进入特效编辑器。
![image-20220906134149945](./image/1_24.png)
在资源管理,点击中国版特效,挑选一个特效文件:
<img src="./image/1_25.png" alt="image-20220906152305916" style="zoom:50%;" />
我们要浏览这个特效将其拖拽至模型挂接栏挂接到bottom骨骼下
![image-20220906152333000](./image/1_26.png)
挂接好后,点击时间轴的播放按钮。
<img src="./image/1_27.png" alt="image-20220906134505434" style="zoom:50%;" />
可以看到特效播放出来了:
![effect1](./image/effect1.gif)
但是我们希望这个光环在玩家脚底下,所以在 **右侧属性栏** 找到 **渲染** - **粒子朝向模式** ,选择水平。
<img src="./image/1_29.png" alt="image-20220906152615676" style="zoom:50%;" />
可以看到特效变成了正确的脚底光环:
![image-20220906152649324](./image/1_30.png)
好了,准备好特效后,返回预设编辑器,创建新的特效预设。
![image-20220906135142712](./image/1_31.png)
![image-20220906135149553](./image/1_32.png)
在属性栏选择特效文件,也就是我们刚才挂载的特效文件:
<img src="./image/1_33.png" alt="image-20220906152929383" style="zoom:50%;" />
由于这是商品,需要选择性对已购买的玩家生效,所以我们取消勾选属性栏的自动播放:
<img src="./image/1_34.png" alt="image-20220906153421773" style="zoom:50%;" />
**特效预设** 挂接到 **玩家预设** 下:
![image-20220906153028935](./image/1_35.png)
可以看到玩家的脚底有了光环,这里有播放是因为默认勾选了浏览,游戏里是不会播放的。
![image-20220906153113282](./image/1_36.png)
由于脚底光环很容易和地面重合导致深度检测抽风所以最好这类特效增加一些y轴坐标变换
<img src="./image/1_37.png" alt="image-20220906154556955" style="zoom:50%;" />
那么上面取消勾选了自动播放,现在特效预设即使挂载也不会播放,就需要一个零件来根据玩家是否有权限控制特效的播放。
控制播放很简单,特效预设已经有`Play()``Stop()`这两个开始/停止播放接口,只需要一个挂接在特效预设下的零件,负责像按一个灯的开关一样,拨打特效的播放开关即可。
创建一个VipEffect蓝图零件命名为会员特效零件进入逻辑编辑器。
在Graph空白处右键输入 **uiinit** ,调出监听界面初始化完成节点。
![image-20220904073627091](./image/3_86.png)
我们现在要在界面初始化好后,按下让特效预设开始播放的 **开关**
继续依次调出 **获取父对象****转换为特效预设****播放特效** 接口节点。
![image-20220904073627091](./image/3_87.png)
![image-20220904073627091](./image/3_88.png)
![image-20220904073627091](./image/3_89.png)
全部调出完后,按照下图连接:
![image-20220904073627091](./image/3_90.png)
连接好后,退出逻辑编辑器,将会员特效零件挂载到特效预设下:
![image-20220906154144389](./image/1_38.png)
返回玩家特效,可以看到玩家预设下有特效预设,特效预设下有一个控制播放的零件:
<img src="./image/1_39.png" alt="image-20220906154236264" style="zoom:50%;" />
进入游戏,测试效果:
![image-20220906154144389](./image/3_91.png)