本篇教程的视频

本篇教程的源代码

Github地址:TutorialMod-MusicDisc-1.21

介绍

前面我们讲到了自定义声音,那么这一篇我们就来讲讲自定义音乐唱片

1.20中我们也讲到了音乐唱片,但是在1.21中,音乐唱片的注册方式有了一些变化,它有单独的数据文件得写

那么同样的,在开始之前准备一个ogg格式的音频文件,这个是我们音乐唱片对应的音频文件

查看源代码

关于注册的源代码就粗略看一眼,前一篇教程都解释过了,这里就不再赘述了

1
public static final RegistryEntry.Reference<SoundEvent> MUSIC_DISC_5 = registerReference("music_disc.5");

我们就直接以这个音乐唱片为例,这里注册的类型是RegistryEntry.Reference<SoundEvent>,其注册方法也是层层叠叠的,后面我们整合一下即可

而后,我们查找这个字段的用法

1
2
3
4
5
static void bootstrap(Registerable<JukeboxSong> registry) {
...
register(registry, FIVE, SoundEvents.MUSIC_DISC_5, 178, 15);
...
}

这个是在JukeboxSongs这个接口中的bootstrap方法,用于动态注册,后面的两个数字代表的是音乐唱片的播放时间比较器输出的红石信号强度(0~15)

1
2
3
4
5
RegistryKey<JukeboxSong> FIVE = of("5");
...
private static RegistryKey<JukeboxSong> of(String id) {
return RegistryKey.of(RegistryKeys.JUKEBOX_SONG, Identifier.ofVanilla(id));
}

上面的FIVE是一个RegistryKey类型的字段,它是音乐唱片的注册键(说白了,其实就是你音乐的音频文件的名字,你后面就可以发现)

下面的of方法,返回的是一个RegistryKey类型的字段,用于注册音乐唱片的注册键

再进一步发掘,除了这个接口本身的bootstrap方法,还有一个地方用到了FIVE这个字段

1
2
3
public static final Item MUSIC_DISC_5 = register(
"music_disc_5", new Item(new Item.Settings().maxCount(1).rarity(Rarity.RARE).jukeboxPlayable(JukeboxSongs.FIVE))
);

它便是我们物品的注册,这里的jukeboxPlayable方法就是用于设置音乐唱片的播放音乐

这里的JukeboxSongs.FIVE就是我们上面注册的音乐唱片的注册键

