程序丨教程:制作一个类似《神秘海域》的装备系统(上)
翻译:王成林(麦克斯韦的麦斯威尔 )
审校:黄秀美(厚德载物)
《神秘海域》中的装备系统设计的很好,有人要求我使用第三人称角色制作一篇关于类似《神秘海域》中的装备系统的教程。在这篇教程中你将学到如何:
· 将武器附加给第三人称角色
· 建立简单的物品栏系统管理物品
· 丢弃和装备物品
· 使用UMG和物品栏管理器与物品进行通信
该系统基于事件,因此它的效率很高。
该系统非常简单!
本教程使用了虚幻引擎4.10版本。确保你使用相同版本的引擎。
直播流
你可以观看关于该教程建立的直播流:
https://v.qq.com/txp/iframe/player.html?vid=c0399zzccju&width=500&height=375&auto=0
信息
我使用了军事武器白银包中的武器,对于本教程它不是必须的,但是如果你在开发和军事武器相关的游戏我推荐你使用这个包。
本项目以第三人称模板为基础,你可以在新建项目时找到它。
我在市场中发现了相似的系统,但是我不知道它是否是基于事件的。
创建插槽(Socket)
我们首先为角色加入插槽。打开SK_Mannequin然后打开骨骼。添加这些插槽:
Right,left,back和backsecond被用作武器的附加点。其余为武器被装备时的位置。
基础类
整个系统将使用两个基础类:
· BP_BaseWeapon
· InventoryManager
新建一个继承自Actor的蓝图,将其命名为BP_BaseWeapon。然后新建一个继承自Actor组件的蓝图,命名为InventoryManager。
先不管它们,我们添加一些基本数据。
AmmoType枚举
新建一个名为AmmoInfo的枚举(Enum),然后加入以下:
· Pistol
· Rifle
· Launcher
该枚举被用来决定武器正在使用哪一种子弹。
AttachPoint枚举
建立另一个名为AttachPoint的枚举并加入以下:
· Right
· Left
· Back
· BackSecond
该枚举被用来决定武器可以佩戴在身体的哪个部位。
AttachInfo结构体
新建一个名为AttachInfo的结构体,它应包含:
· inUse(bool)
· AttachPoint(AttachPoint)
· WeaponRef(BP_BaseWeapon)
AmmoInfo结构体
添加另一个名为AmmoInfo的结构体然后加入以下:
· AmmoType(AmmoType枚举)
· AmmoCount(int)
该结构体被用于储存我们所拥有的各类型子弹的数量
MyLibrary
新建一个名为MyLibrary的函数库。
所有类都能调用库函数,因此它们超级有用。需要注意一点——它们没有世界语境(World Context),意味着像GetPlayerController,GetGameState,GetPlayerPawn之类的节点不能正常工作因为它们需要世界语境。如果你需要世界语境的话你应该将它们传递给函数。函数库可用于储存你在不同类中使用的数学函数。
在我们的例子中我们将建立一些函数,它们根据AttachPoint枚举返回插槽名称。
打开MyLibrary然后加入两个函数。
GetSocketNameByPoint
· 输入:Type(AttachPoint)
· 输出:SocketName(name)
它应该为纯函数
然后加入GetSocketPointByName:
· 输入:Name(name)
· 输出:Type(AttachPoint)
它也应该是纯函数
Switch中的名称应该和在角色网格物体中加入的插槽名称相同。
BP_BaseWeapon
现在我们可以开始建立基础类了。打开BP_BaseWeapon然后加入这些组件:
· 名为Box的BoxCollision(盒状碰撞)。同时加入它的OnBeginOverlap事件(该组件应为根组件)
· 名为SkeletalMesh的Skeletal Mesh(骨骼网格物体)。
现在打开事件图表然后加入这些变量:
在事件图表中新建一个名为AttachWeaponToCharacter的自定义事件:
· 输入:SocketName(Name)
目前我们先不管BP_BaseWeapon——我们后面再返回这里。
InventoryManager
打开InventoryManager然后加入这些变量:
SocketStates:
我们定义了该物品栏默认可以支持的“插槽”。
Ammo:
它存储了各种类型弹药的默认数量。
打开事件图表然后加入一个事件调度器(Event Dispatcher):
· OnInventoryStateUpdated。每当inventorymanager中发生变化时它都会被调用——这样我们可以和拥有InventoryManager的角色进行通信。
函数:
现在我们可以加入一些基本的函数了。新建一个名为FindFreeSocket的函数:
· 输入:For Weapon(BP_BaseWeapon引用)
· 输出:FreePoint(AttachPoint)
· 输出:Found?(bool)
· 名为Local_FoundSocket的本地变量(bool)
· 名为Local_SocketPoint的本地变量(AttachPoint)
它检查了武器插槽是否匹配插槽状态以及插槽状态是否为可用。我们不希望将武器附加在已经有武器附加的插槽上。
建立另一个名为MarkSocketAsInUse的函数:
· 输入:Point(AttachPoint)
· 输入:WeaponRef(BP_BaseWeapon引用)
· 本地变量:Local_Index(int)
· 本地变量:Local_NewAttachInfo(AttachInfo)
该函数寻找SocketStates中相匹配的插槽然后将InUse标记为真。这是在蓝图中使用数组结构体的最佳方法了。储存索引,储存新的结构体信息然后设置数组元素。在独立窗口/移动端和已烘焙的编译模式下都可以正常运行。听上去很复杂但是其实超级简单。
现在建立另一个名为MarkSocketAsAvailable的函数,它的功能相同,只是将InUse标记为否:
· 输入:Point(AttachPoint)
· 本地变量:Local_Index(int)
· 本地变量:Local_NewAttachInfo(AttachInfo)
我在这里没有使用WeaponReference,因为我希望将它从SocketStates中清除出去因为我们要将插槽标记为可用于附加。
建立另一个名为AddAmmo的函数:
· 输入:Type(AmmoType)
· Ammount(int)
这是关于如何使用结构体数组的最简单的例子了。我们想要添加某类武器的弹药。我在数组中搜索匹配的类型,然后储存索引并储存更新后的子弹信息。然后设置数组元素。
例如我们想要增加5颗手枪子弹。它会在Ammo数组中搜索Pistol类型,然后增加子弹数并设置数组元素。
建立另一个名为GetCurrentAmmo的函数:
· 输入:ForType(AmmoType)
· 输出:Ammo(int)
· 可以为纯函数
新建另一个名为SetEquipedWeapon的函数:
· 输入:Weapon(BP_BaseWeapon引用)
该函数只是设置了EquippedWeapon变量。
帮助性建议:当我需要设置不同类中的变量时,我一定使用函数来修改变量。在已烘焙编译模式中设置不同类中的变量会导致崩溃,因为这会产生循环依赖。调用设置变量的函数是个不错的编程方法,你也应该在蓝图中使用该方法。
新建另一个名为ClearEquipedWeapon的函数:
新建另一个名为GetCurrentEquippedWeapon的函数:
· 输出:Weapon(BP_BaseWeapon 引用)
· 输出:Equipping(bool)
· 函数可以为纯函数
该函数返回当前装备的武器和信息,如果有效的话。(有效意味着当前正在装备着某武器)
现在创建一些最重要的函数。新建一个名为AddToInventory的函数:
· 输入:Weapon(BP_BaseWeapon引用)
这很简单。它搜索了可以装备武器的插槽,并检查它是否可用。如果是的话,更新该插槽,保存武器的引用并对武器调用Attach。
建立最后一个名为RemoveFromInventory的函数:
· 输入:Weapon(BP_BaseWeapon引用)
同样它非常简单。在Socket States中寻找相配的武器,然后将它清除以便再次使用。
事件
我们已经添加了所有需要的函数,现在我们添加三个事件。
新建一个名为Equip的事件,它含有一个输入,类型为BP_BaseWeapon引用:
以及UnEquip事件:
最后是DropItem,有一个输入,类型为BP_BaseWeapon引用:
这就是InventoryManager中的所有内容了!现在我们需要返回BP_BaseWeapon。
确保你已经在第三人称角色蓝图中添加了InventoryManager组件!
返回BP_BaseWeapon
返回BP_BaseWeapon。如果你还没有添加Box的OnComponentBeginOverlap事件现在添加它:
这里我们开始向物品栏添加物品了。
现在添加Equip和UnEquip事件:
这应该无需解释吧。
最后一个事件——Drop, 我们将其作为一个占位符。
这就是BP_BaseWeapon中的所有内容了!
创建武器
现在我们创建继承自BP_BaseWeapon的Weapon_Rifle, Weapon_Launcher和Weapon_Pistol蓝图并设置它们的变量。
Weapon_Pistol:
Weapon_Rifle:
Weapon_Launcher:
现在将这些武器放在你的关卡中以便你可以捡起它们!
创建Ammo Pickups(可拾取子弹)
新建一个继承自Actor的蓝图,将其命名为BP_AmmoPickup。它应该有两个变量:
组件:
· 名为StaticMesh的静态网格物体组件。
· 名为Box的盒状碰撞。加入它的OnComponentBeginOverlap事件
我们在构造脚本中根据AmmoType改变可拾取物体的网格物体。
你可以使用不同的网格物体。
BoxOverlap会增加子弹数:
就是这样了。现在你可以拾取子弹了!
创建UMG
Widget_ItemActions
新建一个名为Widget_ItemActions的控件(Widget)。你在这里可以找到它的层级:
https://v.qq.com/txp/iframe/player.html?vid=f03993vsbzk&width=500&height=375&auto=0
打开事件图表并添加这些变量:
添加一个名为SetInvManager的函数,它有一个InventoryManager引用类型的输入:
添加另一个名为SetItemRef的函数,它有一个BP_BaseWeapon引用类型的输入:
添加一个新的自定义事件,名为UpdateWidget:
在OnClicked(Button_Equip)中:
这里调用了物品和管理器中的equip和unequip事件。然后在OnClicked(Button_Drop)中对武器和管理器调用Drop:
现在添加Tick函数来决定角色是否点击了控件以外的区域,是的话我们就可以关闭它了:
目前(UE4.10)还不能在控件中获取输入。你需要使用玩家控制器(Player Controller)。
这是该控件的所有内容了!
明天将放出这个教程的下半部分,敬请期待!
今日推荐
添加小编微信,可享双重福利
1.加入GAD程序猿交流基地
获取行业干货资讯,观看大牛分享直播
2.直接领取60G独家程序资料库,地址在小编朋友圈
包括腾讯内部分享、文章教程、视频教程等全套资料
↓长按添加小编GAD苏苏↓