distance delayed sound

distance delayed sound

在本章中,我们将讨论在游戏音频中使用距离延迟的重要性。我们将首先通过一个常见的例子——闪电和雷鸣,来展示这种重要性并解释距离延迟音频的基础知识。我们将讨论计算速度、距离和时间的数学和方程式,以确定距离延迟。这将最终给我们一个以秒为单位的距离延迟值,用于确定延迟声音的时间长度以创建逼真的声音距离效果。然后,我们将进入几个代码示例,以演示一个示例声音调度器,并基于虚幻引擎4创建一个实际的游戏引擎项目。让我们开始吧!
 

基本理论

闪电

每个人都见过闪电,几秒钟后伴随着雷鸣。孩子们从小就学到,每三秒钟声音大约传播一公里(或五秒钟传播一英里)。他们被告知从看到闪电到听到雷鸣的时间间隔,用这个信息可以确定他们与闪电之间的距离。

雷鸣声比闪电的光到达我们需要更多的时间,所以我们知道声音传播比光慢得多。光也需要时间到达我们,但它的速度非常快(299,792,458米/秒或186,282英里/秒)。换句话说,当你看到闪电闪烁时,它发生在你看到它的几微秒之前。

为什么声音比光慢这么多?这是因为它们必须通过的介质(在这种情况下是空气)来到达我们。科学家们通过改变光传播的介质,已经能够将光速减慢到每秒17米以下。光和声音在水中的传播速度与在空气中不同。光在水中减慢,这使得水看起来比实际更浅。另一方面,声音在水中的传播速度更快,因为水比空气密度大得多。通过钢传播的声音比在水中更快,因为钢是固体且密度很高。

在空气中声音的平均公认速度是340米/秒。实际上,空气中的声音速度取决于许多因素,如气压、温度、湿度等。如果你的游戏在水下进行,那么声音传播速度比在空气中快得多,大约是四到五倍(1400-1500米/秒)。如果你的游戏发生在高山上,你可能想用320米/秒。对于我们的目的,我们将围绕空气中声音传播的标准速度340米/秒设计和实施我们的系统。

声音距离方程

本节将包含一些基本代数,以便我们计算出声音到达我们所需的时间。速度的定义是每传播时间所传播的距离。例如,闪电电光已经被我们看见而雷声在 5.2 秒后才传到我们耳中,那么我们就可以利用这一信息计算出闪电离我们有多远。

                                                                                v = d / t;
其中v是速度,d是距离,t是时间。

我们知道 v = 340m/s 和 t = 5.2 s的时间,但我们不知道距离d是什么,这是我们需要找到的。

使用上面的公式,用上面的值替换v和t,我们得到以下结果:

                                                         



右边的两个5.2 s抵消,留下以下:
                                           



交换2边:
                                                        


所以,在340米/秒的速度下,闪电需要5.2秒才能到达我们只有1768米远。

如果我们只知道速度v和距离d,我们现在也可以用类似的方式来计算时间t。所以,使用上面相同的例子,但是使用时间,t,作为我们的未知:                                       
                                                 



这给了我们之前相同的方程,但我们必须更进一步,用两边除以速度v:
                                              
                                                



插入我们之前已知的值,用距离d=1768m,和速度v = 340m/s,我们得到:
                                          


最后一个方程,t  = d / v ,是我们将在游戏中使用的方程。虽然我们一直在使用340m/s来表示v的值,但如果你使用不同的音速,你可以替换一个不同的值。

为了模拟远处的声音,我们延迟播放声音,以模拟它需要额外的时间才能到达我们。我们可以使用t  = d / v 方程来确定我们应该延迟我们的声音来模拟它随距离传播的影响。这种效果对于那些跨越很远距离且声音非常响亮的游戏非常有用,比如在远处可以听到的爆炸声或枪声。这种效果对于拥有短距离、远距离安静声音的游戏,或者不需要模拟远距离声音所提供的现实性的游戏来说,都是不值得的。中程比赛(100-300米以内)的比赛将受益于这种效果,因为100米的声音会延迟大约三分之一秒,300米的声音会延迟近1秒。
 

设计和需求

需求

要使用距离延迟声音,你的游戏音频系统必须具备以下能力:

  • 支持基于位置的声音;
  • 具备安排声音的机制;
  • 能够标记某些声音不参与距离延迟机制。

基于位置的声音要求你的游戏音频系统知道声音的生成位置或其附属的角色或物体的位置。大多数现代游戏引擎具备这些信息并且知道声音的生成位置,所以这通常不是问题。如果你编写了自己的游戏音频引擎,并且希望使用距离延迟声音,那么你的系统必须知道声音的播放或生成位置。

