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.mzph.cn/news/863807.shtml

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

相关文章

数据倾斜优化:Hive性能提升的核心

文章目录 1. 定义2. 数据倾斜2.1 Map2.2 Join2.3 Reduce 3. 写在最后 1. 定义 数据倾斜&#xff0c;也称为Data Skew&#xff0c;是在分布式计算环境中&#xff0c;由于数据分布不均匀导致某些任务处理的数据量远大于其他任务&#xff0c;从而形成性能瓶颈的现象。这种情况在H…

PotPlayer安装及高分辨率设置

第1步&#xff1a; 下载安装PotPlayer软件 PotPlayer链接&#xff1a;https://pan.baidu.com/s/1hW168dJrLBonUnpLI6F3qQ 提取码&#xff1a;z8xd 第2步&#xff1a; 下载插件&#xff0c;选择系统对应的位数进行运行&#xff0c;该文件不能删除&#xff0c;删除后将失效。 …

【强化学习的数学原理】课程笔记--2(贝尔曼最优公式,值迭代与策略迭代)

目录 贝尔曼最优公式最优 Policy求解贝尔曼最优公式求解最大 State Value v ∗ v^* v∗根据 v ∗ v^* v∗ 求解贪婪形式的最佳 Policy π ∗ \pi^* π∗一些证明过程 一些影响 π ∗ \pi^* π∗ 的因素如何让 π ∗ \pi^* π∗ 不 “绕弯路” γ \gamma γ 的影响reward 的…

2024/6/30周报

文章目录 摘要ABSTRACT文献阅读题目问题本文贡献方法LSTMTCN模型总体架构 实验实验结果 深度学习TCN-LSTM代码运行结果 总结 摘要 本周阅读了一篇关于TCN和LSTM进行光伏功率预测的文章&#xff0c;本文提出了一种利用LSTM-TCN预测光伏功率的新模型。它由长短期记忆和时间卷积网…

ThreadPoolExecutor基于ctl变量的声明周期管理

个人博客 ThreadPoolExecutor基于ctl变量的声明周期管理 | iwts’s blog 总集 想要完整了解下ThreadPoolExecutor&#xff1f;可以参考&#xff1a; 基于源码详解ThreadPoolExecutor实现原理 | iwts’s blog ctl字段的应用 线程池内部使用一个变量ctl维护两个值&#xff…

树莓派开发之文件传输

文章目录 一、简介使用U盘传输文件使用SD卡传输文件使用Xftp 7传输文件 二、 总结 一、简介 在树莓派开发中经常会用到文件传输&#xff0c;下面介绍几种树莓派文件传输的几种方法。 使用U盘传输文件 &#xff08;1&#xff09;复制所需传输文件到U盘 &#xff08;2&#…

C++:typeid4种cast转换

typeid typeid typeid是C标准库中提供的一种运算符&#xff0c;它用于获取类型的信息。它主要用于类型检查和动态类型识别。当你对一个变量或对象使用typeid运算符时&#xff0c;它会返回一个指向std::type_info类型的指针&#xff0c;这个信息包含了关于该类型名称、大小、基…

Pikachu靶场--Sql Inject

参考借鉴 pikachu靶场练习&#xff08;详细&#xff0c;完整&#xff0c;适合新手阅读&#xff09;-CSDN博客 数字型注入(post) 这种类型的SQL注入利用在用户输入处插入数值&#xff0c;而不是字符串。攻击者试图通过输入数字来修改SQL查询的逻辑&#xff0c;以执行恶意操作。…

Unity Shader 极坐标

Unity Shader 极坐标 前言项目简单极坐标极坐标变体之方形极坐标变体之圆形拉花 鸣谢 前言 极坐标记录 项目 简单极坐标 极坐标变体之方形 极坐标变体之圆形 拉花 鸣谢 【菲兹杂货铺】【Unity Shader教程】极坐标实现以及极坐标的两种变体

【87 backtrader期权策略】基于50ETF期权的covered-call-strategy

