Skip to content

Latest commit

 

History

History
1135 lines (700 loc) · 74.7 KB

File metadata and controls

1135 lines (700 loc) · 74.7 KB

十四、产生玩家投射物

概观

在本章中,您将了解Anim NotifiesAnim States,它们可以在动画蒙太奇中找到。您将使用 C++ 编写自己的Anim Notify代码,并在Throw动画蒙太奇中实现该通知。最后,您将了解视觉和听觉效果,以及这些效果如何在游戏中使用。

到本章结束时,您将能够在蓝图和 C++ 中玩动画蒙太奇,并知道如何使用 C++ 和UWorld类将对象繁殖到游戏世界中。游戏中的这些元素将被赋予音频和视觉成分,作为一层额外的润色,你的SuperSideScroller玩家角色将能够投掷摧毁敌人的投射物。

简介

在上一章中,你通过创建一个行为树来让敌人从你创建的BP_AIPoints演员中随机选择点,从而在敌人的人工智能方面取得了很大的进步。这给了SuperSideScroller游戏更多的生命,因为你现在可以有多个敌人在你的游戏世界里移动。此外,您还学习了虚幻引擎 4 中可用的不同工具,这些工具一起用于制造各种复杂程度的人工智能。这些工具包括Navigation Mesh、行为树和黑板。

现在你有敌人在你的关卡中跑来跑去,你需要允许玩家用你在上一章结束时开始创造的玩家投射物来击败这些敌人。

在本章中,您将学习如何使用UAnimNotify类在Throw动画蒙太奇的特定帧中生成玩家投射物。您还将学习如何将这个新的通知添加到蒙太奇本身,以及如何将一个新的Socket添加到主角色骨骼中,投射物将从该骨骼中产生。最后,您将学习如何使用Particle SystemsSoundCues为游戏增加一层视觉和听觉的润色。

让我们从学习Anim NotifiesAnim Notify States开始这一章。之后,你将通过创建你自己的UAnimNotify职业来弄脏你的手,这样你就可以在Throw动画蒙太奇中产生玩家投射物。

动画通知和动画通知状态

当创建精致复杂的动画时,动画师和程序员需要有一种方法在动画中添加自定义事件,以允许额外的效果、层和功能发生。虚幻引擎 4 中的解决方案是使用Anim NotifiesAnim Notify States

Anim NotifyAnim Notify State的主要区别在于Anim Notify State拥有Anim Notify没有的三个截然不同的事件。这些事件分别是Notify BeginNotify EndNotify Tick,都可以在蓝图或者 C++ 中使用。当涉及到这些事件时,虚幻引擎 4 会确保以下行为:

  • Notify State永远从Notify Begin Event开始。
  • Notify State永远以Notify End Event结束。
  • Notify Tick Event将永远发生在Notify BeginNotify End事件之间。

然而,Anim Notify是一个简单得多的版本,它只使用一个函数Notify(),允许程序员向 notify 本身添加功能。它以开火而忘记的心态工作,这意味着你不需要担心在Notify()事件的开始、结束或中间的任何地方会发生什么。正是由于Anim Notify的这种简单性,并且由于我们不需要Anim Notify State中包含的事件,我们将使用Anim Notify来为超级侧滚游戏生成玩家抛射物。

在继续下面的练习之前,您将在 C++ 中创建自己的自定义Anim Notify,让我们简单讨论一下虚幻引擎 4 默认提供的现有Anim Notifies的一些示例。默认Anim Notifies状态的完整列表可以在下面的截图中看到:

Figure 14.1: The full list of default Anim Notifies provided in Unreal Engine 4

图 14.1:虚幻引擎 4 中提供的默认动画通知的完整列表

本章后面会用到两个Anim Notifies:Play Particle EffectPlay Sound。让我们更详细地讨论这两个问题,以便您在使用时熟悉它们:

  • Play Particle Effect: The Play Particle Effect notify, as the name suggests, allows you to spawn and play a particle system at a certain frame of your animation. As shown in the following screenshot, you have options to change the VFX being used, such as updating the location, rotation, and scale settings of the particle. You can even attach the particle to a specified Socket Name if you so choose:

    Figure 14.2: The Details panel of the Play Particle Effect notify, which  allows you to customize the particle

图 14.2:播放粒子效果通知的详细信息面板,允许您自定义粒子

注意

视觉效果,简称 VFX,是任何游戏的关键元素。在虚幻引擎 4 中,视觉效果是使用编辑器中名为级联的工具创建的。自虚幻引擎 4.20 版本以来,一个名为尼亚加拉的新工具作为免费插件被引入,以提高 VFX 的制作质量和流水线。你可以在这里了解更多关于尼亚加拉的信息。

游戏中使用的一个非常常见的例子是,当玩家行走或奔跑时,使用这种类型的通知在他们的脚下产生污垢或其他效果。能够指定这些效果在动画的哪一帧产生是非常强大的,并且允许你为你的角色创建令人信服的效果。

  • Play Sound: The Play Sound notify allows you to play a Soundcue or Soundwave at a certain frame of your animation. As shown in the following screenshot, you have options to change the sound being used, update its volume and pitch values, and even have the sound follow the owner of the sound via attaching it to a specified Socket Name:

    Figure 14.3: The Details panel of the Play Sound notify, which

图 14.3:播放声音通知的详细信息面板,允许您自定义声音

很像Play Particle Effect notify 给出的例子,Play Sound notify 也可以常用于在角色移动时播放脚步声。通过控制动画时间线上可以播放声音的确切位置,可以创建可信的声音效果。

虽然您不会使用Anim Notify States,但至少知道默认情况下您可以使用的选项仍然很重要,如下图截图所示:

Figure 14.4: The full list of default Anim Notify States provided to you in Unreal Engine 4

图 14.4:虚幻引擎 4 中提供给你的默认动漫通知状态的完整列表

注意

动画序列中不可用的两个Notify状态是蒙太奇通知窗口禁用根动作状态,如前面的截图所示。有关通知的更多信息,请参考以下文档:docs . unrealengine . com/en-US/Engine/Animation/sequence/Notifies/index . html

现在您对Anim NotifyAnim Notify State更加熟悉了,让我们继续下一个练习,您将在 C++ 中创建自己的自定义Anim Notify,您将使用它来生成玩家投射物。

练习 14.01:创建立陶宛通知类

SuperSideScroller游戏中玩家角色将拥有的主要进攻能力是玩家可以向敌人投掷的弹丸。在前一章中,你设置了投射物的框架和基本功能,但是现在,玩家没有办法使用它。为了使产卵或投掷的投射物令人信服,您需要创建一个自定义的Anim Notify,然后将其添加到Throw动画蒙太奇中。这个Anim Notify会让玩家知道是时候产卵了。

执行以下操作创建新的UAnimNotify类:

  1. 在虚幻引擎 4 中,导航至File选项,左键单击选择New C++ Class选项。

  2. Choose Parent Class对话窗口中,搜索AnimNotify和*,左键单击*AnimNotify选项。然后,左键单击Next选项来命名新类。

  3. 命名这个新类Anim_ProjectileNotify。命名后,左键点击选择Create Class选项,虚幻引擎 4 在 Visual Studio 中重新编译并热重加载新类。bOnce Visual Studio 打开后,你将同时拥有头文件Anim_ProjectileNotify.h和源文件Anim_ProjectileNotify.cpp

  4. The UAnimNotify base class has one function that needs to be implemented inside your class:

    virtual void Notify(USkeletalMeshComponent* MeshComp,   UAnimSequenceBase* Animation); 

    当在时间线上点击 notify 时,会自动调用该函数。通过重写该函数,您将能够向 notify 添加自己的逻辑。该功能还允许您访问拥有通知的Skeletal Mesh组件和当前正在播放的动画序列。

  5. Next, let's add the override declaration of this function to the header file. In the header file Anim_ProjectileNotify.h, add the following code underneath the GENERATED_BODY():

    public:  virtual void Notify(USkeletalMeshComponent*   MeshComp,UAnimSequenceBase* Animation) override;

    既然已经在头文件中添加了函数,那么就该在Anim_ProjectileNotify源文件内部定义函数了。

  6. Inside the Anim_ProjectileNotify.cpp source file, define the function and add a UE_LOG() call that prints the text "Throw Notify", as shown in the following code:

    void UAnim_ProjectileNotify::Notify(USkeletalMeshComponent*   MeshComp, UAnimSequenceBase* Animation)
    {
      UE_LOG(LogTemp, Warning, TEXT("Throw Notify"));
    }

    现在,您只需使用这个UE_LOG()调试工具,就可以知道当您在下一个练习中将这个 notify 添加到Throw动画蒙太奇时,这个函数正在被正确调用。

在本练习中,您通过添加以下函数创建了实现自己的AnimNotify类所需的基础:

Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation)

在该功能中,您使用UE_LOG()打印输出日志中的自定义文本"Throw Notify",以便您知道该通知工作正常。

