本篇教程的视频

本篇教程的源代码

GitHub地址:TutorialMod-Sound-1.20.1

本篇教程目标

  • 理解原版的声音事件注册
  • 学会注册声音事件并使用

查看源代码

声音事件出现在我们教程中的话,最早是在方块的注册

1
2
3
public static final Block GRASS_BLOCK = register(
"grass_block", new GrassBlock(AbstractBlock.Settings.create().mapColor(MapColor.PALE_GREEN).ticksRandomly().strength(0.6F).sounds(BlockSoundGroup.GRASS))
);

在这个GrassBlock中,我们看到了一个sounds方法,这个方法就是用来给方块一个声音组的,即BlockSoundGroup

1
2
3
4
5
6
7
8
9
public static final BlockSoundGroup GRASS = new BlockSoundGroup(
1.0F,
1.0F,
SoundEvents.BLOCK_GRASS_BREAK,
SoundEvents.BLOCK_GRASS_STEP,
SoundEvents.BLOCK_GRASS_PLACE,
SoundEvents.BLOCK_GRASS_HIT,
SoundEvents.BLOCK_GRASS_FALL
);

一个声音组的参数有7个,前面两个是音量音调,后面的分别是该方块的破坏行走放置击中掉落的声音,这些声音都是SoundEvent,即声音事件

除此之外,我们也接触过很多SoundEvents的声音事件,原版所有的声音事件都是在这个类中注册的

1
public static final SoundEvent BLOCK_GRASS_BREAK = register("block.grass.break");

它的注册方法是register,不过也是叠起来的

1
2
3
4
5
6
7
8
9
10
11
private static SoundEvent register(String id) {
return register(new Identifier(id));
}

private static SoundEvent register(Identifier id) {
return register(id, id);
}

private static SoundEvent register(Identifier id, Identifier soundId) {
return Registry.register(Registries.SOUND_EVENT, id, SoundEvent.of(soundId));
}

最后也是同样的的Registry.register方法,在我们自己写的时候,命名空间是要改的

注册声音事件

那么现在,我们就来注册自定义的声音事件

创建一个ModSoundEvents类

1
2
3
public class ModSoundEvents {

}

然后我们写一个注册方法

1
2
3
4
private static SoundEvent register(String name) {
Identifier id = new Identifier(TutorialMod.MOD_ID, name);
return Registry.register(Registries.SOUND_EVENT, id, SoundEvent.of(id));
}

这里的id,我们要加上自己的命名空间

然后我们就可以来注册我们的声音事件了

1
2
3
4
5
6
7
public static final SoundEvent TEST = register("test");

public static final SoundEvent BLOCK_BREAK = register("block_break");
public static final SoundEvent BLOCK_PLACE = register("block_place");
public static final SoundEvent BLOCK_STEP = register("block_step");
public static final SoundEvent BLOCK_FALL = register("block_fall");
public static final SoundEvent BLOCK_HIT = register("block_hit");

这里我们写6个,下面的5个将作为一个声音组,注册之后给一个方块使用

上面的1个则使用在我们的物品上

接下来我们注册一个声音组

1
2
public static final BlockSoundGroup BLOCK_SOUND_GROUP = new BlockSoundGroup(1.0F, 1.0F,
BLOCK_BREAK, BLOCK_STEP, BLOCK_PLACE, BLOCK_HIT, BLOCK_FALL);

最后还要写一个用于初始化的方法

1
2
3
public static void registerSounds() {

}

以及主类的调用

1
ModSoundEvents.registerSounds();

使用声音事件

方块声音组

和原版一样,我们使用sounds方法来给方块一个声音组

1
public static final Block ICE_ETHER_BLOCK = register("ice_ether_block", new Block(AbstractBlock.Settings.copy(Blocks.STONE).sounds(ModSoundEvents.BLOCK_SOUND_GROUP)));

我们可以给之前注册好的方块加上这个方法,然后加上声音组

物品声音事件

物品吧,我们之前就写了各自定义的斧头和镐子相结合的物品,我们可以加上一个声音事件,来判断当前方块是否可以被我们的工具挖掘

这里我们来重写useOnBlock方法,这个方法会在使用物品右键方块时调用

