--- 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钻石(不可重复购买) - 类型:购买后持久化,永久生效 image-20220903093522098 仅有增值职业还不够,我们还可以售卖一些单局消耗品,最简单的当然是原版的物品,这里使用demo自带的一个补给类物品吧: - 美味鲜菇(testmap:testitem1) - 售价:10绿宝石,可重复购买。 - 类型:单局消耗品。 ![image-20220906160311234](./image/1_6.png) 有了永久商品、单局消耗品,我们再设计一个带有效期的定时商品。特效是个不错的选择,因为特效不会影响PVP中的平衡性,维持良好的作品评分,又可以为付费玩家提供很好的游戏体验。 - 脚底光圈 - 售价:30钻石/1天(可重复购买以叠加时长)。 - 类型:持久化,定期商品。 image-20220906160208593 ## Python程序向实战-制作新职业 由于该demo原本没有治疗师职业,所要简单制作一下,首先创建一个实体预设。 ![image-20220904073700649](./image/1_8.png) 选择女巫模板,因为要借用它的模型,遵循原作者命名规则,命名为testPresetEntity4。 ![image-20220904073750181](./image/1_9.png) 在属性栏勾选免疫伤害,取消勾选保留AI,再清理掉不需要的行为组件,这样让女巫成为一个NPC,站着不会动,作为摆设,也不会攻击玩家。 image-20220904073908374 image-20220904073933268 我们看到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) image-20220904092051635 **触发器不难理解,就是框定一个区域,有任何实体进入、停留、退出这个区域都会被触发器检测到。** 点击新建的零件,属性面板,修改下面的参数: - **区域** :参数如图,触发器挂载在实体预设下时,坐标将会使用相对于预设的坐标。 - **监听** :由于只需要检测玩家进入(即为玩家意图选择此职业),离开和停留都不需要,所以只勾选进入。 - **监听间隔**: 由于原demo使用命令方块做出来的效果是反应非常迅速的,为了不造成割裂感,设置此参数为1。 image-20220904092202193 将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) })), } ``` 这样,治疗师零件的属性面板就出现了如下设置选项: image-20220904103005406 把我们刚才硬编码在代码里的配置应用上去: image-20220904104017464 这样将属性用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,站着不会动,作为摆设,也不会攻击玩家。 image-20220904073908374 image-20220904073933268 我们看到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) 实体类型,需要传入欲监听的实体类型,即你希望哪些实体走进/走出此区域时收到事件通知,此情景下我们是想监听玩家靠近实体,所以需要传入玩家枚举。有两个办法,一是查阅EntityType枚举文档,二是使用构造枚举节点传入。在空白处右键,输入 **构造 实体类型** ,点击创建节点。 ![image-20220904073627091](./image/3_15.png) 点击节点,在右侧的属性面板找到 **实体类型** ,选择 **玩家** 。 ![image-20220904073627091](./image/3_16.png) 将 **构造节点的输出值** 与 **注册接口节点的实体类型输入端口** 连接。 ![image-20220904073627091](./image/3_17.png) 最后比较复杂的一个参数 **区域范围** ,通过查阅注册感应区域接口文档可知,此参数通过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组-1,max组+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** 连接,物品信息字典需要构造。右键输入 **构造物品信息字典** ,调出节点。 ![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程序向实战-制作会员特效 除了购买职业权限,我们当然还可以售卖一些特殊外观服务,下面简单做一个绑定在玩家骨骼上的特效。为了减少工作量,进入**开发者工具台**的内容库,下载一个特效包。 image-20220906132847402 在编辑器打开时,点击导入此特效包。 ![image-20220906132906385](./image/1_23.png) 进入特效编辑器。 ![image-20220906134149945](./image/1_24.png) 在资源管理,点击中国版特效,挑选一个特效文件: image-20220906152305916 我们要浏览这个特效,将其拖拽至模型挂接栏,挂接到bottom骨骼下: ![image-20220906152333000](./image/1_26.png) 挂接好后,点击时间轴的播放按钮。 image-20220906134505434 可以看到特效播放出来了。 ![effect1](./image/effect1.gif) 但是我们希望这个光环在玩家脚底下,所以在 **右侧属性栏** 找到 **渲染** - **粒子朝向模式** ,选择 **水平** 。 image-20220906152615676 可以看到特效变成了正确的脚底光环。 ![image-20220906152649324](./image/1_30.png) 好了,准备好特效后,返回预设编辑器,创建新的特效预设。 ![image-20220906135142712](./image/1_31.png) ![image-20220906135149553](./image/1_32.png) 在属性栏选择特效文件,也就是我们刚才挂载的特效文件。 image-20220906152929383 由于这是商品,需要选择性对已购买的玩家生效,所以我们取消勾选属性栏的自动播放。 image-20220906153421773 将 **特效预设** 挂接到 **玩家预设** 下。 ![image-20220906153028935](./image/1_35.png) 可以看到玩家的脚底有了光环,这里有播放是因为默认勾选了浏览,游戏里是不会播放的。 ![image-20220906153113282](./image/1_36.png) 由于脚底光环很容易和地面重合,导致深度检测抽风,所以最好这类特效增加一些y轴坐标变换。 image-20220906154556955 那么上面取消勾选了自动播放,现在特效预设即使挂载也不会播放,就需要一个零件来根据玩家是否有权限控制特效的播放。创建一个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) 返回玩家特效,可以看到玩家预设下有特效预设,特效预设下有一个控制播放的零件: image-20220906154236264 进入游戏,测试效果: ![image-20220906154726362](./image/1_40.png) ## 逻辑编辑器程序向实战-制作会员特效 除了购买职业权限,我们当然还可以售卖一些特殊外观服务,下面简单做一个绑定在玩家骨骼上的特效。为了减少工作量,进入**开发者工具台**的内容库,下载一个特效包。 image-20220906132847402 在编辑器打开时,点击导入此特效包。 ![image-20220906132906385](./image/1_23.png) 进入特效编辑器。 ![image-20220906134149945](./image/1_24.png) 在资源管理,点击中国版特效,挑选一个特效文件: image-20220906152305916 我们要浏览这个特效,将其拖拽至模型挂接栏,挂接到bottom骨骼下: ![image-20220906152333000](./image/1_26.png) 挂接好后,点击时间轴的播放按钮。 image-20220906134505434 可以看到特效播放出来了: ![effect1](./image/effect1.gif) 但是我们希望这个光环在玩家脚底下,所以在 **右侧属性栏** 找到 **渲染** - **粒子朝向模式** ,选择水平。 image-20220906152615676 可以看到特效变成了正确的脚底光环: ![image-20220906152649324](./image/1_30.png) 好了,准备好特效后,返回预设编辑器,创建新的特效预设。 ![image-20220906135142712](./image/1_31.png) ![image-20220906135149553](./image/1_32.png) 在属性栏选择特效文件,也就是我们刚才挂载的特效文件: image-20220906152929383 由于这是商品,需要选择性对已购买的玩家生效,所以我们取消勾选属性栏的自动播放: image-20220906153421773 将 **特效预设** 挂接到 **玩家预设** 下: ![image-20220906153028935](./image/1_35.png) 可以看到玩家的脚底有了光环,这里有播放是因为默认勾选了浏览,游戏里是不会播放的。 ![image-20220906153113282](./image/1_36.png) 由于脚底光环很容易和地面重合,导致深度检测抽风,所以最好这类特效增加一些y轴坐标变换: image-20220906154556955 那么上面取消勾选了自动播放,现在特效预设即使挂载也不会播放,就需要一个零件来根据玩家是否有权限控制特效的播放。 控制播放很简单,特效预设已经有`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) 返回玩家特效,可以看到玩家预设下有特效预设,特效预设下有一个控制播放的零件: image-20220906154236264 进入游戏,测试效果: ![image-20220906154144389](./image/3_91.png)