在本章的后面,您将更新这个函数,以便它调用逻辑来生成玩家投射物,但是首先,让我们将新的通知添加到Throw动画蒙太奇中。

练习 14.02:将通知添加到投掷蒙太奇中

现在您已经有了Anim_ProjectileNotify通知,是时候将此通知添加到Throw动画蒙太奇中,以便它实际上对您有用。

在本练习中,您将把Anim_ProjectileNotify添加到Throw蒙太奇的时间线中,在动画的确切帧处,您将期望投射体产生。

完成以下步骤来实现这一点:

  1. Back inside Unreal Engine, navigate to the Content Browser interface and go to the /MainCharacter/Animation/ directory. Inside this directory, double-click the AM_Throw asset to open the Animation Montage editor.

    Animation Montage编辑器的最底部,你会找到动画的时间线。默认情况下,当动画播放时,您会观察到红色条将沿着时间线移动。

  2. Left-click this red bar and manually move it to the 22nd frame, as close as you can, as shown in the following screenshot:

    Figure 14.5: The red colored bar allows you to manually position  notifies anywhere on the timeline

    图 14.5:红色条允许您手动将通知放置在时间线上的任何位置

    Throw动画的第 22 帧是投掷中你期望投射物产生并被玩家投掷的准确时刻。以下截图显示了投掷动画的框架,如在Persona内的编辑器中所见:

    Figure 14.6: The exact moment the player projectile should spawn

    图 14.6:玩家投射物应该诞生的确切时刻

  3. Now that you know the position on the timeline that the notify should be played, you can now right-click on the thin red line within the Notifies timeline.

    这将显示一个弹出窗口,您可以在其中添加NotifyNotify State。在某些情况下,Notifies时间线可能会崩溃,很难找到;只需左键点击Notifies即可在折叠和展开之间切换。

  4. 选择Add Notify,从提供的选项中,找到并选择Anim Projectile Notify

  5. After selecting to add Anim Projectile Notify to the Notifies timeline, you will see the following:

    Figure 14.7: Anim_ProjectileNotify successfully added to the Throw Animation Montage

    图 14.7:动画 _ 项目通知成功添加到投掷动画蒙太奇

  6. Throw动画蒙太奇时间轴上Anim_ProjectileNotify通知到位后,保存蒙太奇。

  7. 如果Output Log窗口不可见,请通过导航至Window选项重新启用该窗口,并将鼠标悬停在该窗口上以查看Developer Tools。找到Output Log选项,左键点击启用。

  8. 现在,使用PIE,一旦进入游戏,使用鼠标左键开始播放Throw蒙太奇。

在动画中添加 notify 的位置,您将看到调试日志文本Throw Notify出现在输出日志中。

大家可能还记得第 12 章动画融合与蒙太奇中,给玩家角色蓝图BP_SuperSideScroller_MainCharacter增加了Play Montage功能。为了在虚幻引擎 4 的上下文中学习 C++,在接下来的练习中,您将把这个逻辑从蓝图移动到 C++。这是为了让玩家角色的基本行为不会过于依赖蓝图脚本。

完成本练习后,您已成功将自定义Anim NotifyAnim_ProjectileNotify添加到Throw动画蒙太奇中。这个通知是在你期望从玩家手里扔出一枚炮弹的精确时刻添加的。由于您在第 12 章动画混合和蒙太奇中为玩家角色添加了蓝图逻辑,因此当使用鼠标左键调用InputAction事件ThrowProjectile时,您可以播放此Throw动画蒙太奇。在从在蓝图中播放投掷动画蒙太奇过渡到在 C++ 中播放蒙太奇之前,让我们进一步讨论播放动画蒙太奇。

播放动画蒙太奇

正如您在第 12 章动画混合和蒙太奇中所学的,这些项目对于允许动画师将单个动画序列组合成一个完整的蒙太奇非常有用。通过将蒙太奇分成自己独特的部分,并添加粒子和声音通知,动画师和动画程序员可以制作复杂的蒙太奇集,处理动画的所有不同方面。

但是一旦动画蒙太奇准备好了,我们如何在一个角色身上播放这个蒙太奇呢?您已经熟悉了第一种方法,即通过蓝图。

在蓝图中播放动画蒙太奇

在蓝图中,Play Montage功能可供您使用,如下图所示:

Figure 14.8: The Play Montage function in Blueprints

图 14.8:蓝图中的播放蒙太奇功能

您已经使用该功能播放了AM_Throw动画蒙太奇。该功能要求必须在Skeletal Mesh组件上播放蒙太奇,并且要求播放动画蒙太奇。

其余参数是可选的,这取决于蒙太奇的工作方式。让我们快速了解一下这些参数:

  • Play Rate:参数Play Rate可以增加或减少动画蒙太奇的播放速度。为了加快播放速度,您可以增加该值;否则,您将降低较慢播放速度的值。
  • Starting Position:Starting Position参数允许您设置蒙太奇时间线的开始位置,以秒为单位,蒙太奇将从该位置开始播放。例如,在有 3 秒时间线的动画蒙太奇中,您可以选择在1.0f位置开始蒙太奇,而不是在0.0f位置。
  • Starting Section:参数Starting Section可以让你告诉动画蒙太奇从特定的部分开始。根据蒙太奇的设置方式,您可以为蒙太奇的不同部分创建多个部分。例如,霰弹枪武器重装动画蒙太奇将包括一个用于重装的初始移动部分,一个用于实际子弹重装的环形部分,以及一个用于重新装备武器以便准备再次开火的最终部分。

谈到Play Montage功能的输出,您有几个不同的选项:

  • On Completed:动画蒙太奇播放完毕,完全融合出来后,调用On Completed输出。
  • On Blend Out:当动画蒙太奇开始融合时,调用On Blend Out输出。这可能发生在Blend Out Trigger Time期间,或者如果蒙太奇提前结束。
  • On Interrupted:当蒙太奇开始融合时,由于该蒙太奇被另一个试图在同一骨架上播放的蒙太奇打断,调用On Interrupted输出。
  • On Notify Begin & On Notify End:如果您使用的是动画蒙太奇中Notifies类别下的Montage Notify选项,则会调用On Notify BeginOn Notify End输出。Montage Notify的名称通过Notify Name参数返回。

用 C++ 播放动画蒙太奇

在 C++ 端,你只需要知道一件事,那就是UAnimInstance::Montage_Play()函数。该功能需要播放动画蒙太奇、回放蒙太奇的播放速率、EMontagePlayReturnType类型的值、用于确定播放蒙太奇的开始位置的float值以及用于确定播放该蒙太奇是否应该停止或中断所有蒙太奇的Boolean值。

虽然您不会更改EMontagePlayReturnType的默认参数,即EMontagePlayReturnType::MontageLength,但了解该枚举器存在的两个值仍然很重要:

  • Montage Length:值Montage Length返回蒙太奇本身的长度,以秒为单位。

  • Duration: The Duration value returns the play duration of the montage, which is equal to the length of the montage, divided by the play rate.

    注意

    有关UAnimMontage类的更多详细信息,请参考以下文档:

在下一个练习中,您将了解更多关于播放动画蒙太奇的 C++ 实现。

练习 14.03:用 C++ 播放投掷动画

现在你已经通过蓝图和 C++ 更好地理解了在虚幻引擎 4 中播放动画蒙太奇,是时候将播放Throw动画蒙太奇的逻辑从蓝图迁移到 C++ 了。这一变化背后的原因是因为蓝图逻辑作为占位符方法被放置到位,以便您可以预览Throw蒙太奇。这本书是一个更侧重于游戏开发的 C++ 指南,因此,学习如何在代码中实现这个逻辑是很重要的。

让我们首先从蓝图中移除逻辑,然后继续在玩家角色类中用 C++ 重新创建逻辑。

