音符和实体数据
在本章中,您将会学习到:
- 如何创建音符原型
- 如何编写谱面转换器
音符原型
让我们先初始化一个 Note
原型:
class Note: public Archetype {
public:
static constexpr const char* name = "My Note";
};
// ...
#ifdef play
using namespace playData;
#include"play/Initialization.cpp"
#include"play/Stage.cpp"
+ #include"play/Note.cpp"
#elif tutorial
// ...
// ...
#ifdef play
buffer data, configuration;
build<
// Replace with your archetypes here
Initialization,
Stage,
+ Note
>(configuration, data);
// ...
实体数据
直到现在,我们都只有 Initialization
原型和 Stage
原型,它们在所有关卡的行为都应该是一样的。
然而音符并不是那样的: 在一个关卡里,第一个音符可能在 5
秒处,而在另一个关卡里,第一个音符可能在 2
秒处; 一个关卡可以有 200
个音符,而在另一个关卡里可能只有 30
个音符。
那么引擎要如何才能处理由关卡提供的有不同数量,不同信息的音符呢?那就是 EntityData
发挥作用的地方。每一个关卡可以指定所有的实体并且向其注入数据。
让我们定义 Note
原型有一个名为 time
的数据,代表音符所在的时间。我们可以使用它的名字来导入数据:
class Note: public Archetype {
// ...
defineImports(time);
};
现在我们可以很轻松地访问它了。为了测试一下,让我们将它输出到日志中:
class Note: public Archetype {
// ...
SonolusApi updateParallel() {
FUNCBEGIN
Debuglog(time);
return VOID;
}
};
数据转换
现在,我们该创建我们的关卡了。
在 Sonolus.js 中,并没有辅助开发者创建关卡数据的套件。但在 Sonolus.h 里,您可以很轻松地处理来自原始格式的谱面数据(例如 *.sus
和 *.txt
)。
在此例中,我们假设用户提供一个很简单的文本文件,其中每行包含一个实数数据,表示音符所在的时间。
我们能够很简单地创建一个谱面转换器:
class InitializationEntity: public LevelEntity {
public:
defineArchetypeName("My Initialization");
};
class StageEntity: public LevelEntity {
public:
defineArchetypeName("My Stage");
};
class NoteEntity: public LevelEntity {
public:
defineArchetypeName("My Note");
defineLevelDataValue(time);
};
string fromTXT(string path) {
LevelRawData levelData;
levelData.append(InitializationEntity()); // 插入一个 Initialization 实体
levelData.append(StageEntity()); // 插入一个 Stage 实体
ifstream fin(path);
while (!fin.eof()) {
double time;
fin >> time; // 读取时间
NoteEntity note; // 定义一个 Note 实体
note.time = time; // 修改实体数据
levelData.append(note); // 插入该实体
}
fin.close();
return json_encode(levelData.toJsonObject()); // 输出为 json 数据
}
现在,我们需要给我们的程序提供能够转换谱面的接口,这也很容易实现:
// ...
int main(int argc, char** argv) {
if (argc > 3) {
string txtPath = argv[1], dataPath = argv[2]; // 从命令行获取文件路径
string rawData = fromTXT(txtPath); // 转换谱面为 json 格式
string data = compress_gzip(rawData, 9).asString(); // 压缩 json 数据,转为 LevelData 数据
ofstream fout(dataPath); // 输出 LevelData
fout.write(data.c_str(), data.size());
fout.close(); return 0; // 直接退出程序,避免再生成一次引擎数据,浪费时间
}
// ...
}
接下来,在引擎根目录下创建一个文件 chart.txt
,并向其中写入音符数据。然后修改 package.json
中的测试关卡信息,并向关卡信息中的 generate
选项填写 chart.txt dist/LevelData
,编译引擎后用 Sonolus 打开便可以看到您所创建的关卡了。