1
2
3
4
5
6
7
8
9
10
11
@Override
public ActionResult useOnBlock(ItemUsageContext context) {
super.useOnBlock(context);
if (!context.getWorld().isClient()) {
BlockState state = context.getWorld().getBlockState(context.getBlockPos());
if (state.isIn(ModBlockTags.PICKAXE_AXE)) {
context.getWorld().playSound(null, context.getBlockPos(), ModSoundEvents.TEST, SoundCategory.BLOCKS, 1.0F, 1.0F);
}
}
return ActionResult.SUCCESS;
}

原来的super得留着,不然斧头削皮的操作就没了

然后我们判断右键的方块是否在我们之前定义的PICKAXE_AXE这个方块标签中,
如果是,那么就发出提示音

这里我们使用playSound方法来播放声音,它的参数有5

第一个是玩家实体,这里我们传入null,因为这个声音不是玩家发出的

第二个是方块位置,也就是声音发出的位置

第三个是声音事件,这里我们传入我们之前定义好的TEST

第四个是声音类别,这里我们传入BLOCKS,因为声音是方块发出的

最后两个是音量音调,一般都用1.0即可

数据文件

声音文件

注意,Minecraft只支持ogg的音频格式,所以在使用前,我们要将其他格式的音频转化为ogg格式的

同时注意,音频需要处理为单声道的,这个东西在后面的音乐唱片中我们还会讲

音频文件放置的位置在<modid>/sounds

sounds.json

assets/<modid>下,我们还要创建一个sounds.json文件

这个是用于音频文件的映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
{
"test":{
"subtitle":"sounds.tutorial-mod.test",
"sounds":[
"tutorial-mod:test"
]
},
"block_break":{
"subtitle":"sounds.tutorial-mod.block_break",
"sounds":[
"tutorial-mod:block_break"
]
},
"block_fall":{
"subtitle":"sounds.tutorial-mod.block_fall",
"sounds":[
"tutorial-mod:block_fall"
]
},
"block_hit":{
"subtitle":"sounds.tutorial-mod.block_hit",
"sounds":[
"tutorial-mod:block_hit"
]
},
"block_place":{
"subtitle":"sounds.tutorial-mod.block_place",
"sounds":[
"tutorial-mod:block_place"
]
},
"block_step":{
"subtitle":"sounds.tutorial-mod.block_step",
"sounds":[
"tutorial-mod:block_step"
]
}
}

里面的键值是我们注册声音事件用的id

subtitle是字幕显示用的,下面的语言文件翻译就用上了

sounds是指定的声音文件,指向sounds文件夹下的那些文件

语言文件

1
2
3
4
5
6
translationBuilder.add("sounds.tutorial-mod.test", "Test");
translationBuilder.add("sounds.tutorial-mod.block_break", "Block Break");
translationBuilder.add("sounds.tutorial-mod.block_place", "Block Place");
translationBuilder.add("sounds.tutorial-mod.block_step", "Block Step");
translationBuilder.add("sounds.tutorial-mod.block_hit", "Block Hit");
translationBuilder.add("sounds.tutorial-mod.block_fall", "Block Fall");

这里写的翻译文件是用在字幕显示上的,也就是你将显示字幕打开之后,会在屏幕右边显示的那些像脚步声方块破坏这样的字幕

测试

那么现在我们就可以进入游戏进行测试了

实际游戏的音频文件

其实在Minecraft中,我们拆包就会发现,根本找不到原版的那些声音文件

这些声音文件是外置的,在我们下载游戏后,.minecraft下会有一个assets文件夹,里面有一个objects文件夹,这里面塞着一堆没有后缀名的文件,其中一些就是我们的声音文件

不过,你也不知道谁是谁,因为那些文件名是加密过的

assets文件夹下还有一个indexes文件夹,里面有一个indexes.json文件,这个文件是用来记录所有文件的hash值的

1
2
3
"minecraft/sounds/music/game/a_familiar_room.ogg": 
{"hash": "c1c3b7233b080ffc17389af8b68a46b2df798ef1",
"size": 6669455}

看其中的一个,我们用这里的这一串c1c3b7233b080ffc17389af8b68a46b2df798ef1就可以到objects文件夹下找到对应的文件,而后在这个文件后面加上.ogg就是这个a_familiar_room音频文件

不过在各个模组中,我们不需要搞这么复杂,只需要把音频文件放对位置就行了,再有一个sounds.json文件来对应就行了(这个应该是Minecraft为了资源包制作者和开发者专门留的一个口,从1.7版本开始的)

好了,这只是题外话,如果你想提取音频文件,可以这样找