以下步骤将帮助您完成本练习:

  1. 导航到玩家角色蓝图BP_SuperSideScroller_MainCharacter,可以在以下目录中找到:/MainCharacter/Blueprints/双击该资产打开。

  2. Inside this Blueprint, you will find the InputAction ThrowProjectile event and the Play Montage function that you created to preview the Throw Animation Montage, as shown in the following screenshot. Delete this logic and then recompile and save the player character Blueprint:

    Figure 14.9: You no longer need this placeholder logic inside the player character Blueprint

    图 14.9:玩家角色蓝图中不再需要这个占位符逻辑

  3. 现在,使用PIE并使用鼠标左键尝试与玩家角色投掷。你会观察到玩家角色不再播放Throw动画蒙太奇。让我们通过在 C++ 中添加所需的逻辑来解决这个问题。

  4. 在 Visual Studio 中打开玩家角色的头文件,SuperSideScroller_Player.h

  5. The first thing you need to do is create a new variable for the player character that will be used for the Throw animation. Add the following code under the Private access modifier:

    UPROPERTY(EditAnywhere)
    class UAnimMontage* ThrowMontage;

    现在你有了一个代表Throw动画蒙太奇的变量,是时候在SuperSideScroller_Player.cpp文件中添加播放蒙太奇的逻辑了。

  6. Before you can make the call to UAnimInstance::Montage_Play(), you need to add the following include directory to the existing list at the top of the source file in order to have access to this function:

    #include "Animation/AnimInstance.h"

    第九章**视听元素中我们知道,玩家角色已经有了一个叫ThrowProjectile的功能,只要按下鼠标左键就会被调用。提醒一下,这是 C++ 中绑定发生的地方:

    //Bind pressed action ThrowProjectile to your ThrowProjectile   function
    PlayerInputComponent->BindAction("ThrowProjectile", IE_Pressed,   this, &ASuperSideScroller_Player::ThrowProjectile);
  7. Update ThrowProjectile so that it plays ThrowMontage, which you set up earlier in this exercise. Add the following code to the ThrowProjectile() function. Then, we can discuss what is happening here:

    void ASuperSideScroller_Player::ThrowProjectile()
    {
      if (ThrowMontage)
      {
        bool bIsMontagePlaying = GetMesh()->GetAnimInstance()->      Montage_IsPlaying(ThrowMontage);
        if (!bIsMontagePlaying)
        {
          GetMesh()->GetAnimInstance()->Montage_Play(ThrowMontage,         2.0f);
        }
        }    }

    第一行是检查ThrowMontage是否有效;如果我们没有分配有效的动画蒙太奇,继续逻辑是没有意义的,并且在进一步的函数调用中使用空对象也是危险的,因为它可能导致崩溃。接下来,我们将声明一个新的布尔变量,称为bIsMontagePlaying,它决定了ThrowMontage是否已经在玩家角色的骨骼网格上玩了。进行此检查是因为Throw动画蒙太奇不应在已经播放时播放;如果玩家反复按下鼠标左键,将导致动画中断。

    接下来是If语句,检查ThrowMontage是否有效,蒙太奇是否没有播放。只要满足这些条件,继续前进,播放动画蒙太奇是安全的。

  8. If语句中,您告诉玩家的骨骼网格以1.0f的播放速率播放ThrowMontage动画蒙太奇。使用1.0f值,以便动画蒙太奇以其预期的速度回放。大于1.0f的值会使蒙太奇回放更快,而小于1.0f的值会使蒙太奇回放更慢。您所了解的其他参数,如开始位置或EMontagePlayReturnType参数,可以留在它们的defaults.Head处。回到虚幻引擎 4 编辑器中,像过去一样重新编译代码。

  9. 代码重新编译成功后,导航回玩家角色蓝图BP_SuperSideScroller_MainCharacter,可以在以下目录找到:/MainCharacter/Blueprints/双击该资产打开。

  10. 在玩家角色的Details面板中,你现在会看到你添加的Throw Montage参数。

  11. Left-click on the drop-down menu for the Throw Montage parameter to find the AM_Throw montage. Left-click again on the AM_Throw option to select it for this parameter. Please refer to the following screenshot to see how the variable should be set up:

![Figure 14.10: Now, the Throw Montage is assigned the AM_Throw montage ](img/B16183_14_10.jpg)

图 14.10:现在,投掷蒙太奇被分配了 AM_Throw 蒙太奇
  1. Recompile and save the player character blueprint. Then, use PIE to spawn the player character and use the left mouse button to play Throw Montage. The following screenshot shows this in action:
![Figure 14.11: The player character is now able to perform the Throw animation again ](img/B16183_14_11.jpg)

图 14.11:玩家角色现在可以再次执行投掷动画

通过完成本练习,您已经学习了如何为玩家角色添加Animation Montage参数,以及如何在 C++ 中播放蒙太奇。除了在 C++ 中播放Throw 动画蒙太奇之外,您还添加了通过检查蒙太奇是否已经在播放来控制Throw动画播放频率的功能。这样做,可以防止玩家滥发Throw输入,导致动画中断或不完整播放。

注意

尝试将Animation Montage的播放速率从1.0f设置为2.0f,并重新编译代码。观察提高动画的播放速率如何影响玩家对动画的观感。

游戏世界和产卵对象

当涉及到向游戏世界中产卵对象时,实际上是代表你的级别的World对象处理所述对象的创建。您可以将UWorld类对象视为代表您的级别的单个顶级对象。

UWorld类可以做很多事情,例如从世界中产生和移除对象,检测级别何时被更改或流入/流出,甚至执行线跟踪来帮助对象间检测。为了这一章,我们将集中讨论生成对象。

UWorld类有SpawnActor()函数的多种变体,这取决于您想要如何生成对象,或者您在生成该对象的上下文中可以通过哪些参数访问。要考虑的三个一致参数如下:

  • UClass:UClass参数只是你想要在其中繁殖的对象的类。

  • FActorSpawnParameters: This is a struct of variables that give the spawned object more context and references to what has spawned it. For a list of all of the variables included within this struct, please refer to this article from the Unreal Engine 4 Community Wiki: https://www.ue4community.wiki/Actor#Spawn

    让我们简单讨论一下FActorSpawnParameters中包含的一个更关键的变量:Owner演员。Owner是催生这个对象的演员,在玩家角色和投射物的情况下,明确引用玩家作为投射物的拥有者将非常重要。这背后的原因,尤其是在这个游戏的背景下,就是你不希望弹丸与它的Owner发生碰撞;你想让这个投射物完全忽略拥有者,这样它只能和敌人或者关卡环境碰撞。

  • Transform:向世界产卵一个物体时,世界需要知道这个演员的locationrotationscale属性才能产卵。在SpawnActor()功能的一些模板中,需要传递完整的Transform,而在其他模板中,LocationRotation需要单独传递。

在继续生成玩家投射物之前,让我们在玩家角色的Skeleton中设置Socket位置,以便在Throw动画期间投射物可以从玩家的手中生成。

练习 14.04:创建投射物产卵窝

为了生成玩家投射物,你需要确定投射物将在哪个Transform中生成,同时主要关注LocationRotation,而不是Scale

在本练习中,您将在玩家角色的Skeleton上创建一个新的Socket,然后您可以在代码中引用该新的Socket,以便获得产生投射物的位置。

让我们开始吧:

  1. 在虚幻引擎 4 中,导航到Content Browser界面,找到/MainCharacter/Mesh/目录。

  2. In this directory, find the Skeleton asset; that is, MainCharacter_Skeleton.uasset. Double-click to open this Skeleton.

    为了确定投射物应该在哪里产卵的最佳位置,我们需要添加Throw动画蒙太奇作为骨骼的预览动画。

  3. Details面板中的Animation类别下,找到Preview Controller参数并选择Use Specific Animation选项。

  4. Next, left-click on the drop-down menu to find and select the AM_Throw Animation Montage from the list of available animations.

    现在,玩家角色的Skeleton将开始预览Throw动画蒙太奇,如下图截图所示:

    Figure 14.12: The player character previewing the Throw Animation Montage

    图 14.12:玩家角色预览投掷动画蒙太奇

    如果你回忆起练习 14.02在投掷蒙太奇中添加了通知,你在Throw动画的第 22 帧添加了Anim_ProjectileNotify

  5. Using the timeline at the bottom of the Skeleton editor, move the red bar to as close to the 22nd frame as you can. Please refer to the following screenshot:

    Figure 14.13: The same 22nd frame in which you added Anim_ProjectileNotify i n an earlier exercise

    图 14.13:在前面的练习中添加了 Anim_ProjectileNotify 的第 22 帧

    Throw动画的第 22 帧,玩家角色应该如下所示:

    Figure 14.14: At the 22nd frame of the Throw Animation Montage, the character’s hand is in position to release a projectile

    图 14.14:在投掷动画蒙太奇的第 22 帧,角色的手处于释放投射物的位置

    如你所见,玩家角色将从他们的右手投掷弹丸,所以新的Socket应该附着在的右手上。让我们看看玩家角色的骨架层次,如下图所示:

    Figure 14.15: The RightHand bone found within the hierarchy  of the player character’s skeleton

    图 14.15:在玩家角色骨骼层次中找到的右手骨

  6. 从骨骼层次中,找到RightHand骨骼。这可以在RightShoulder骨骼层次结构下找到。

  7. Right-click on the RightHand bone and left-click the Add Socket option from the list of options that appear. Name this socket ProjectileSocket.

    另外,当添加新的Socket时,整个RightHand的层次将扩展,新的插座将出现在底部。

  8. With ProjectileSocket selected, use the Transform widget gizmo to position this Socket at the following location:

    Location = (X=12.961717,Y=25.448450,Z=-7.120584)

    最终结果应该如下所示:

    Figure 14.16: The final position of ProjectileSocket at the 22nd frame of the Throw Animation in world space.

    图 14.16:世界空间中投掷动画第 22 帧的投影插座最终位置。

    如果你的小控件看起来有点不同,那是因为上图显示的是世界空间中的插座位置,而不是本地空间。

  9. 现在ProjectileSocket已经定位在你想要的位置,保存MainCharacter_Skeleton资产。

