本篇教程的视频

本篇教程的源代码

Github地址:TutorialMod-Sound-1.21

介绍

在Minecraft中,有着各种各样的音效,比如玩家的行走声、村民的喃喃自语、游泳的声音等等

还有不同生物群系可能触发的不同背景音乐,不同唱片中的音乐等等

那么同样的,我们也可以自定义音效,新增声音事件

不过在开始之前,我们需要准备一些ogg格式的音频文件,这个文件是我们自定义的音效

但是不是直接改后缀名!!!不要把mp3改成ogg,这样是不行的,需要使用专业的音频编辑软件来转换(Fabric文档推荐的是Audacity

另外,音频需处理为单声道,否则声音将在整个世界中都可以被听到(没衰减了)

查看源代码

我们在开始写声音事件之前,我们先来看看SoundEvents这个类,这个类是定义了原版的声音事件

1
2
3
4
5
6
7
8
9
public static final SoundEvent BLOCK_NETHER_BRICKS_BREAK = register("block.nether_bricks.break");
public static final SoundEvent BLOCK_NETHER_BRICKS_STEP = register("block.nether_bricks.step");
public static final SoundEvent BLOCK_NETHER_BRICKS_PLACE = register("block.nether_bricks.place");
public static final SoundEvent BLOCK_NETHER_BRICKS_HIT = register("block.nether_bricks.hit");
public static final SoundEvent BLOCK_NETHER_BRICKS_FALL = register("block.nether_bricks.fall");

public static final RegistryEntry.Reference<SoundEvent> AMBIENT_CAVE = registerReference("ambient.cave");

public static final RegistryEntry<SoundEvent> ITEM_ARMOR_EQUIP_CHAIN = registerReference("item.armor.equip_chain");

这个类里面有一堆的声音事件,我们这里就几个不同类型的声音事件来看看

SoundEvent类型的我这里专门放了一个组,这个类型的大多数是简单的音效

比如这里的下界砖的破坏、踩踏、放置、击打、掉落(确切来讲是玩家掉在上面时)这五个音效,这也是我们待会要写的一组声音事件

RegistryEntry.Reference<SoundEvent>这类是比较特殊的音效,这些是属于氛围音(环境音或者背景音乐),比如这里的洞穴环境音(字幕中称怪异的噪声,它是由游戏算法随机播放的一组声音)

下一篇教程要讲到的音乐唱片也是这个类型的音效

RegistryEntry<SoundEvent>这也是比较特殊的音效,不过在原版中,只有穿戴盔甲拉弓三叉戟的投掷(包括带激流的附魔状态)三叉戟引雷羊驼被装饰这几个音效是这个类型的

不过,其实后面两个例子是确切来讲是同一个类型,因为在后面的那个注册方法中,它的返回值是RegistryEntry.Reference<SoundEvent>

1
2
3
private static RegistryEntry.Reference<SoundEvent> registerReference(String id) {
return registerReference(Identifier.ofVanilla(id));
}

这里我就放一个方法,后面层层叠叠的就不看了,这个方法的返回值是RegistryEntry.Reference<SoundEvent>,但我们上面它的类型却是RegistryEntry<SoundEvent>

要较真的话确实有点离谱,而且这里还有一个返回值是RegistryEntry<SoundEvent>的方法,但是并没有用上

1
2
3
private static RegistryEntry<SoundEvent> register(Identifier id, Identifier soundId, float distanceToTravel) {
return Registry.registerReference(Registries.SOUND_EVENT, id, SoundEvent.of(soundId, distanceToTravel));
}

这个方法没有一个地方用到它,但它就在这,不知道是写了一半就匆匆忙忙发新版本了还是干啥的,我们这里就不管了

所以简单而言,我们不管RegistryEntry<SoundEvent>这个类型,我们只需要关注SoundEventRegistryEntry.Reference<SoundEvent>这两个类型就行了

方块音效组

这里我们具体来看看方块的破坏踩踏放置击打掉落这五个音效用在何处,以下界砖为例

1
2
3
4
5
6
7
8
9
public static final BlockSoundGroup NETHER_BRICKS = new BlockSoundGroup(
1.0F,
1.0F,
SoundEvents.BLOCK_NETHER_BRICKS_BREAK,
SoundEvents.BLOCK_NETHER_BRICKS_STEP,
SoundEvents.BLOCK_NETHER_BRICKS_PLACE,
SoundEvents.BLOCK_NETHER_BRICKS_HIT,
SoundEvents.BLOCK_NETHER_BRICKS_FALL
);

我们可以在BlockSoundGroup这个类中看到,这个类是用来存放方块的音效组的,这里我们可以看到下界砖的五个音效事件

上面的两个数字代表的是音量和音调,后面按顺序是破坏、踩踏、放置、击打、掉落这五个音效

1
2
3
4
5
6
7
8
9
10
11
public static final Block NETHER_BRICKS = register(
"nether_bricks",
new Block(
AbstractBlock.Settings.create()
.mapColor(MapColor.DARK_RED)
.instrument(NoteBlockInstrument.BASEDRUM)
.requiresTool()
.strength(2.0F, 6.0F)
.sounds(BlockSoundGroup.NETHER_BRICKS)
)
);

而这一个音效组是在方块注册的时候用到的,这里我们可以看到下界砖方块的注册,这里的sounds就是音效组

所以当我们要给一个方块加音效时,需要自定义整个音效组,然后在方块注册的时候用上这个音效组(当然,如果你自己找不到音效的话,可以拿原版的声音事件东拼西凑)

注册声音事件

创建ModSoundEvents类

1
2
3
public class ModSoundEvents {

}

当然,上面源代码中的注册方法也是层层叠叠的,我们还是来整合一下

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

本期我们就写SoundEvent这个类型的方法,下一篇还将写到RegistryEntry.Reference<SoundEvent>这个类型的方法

而后我们就可以开始注册我们的声音事件了

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

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

这里我们注册了一个prospector_found_ore的声音事件,这个声音事件是用来在找到矿石时播放的

另外我们还注册了五个方块音效事件,这五个事件是用来在破坏、踩踏、放置、击打、掉落这五个时刻播放的

注册方块音效组

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 registerModSoundEvents() {

}

并在模组主类中调用这个方法

1
ModSoundEvents.registerModSoundEvents();

使用声音事件

在游戏里面,我们可以通过/playsound命令来播放特定的声音

不过在这里,我们还是将注册好的声音事件用在我们的物品和方块上

Prospector提示音

之前写了探矿器,我们可以在找到矿石的时候播放一个声音

1
2
3
4
5
6
if (isRightBlock(blockState)) {
player.sendMessage(Text.of("Found" + name + "!"));
world.playSound(null, pos, ModSoundEvents.PROSPECTOR_FOUND_ORE, SoundCategory.BLOCKS, 1.0f, 1.0f);
foundBlock = true;
break;
}

这里我们在找到矿石的时候播放prospector_found_ore这个声音事件

使用world.playSound方法,第一个参数是PlayerEntity(发出声音的实体,可以填null),第二个参数是BlockPos(位置),第三个参数是声音事件,第四个参数是声音类别,第五个参数是音量,第六个参数是音调

这样当我们扫到矿石的时候,就会播放这个声音

方块音效组

我们可以在方块注册的时候用上我们的方块音效组

1
2
public static final Block ICE_ETHER_BLOCK = register("ice_ether_block", new Block(AbstractBlock.Settings.create()
.requiresTool().strength(3.0f, 3.0f).sounds(ModSoundEvents.BLOCK_SOUND_GROUP)));

我们直接加上sounds这个方法,然后填入我们的音效组就行了

数据文件

语言文件

1
2
3
4
5
6
translationBuilder.add("sounds.tutorialmod.prospector_found_ore", "Prospector Found Ore");
translationBuilder.add("sounds.tutorialmod.block_break", "Block Break");
translationBuilder.add("sounds.tutorialmod.block_step", "Block Step");
translationBuilder.add("sounds.tutorialmod.block_place", "Block Place");
translationBuilder.add("sounds.tutorialmod.block_hit", "Block Hit");
translationBuilder.add("sounds.tutorialmod.block_fall", "Block Fall");

这些确切来讲是开启显示字幕时用到的,不如蹦出来的是一串

sounds.json

这个文件是存放声音事件对应的音频文件的

路径为resources/assets/tutorialmod/sounds.json

而我们的音频文件在resources/assets/tutorialmod/sounds下面

再说一次!ogg格式!包括后缀名在内都不能大小!不要直接改文件后缀名!

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
{
"prospector_found_ore": {
"subtitle": "sounds.tutorialmod.prospector_found_ore",
"sounds": [
"tutorialmod:prospector_found_ore"
]
},
"block_break": {
"subtitle": "sounds.tutorialmod.block_break",
"sounds": [
"tutorialmod:block_break"
]
},
"block_fall": {
"subtitle": "sounds.tutorialmod.block_fall",
"sounds": [
"tutorialmod:block_fall"
]
},
"block_hit": {
"subtitle": "sounds.tutorialmod.block_hit",
"sounds": [
"tutorialmod:block_hit"
]
},
"block_place": {
"subtitle": "sounds.tutorialmod.block_place",
"sounds": [
"tutorialmod:block_place"
]
},
"block_step": {
"subtitle": "sounds.tutorialmod.block_step",
"sounds": [
"tutorialmod:block_step"
]
}
}

这里我们可以看到,每个声音事件都有一个subtitle,这个是用来显示字幕的(要和语言文件中的键一致,不然还是会有问题)

后面的sounds命名空间+音频文件名,音频文件放对位置就可以了

随后我们就可以进入游戏测试了

题外话

其实在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版本开始的)

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