BPM 与节拍
在本章中,您将会学习到:
- BPM 与节拍的优势
- 如何使用 BPM 与节拍
- 重构音符实体
BPM 与节拍
目前 Note
原型使用的是以秒为单位的时间,然而那并不是一个节奏游戏谱面的通常做法。通常情况下,每个音符都有一个节拍数,其时间是通过节拍数以及一系列的背景音乐的 BPM(每分钟节拍数) 计算出来的。
尽管引擎能够存储 BPM 并自行进行转换,但 Sonolus 提供更方便的函数来进行转换。让我们来看一下如何将这项功能移植到我们的引擎中。
BPM 变化
我们首先需要告诉 Sonolus 所有的 BPM 变化。
通过在关卡数据中使用一系列包含针对 BPM 变化的特殊原型名,针对节拍以及 BPM 值的特殊数据名称的实体列表,这项工作能够很轻松地完成。
class BpmChangeEntity: public LevelEntity {
public:
defineArchetypeName("#BPM_CHANGE");
defineLevelDataValueDetailed(beat, "#BEAT");
defineLevelDataValueDetailed(bpm, "#BPM");
};
// ...
string fromTXT(string path) {
// ...
BpmChangeEntity bpmChange; bpmChange.beat = 0; bpmChange.bpm = 120;
levelData.append(bpmChange); // 插入一个 BpmChange 实体
// ...
}
这个实体告诉 Sonolus 在第 0
拍时,BPM 变为了 120
。
注意到我们不需要实现 BPM 变化原型。总的来说,任何带有没有实现的原型名的实体会被忽略,因此我们可以安全地使用它们在我们的关卡中存储元数据。
重构
有了 BPM 变化的设置,我们可以重构 Note
原型,以使用关卡数据中的节拍而不是时间:
// ...
class NoteEntity: public LevelEntity {
public:
defineArchetypeName("My Note");
defineLevelDataValue(beat);
};
// ...
string fromTXT(string path) {
// ...
ifstream fin(path);
while (!fin.eof()) {
double beat;
fin >> beat; // 读取节拍
NoteEntity note; // 定义一个 Note 实体
note.beat = beat; // 修改实体数据
levelData.append(note); // 插入该实体
}
fin.close();
// ...
}
注意到,在这里我们可以不使用针对节拍的特殊数据名。