随着这个练习的完成,你现在知道玩家投射物将从哪里产生了。既然你在预览中使用了Throw动画蒙太奇,并且使用了相同的第 22 帧动画,你就知道这个位置会根据Anim_ProjectileNotify什么时候开火来修正。

现在,让我们继续在 C++ 中生成玩家投射体。

练习 14.05:准备产卵器()功能

现在你已经有了ProjectileSocket并且现在有了一个可以产生玩家投射物的位置,让我们添加产生玩家投射物所需的代码。

在本练习结束时,您将拥有准备生成投射体的功能,并且它将准备从Anim_ProjectileNotify类调用。

请执行以下步骤:

  1. 从 Visual Studio 中,导航到SuperSideScroller_Player.h头文件。

  2. You need a class reference variable to the PlayerProjectile class. You can do this using the variable template class type known as TSubclassOf. Add the following code to the header file, under the Private access modifier:

    UPROPERTY(EditAnywhere)
    TSubclassOf<class APlayerProjectile> PlayerProjectile;

    现在你已经准备好了变量,是时候声明你将用来产生投射体的函数了。

  3. 在 void ThrowProjectile()函数和Public访问修饰符的声明下添加以下函数声明:

    void SpawnProjectile();
  4. Before preparing the definition of the SpawnProjectile() function, add the following include directories to the list of includes in the SuperSideScroller_Player.cpp source file:

    #include "PlayerProjectile.h"
    #include "Engine/World.h"
    #include "Components/SphereComponent.h"

    您需要包括PlayerProjectile.h,因为它是为了引用射弹类的碰撞成分而需要的。接下来,使用Engine/World.h include 是使用SpawnActor()功能和访问FActorSpawnParameters结构所必需的。最后,你需要使用Components/SphereComponent.h包含来更新玩家投射物的碰撞部分,这样它就会忽略玩家。

  5. Next, create the definition of the SpawnProjectile() function at the bottom of the SuperSideScroller_Player.cpp source file, as shown here:

    void ASuperSideScroller_Player::SpawnProjectile()
    {
    }

    这个函数需要做的第一件事就是检查PlayerProjectile类变量是否有效。如果这个对象无效,那么继续尝试并派生它是没有意义的。

  6. Update the SpawnProjectile() function so that it looks as follows:

    void ASuperSideScroller_Player::SpawnProjectile()
    {
      if(PlayerProjectile)
        {
        }
    }

    现在,如果PlayerProjectile对象有效,你会想要获得玩家当前存在的UWorld对象,并确保这个世界有效,然后继续。

  7. Update the SpawnProjectile() function to the following:

    void ASuperSideScroller_Player::SpawnProjectile()
    {
      if(PlayerProjectile)
        {
          UWorld* World = GetWorld();
          if (World)
            {
            }
        }
    }

    此时,您已经进行了安全检查,以确保PlayerProjectileUWorld都有效,因此现在可以安全地尝试生成射弹。首先要做的是声明一个FactorSpawnParameters类型的新变量,并将玩家指定为所有者。

  8. Add the following code within the most recent if statement so that the SpawnProjectile() function looks like this:

    void ASuperSideScroller_Player::SpawnProjectile()
    {
      if(PlayerProjectile)
        {
          UWorld* World = GetWorld();
          if (World)
            {
              FActorSpawnParameters SpawnParams;
              SpawnParams.Owner = this; 
            }
        }
    }

    如您之前所知,来自UWorld对象的SpawnActor()函数调用将需要FActorSpawnParameters结构作为衍生对象初始化的一部分。在玩家投射物的情况下,可以使用this关键字作为投射物拥有者的玩家角色类的参考。当你在投射物产生后更新它的碰撞时,这将在这个函数的后面派上用场。

  9. Next, you need to handle the Location and Rotation parameters of the SpawnActor() function. Add the following lines under the latest line, SpawnParams.Owner = this:

    FVector SpawnLocation = this->GetMesh()-  >GetSocketLocation(FName("ProjectileSocket"));
    FRotator Rotation = GetActorForwardVector().Rotation();

    在第一行中,您声明了一个名为SpawnLocation的新FVector变量。该向量使用您在上一练习中创建的ProjectileSocket插座的Socket位置。从GetMesh()函数返回的Skeletal Mesh组件包含一个名为GetSocketLocation()的函数,该函数将使用传入的FName返回插座的位置;在这种情况下,名称ProjectileSocket

    在第二行,您正在声明一个名为Rotation的新FRotator变量。该值被设置为玩家的前进向量,转换成一个Rotator容器。这将确保旋转,或者换句话说,玩家投射物将产生的方向,将在玩家的前面,并且它将远离玩家。

    现在,所有产生射弹所需的参数都准备好了。

  10. Add the following line underneath the code from the previous step:

```cpp
APlayerProjectile* Projectile = World-  >SpawnActor<APlayerProjectile>(PlayerProjectile, SpawnLocation,   Rotation, SpawnParams);
```

`World->SpawnActor()`函数将返回一个你试图在其中繁殖的类的对象;在这种情况下,`APlayerProjectile`。这就是为什么你要在实际产卵前添加`APlayerProjectile* Projectile`。然后,传递`SpawnLocation`、`Rotation`和`SpawnParams`参数,以确保射弹在您想要的位置和方式下产卵。
  1. Finally, you can add the player character to the array of actors to ignore on the player projectile by adding the following lines of code:
```cpp
if (Projectile)
{
  Projectile->CollisionComp->    MoveIgnoreActors.Add(SpawnParams.Owner);
}
```

现在你已经有了射弹的参考,这一行正在更新`CollisionComp`组件,这样玩家,或者`SpawnParams.Owner`,就被添加到了`MoveIgnoreActors`阵中。这个演员阵列在移动时会被投射物的碰撞所忽略,这是完美的,因为这个投射物不应该与投掷它的玩家发生碰撞。
  1. 返回编辑器重新编译新添加的代码。代码编译成功后,本练习就完成了。

随着这个练习的完成,你现在有了一个功能,可以生成玩家角色内部分配的玩家投射类。通过为投射体和世界的有效性添加安全检查,您可以确保如果产生了一个对象,它就是有效世界中的有效对象。

接下来,您为UWorld SpawnActor()功能设置适当的locationrotationFActorSpawnParameters参数,以确保玩家投射物在正确的位置产生,基于前一练习中的插座位置,具有适当的方向,使其远离玩家,并以玩家角色作为其Owner

现在,是时候更新Anim_ProjectileNotify源文件,让它生成投射物了。

练习 14.06:更新 Anim_ProjectileNotify 类

你已经准备好了允许玩家投射物产生的功能,但是你还没有在任何地方调用这个功能。回到练习 14.01创建一个 UAnim 通知类,你创建了Anim_ProjectileNotify类,而在练习 14.02将通知添加到投掷蒙太奇中,你将此通知添加到Throw动画蒙太奇中。

现在是时候更新Uanim Notify类了,这样它就可以调用SpawnProjectile()函数了。

为此,请执行以下操作:

  1. In Visual Studio, open the Anim_ProjectileNotify.cpp source file.

    在源文件中,您有以下代码:

    #include "Anim_ProjectileNotify.h"
    void UAnim_ProjectileNotify::Notify(USkeletalMeshComponent*   MeshComp, UAnimSequenceBase* Animation)
    {
      UE_LOG(LogTemp, Warning, TEXT("Throw Notify"));
    }
  2. Notify()功能中移除UE_LOG()线。

  3. Next, add the following include lines underneath Anim_ProjectileNotify.h:

    #include "Components/SkeletalMeshComponent.h"
    #include "SuperSideScroller/SuperSideScroller_Player.h"

    您需要包含SuperSideScroller_Player.h头文件,因为它是调用您在上一个练习中创建的SpawnProjectile()函数所必需的。我们还包含了SkeletalMeshComponent.h,因为我们将在Notify()函数中引用这个组件,所以最好也包含在这里。

    Notify()函数传入一个对拥有的Skeletal Mesh的引用,标记为MeshComp。您可以使用骨骼网格,通过使用GetOwner()功能并将返回的演员转换到您的SuperSideScroller_Player类来获取对玩家角色的引用。我们接下来要做这个。

  4. Notify()功能中,增加以下一行代码:

    ASuperSideScroller_Player* Player =   Cast<ASuperSideScroller_Player>(MeshComp->GetOwner());
  5. 现在您已经有了对玩家的引用,在调用SpawnProjectile()函数之前,您需要添加Player变量的有效性检查。在前一步的代码行后添加以下代码行:

    if (Player)
    {
      Player->SpawnProjectile();
    }
  6. Now that the SpawnProjectile() function is being called from the Notify() function, return to the editor to recompile and hot-reload the code changes you have made.

    在你能够使用PIE跑来跑去并投掷玩家投射物之前,你需要分配上一个练习中的Player Projectile变量。

  7. Content Browser界面,导航到/MainCharacter/Blueprints目录找到BP_SuperSideScroller_MainCharacter蓝图。双击打开蓝图。

  8. Details面板中,Throw Montage参数下面,你会发现Player Projectile参数。左键点击该参数的下拉选项,找到BP_PlayerProjectile。在该选项上左键单击将其分配给Player Projectile变量。

  9. 重新编译并保存BP_SuperSideScroller_MainCharacter蓝图。

  10. Now, use PIE and use the left mouse button. The player character will play the Throw animation and the player projectile will spawn.