另一个重要的考虑因素是你的游戏音频系统必须具备安排声音或为这些声音设置开始时间的手段。并不是所有的游戏引擎都支持这一点,因此你可能需要实施一些变通方法来支持这个功能。你甚至可能需要在将声音输入游戏音频引擎之前实现你自己的基于时间的调度器。

最后的需求是你的游戏音频系统必须考虑有些声音永远不应该应用距离延迟,例如音乐、角色对话、用户界面声音和近距离声音。有些系统可能需要你在近距离声音上禁用延迟,因为计算延迟并将其放入队列的开销可能比直接播放声音更昂贵。

考虑那些在玩家一米范围内播放的声音,例如全自动武器射击后抛出的黄铜弹壳的声音。发生在玩家一米范围内的声音有大约3毫秒的延迟,而大多数游戏以60帧每秒运行,这意味着每帧16毫秒。通常情况下,你可以安全地忽略单帧内的距离延迟。对于运行在60帧每秒的游戏,并且使用标准的340米/秒的声音速度,这意味着任何距离听者大约5米的声音。如果你有一个实时音频系统并且希望完全实现距离延迟声音的效果,那么你可能仍然选择对短距离声音应用距离延迟。

10.3.2 声音调度器和数据结构

声音调度器本质上是一个按升序开始时间排序的优先级队列。这个示例声音调度器适用于慢速移动的场景角色和玩家。如果本地听者玩家快速向某个声音移动或远离某个声音,那么除非你不断检查并更新距离和计算的开始时间,否则计算距离和声音的开始时间将不准确,以适应快速移动的听者或发声者。基本上,如果听者玩家快速向发声者移动,那么根据距离延迟,声音会播放得太晚,因为听者玩家会在计算时间之前截获发出的声音波。如果听者玩家快速远离发声者,那么情况相反,声音会播放得太早。请参见图10.1了解问题。如果你的使用场景需要快速移动的角色和玩家,并且你需要高度准确的声音时机,那么最好在每帧检查和更新声音的距离和开始时间。对于这个例子,我们将忽略这个特殊的使用场景,并假设我们的角色移动缓慢到时间差异无关紧要,并且距离延迟效果足够好。

                                                                



为了使声音调度器易于阅读和理解,我使用了一个MIT许可下的C# SimplePriorityQueue,它包括对Unity的支持。这段代码,包括SimplePriorityQueue,作为本书的补充材料。使用优先级队列,我们现在可以用以下代码在C#中创建我们的声音调度器:

                                                                          

