注解
大家好 相对最近,我写了一篇文章,在Unity3D中基于声音和音乐生成环境 ,其中给出了一些游戏示例,这些示例使用了基于音乐生成内容的机制,还谈到了此类游戏的基本方面。 文章中几乎没有代码,我保证会有续集。 在这里,就在您的面前。 这次,我们将尝试根据您的音乐,以Hill Climb的风格创建2D竞赛的音轨。 让我们看看我们得到了..

引言
我提醒您,本系列文章是为初学者和刚开始使用声音的开发人员设计的。 如果您在脑海中进行快速的傅立叶变换,那么您可能会感到无聊。
这是我们今天的路线图:
- 考虑什么是离散化。
- 找出我们可以从Audio Clip Unity获得的数据
- 了解如何处理这些数据。
- 找出我们可以从这些数据中生成什么。
- 了解如何利用所有这些(或者类似游戏的东西)制作游戏
所以走吧!
模拟僧伽罗语的离散化
众所周知,为了在数字系统中使用信号,我们需要对其进行转换。 转换步骤之一是对信号进行采样,其中将模拟信号分为多个部分(临时报告),然后为每个报告分配选定时刻的振幅值。

字母T表示采样周期。 周期越短,信号转换将越准确。 但大多数情况下,他们谈论的是相反的情况:采样率(这是F = 1 / T是合乎逻辑的)。 8,000 Hz足以用于电话信号,例如,DVD音频格式的选项之一需要192,000 Hz的采样频率。 数字录音(在游戏编辑器,音乐编辑器中)的标准是44 100 Hz-这是CD音频的频率。
振幅的数值存储在所谓的样本中,我们将与它们一起工作。 样本的值是float,可以是-1到1。经过简化,看起来像这样。

声波渲染(静态)
波形(或音频形式,在普通人中为“鱼”)是声音信号随时间变化的视觉表示。 该波形可以向我们显示声音在哪个点上发生了有源相位,以及在哪里衰减。 通常,每个通道的波形分别显示,例如:

想象一下,我们已经有了一个AudioSource和一个可以在其中工作的脚本。 让我们看看Unity可以给我们带来什么。
选择报告数
在继续之前,我们需要谈谈声音的渲染深度。 以每秒44100 Hz的采样频率,我们能够处理44100个报告。 假设我们需要渲染10秒长的轨道。 我们将用像素宽度的线条绘制每个报告。 事实证明,我们的波形长为441,000像素。 您会得到一个很长,很细且几乎无法理解的声波。 但是,您可以在其中看到每个特定的报告! 无论您如何绘制,都将极大地加载系统。

如果您不制作专业音频软件,则不需要这种准确性。 对于一般的音频图片,我们可以将所有采样分成更大的周期,例如,取每100个采样的平均值。 然后我们的wave将具有非常独特的形式:

当然,这并不是完全准确的,因为您可以跳过可能需要的体积峰值,因此您可以尝试的不是平均值,而是该分段的最大值。 这将产生稍微不同的图像,但是您的峰值不会消失。
准备接收音频
让我们将样本的准确性定义为质量,将最终报告的数目定义为sampleCount。
int quality = 100; int sampleCount = 0; sampleCount = freq / quality;
下面是计算所有数字的示例。
接下来,我们需要自己获取样本。 可以使用GetData方法从音频剪辑中完成此操作。
public bool GetData(float[] data, int offsetSamples);
此方法将一个数组写入示例。 offsetSamples-负责读取数据数组的起点的参数。 如果从头开始读取数组,则应该为零。
要记录样本,我们需要为它们准备一个数组。 例如,像这样:
float[] samples; float[] waveFormArray;
为什么我们将长度乘以通道数? 现在我告诉...
许多人都知道,在声音上,我们通常使用两个声道:左声道和右声道。 有人知道有2.1系统以及5.1和7.1,其中声源从四面八方环绕。 Wiki上对频道的主题进行了很好的描述。 这在Unity中如何工作?
下载文件时,打开剪辑时,您可以找到以下图像:

此处仅显示了我们有两个渠道,您甚至可以注意到它们彼此不同。 Unity依次记录这些通道的样本。 原来这张图:
这就是为什么我们在数组中需要的空间比仅是样本数大两倍的原因。
如果选择“强制到单声道”剪辑选项,则声道将为一个,所有声音将位于中央。 您的wave的预览将立即更改。


接收音频数据
这是我们得到的:
private int quality = 100; private int sampleCount = 0; private float[] waveFormArray; private float[] samples; private AudioSource myAudio; void Start() { myAudio = gameObject.GetComponent<AudioSource>(); int freq = myAudio.clip.frequency; sampleCount = freq / quality; samples = new float[myAudio.clip.samples * myAudio.clip.channels]; myAudio.clip.GetData(samples,0);
总计,如果音轨走了10秒并且是两个通道,那么我们得到以下信息:
- 剪辑中的样本数(myAudio.clip.sample)= 44100 * 10 = 441000
- 两个通道的样本数组很长(samples.Length)= 441000 * 2 = 882000
- 报告数量(sampleCount)= 44100/100 = 441
- 最终数组的长度= samples.Length / sampleCount = 2000
结果,我们将使用2000点,这足以吸引我们。 现在,您需要发挥想象力,并考虑如何使用这些数据。
使用调试工具创建简单的音轨
众所周知,Unity具有显示各种Debug信息的便捷方式。 基于这些工具的聪明开发人员可以为编辑器进行非常强大的扩展。 我们的案例显示了Debug方法的非典型用法。
为了绘制,我们需要一条线。 我们可以借助将根据数组值创建的向量来完成此操作。 请注意,要制作精美的镜像音频形式,我们需要“粘合”可视化的两个部分。
for (int i = 0; i < waveFormArray.Length - 1; i++) {
接下来,只需使用Debug.DrawLine绘制矢量即可。 任何颜色都可以选择。 所有这些方法都必须在Update中调用,因此我们将在每一帧更新信息。
Debug.DrawLine(upLine, downLine, Color.green);
如果需要,可以添加“滑块”,以显示正在播放的曲目的当前位置。 该信息可以从“ AudioSource.timeSamples”字段获得。
private float debugLineWidth = 5;
总计,这是我们的脚本:
using UnityEngine; public class WaveFormDebug : MonoBehaviour { private readonly int quality = 100; private int sampleCount = 0; private int freq; private readonly float debugLineWidth = 5; private float[] waveFormArray; private float[] samples; private AudioSource myAudio; private void Start() { myAudio = gameObject.GetComponent<AudioSource>();
结果如下:

使用PolygonCollider2D创建平滑的音景
在继续本节之前,我要注意以下几点:当然,沿着由音乐生成的轨道行驶很有趣,但是从游戏性的角度来看,这实际上是没有用的。 这就是为什么:
- 为了使轨道可以通过,我们需要对数据进行平滑处理。 所有的高峰都消失了,您几乎停止了“感觉音乐”
- 通常,音乐曲目是高度压缩的,并且代表一个音砖,不适合2D游戏。
- 我们的运输速度尚未解决的问题,应该适合于轨道的速度。 我想在下一篇文章中考虑这个问题。
因此,作为实验,这种类型的生成非常有趣,但是很难基于此生成真实的游戏功能。 无论如何,我们继续。
因此,我们需要使用我们的数据制作PolygonCollider2D。 这很容易做到。 PolygonCollider2D有一个接受Vector2 []的公共点字段。 首先,我们需要将点转移到所需类型的向量上。 让我们做一个函数,将样本数组转换为向量数组:
private Vector2[] CreatePath(float[] src) { Vector2[] result = new Vector2[src.Length]; for (int i = 0; i < size; i++) { result[i] = new Vector2(i * 0.01f, Mathf.Abs(src[i] * lineScale)); } return result; }
之后,只需将我们得到的向量数组传递给对撞机:
path = CreatePath(waveFormArray); poly.points = path;
我们看一下结果。 这是我们旅程的开始……嗯……看起来不太顺利(暂时不要考虑可视化,以后再发表评论)。

我们的音频形式过于清晰,因此音轨显得很奇怪。 需要使其平滑。 在这里,我们使用移动平均算法。 您可以在文章《移动平均算法(简单移动平均)》中了解有关Habr的更多信息。
在Unity中,该算法的实现方式如下:
private float[] MovingAverage(int frameSize, float[] data) { float sum = 0; float[] avgPoints = new float[data.Length - frameSize + 1]; for (int counter = 0; counter <= data.Length - frameSize; counter++) { int innerLoopCounter = 0; int index = counter; while (innerLoopCounter < frameSize) { sum = sum + data[index]; innerLoopCounter += 1; index += 1; } avgPoints[counter] = sum / frameSize; sum = 0; } return avgPoints; }
我们修改路径创建:
float[] avgArray = MovingAverage(frameSize, waveFormArray); path = CreatePath(avgArray); poly.points = path;
正在检查...

现在我们的轨迹看起来很正常。 我使用的窗口宽度为10。您可以修改此参数以选择所需的平滑度。
这是此部分的完整脚本:
using UnityEngine; public class WaveFormTest : MonoBehaviour { private const int frameSize = 10; public int size = 2048; public PolygonCollider2D poly; private readonly int lineScale = 5; private readonly int quality = 100; private int sampleCount = 0; private float[] waveFormArray; private float[] samples; private Vector2[] path; private AudioSource myAudio; private void Start() { myAudio = gameObject.GetComponent<AudioSource>(); int freq = myAudio.clip.frequency; sampleCount = freq / quality; samples = new float[myAudio.clip.samples * myAudio.clip.channels]; myAudio.clip.GetData(samples, 0); waveFormArray = new float[(samples.Length / sampleCount)]; for (int i = 0; i < waveFormArray.Length; i++) { waveFormArray[i] = 0; for (int j = 0; j < sampleCount; j++) { waveFormArray[i] += Mathf.Abs(samples[(i * sampleCount) + j]); } waveFormArray[i] /= sampleCount * 2; }
正如我在本节开头所说的那样,通过这种平滑处理,我们不再感觉到音轨,此外,机器的速度与音乐速度(BPM)无关。 我们将在本系列文章的下一部分中分析此问题。 此外,我们将在此讨论特殊主题。 节拍下的效果。 顺便说一句,我从这笔免费资产中拿了一台打字机。
也许很多人在看屏幕截图时都想知道我是如何绘制曲目的? 毕竟,对撞机是不可见的。
我利用Internet的智慧,找到了一种方法,可以将多边形对撞机变成可以分配任何材质的网格,并且线条渲染器将绘制出时尚的轮廓。 此方法在此处详细介绍。 您可以在Unity Community上使用Triangulator。
完成时间
我们从本文中学到的内容是音乐游戏的基本草图。 是的,到目前为止,这种形式有点难看,但是您可以放心地说:“伙计们,我让机器沿着音轨走了!”。 为了使它成为一个真正的游戏,您需要付出很多努力。 以下是我们可以在此处执行的操作的列表:
- 将机器的速度绑定到BPM轨道。 播放器只能控制汽车的倾斜度,而不能控制速度。 然后在整个过程中,音乐会变得更加强烈。
- 做一个位检测器,并添加特价。 在节拍下将起作用的效果。 此外,您可以向汽车车身添加动画,动画将随着拍子的跳动而反弹。 这完全取决于您的想象力。
- 而不是移动平均线,您需要更熟练地处理轨迹并获取数据数组,以使峰不会消失,但是创建轨迹很容易。
- 好吧,当然,您需要使游戏玩法有趣。 您可以在每次击中放置硬币位,添加危险区域等。
我们将在本系列文章的其余部分中研究所有这些以及更多内容。 谢谢大家的阅读!