请注意,射弹是从你创建的`ProjectileSocket`功能中衍生出来的,它会远离玩家。下面的截图显示了这一点:

![Figure 14.17: The player can now throw the player projectile ](img/B16183_14_17.jpg)

图 14.17:玩家现在可以投掷玩家射弹了

完成这个练习后,玩家现在可以投掷玩家射弹了。玩家的投射物,在当前状态下,对敌人无效,只是在空中飞行。在Throw 动画蒙太奇、Anim_ProjectileNotify类和玩家角色之间花费了大量的移动部件来让玩家投掷弹丸。

在接下来的练习中,你将更新玩家投射物,使其摧毁敌人,并发挥额外的效果,如粒子和声音。

毁灭演员

到目前为止,在这一章中,我们已经把大量的焦点放在了游戏世界中的演员的培养或创造上;玩家角色使用UWorld职业来产生投射物。虚幻引擎 4 和它的基础Actor类有一个默认的功能,你可以用它来摧毁或移除游戏世界中的一个演员:

bool AActor::Destroy( bool bNetForce, bool bShouldModifyLevel )

/Source/Runtime/Engine/Actor.cpp目录中找到Actor.cpp源文件,就可以在 Visual Studio 中找到这个功能的完整实现。这个功能存在于从Actor类延伸出来的所有类中,在虚幻引擎 4 的情况下,它存在于所有可以在游戏世界中产生或放置的类中。更明确地说,EnemyBasePlayerProjectile两个班都是班的孩子,因此可以被消灭。

进一步查看AActor::Destroy()功能,您会发现下面一行:

World->DestroyActor( this, bNetForce, bShouldModifyLevel );

我们将不进一步详细讨论UWorld类为了毁灭一个演员到底做了什么,但重要的是要强调这样一个事实,即UWorld类负责创造和毁灭世界内部的演员。请随意深入挖掘源代码引擎代码,以找到更多关于UWorld类如何处理演员的毁灭和繁殖的信息。

现在你有了更多关于虚幻引擎 4 如何处理游戏世界中演员的毁灭和移除的上下文,我们将自己为敌人角色实现这一点。

练习 14.07:创建毁灭者()函数

Super SideScroller游戏的主要玩法是玩家在关卡周围移动,用弹丸消灭敌人。在项目的这一点上,你已经处理了玩家的移动并产生了玩家投射物。然而,射弹还不能消灭敌人。

为了使这个功能到位,我们将从给EnemyBase类添加一些逻辑开始,这样它就知道如何处理它的破坏,并在它与玩家投射物碰撞时将其从游戏中移除。

完成以下步骤来实现这一点:

  1. 首先,导航到 Visual Studio,打开EnemyBase.h头文件。

  2. In the header file, create the declaration of a new function called DestroyEnemy() under the Public access modifier, as shown here:

    public:
      void DestroyEnemy();

    确保这个函数定义写在类定义的GENERATED_BODY()下面。

  3. 将这些更改保存到头文件中,打开EnemyBase.cpp源文件,以便添加该功能的实现。

  4. Below the #include lines, add the following function definition:

    void AEnemyBase::DestroyEnemy()
    {
    }

    目前,这个功能将非常简单。您所需要做的就是从基础Actor类中调用继承的Destroy()函数。

  5. 更新DestroyEnemy()功能,使其看起来像这样:

    void AEnemyBase::DestroyEnemy()
    {
      Destroy();
    }
  6. 这个函数完成后,保存源文件并返回编辑器,这样您就可以重新编译并热重新加载代码。

随着这个练习的完成,敌人角色现在有了一个功能,只要你选择,就可以轻松处理演员的毁灭。DestroyEnemy()功能是可以公开访问的,这样就可以被其他类调用,这在以后处理玩家抛射物的销毁时会派上用场。

你之所以创建自己独特的摧毁敌人行动者的功能,是因为你将在本章的后面使用这个功能,当 VFX 和 SFX 被玩家投射物摧毁时,将它们添加到敌人中。

在继续讨论敌人毁灭的抛光元素之前,让我们在玩家投射类中实现一个类似的功能,这样它也可以被摧毁。

练习 14.08:摧毁射弹

现在敌人角色可以通过你在上一个练习中实现的新DestroyEnemy()功能来处理被摧毁的情况,是时候对玩家投射物进行同样的操作了。

在这个练习结束时,玩家投射物将有自己独特的功能来处理自己的毁灭和从游戏世界中移除。

让我们开始吧:

  1. 在 Visual Studio 中,打开播放器弹丸的头文件;也就是PlayerProjectile.h

  2. Public访问修饰符下,添加如下函数声明:

    void ExplodeProjectile();
  3. 接下来,打开玩家投射物的源文件;也就是PlayerProjectile.cpp

  4. Underneath the void APlayerProjectile::OnHit function, add the definition of the ExplodeProjectile() function:

    void APlayerProjectile::ExplodeProjectile()
    {
    }

    目前,该功能的工作方式与上一练习中的DestroyEnemy()功能相同。

  5. 将继承的Destroy()函数添加到新的ExplodeProjectile()函数中,比如:

    void APlayerProjectile::ExplodeProjectile()
    {
      Destroy();
    }
  6. 这个函数完成后,保存源文件并返回编辑器,这样您就可以重新编译并热重新加载代码。

随着这个练习的完成,玩家投射物现在有了一个功能,无论你选择什么时候,它都可以轻松处理演员的毁灭。你需要创建自己独特的函数来处理摧毁玩家投射物行动者的原因与你创建DestroyEnemy()函数的原因是一样的——当玩家投射物与另一个行动者碰撞时,你将在本章的后面使用这个函数向玩家投射物添加 VFX 和 SFX。

现在你已经有了在玩家投射体和敌人角色中实现Destroy()功能的经验,是时候把这两个元素放在一起了。

在下一个活动中,你将启用玩家投射物,以便在敌人角色碰撞时摧毁他们。

活动 14.01:射弹摧毁敌人

现在玩家投射物和敌人角色都可以处理被摧毁的情况,是时候多走一步,让玩家投射物在碰撞时摧毁敌人角色了。

为此,请执行以下步骤:

  1. EnemyBase.h头文件的#include语句添加到PlayerProjectile.cpp源文件的顶部。

  2. 在 void APlayerProjectile::OnHit()函数中,创建一个新的AEnemyBase*类型的变量,并调用这个变量Enemy

  3. APlayerProjectile::OnHit()函数的OtherActor参数转换为AEnemyBase*类,并将Enemy变量设置为该转换的结果。

  4. 使用if()语句检查Enemy变量的有效性。

  5. 如果Enemy有效,从这个Enemy调用DestroyEnemy()功能。

  6. if()块之后,调用ExplodeProjectile()功能。

  7. 保存对源文件的更改,并返回到虚幻引擎 4 编辑器。

  8. Use PIE and then use the player projectile against an enemy to observe the results.

    预期产出如下:

    Figure 14.18: The player throwing the projectile

图 14.18:投掷弹丸的玩家

当射弹击中敌人时,敌人角色被摧毁,如下图所示:

Figure 14.19: The projectile and enemy destroyed

图 14.19:射弹和敌人被摧毁

这项活动完成后,玩家投射物和敌人角色在相互碰撞时可以被摧毁。此外,每当另一个演员触发其APlayerProjectile::OnHit()功能时,玩家投射物将被摧毁。

至此,Super SideScroller游戏的一个主要元素已经完成:玩家投射物产卵,敌人与投射物碰撞时被消灭。你可以观察到消灭这些演员非常简单,玩家也不太感兴趣。

这就是为什么,在本章即将到来的练习中,你将分别学习更多关于视觉和听觉效果,或者 VFX 和 SFX 的知识。你也将实施这些关于敌人角色和玩家投射物的元素。

现在,敌人角色和玩家投射物都可以被摧毁,让我们简单讨论一下什么是 VFX 和 SFX,以及它们将如何影响这个项目。

注意

这个活动的解决方案可以在:https://packt.live/338jEBx找到。

视觉和听觉效果

粒子系统等视觉效果和声音提示等声音效果在视频游戏中发挥着重要作用。它们在系统、游戏机制甚至基本动作的基础上增加了一定程度的润色,使这些元素变得更有趣或更令人愉悦。