前段时间有读者希望能够实现一个期权策略的模板,这段时间通过akshare下载了期权的数据,并进行了清洗,写了一个最简单的期权策略,供大家参考。 策略逻辑: 这是151 trading strategies中的一个期权策略。 买入50ETF基金,手续费按照万分之二计算,一直持有卖出一个最远期的…

【实施】系统实施方案(软件方案Word)

软件实施方案 二、 项目介绍 三、 项目实施 四、 项目实施计划 五、 人员培训 六、 项目验收 七、 售后服务 八、 项目保障措施 软件开发全套资料获取&#xff1a;私信或者进主页获取。 软件产品&#xff0c;特别是行业解决方案软件产品不同于一般的商品&#xff0c;用户购买软…

13_旷视轻量化网络--ShuffleNet V2

回顾一下ShuffleNetV1:08_旷视轻量化网络--ShuffleNet V1-CSDN博客 1.1 简介 ShuffleNet V2是在2018年由旷视科技的研究团队提出的一种深度学习模型&#xff0c;主要用于图像分类和目标检测等计算机视觉任务。它是ShuffleNet V1的后续版本&#xff0c;重点在于提供更高效的模…

antd Select前端加模糊搜索

背景&#xff1a;前端的小伙伴经常在开发antd Select的时候后端不提供搜索模糊搜索接口&#xff0c;而是全量返回数据&#xff0c;这个时候就需要我们前端自己来写一个模糊搜索了。 效果 代码截图 代码 <SelectshowSearchmode"multiple"options{studioList}filte…

运维锅总详解Prometheus

本文尝试从Prometheus简介、架构、各重要组件详解、relable_configs最佳实践、性能能优化及常见高可用解决方案等方面对Prometheus进行详细阐述。希望对您有所帮助&#xff01; 一、Prometheus简介 Prometheus 是一个开源的系统监控和报警工具&#xff0c;最初由 SoundCloud …

基于模糊神经网络的时间序列预测(以hopkinsirandeath数据集为例,MATLAB)

模糊神经网络从提出发展到今天,主要有三种形式&#xff1a;算术神经网络、逻辑模糊神经网络和混合模糊神经网络。算术神经网络是最基本的&#xff0c;它主要是对输入量进行模糊化&#xff0c;且网络结构中的权重也是模糊权重&#xff1b;逻辑模糊神经网络的主要特点是模糊权值可…

Python技术笔记汇总(含语法、工具库、数科、爬虫等)

对Python学习方法及入门、语法、数据处理、数据可视化、空间地理信息、爬虫、自动化办公和数据科学的相关内容可以归纳如下&#xff1a; 一、Python学习方法 分解自己的学习目标&#xff1a;可以将学习目标分基础知识&#xff0c;进阶知识&#xff0c;高级应用&#xff0c;实…

2024 vue3入门教程:windows系统下部署node环境

一、打开下载的node官网 Node.js — 下载 Node.js 二、根据个人喜好的下载方法&#xff0c;下载到自己的电脑盘符下 三、我用的是方法3下载的压缩包&#xff0c;解压到E盘nodejs目录下&#xff08;看个人&#xff09; 四、配置电脑的环境变量&#xff0c;新建环境变量的时候…

【ESP32】打造全网最强esp-idf基础教程——14.VFS与SPIFFS文件系统

VFS与SPIFFS文件系统 这几天忙着搬砖&#xff0c;差点没时间更新博客了&#xff0c;所谓一日未脱贫&#xff0c;打工不能停&#xff0c;搬砖不狠&#xff0c;明天地位不稳呀。 不多说了&#xff0c;且看以下内容吧~ 一、VFS虚拟文件系统 先来看下文件系统的定义&#x…

vue中【事件修饰符号】详解

在Vue中&#xff0c;事件修饰符是一种特殊的后缀&#xff0c;用于修改事件触发时的默认行为。以下是Vue中常见的事件修饰符的详细解释&#xff1a; .stop 调用event.stopPropagation()&#xff0c;阻止事件冒泡。当你在嵌套元素中都有相同的事件监听器&#xff08;如click事件…