其他的方法像maxCount,这是物品最大堆叠数量,rarity是物品的稀有度,直接影响的是物品名称文字的颜色,这里的RARE是稀有的意思,文字是蓝色的(AQUA),它还有EPIC,这是史诗的意思,文字是紫色的(LIGHT_PURPLE

这里就是一个完整的注册流程

稍微捋一下,SoundEvents注册的是播放这个音乐唱片的事件,JukeboxSongs注册的是实际音乐唱片对应的音频文件,Item注册的是音乐唱片这个物品,这三者之间的关系就是这样的

注册音乐唱片

注册声音事件

首先我们要注册声音事件,这个是播放音乐唱片的事件

不过在注册之前,先把注册方法整合一下

1
2
3
4
private static RegistryEntry.Reference<SoundEvent> registerReference(String name) {
Identifier id = Identifier.of(TutorialMod.MOD_ID, name);
return Registry.registerReference(Registries.SOUND_EVENT, id, SoundEvent.of(id));
}

其实就是原版的方法,整合到了一起,并用我们自己的命名空间

然后我们就可以注册音乐唱片的声音事件了

1
public static final RegistryEntry.Reference<SoundEvent> MUSIC_DISC_TEST = registerReference("music_disc.test");

这里我们注册了一个名为music_disc.test的音乐唱片的声音事件

创建ModJukeboxSongs接口

和原版的一样,我们创建一个接口,用于注册音乐唱片的对应的音频文件

1
2
3
public interface ModJukeboxSongs {

}

当然,这里我们需要写一个注册方法

1
2
3
private static RegistryKey<JukeboxSong> of(String id) {
return RegistryKey.of(RegistryKeys.JUKEBOX_SONG, Identifier.of(TutorialMod.MOD_ID, id));
}

随后我们就可以用这个注册方法来注册音乐唱片的音频文件了

1
RegistryKey<JukeboxSong> TEST = of("test");

再接下来我们写一个bootstrap方法,用于动态注册

不过同样的,它也有一个注册方法

1
2
3
4
5
6
7
8
9
10
11
private static void register(
Registerable<JukeboxSong> registry, RegistryKey<JukeboxSong> key, RegistryEntry.Reference<SoundEvent> soundEvent, int lengthInSeconds, int comparatorOutput
) {
registry.register(
key, new JukeboxSong(soundEvent, Text.translatable(Util.createTranslationKey("jukebox_song", key.getValue())), (float)lengthInSeconds, comparatorOutput)
);
}

static void bootstrap(Registerable<JukeboxSong> registry) {
register(registry, TEST, ModSoundEvents.MUSIC_DISC_TEST, 247, 15);
}

这个方法用于注册音乐唱片的音频文件,lengthInSeconds是音乐唱片的播放时间,comparatorOutput是比较器输出的红石信号强度

在下面的bootstrap方法中,我们就可以用这个方法来注册音乐唱片的音频文件了

DataGen调用

以后见到bootstrap,都记得去我们的数据生成类中调用一下,因为这个方法是动态注册的,即你要用到它的时候才会给你注册,塞到注册表里去(并不是像前面的方块或者物品那样,启动游戏就完成了注册)

1
2
3
4
@Override
public void buildRegistry(RegistryBuilder registryBuilder) {
registryBuilder.addRegistry(RegistryKeys.JUKEBOX_SONG, ModJukeboxSongs::bootstrap);
}

这里我们在TutorialModDataGenerator中重写buildRegistry方法,调用ModJukeboxSongsbootstrap方法

注册物品

接下来我们注册对应的音乐唱片这个物品

1
2
public static final Item TEST_MUSIC_DISC = registerItems("test_music_disc",
new Item(new Item.Settings().maxCount(1).rarity(Rarity.RARE).jukeboxPlayable(ModJukeboxSongs.TEST)));

和原版一样写就好了

加入物品栏

最后我们把这个音乐唱片加入到物品栏中

1
entries.add(ModItems.TEST_MUSIC_DISC);

数据文件

语言文件

1
2
3
4
translationBuilder.add(ModItems.TEST_MUSIC_DISC, "Test Music Disc");
translationBuilder.add("jukebox_song.tutorialmod.test", "Test");

translationBuilder.add("item.tutorialmod.test.desc", "Test");

一个是我们音乐唱片这个物品的名字,另一个则是当我们播放唱片的时候,蹦出来的那一段彩色字幕(正在播放:...

而最后一句,是音乐唱片的描述信息,这个不写其实没关系,不知为何如果不写也会显示用jukebox_song.tutorialmod.test翻译的Test

模型文件

1
itemModelGenerator.register(ModItems.TEST_MUSIC_DISC, Models.TEMPLATE_MUSIC_DISC);

模型文件也有些特殊,它的模型父级用的是TEMPLATE_MUSIC_DISC,这个是原版的音乐唱片的模型

音乐唱片的音频文件

和前面一样,放在src/main/resources/assets/tutorialmod/sounds

sounds.json

1
2
3
4
5
"music_disc.test": {
"sounds": [
"tutorialmod:test"
]
}

在我们之前写的上面加上这一段就好

材质文件

材质文件也不要忘记了

但是,当我们这里都设置好之后,我们进入游戏想拿音乐唱片来播放时,却发现它虽然可以放到唱片机里面,也有这个彩色的字幕蹦出来,但却没有声音(当然,先确保音频文件没问题)

为什么嘞?因为在1.21中,我们还得为音乐唱片写单独的数据文件

test.json

这个文件名是我们在JukeboxSongs中注册的音乐唱片的注册键的名字,这里是test

路径是resources/data/tutorialmod/jukebox_song/test.json

1
2
3
4
5
6
7
8
{
"comparator_output": 15,
"description": {
"translate": "jukebox_song.tutorialmod.test"
},
"length_in_seconds": 247.0,
"sound_event": "tutorialmod:music_disc.test"
}

在这里面我们塞一些参数

comparator_output是比较器输出的红石信号强度,这里是15(和bootstrap中的一致)

description是音乐唱片的描述信息,这里是jukebox_song.tutorialmod.test(对应我们语言文件写的第二句)

length_in_seconds是音乐唱片的播放时间,这里是247.0(单位秒,要自己换算一下,和bootstrap中的一致)

sound_event是音乐唱片的声音事件,这里是tutorialmod:music_disc.test

在此之后,我们进入游戏就可以正常播放音乐唱片了

同时,你也可以感受一下单声道音频文件和立体声(双声道或是多声道)的音频文件的区别,这个在音乐唱片上尤为明显(在视频教程中用的是单声道的)

单声道当我们玩家转到不同方向时,左右声道的声音会有所变化(戴耳机的话);同时远离唱片机会感受到声音的衰减

但是立体声就不一样了,它没有衰减,也没有左右声道的变化(除非你写的是整个游戏的背景音乐,那到可以用立体声的)