让我们从理解视觉效果开始,然后是音频效果。

视觉效果(VFX)

在虚幻引擎 4 的背景下,视觉效果由所谓的粒子系统组成。粒子系统由发射器组成,发射器由模块组成。在这些模块中,您可以使用材质、网格和数学模块来控制发射器的外观和行为。最终的结果可能是任何事情,从火把,或雪落下,到雨,灰尘,等等。

注意

您可以在这里了解更多信息:https://docs . unrealengine . com/en-US/Resources/Showcases/Effects/index . html

音效(SFX)

在虚幻引擎 4 的上下文中,音频效果由声波和声音提示的组合组成:

我们以 Valve 开发的游戏传送门 2 为例。

传送门 2 中,玩家使用传送门枪发射两个传送门:一个橙色和一个蓝色。这些入口允许玩家穿越间隙,将物体从一个位置移动到另一个位置,并利用其他简单的机制来创建复杂的谜题。这些传送门的使用,发射传送门的音效,以及这些传送门的视觉 VFX 让游戏玩起来更加愉快。如果你对游戏不熟悉,请在这里观看完整的演练:https://www.youtube.com/watch?v=ZFqk8aj4-PA

注意

关于声音和声音设计的重要性的进一步阅读,请参考以下 Gamasutra 文章:https://www . Gamasutra . com/view/news/318157/7 _ games _ worthy _ study _ for _ theory _ sound _ design . PHP

在虚幻引擎 4 的背景下,VFX 最初是使用名为 Cascade 的工具创建的,艺术家可以结合使用materialsstatic meshesmath为游戏世界创建有趣且令人信服的效果。本书不会深入探讨这个工具是如何工作的,但是你可以在这里找到关于 Cascade 的信息:https://www . UE4 community . wiki/Legacy/Introduction _ to _ Particles _ in _ UE4 - 2 - Cascade _ at _ a _ sketch

在引擎的更新版本中,从 4.20 更新开始,有一个名为尼亚加拉的插件可以创建视觉效果。Niagara与 Cascade 不同,它使用了一个类似于蓝图的系统,在这个系统中,您可以可视化地编写效果的行为脚本,而不是使用一组具有预定义行为的预设模块。你可以在这里找到更多关于尼亚加拉的信息。

第九章视听元素中,您了解到了更多关于音频以及音频在虚幻引擎 4 中的处理方式。现在需要知道的是虚幻引擎 4 使用.wav文件格式将音频导入引擎。从那里,您可以直接使用.wav文件,在编辑器中称为声波,或者您可以将这些资产转换为声音提示,这允许您在声波之上添加音频效果。

最后,在接下来的练习中,有一个重要的课程需要了解,这个课程叫做UGameplayStatics。这是虚幻引擎中的一个静态类,可以从 C++ 和蓝图中使用,它提供了各种有用的游戏相关功能。在接下来的练习中,您将使用以下两个功能:

UGameplayStatics::SpawnEmitterAtLocation
UGameplayStatics:SpawnSoundAtLocation

这两种功能的工作方式非常相似;它们都需要一个World上下文对象来产生效果,粒子系统或音频来产生效果,以及产生效果的位置。在下一个练习中,你将使用这些功能为敌人产生摧毁效果。

练习 14.09:消灭敌人时增加效果

在本练习中,您将向本章和练习中包含的项目添加新内容。这包括粒子 VFX 和声音 SFX,以及它们所需的所有资产。然后,你将更新EnemyBase类,这样它就可以使用音频和粒子系统参数来添加当敌人被玩家投射物摧毁时所需的抛光层。

在这个练习结束时,你将有一个敌人,当它与玩家的投射物碰撞时,会在视觉和听觉上被摧毁。

让我们开始吧:

  1. 首先,我们需要从Action RPG项目中迁移特定的资产,这可以在Unreal Engine LauncherLearn选项卡中找到。

  2. From Epic Games Launcher, navigate to the Learn tab and, under the Games category, you will find Action RPG:

    注意

    在本章后面的练习中,您将从 Action RPG 项目中获取额外的资产,因此您应该保持该项目打开,以避免重复打开该项目。

  3. 左键单击Action RPG游戏项目,然后左键单击Create Project选项。

  4. 从这里,选择引擎版本 4.24,并选择将项目下载到哪个目录。然后,左键点击Create按钮开始安装项目。

  5. 一旦Action RPG项目下载完毕,导航至Epic Games LauncherLibrary选项卡,找到My Projects部分下的ActionRPG

  6. 双击ActionRPG项目,在虚幻引擎编辑器中打开。

  7. 在编辑器中,在Content Browser界面找到A_Guardian_Death_Cue音频资产。右键点击该资产,选择Asset Actions,然后选择Migrate

  8. 选择Migrate后,会出现A_Guardian_Death_Cue中引用的所有资产。这包括所有音频类和声波文件。从Asset Report 对话窗口中选择OK

  9. 接下来,您需要导航到您的Super SideScroller项目的Content文件夹,然后左键单击 Select Folder

  10. 一旦迁移过程完成,您将在编辑器中收到通知,告知您迁移已成功完成。

  11. Do the same migration steps for the P_Goblin_Death VFX asset. The two primary assets you are adding to the project are as follows:

```cpp
A_Guardian_Death_Cue
P_Goblin_Death
```

`P_Goblin_Death`粒子系统资源引用包含在`Effects`目录中的附加资源,如材料和纹理,而`A_Guardian_Death_Cue`引用包含在`Assets`目录中的附加声波资源。
  1. After migrating these folders into your Content directory, open the Unreal Engine 4 editor of your SuperSideScroller project to find the new folders included in your project's Content Browser.
你将用于摧毁敌人角色的粒子叫做`P_Goblin_Death`,可以在`/Effects/FX_Particle/`目录中找到。你用来摧毁敌方角色的声音叫做`A_Guardian_Death_Cue`,可以在`/img/Sounds/Creatures/Guardian/`目录中找到。现在您需要的资产已经导入到编辑器中,让我们继续代码。
  1. 打开 Visual Studio,导航到敌方基类的头文件;也就是EnemyBase.h
  2. 添加以下UPROPERTY()变量。这将代表敌人被消灭时的粒子系统。确保这是在Public访问修饰符
```cpp
UPROPERTY(EditAnywhere, BlueprintReadOnly)
class UParticleSystem* DeathEffect;
```

下声明的
  1. Add the following UPROPERTY() variable. This will represent the sound for when the enemy is destroyed. Make sure this is declared under the Public access modifier:
```cpp
UPROPERTY(EditAnywhere, BlueprintReadOnly)
class USoundBase* DeathSound;
```

定义了这两个属性之后,让我们继续前进,添加在敌人被消灭时产生和使用这些效果所需的逻辑。
  1. Inside the source file for the enemy base class, EnemyBase.cpp, add the following includes for the UGameplayStatics and UWorld classes:
```cpp
#include "Kismet/GameplayStatics.h"
#include "Engine/World.h"
```

当敌人被消灭时,你将使用`UGameplayStatics`和`UWorld`职业将声音和粒子系统繁殖到世界中。
  1. AEnemyBase::DestroyEnemy()功能中,你有一行代码:
```cpp
Destroy();
```
  1. Add the following line of code above the Destroy() function call:
```cpp
UWorld* World = GetWorld();
```

在尝试生成粒子系统或声音之前,有必要定义`UWorld`对象,因为需要`World`上下文对象。
  1. 接下来,使用if()语句检查刚刚定义的World对象的有效性:
```cpp
if(World)
{
}
```
  1. Within the if() block, add the following code to check the validity of the DeathEffect property, and then spawn this effect using the SpawnEmitterAtLocation function from UGameplayStatics:
```cpp
if(DeathEffect)
{
    UGameplayStatics::SpawnEmitterAtLocation(World,       DeathEffect, GetActorTransform());
}
```

在尝试派生或操作对象之前,您应该确保对象是有效的,这一点怎么强调都不为过。通过这样做,您可以避免引擎崩溃。
  1. After the if(DeathEffect) block, perform the same validity check of the DeathSound property and then spawn the sound using the UGameplayStatics::SpawnSoundAtLocation function:
```cpp
if(DeathSound)
{
    UGameplayStatics::SpawnSoundAtLocation(World,       DeathSound, GetActorLocation());
}
```