namespace SoundScheduler
{class GAPVector{public double X = 0.0f, Y = 0.0f, Z = 0.0f;public GAPVector(){}public GAPVector(Random inRandom, double inMaxDistanceInMeters){X = inRandom.NextDouble();Y = inRandom.NextDouble();Z = inRandom.NextDouble();double dist = inRandom.NextDouble() * inMaxDistanceInMeters;double distFromOrigin = GetDistanceToOrigin();X = X / distFromOrigin * dist;Y = Y / distFromOrigin * dist;Z = Z / distFromOrigin * dist;}public double GetDistanceToOrigin(){return Math.Sqrt(X * X + Y * Y + Z * Z);}public double GetDistanceToVector(GAPVector inVector){double deltaX = (inVector.X - X);double deltaY = (inVector.Y - Y);double deltaZ = (inVector.Z - Z);return Math.Sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ);}}class Sound{public string SoundFileName;public double StartTime;public GAPVector Location;public Sound(string inSoundFileName, GAPVector inLocation){SoundFileName = inSoundFileName;Location = inLocation;long milliseconds =DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;double seconds = milliseconds / 1000.0;SetStartTimeBasedOnDistanceDelay(seconds);}public void SetStartTimeBasedOnDistanceDelay(double inCurrentTimeInSeconds){SetStartTimeBasedOnDistanceDelay(inCurrentTimeInSeconds, new GAPVector());}public void SetStartTimeBasedOnDistanceDelay(double inCurrentTimeInSeconds, GAPVector inListenerLocation){double dist =Location.GetDistanceToVector(inListenerLocation);//340 m/s is the approximate speed of sound on Earth near//sea-leveldouble speedOfSound = 340.0;StartTime = inCurrentTimeInSeconds + dist / speedOfSound;}public void Play(){//NOTE: To simulate playing the sound we will just print//a string to the consolelong milliseconds =DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;double seconds = milliseconds / 1000.0;string soundStr =string.Format("{0:0.000}: Sound \"{1}\" @ {2:0.000}s with dist: {3:0.00}m",seconds, SoundFileName, StartTime,Location.GetDistanceToOrigin());Console.WriteLine(soundStr);}}class Program{static void Main(string[] args){//First, we create the priority queue.//By default, priority-values are of type 'float'SimplePriorityQueue<Sound, double> priorityQueue =new SimplePriorityQueue<Sound, double>();Random random = new Random();//Create the Sounds - this could be done in various ticks,//but for simplicity we'll do them all at onceSound sound1 = new Sound("Lrg_Exp",new GAPVector(random, 900));Sound sound2 = new Sound("Gunshots",new GAPVector(random, 100));Sound sound3 = new Sound("Footstep",new GAPVector(random, 50));Sound sound4 = new Sound("Med_Exp",new GAPVector(random, 600));Sound sound5 = new Sound("Sm_Exp",new GAPVector(random, 300));//Enqueue all of the sounds based on when they should//start playingpriorityQueue.Enqueue(sound1, sound1.StartTime);priorityQueue.Enqueue(sound2, sound2.StartTime);priorityQueue.Enqueue(sound3, sound3.StartTime);priorityQueue.Enqueue(sound4, sound4.StartTime);priorityQueue.Enqueue(sound5, sound5.StartTime);long milliseconds =DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;double seconds = milliseconds / 1000.0;Console.WriteLine("Scheduler Start Time: " + seconds + "s");//Dequeue each Sound from the Priority Queue and print out//the relevant Sound information.while (priorityQueue.Count != 0){milliseconds =DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;seconds = milliseconds / 1000.0;Sound peekSound = priorityQueue.First();if (peekSound.StartTime <= seconds){Sound nextSound = priorityQueue.Dequeue();//NOTE: This is where you would send the sound to your//audio engine and play itnextSound.Play();}}milliseconds =DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;seconds = milliseconds / 1000.0;Console.WriteLine("Scheduler End Time: " + seconds + "s");Console.Write("Please press Enter/Return to exit...");Console.ReadLine();}}
}


 

这个声音调度器的简单测试程序会检查每一帧的时间,并在应该播放声音时通过打印一条消息来模拟播放声音。在这种情况下,所有五个声音都在完全相同的时间被触发。在游戏过程中,声音会在不同的时间触发,但优先队列能够准确且高效地根据声音应该开始播放的时间重新排序。以下是一次测试程序运行的输出:

Scheduler Start Time: 63648662051.762s

63648662051.860: Sound "Footstep" @ 63648662051.858s with dist: 33.39m

63648662051.958: Sound "Gunshots" @ 63648662051.958s with dist: 67.24m

63648662052.045: Sound "Med_Exp" @ 63648662052.045s with dist: 96.86m

63648662052.516: Sound "Sm_Exp" @ 63648662052.515s with dist: 256.73m

63648662054.254: Sound "Lrg_Exp" @ 63648662054.254s with dist: 849.16m

Scheduler End Time: 63648662054.254s

10.4 真实世界中的虚幻引擎4示例

对于一个真实世界的示例,我们将使用Offworld Industries公司开发的《Squad》游戏中使用的距离延迟声音节点方法,该方法利用了虚幻引擎4和距离延迟技术。我们将演示如何在一个真实的虚幻引擎4演示项目中创建同样的效果,使用距离延迟声音节点来延迟在游戏中任何地方播放的声音提示的开始时间。我们将展示如何使用蓝图将其连接到声音提示,并如何与粒子效果一起重复播放,以便您可以体验距离延迟效果并试验各种设置对延迟的影响。在这个示例中,我们将使用Windows,但您可以为Mac或Linux遵循类似的步骤。

10.4.1 启动

首先,您需要获取虚幻引擎4,可以在 unrealengine.com 免费下载。打开Epic Games启动器并安装一个版本的虚幻引擎4到您的计算机上。您还需要Visual Studio来编译C++代码。对于本示例,我们使用的是虚幻引擎4.18.3版和Visual Studio 2017,这是写作时的最新版本。后来的虚幻引擎4版本可能有一些API更改,但概念应该相当直接地转换。安装完成后,打开虚幻引擎4启动器,如图10.2所示,并创建一个新的C++飞行项目,我们将其命名为DistanceDelayTest,如图10.3所示。确保选择了“新项目”选项卡(步骤1),然后选择C++选项卡(步骤2)。创建此项目时,请务必保持包含入门内容的默认设置。如果您在上述步骤中遇到任何问题,可以通过UE4的AnswerHub或其论坛寻求帮助。我们也在本书的网站上包括了此项目,以防您在自行复制项目时遇到任何问题。

                                  



                                  



                               



创建此新项目后,编辑器将打开一个名为FlyingExampleMap的选项卡。点击文件 -> 新建C++类...,在对话框中选择显示所有类的复选框。在搜索框中输入SoundNode并选择它作为父类,然后点击下一步,如图10.4所示。将此类命名为DistanceDelaySoundNode并点击创建类,如图10.5所示。这将在您的项目中的Source文件夹下创建两个新文件,分别是DistanceDelaySoundNode.h和DistanceDelaySoundNode.cpp。

                         

10.4.2 实现距离延迟节点

在DistanceDelaySoundNode.h源文件中,我们将需要三个UPROPERTY变量来控制行为。第一个是用于设置声音速度的变量,我们将其称为SpeedOfSound。在本示例中,我们将其设为可配置属性,但您可以将其硬编码,从物理体积中提取,或通过其他任何方法进行数据驱动。第二个属性是该节点允许的最大延迟,我们将其称为DelayMax。最后一个属性在编辑器中测试距离延迟功能时很有用,我们将其称为TestDistance。我们还需要添加一个构造函数,几个USoundNode所需的函数重载,最后是我们的自定义GetSoundDelay()函数,该函数接受两个基于位置的向量来计算延迟量。您的头文件现在应该如下所示:

              

#pragma once
#include "CoreMinimal.h"
#include "Sound/SoundNode.h"
#include "DistanceDelaySoundNode.generated.h"
/*** Defines a delay for sounds that contain this based upon the* distance to the listener.*/
UCLASS()
class DISTANCEDELAYTEST_API UDistanceDelaySoundNode :public USoundNode
{GENERATED_BODY()
protected:/** This is the speed of sound in meters per second (m/s) to usefor this delay. */UPROPERTY(EditAnywhere, Category = Physics)float SpeedOfSound;/** The upper bound of delay time in seconds, used in GetDurationcalculation and as an upper bounds for sound effects, 3.0 isprobably a good setting for this. */UPROPERTY(EditAnywhere, Category = Delay)float DelayMax;/** Used to test distance in the editor (in meters). */UPROPERTY(EditAnywhere, Category = Testing)float TestDistance;
public:UDistanceDelaySoundNode(const FObjectInitializer& ObjectInitializer);// Begin USoundNode interface.virtual void ParseNodes(FAudioDevice* AudioDevice,const UPTRINT NodeWaveInstanceHash,FActiveSound& ActiveSound,const FSoundParseParameters& ParseParams,TArray<FWaveInstance*>& WaveInstances) override;virtual float GetDuration() override;// End USoundNode interface.virtual float GetSoundDelay(const FVector& ListenerLocation, const FVector& Location,const float SpeedOfSoundInUU) const;
};

  
现在,我们需要在DistanceDelaySoundNode.cpp中实现这些函数。以下代码有详细注释,但我特别想强调ParseNodes()函数,这是USoundNode的重载函数。当声音提示实例处于活动状态时,每个tick都会调用此函数。第一次调用此函数时,我们将初始化距离延迟,然后阻止此节点的子节点播放,直到达到延迟时间。一旦达到该时间,我们将调用父函数继续解析子节点。GetSoundDelay()函数用于确定我们应该延迟此声音的时间,基于声音发射器的位置、声音监听器的位置以及声音速度等各种参数。

                        

#include "DistanceDelaySoundNode.h"
// Required for FActiveSound, FAudioDevice, FSoundParseParameters,
// etc.
#include "SoundDefinitions.h"
/*----------------------------------------------------------------
UDistanceDelaySoundNode implementation.
------------------------------------------------------------------*/
// Constructor used to set the SpeedOfSound to 340 m/s and the
// DelayMax to 3 seconds, or about 1 km.
UDistanceDelaySoundNode::UDistanceDelaySoundNode(const FObjectInitializer& ObjectInitializer): Super(ObjectInitializer)
{SpeedOfSound = 340.0f;DelayMax = 3.0f;
}
// ParseNodes is used to initialize and update the sound per tick.
// Other effects like pitch bending can be applied here as well.
// Initial call to this function per instance will have
// RequiresInitialization set to true, subsequent calls will be
// false.
void UDistanceDelaySoundNode::ParseNodes(FAudioDevice* AudioDevice, const UPTRINT NodeWaveInstanceHash,FActiveSound& ActiveSound,const FSoundParseParameters& ParseParams,TArray<FWaveInstance*>& WaveInstances)
{// Define the data that is stored with this instance as the size// of a single float value.RETRIEVE_SOUNDNODE_PAYLOAD(sizeof(float));// Declare the data that is stored with this instance as the// EndOfDelay, this is the time when the sound should start playing.DECLARE_SOUNDNODE_ELEMENT(float, EndOfDelay);// Check to see if this is the first time through.if (*RequiresInitialization){// Make sure we do not go through this initialization more// than once.// NOTE: for actors that are fast moving you may consider// updating EndOfDelay more often, but here we only do it the// first time.*RequiresInitialization = false;// Get the default unreal unit conversion and store it// statically in this class, this value will not change during// gameplaystatic const float WorldToMeters =(ActiveSound.GetWorld() != nullptr) ?(IsValid(ActiveSound.GetWorld()->GetWorldSettings()) ?ActiveSound.GetWorld()->GetWorldSettings()->WorldToMeters :100.0f) :100.0f;// The WITH_EDITOR tag is used to only compile this section for// editor builds, the else clause is for live/shipping builds.
#if WITH_EDITOR// This is where we determine the actual delay of the sound// based upon sound emitter and sound listener locations.// The transform stores location, rotation, and scaling// information but this function only requires the
// location / translation.float ActualDelay =GetSoundDelay(AudioDevice->GetListeners()[0].Transform.GetTranslation(),ParseParams.Transform.GetTranslation(),SpeedOfSound * WorldToMeters);// If we are testing this sound inside of the editor's SoundCue// window then the World will be nullptr and we will use our// TestDistance value defined for this node instead of the// in-game calculated distance.// This is very useful for testing that the delay is working// according to your design.if (ActiveSound.GetWorld() == nullptr){ActualDelay = GetSoundDelay(FVector(),FVector(TestDistance * WorldToMeters, 0.0f, 0.0f),SpeedOfSound * WorldToMeters);}
#else// This is the calculation used for shipping and other// non-editor builds.const float ActualDelay =GetSoundDelay(AudioDevice->GetListeners()[0].Transform.GetTranslation(),ParseParams.Transform.GetTranslation(),SpeedOfSound * WorldToMeters);
#endif// Check if there is any need to delay this sound, if not// then just start playing it.if (ParseParams.StartTime > ActualDelay){FSoundParseParameters UpdatedParams = ParseParams;UpdatedParams.StartTime -= ActualDelay;EndOfDelay = -1.0f;Super::ParseNodes(AudioDevice, NodeWaveInstanceHash,ActiveSound, UpdatedParams, WaveInstances);return;}// Set the EndOfDelay value to the offset time when this sound// should start playing.else{EndOfDelay =ActiveSound.PlaybackTime + ActualDelay- ParseParams.StartTime;}}// If we have not waited long enough then just keep waiting.if (EndOfDelay > ActiveSound.PlaybackTime){// We're not finished even though we might not have any wave
// instances in flight.ActiveSound.bFinished = false;}// Go ahead and play the sound.else{Super::ParseNodes(AudioDevice, NodeWaveInstanceHash,ActiveSound, ParseParams, WaveInstances);}
}
// This is used in the editor and engine to determine maximum
// duration for this sound cue. This is used for culling out sounds
// when too many are playing at once and for other engine purposes.
float UDistanceDelaySoundNode::GetDuration()
{// Get length of child node, if it exists.float ChildDuration = 0.0f;if (ChildNodes[0]){ChildDuration = ChildNodes[0]->GetDuration();}// And return the two together.return (ChildDuration + DelayMax);
}
// This is the bread and butter of the distance delay custom sound
// node. Pass in both the listener location and the sound emitter
// location along with the speed of sound (in unreal units (cm)) to
// get the amount of delay to use.
float UDistanceDelaySoundNode::GetSoundDelay(const FVector& ListenerLocation, const FVector& Location,const float SpeedOfSoundInUU) const
{// Calculate the distance from the listener to the emitter and// get the size of the vector, which is the length / distance.const float DistanceToSource =(ListenerLocation - Location).Size();// Calculate the amount of delay required to simulate the sound// traveling over the distance to reach the listener.const float TimeDelayFromSoundSource =DistanceToSource / SpeedOfSoundInUU;// Useful to verify the values during testing and development,// should be commented out during production.UE_LOG(LogAudio, Log,TEXT("UDistanceDelaySoundNode::GetSoundDelay: %f cm => %f s"),DistanceToSource, TimeDelayFromSoundSource);// Returns the distance delay after making sure it is between 0// and the maximum delay.return FMath::Clamp(TimeDelayFromSoundSource, 0.0f, DelayMax);
}

构建音效提示

写好代码后,返回 UE4 编辑器并点击编译,如图 10.6 所示。如果一切顺利,你就可以创建一个使用这个新节点的新音效提示。如果遇到问题,最好关闭编辑器,在 Visual Studio 中编译代码,然后重新打开编辑器。

要创建一个新的 Sound Cue,导航到内容浏览器选项卡,点击 C++ Classes 旁边的文件夹图标,然后选择名为 Content 的文件夹,如图 10.7 所示。在内容浏览器中,点击 +Add New,在 Create Advanced Asset 标题下,悬停在 Sounds 菜单上,然后点击 Sound Cue,如图 10.8 所示。给它命名,例如 DistanceDelayedExplosion,然后双击打开它。                                      
                            


                                       

你现在应该能在右侧的调色板选项卡的声音节点类别下看到 Distance Delay Sound Node。要使用你的新声音节点,只需将其拖出并连接到输出扬声器旁边,然后拖出一个 Wave Player 节点并将其连接到 Distance Delay Sound Node。在 Wave Player 节点中,将 Sound Wave 变量选择为 Explosion01 音效。点击 Distance Delay Sound Node 并根据需要调整参数,然后按下 Play Cue 按钮测试实现,如图 10.9 所示。点击 Save 按钮保存你的工作。
                                   

10.4.4 创建一个 Actor 蓝图

返回内容浏览器,再次点击 +Add New 创建一个新的蓝图类,在 Create Basic Asset 标题下选择 Actor 作为此类的父类,并将其命名为 DistanceDelayActor。双击新建的演员以打开蓝图编辑器。

在组件选项卡下,点击绿色的 +Add Component 按钮,添加两个组件:一个音频组件和一个粒子系统组件。点击音频组件,并在详情选项卡的声音类别中将 Sound 变量设置为 DistanceDelayedExplosion

现在音频已设置好,我们需要设置一个粒子系统,以便在延迟开始时有一个视觉效果。点击粒子系统组件,并在详情选项卡的粒子类别中将 Template 变量设置为 P_Explosion。第一次选择此项时,可能需要等待着色器编译,但在着色器编译完成后,你现在应该能够在点击模拟按钮时看到爆炸粒子效果,但听不到声音。要继续编辑你的蓝图,需确保模拟按钮未激活。

最后一部分是设置蓝图脚本。转到事件图表选项卡,访问此演员的蓝图代码。可以删除 Event ActorBeginOverlap 和 Event Tick 节点。点击 Event Begin Play 节点的执行引脚并拖出,创建一个新的 Set Timer by Event 节点,将该节点的时间设置为 5 秒以实现五秒延迟,并选中循环复选框。拖出事件引脚,在添加事件类别下有一个 Add Custom Event… 菜单项。选择它并将这个新自定义事件命名为 Explode

对于 Explode 自定义事件,拖出执行引脚并输入 Play (Audio)。该节点将播放我们之前添加的音频组件关联的声音。拖出播放节点的执行引脚并选择 Activate (ParticleSystem)。你可能还希望在触发 Explode 自定义事件时包括一个 Print String 节点以进行测试。你的完整蓝图图表应该如图 10.10 所示。点击编译,然后点击保存按钮保存你的工作。
                                            

10.4.5 测试 Distance Delay Actor

每次在世界中生成 DistanceDelayActor 时,它都会播放带有距离延迟效果的爆炸粒子系统和爆炸声音,循环计时器将每五秒触发一次。如果你希望首次生成演员时禁用自动触发粒子和声音效果,可以取消选中音频和粒子系统组件上的 Auto Activate 变量。类似地,如果你不希望这个演员每五秒循环一次并希望在游戏事件期间自己生成演员,那么你可以修改蓝图,通过直接将 Event Begin Play 连接到播放和激活节点并删除 Set Timer by Event 和 Explode 自定义事件节点来实现。

现在一切设置完毕,关闭 DistanceDelayActor 蓝图,返回内容浏览器,然后将你的新 DistanceDelayActor 拖入世界中,然后点击播放按钮在编辑器中测试爆炸效果。你将看到粒子效果,然后根据你与演员的距离和声音速度设置(尝试将声音提示的距离延迟声音节点中的声音速度设置为 34m/s 以更快体验效果)听到声音。

你可以将演员拖到不同的位置,在世界中放置多个,设置关卡蓝图在计时器上随机生成它们,或设置导弹从你的飞船发射并在撞击时生成这个演员。不管你如何使用它们,只要在你的声音提示中使用 Distance Delay Sound Node,距离延迟效果将始终被尊重。

10.5 问题和考虑事项

在使用距离延迟声音时,应考虑一些问题和事项。

10.5.1 快速移动的Actor或玩家需要更新的延迟开始时间

快速移动的Actor或物体会显著影响距离引起的延迟,使其变短或变长,应更频繁地更新这种延迟。如果两者快速靠近,应缩短延迟;如果快速远离,则应延长延迟。有多种方法可以处理这种情况,但对于需要这种用例的游戏来说,这是一个需要考虑的问题。

10.5.2 循环音频需要时间延迟参数

循环播放的音频仅延迟其开始时间效果不好。你需要考虑处理循环声音的其他方法,特别是对于具有与声音协调的粒子效果的移动演员。例如,如果你有一个带有控制参数的声音,你需要延迟设置这些参数,但不能对粒子系统延迟设置。一个可能出现的例子是远处一辆车爬上山坡时:你会看到发动机努力爬坡时产生的额外排气粒子效果。影响发动机转速的声音参数应该与车辆和听者之间的距离延迟相对应,并与排气的粒子效果同步。

10.5.3 平台延迟

一些音频系统具有非常高的音频延迟,对于某些移动设备可高达 200–500 毫秒。在基于平台延迟距离声音时应考虑这些延迟。你可能需要从延迟中减去这个时间以抵消效果以获得更准确的时间。例如,如果计算出的距离延迟为 150 毫秒,但平台延迟为 200 毫秒,则应该立即播放声音,因为延迟高于距离延迟。如果距离延迟为 300 毫秒,平台延迟为 200 毫秒,则可以将距离延迟偏移设置为 100 毫秒,这将在 300 毫秒时准时播放。

10.6 结论

在本章中,我们讨论了使用距离延迟对游戏音频的重要性。我们展示了如何确定声音到达听者的时间,基于他们的距离。这为我们提供了需要延迟声音的时间,以创建真实的声音距离效果。最后,我们学习了如何将其应用于示例声音调度器以及基于虚幻引擎 4 的实际游戏引擎项目,并提供了两个示例代码。
 

REFERENCE

1. BlueRaja. 2013. A C# priority queue optimized for pathfinding applications.

GitHub - BlueRaja/High-Speed-Priority-Queue-for-C-Sharp: A C# priority queue optimized for pathfinding applications

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/365076.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【漏洞复现】安美数字酒店宽带运营系统——命令执行漏洞(CNVD-2021-37784)

声明&#xff1a;本文档或演示材料仅供教育和教学目的使用&#xff0c;任何个人或组织使用本文档中的信息进行非法活动&#xff0c;均与本文档的作者无关。 文章目录 漏洞描述漏洞复现测试工具 漏洞描述 安美数字酒店宽带运营系统 server_ping.php 存在远程命令执行漏洞&#…

【TB作品】温度DS18B20读取,温控风扇,ATMEGA128单片机,Proteus仿真

读取温度&#xff1b; PWM风扇控制&#xff1b; 蜂鸣器控制。 写博客介绍这个基于ATmega128的作品时&#xff0c;可以从以下几个方面展开描述&#xff1a; 概述 介绍项目的背景和目的&#xff0c;说明使用ATmega128的原因以及项目的整体架构。 硬件设计 主要元件 详细列出…

高性能并行计算华为云实验五:PageRank算法实验

目录 一、实验目的 二、实验说明 三、实验过程 3.1 创建PageRank源码 3.2 makefile的创建和编译 3.3 主机配置文件建立与运行监测 四、实验结果与分析 4.1 采用默认的节点数量及迭代次数进行测试 4.2 分析并行化下节点数量与耗时的变化规律 4.3 分析迭代次数与耗时的变…

Elasticsearch开启认证|为ES设置账号密码|ES账号密码设置|ES单机开启认证|ES集群开启认证

文章目录 前言单节点模式开启认证生成节点证书修改ES配置文件为内置账号添加密码Kibana修改配置验证 ES集群开启认证验证 前言 ES安装完成并运行&#xff0c;默认情况下是允许任何用户访问的&#xff0c;这样并不安全&#xff0c;可以为ES开启认证&#xff0c;设置账号密码。 …

加速科技Flash存储测试解决方案 全面保障数据存储可靠性

Flash存储芯片 现代电子设备的核心数据存储守护者 Flash存储芯片是一种关键的非易失性存储器&#xff0c;作为现代电子设备中不可或缺的核心组件&#xff0c;承载着数据的存取重任。这种小巧而强大的芯片&#xff0c;以其低功耗、可靠性、高速的读写能力和巨大的存储容量&…

AttGAN实验复现 2024

AttnGAN 代码复现 2024 文章目录 AttnGAN 代码复现 2024简介环境python 依赖数据集TrainingPre-train DAMSMTrain AttnGAN SamplingB_VALIDATION 为 False (默认)B_VALIDATION 为 True 参考博客 简介 论文地址&#xff1a; https://arxiv.org/pdf/1711.10485.pdf 代码 python…

Amazon OpenSearch Service 现在支持 JSON Web Token(JWT)身份验证和授权

最近&#xff0c;Amazon OpenSearch 推出了一个新功能&#xff0c;支持 JWT 认证和授权。虽然这个功能在开源的 OpenSearch 中早已存在&#xff0c;但在托管的 Amazon OpenSearch 中的实现一直不够理想。 此前的授权方式 控制台登录 内部数据库&#xff1a;使用基本的用户名…

【机器学习】机器学习的重要方法——强化学习:理论,方法与实践

目录 一、强化学习的核心概念 二、强化学习算法的分类与示例代码 三.强化学习的优势 四.强化学习的应用与挑战 五、总结与展望 强化学习&#xff1a;理论&#xff0c;方法和实践 在人工智能的广阔领域中&#xff0c;强化学习&#xff08;Reinforcement Learning, RL&…

Redis-实战篇-编码解决商铺查询的缓存穿透问题(缓存空对象)

文章目录 1、缓存穿透2、常见的解决方案有两种&#xff1a;2.1、缓存空对象2.2、布隆过滤器 3、编码解决商铺查询的缓存穿透问题3.1、queryById3.2、RedisConstants.java 1、缓存穿透 缓存击穿是指客户端请求的数据在缓存中和数据库中都不存在&#xff0c;这样缓存永远不会生效…

【每日刷题】Day77

【每日刷题】Day77 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; 1. LCR 159. 库存管理 III - 力扣&#xff08;LeetCode&#xff09; 2. LCR 075. 数组的相对排序 - 力…

架构师篇-9、从事件风暴到微服务设计的落地过程

用户付款功能第二个版本的设计实现 单一职责原则&#xff08;SRP&#xff09; 软件系统中的每个元素只完成自己职责内的事&#xff0c;将其他的事交给别人去做“职责”通常人理解为一个事情&#xff0c;与该事情相关的事都是它的责任 一个职责是软件变化的一个原因 第二次需求…

test——认识测试

目录 前言 一什么是测试 1测试场景 2为什么需要测试 3测试定义 二测试的岗位 1测开与测试 2测试与开发的区别 a工作内容 b难易程度 c其它不同 三测试人员具备的素质 1综合能力 a沟通能力 b快速学习能力 c开发能力 d文字能力 2掌握自动化测试技术 前言 互联⽹…

QTableView与QSqlQueryModel的简单使用

测试&#xff1a; 这里有一个sqlite数据库 存储了10万多条数据&#xff0c;col1是1,col2是2. 使用QSqlQueryModel和QTableView来显示这些数据&#xff0c;也非常非常流畅。 QString aFile QString::fromLocal8Bit("E:/桌面/3.db");if (aFile.isEmpty())return;//打…

github主页这样优化,让人眼前一亮

我的主页&#xff08;一之十六&#xff09; 1. 创建与账户ID同名的仓库 注意&#xff1a;记得勾选Add a README file 2. markdown语法自定义README.md 3. 辅助工具 优秀profile&#xff1a;https://zzetao.github.io/awesome-github-profile/动态文字&#xff1a;https://r…

【简易版tinySTL】 红黑树- 定义, 插入, 构建

文章目录 旋转左旋右旋 左旋右旋代码实现红黑树的基本性质红黑树的插入红黑树的插入示例红黑树修复代码实现参考资料 旋转 对于一个平衡二叉搜索树&#xff0c;左子树高度为4&#xff0c;右子树高度为2&#xff0c;它们的高度差为2&#xff0c;破坏了平衡性&#xff08;高度差&…

在我们的大数据平台(XSailbaot)上进行企业级数据建模的思路

1. 背景 笔者所在的公司是差不多二十年前搞CIM&#xff08;公共信息模型的&#xff09;起家的。当时公司的前辈搞了基于CIS协议的模型服务器、数据服务器、模式编辑器等&#xff0c;形成了一套基于公共信息模型建模的平台系统。其中可视化建模&#xff0c;建好了模式类以后&am…

SCI二区|北极海鹦优化算法(APO)原理及实现【免费获取Matlab代码】

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献5.代码获取 1.背景 2024年&#xff0c;W Wang受到北极海鹦的生存和捕食行为启发&#xff0c;提出了北极海鹦优化算法&#xff08;Arctic Puffin Optimization, APO&#xff09;。 2.算法原理 2.1算法思想 …

全局静态变量、全局变量以及atexit回调的执行顺序

版本 gcc version 7.5.0 (Ubuntu 7.5.0-6ubuntu2) Linux UM480XT 5.15.0-107-generic #117~20.04.1-Ubuntu SMP Tue Apr 30 10:35:57 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux Microsoft Visual Studio Enterprise 2019, _MSC_VER 1929 #include <stdio.h> #include…

tomcat8.5在windows下运行出现日志中文乱码

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 http://218.75.87.38:9666/ 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码&#xff1a; h…

基于SpringBoot漫画网站系统设计和实现(源码+LW+调试文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f31f;文末获取源码数据库&#x1f31f; 感兴趣的可以先收藏起来&#xff0c;…