在调用`Destroy()`函数之前,您需要检查`DeathEffect`和`DeathSound`属性是否都有效,如果有效,使用适当的`UGameplayStatics`函数生成这些效果。这确保了无论任何一个属性是否有效,敌人角色仍然会被摧毁。
  1. 现在AEnemyBase::DestroyEnemy()函数已经被更新来产生这些效果,返回到虚幻引擎 4 编辑器来编译和热重载这些代码变化。
  2. Content Browser界面内,导航至/Enemy/Blueprints/目录。双击BP_Enemy资产将其打开。
  3. 在敌人蓝图的Details面板中,你会发现Death EffectDeath Sound属性。在Death Effect属性的下拉列表中左键单击,找到P_Goblin_Death粒子系统。
  4. 接下来,在Death Effect参数下,左键单击Death Sound属性下拉列表中的,找到A_Guardian_Death_Cue声音提示。
  5. 现在这些参数已经更新并分配了正确的效果,编译并保存敌人蓝图。
  6. Using PIE, spawn the player character and throw a player projectile at an enemy. If an enemy is not present in your level, please add one. When the player projectile collides with the enemy, the VFX and SFX you added will play, as shown in the following screenshot:
![Figure 14.20: Now, the enemy explodes and gets destroyed in a blaze of glory ](img/B16183_14_20.jpg)

图 14.20:现在,敌人在荣耀的火焰中爆炸并被摧毁

随着这个练习的完成,当敌人角色被玩家投掷物摧毁时,它会播放一个粒子系统和一个声音提示。这给游戏增加了一层很好的抛光,并且它让消灭敌人变得更令人满意。

在下一个练习中,您将为播放器投射体添加一个新的粒子系统和音频组件,以便它在空中飞行时看起来和听起来更有趣。

练习 14.10:为玩家投掷物添加效果

在当前状态下,玩家投射物按照预期的方式运行;它在空中飞行,与游戏世界中的物体碰撞,然后被摧毁。然而,从视觉上看,玩家投射物只是一个具有普通白色纹理的球。

在本练习中,您将通过添加粒子系统和音频组件来为玩家投掷物添加一层抛光,以便投掷物使用起来更愉快。

完成以下步骤来实现这一点:

  1. Much like the previous exercises, we will need to migrate assets from the Action RPG project to our Super SideScroller project. Please refer to Exercise 14.09, Adding Effects When the Enemy Is Destroyed, on how to install and migrate assets from the Action RPG project.

    您要添加到项目中的两个主要资产如下:

    P_Env_Fire_Grate_01
    A_Ambient_Fire01_Cue

    P_Env_Fire_Grate_01粒子系统资产引用了包含在Effects目录中的附加资产,例如材质和纹理,而A_Ambient_Fire01_Cue引用了包含在Assets目录中的附加声波和声音衰减资产。

    你将用于玩家投射物的粒子叫做P_Env_Fire_Grate_01,可以在/Effects/FX_Particle/目录中找到。这与上一练习中P_Goblin_Death VFX 使用的目录相同。你将用于玩家投射的声音叫做A_Ambient_Fire01_Cue,可以在/img/Sounds/Ambient/目录中找到。

  2. Action RPG项目的Content Browser界面中右键单击这些资产中的每一个,选择Asset Actions,然后选择Migrate

  3. Make sure to choose the directory of the Content folder for your Super SideScroller project before confirming the migration.

    现在所需的资产已经迁移到我们的项目中,让我们继续创建玩家投射类。

  4. 打开 Visual Studio,导航到玩家投射类的头文件;也就是PlayerProjectile.h

  5. Private访问修饰符下,在UStaticMeshComponent* MeshComp类组件的声明下,添加以下代码来声明玩家投射体的新音频组件:

    UPROPERTY(VisibleDefaultsOnly, Category = Sound)
    class UAudioComponent* ProjectileMovementSound;
  6. Next, add the following code underneath the declaration of the audio component in order to declare a new particle system component:

    UPROPERTY(VisibleDefaultsOnly, Category = Projectile)
    class UParticleSystemComponent* ProjectileEffect;

    这些效果将成为玩家投射物的组成部分,而不是使用蓝图中可以定义的属性,比如在敌人角色类中。这是因为这些效果应该附加到射弹的碰撞部分,以便在投掷时随着射弹穿过水平面而移动。

  7. With these two components declared in the header file, open the source file for the player projectile and add the following includes to the list of include lines at the top of the file:

    #include "Components/AudioComponent.h"
    #include "Engine/Classes/Particles/ParticleSystemComponent.h"

    您需要引用音频组件和粒子系统类,以便使用CreateDefaultSubobject功能创建这些子对象,并将这些组件附加到RootComponent

  8. 添加以下行以创建ProjectileMovementSound组件的默认子对象,并将该组件附加到RootComponent :

    ProjectileMovementSound = CreateDefaultSubobject<UAudioComponent>  (TEXT("ProjectileMovementSound"));
      ProjectileMovementSound->AttachToComponent(RootComponent,   FAttachmentTransformRules::KeepWorldTransform);
  9. 接下来,添加以下行,以便为ProjectileEffect组件创建默认子对象,并将该组件附加到RootComponent :

    ProjectileEffect = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("Projectile   Effect"));
    ProjectileEffect->AttachToComponent(RootComponent,   FAttachmentTransformRules::KeepWorldTransform);
  10. 现在您已经创建、初始化了这两个组件并将其附加到RootComponent上,请返回到虚幻引擎 4 编辑器重新编译并热重新加载这些代码更改。

  11. From the Content Browser interface, navigate to the /MainCharacter/Projectile/ directory. Find the BP_PlayerProjectile asset and double-click it to open the Blueprint.

在`Components`选项卡中,您将找到使用前面的代码添加的两个新组件。观察这些组件是否连接到`CollisionComp`组件,也称为`RootComponent`。
  1. Left-click to select the ProjectileEffect component and, within the Details panel, assign the P_Env_Fire_Grate_01 VFX asset to this parameter, as shown in the following screenshot:
![Figure 14.21: Now, you can apply the P_Env_fire_Grate_01 VFX asset to the  Particle System component you added earlier ](img/B16183_14_21.jpg)

图 14.21:现在,您可以将 P_Env_fire_Grate_01 VFX 资产应用到之前添加的粒子系统组件中
  1. Before assigning the audio component, let's adjust the Transform of the ProjectileEffect VFX asset. Update the Rotation and Scale parameters of the Transform for the VFX so that they match what is shown in the following screenshot:
![Figure 14.22: The updated Transform of the particle system component  so that it fits better with the projectile ](img/B16183_14_22.jpg)

图 14.22:粒子系统组件的更新变换,使其更好地适应射弹
  1. Navigate to the Viewport tab within the Blueprint to view these changes to the Transform. ProjectileEffect should look as follows:
![Figure 14.23: Now, the fire VFX has been scaled and rotated appropriately ](img/B16183_14_23.jpg)

图 14.23:现在,火 VFX 已经被适当地缩放和旋转
  1. 现在 VFX 已经设置好了,左键单击ProjectileMovementSound组件并将A_Ambient_Fire01_Cue分配给该组件。
  2. Save and recompile the BP_PlayerProjectile Blueprint. Use PIE and observe that when you throw the projectile, it now shows the VFX asset and plays the assigned sound:
![Figure 14.24: The player projectile now has a VFX and an SFX as it flies through the air ](img/B16183_14_24.jpg)

图 14.24:玩家投掷物在空中飞行时现在有一个 VFX 和一个 SFX

完成这个练习后,玩家投射物现在有了一个 VFX 和一个 SFX,当它在空中飞行时可以一起玩。这些元素使射弹栩栩如生,使射弹使用起来更加有趣。

由于 VFX 和 SFX 是作为射弹的部件制造的,所以当射弹被摧毁时,它们也会被摧毁。

在下一个练习中,您将在Throw动画蒙太奇中添加粒子通知和声音通知,以便在玩家投掷玩家投射物时提供更多的冲击力。

练习 14.11:添加 VFX 和 SFX 通知

到目前为止,您已经通过 C++ 实现了游戏的波兰元素,这是一种有效的实现方式。为了丰富多彩,并扩展您对虚幻引擎 4 工具集的了解,本练习将指导您如何使用动画蒙太奇中的通知在动画中添加粒子系统和音频。我们开始吧!

与前面的练习非常相似,我们需要将资产从Action RPG项目迁移到我们的Super SideScroller项目。请参考练习 14.09消灭敌人时添加效果,了解如何从Action RPG项目安装和迁移资产。请执行以下步骤:

  1. Open the ActionRPG project and navigate to the Content Browser interface.

    您要添加到项目中的两个主要资产如下:

    P_Skill_001
    A_Ability_FireballCast_Cue

    P_Skill_001粒子系统资产引用材质纹理等包含在Effects目录中的附加资产,而A_Ability_FireballCast_Cue引用包含在Assets目录中的附加声波资产。

    投掷弹丸时你将为玩家使用的粒子叫做P_Skill_001,可以在/Effects/FX_Particle/目录中找到。这与之前练习中P_Goblin_DeathP_Env_Fire_Grate_01 VFX 资产使用的目录相同。你用来破坏敌人角色的声音叫做A_Ambient_Fire01_Cue,可以在/img/Sounds/Ambient/目录中找到。

  2. Action RPG项目的Content Browser界面中右键单击这些资产中的每一个,选择Asset Actions,然后选择Migrate

  3. Make sure to choose the directory of the Content folder for your Super SideScroller project before confirming the migration.

    现在您需要的资产已经迁移到您的项目中,让我们继续向AM_Throw资产添加所需的通知。在继续本练习之前,请确保返回到您的Super SideScroller项目。

  4. Content Browser界面,导航至/MainCharacter/Animation/目录。找到AM_Throw资产,双击打开。

  5. Animation Montage编辑器中心的预览窗口下方,找到Notifies部分。这就是你在本章前面添加Anim_ProjectileNotify的部分。

  6. To the right of the Notifies track, you will find a + sign that allows you to use additional notify tracks. Left-click to add a new track, as shown in the following screenshot:

    Figure 14.25: It is useful to add multiple tracks to the timeline in order to keep things organized when adding multiple notifies

    图 14.25:为了在添加多个通知时保持事情有条不紊,在时间线中添加多个轨道是很有用的

  7. 在与Anim_ProjectileNotify相同的帧中,右键单击您在上一步中创建的新轨迹内的。从Add Notify列表中,左键单击选择Play Particle Effect

  8. Once created, left-click to select the new notify and access its Details panel. In Details, add the P_Skill_001 VFX asset to the Particle System parameter.

    在你添加了这个新的 VFX 之后,你会注意到 VFX 几乎被放在底部,玩家角色的脚在那里,但不是你想要的地方。这个 VFX 应该直接放在地板上,或者角色的底部。下面的截图展示了这个位置:

    Figure 14.26: The location of the particle notify is not on the ground

    图 14.26:粒子通知的位置不在地面上

    为了解决这个问题,你需要给玩家角色骨架增加一个新的Socket

  9. 导航至/MainCharacter/Mesh/目录。双击MainCharacter_Skeleton资产打开

  10. 从左侧的Skeleton骨骼层次中,右键单击Hips骨骼上的,左键单击选择Add Socket选项。命名这个新插座EffectSocket

  11. Left-click this socket from the hierarchy of bones in order to view its current location. By default, its location is set to the same position as the Hips bone. The following screenshot shows this location:

![Figure 14.27: The default location of this socket is in the center of the player skeleton ](img/B16183_14_27.jpg)

图 14.27:这个插座的默认位置在玩家骨架的中心

使用`Transform`小控件,移动`EffectSocket`的位置,使其位置设置如下:

```cpp
(X=0.000000,Y=100.000000,Z=0.000000)
```

这个位置会更靠近地面和玩家角色的脚。最终位置可以在下面的截图中看到:

![Figure 14.28: Moving the socket location to the base of the player skeleton ](img/B16183_14_28.jpg)

图 14.28:将插座位置移动到玩家骨架的底部
  1. 现在你已经有了粒子通知的位置,回到AM_Throw动画蒙太奇。
  2. Within the Details panel of the Play Particle Effect notify, there is the Socket Name parameter. Use EffectSocket as the name.
注意

如果`EffectSocket`没有通过自动完成功能出现,关闭并重新打开动画蒙太奇。一旦重新打开,`EffectSocket`选项应该会出现。
  1. Lastly, the scale of the particle effect is a little too big, so adjust the scale of the projectile so that its value is as follows:
```cpp
(X=0.500000,Y=0.500000,Z=0.500000)
```

现在,当粒子效果通过此通知播放时,其位置和比例将是正确的,如下所示:

![Figure 14.29: The particle now plays at the base of the player character skeleton ](img/B16183_14_29.jpg)

图 14.29:粒子现在在玩家角色骨架的底部播放
  1. 若要添加Play Sound通知,请在Notifies时间线部分添加新的轨道;你现在应该总共有三个。
  2. On this new track, and at the same frame position as both the Play Particle Effect and Anim_ProjectileNotify notifies, right-click and select the Play Sound notify from the Add Notify selection. The following screenshot shows where to find this notify:
![Figure 14.30: The Play Sound notify that you learned about earlier in this chapter ](img/B16183_14_30.jpg)

图 14.30:播放声音通知您在本章前面已经了解到
  1. 接下来,左键点击选择Play Sound通知并进入其Details面板。
  2. From the Details panel, find the Sound parameter and assign A_Ability_FireballCast_Cue.
有了指定的声音,当`Throw`动画回放时,你会看到 VFX 的戏,你会听到声音。`Notifies`轨迹应如下所示:

![Figure 14.31: The final notify set up on the Throw Animation Montage timeline ](img/B16183_14_31.jpg)

图 14.31:投掷动画蒙太奇时间线上的最终通知设置
  1. 保存AM_Throw资产,使用PIE投掷玩家弹丸。
  2. Now, when you throw the projectile, you will see the particle notify play the P_Skill_001 VFX and you will hear the A_Ability_FireballCast_Cue SFX. The result will look as follows:
![Figure 14.32: Now, when the player throws the projectile, powerful VFX and SFX are played ](img/B16183_14_32.jpg)

图 14.32:现在,当玩家投掷投射物时,会打出强大的 VFX 和 SFX

完成最后一个练习后,当玩家投掷投射物时,玩家现在可以玩强大的 VFX 和 SFX。这给了投掷动画更多的力量,感觉像是玩家角色在使用大量的能量来投掷弹丸。

在接下来的最后一个活动中,你将使用你从最后几个练习中获得的知识,在玩家投射物被摧毁时,将 VFX 和 SFX 添加到其中。

活动 14.02:增加炮弹被摧毁时的效果

在这最后一个活动中,你将使用你从给玩家投掷物和敌人角色添加 VFX 和 SFX 元素中获得的知识来为投掷物与物体碰撞时创建爆炸效果。我们增加这种额外爆炸效果的原因是为了在炮弹与环境物体碰撞时摧毁炮弹的基础上增加一层抛光。如果玩家的抛射物击中一个物体并消失而没有玩家的任何听觉或视觉反馈,这看起来会很尴尬和不合适。

您将向玩家投射物添加粒子系统和声音提示参数,并在投射物与对象碰撞时产生这些元素。

执行以下步骤以实现预期的输出:

  1. PlayerProjectile.h头文件中,添加一个新的粒子系统变量和一个新的声音基础变量。

  2. 命名粒子系统变量DestroyEffect,命名声音基础变量DestroySound

  3. PlayerProjectile.cpp源文件中,将UGameplayStatics的包含添加到包含列表中。

  4. 更新APlayerProjectile::ExplodeProjectile()功能,使其现在同时生成DestroyEffectDestroySound对象。返回到虚幻引擎 4 编辑器,重新编译新的 C++ 代码。在BP_PlayerProjectile蓝图中,将默认情况下已经包含在您的项目中的P_Explosion VFX 指定给射弹的Destroy Effect参数。

  5. 将默认情况下已经包含在项目中的Explosion_Cue SFX 分配给投射体的Destroy Sound参数。

  6. 保存并编译玩家投射物蓝图。

  7. Use PIE to observe the new player projectile's destruction VFX and SFX.

    预期产出如下:

    Figure 14.33: Projectile VFX and SFX

图 14.33:射弹 VFX 和 SFX

完成这项活动后,您现在有了在游戏中添加波兰元素的经验。您不仅通过 C++ 代码添加了这些元素,还通过虚幻引擎 4 中的其他工具添加了元素。在这一点上,你有足够的经验将粒子系统和音频添加到你的游戏中,而不必担心如何实现这些功能。

注意

这个活动的解决方案可以在:https://packt.live/338jEBx找到。

总结

在这一章中,你学到了很多关于视觉和听觉效果在游戏开发世界中的重要性。使用 C++ 代码和通知的组合,您能够为玩家投射体和敌人角色碰撞带来游戏功能,并通过添加 VFX 和 SFX 为该功能增加一层润色。除此之外,你还在虚幻引擎 4 中学习了对象是如何产生和毁灭的。

此外,您还从蓝图和 C++ 中了解了动画蒙太奇是如何播放的。通过将播放Throw动画蒙太奇的逻辑从蓝图迁移到 C++,您学习了这两种方法是如何工作的,以及如何在您的游戏中使用这两种实现。

通过使用 C++ 添加新的动画通知,您可以将此通知添加到Throw动画蒙太奇中,这允许玩家生成您在上一章中创建的玩家投射体。通过使用UWorld->SpawnActor()功能,并为玩家骨骼添加一个新的插座,您可以在Throw动画的精确帧和您想要的精确位置生成玩家投射物。

最后,您学习了如何在投掷动画蒙太奇中使用Play Particle EffectPlay Sound通知将 VFX 和 SFX 添加到玩家投掷物中。这一章给了你机会去了解虚幻引擎 4 中存在的不同方法,当你在游戏中使用 VFX 和 SFX 的时候。

现在玩家可以投掷投射物并摧毁敌人角色,是时候为游戏实施最后一套机制了。在下一章中,你将创建玩家可以收集的收藏品,你还将为玩家创建一个能在短时间内提高玩家运动力学的动力。