自定义声音 1.21 Fabric
本篇教程的视频
本篇教程的源代码
Github地址:TutorialMod-Sound-1.21
介绍
在Minecraft中,有着各种各样的音效,比如玩家的行走声、村民的喃喃自语、游泳的声音等等
还有不同生物群系可能触发的不同背景音乐,不同唱片中的音乐等等
那么同样的,我们也可以自定义音效,新增声音事件
不过在开始之前,我们需要准备一些ogg
格式的音频文件,这个文件是我们自定义的音效
但是不是直接改后缀名!!!不要把mp3
改成ogg
,这样是不行的,需要使用专业的音频编辑软件来转换(Fabric文档推荐的是Audacity)
另外,音频需处理为单声道
,否则声音将在整个世界中都可以被听到(没衰减了)
查看源代码
我们在开始写声音事件之前,我们先来看看SoundEvents
这个类,这个类是定义了原版的声音事件
1 | public static final SoundEvent BLOCK_NETHER_BRICKS_BREAK = register("block.nether_bricks.break"); |
这个类里面有一堆的声音事件,我们这里就几个不同类型的声音事件来看看
SoundEvent
类型的我这里专门放了一个组,这个类型的大多数是简单的音效
比如这里的下界砖的破坏、踩踏、放置、击打、掉落(确切来讲是玩家掉在上面时)这五个音效,这也是我们待会要写的一组声音事件
RegistryEntry.Reference<SoundEvent>
这类是比较特殊的音效,这些是属于氛围音(环境音或者背景音乐),比如这里的洞穴环境音(字幕中称怪异的噪声,它是由游戏算法随机播放的一组声音)
下一篇教程要讲到的音乐唱片
也是这个类型的音效
RegistryEntry<SoundEvent>
这也是比较特殊的音效,不过在原版中,只有穿戴盔甲
、拉弓
、三叉戟的投掷(包括带激流的附魔状态)
、三叉戟引雷
、羊驼被装饰
这几个音效是这个类型的
不过,其实后面两个例子是确切来讲是同一个类型,因为在后面的那个注册方法中,它的返回值是RegistryEntry.Reference<SoundEvent>
1 | private static RegistryEntry.Reference<SoundEvent> registerReference(String id) { |
这里我就放一个方法,后面层层叠叠的就不看了,这个方法的返回值是RegistryEntry.Reference<SoundEvent>
,但我们上面它的类型却是RegistryEntry<SoundEvent>
要较真的话确实有点离谱,而且这里还有一个返回值是RegistryEntry<SoundEvent>
的方法,但是并没有用上
1 | private static RegistryEntry<SoundEvent> register(Identifier id, Identifier soundId, float distanceToTravel) { |
这个方法没有一个地方用到它,但它就在这,不知道是写了一半就匆匆忙忙发新版本了还是干啥的,我们这里就不管了
所以简单而言,我们不管RegistryEntry<SoundEvent>
这个类型,我们只需要关注SoundEvent
和RegistryEntry.Reference<SoundEvent>
这两个类型就行了
方块音效组
这里我们具体来看看方块的破坏
、踩踏
、放置
、击打
、掉落
这五个音效用在何处,以下界砖
为例
1 | public static final BlockSoundGroup NETHER_BRICKS = new BlockSoundGroup( |
我们可以在BlockSoundGroup
这个类中看到,这个类是用来存放方块的音效组的,这里我们可以看到下界砖
的五个音效事件
上面的两个数字代表的是音量和音调,后面按顺序是破坏、踩踏、放置、击打、掉落这五个音效
1 | public static final Block NETHER_BRICKS = register( |
而这一个音效组是在方块注册的时候用到的,这里我们可以看到下界砖
方块的注册,这里的sounds
就是音效组
所以当我们要给一个方块加音效时,需要自定义整个音效组,然后在方块注册的时候用上这个音效组(当然,如果你自己找不到音效的话,可以拿原版的声音事件东拼西凑)
注册声音事件
创建ModSoundEvents类
1 | public class ModSoundEvents { |
当然,上面源代码中的注册方法也是层层叠叠的,我们还是来整合一下
1 | private static SoundEvent register(String name) { |
本期我们就写SoundEvent
这个类型的方法,下一篇还将写到RegistryEntry.Reference<SoundEvent>
这个类型的方法
而后我们就可以开始注册我们的声音事件了
1 | public static final SoundEvent PROSPECTOR_FOUND_ORE = register("prospector_found_ore"); |
这里我们注册了一个prospector_found_ore
的声音事件,这个声音事件是用来在找到矿石时播放的
另外我们还注册了五个方块音效事件,这五个事件是用来在破坏、踩踏、放置、击打、掉落这五个时刻播放的
注册方块音效组
1 | public static final BlockSoundGroup BLOCK_SOUND_GROUP = new BlockSoundGroup(1.0F, 1.0F, |
我们不用单独起个类了,就在这个类里写就好,方法是和源代码一样的,注意这里的参数顺序
随后,也不要忘了初始化的方法
1 | public static void registerModSoundEvents() { |
并在模组主类中调用这个方法
1 | ModSoundEvents.registerModSoundEvents(); |
使用声音事件
在游戏里面,我们可以通过/playsound
命令来播放特定的声音
不过在这里,我们还是将注册好的声音事件用在我们的物品和方块上
Prospector提示音
之前写了探矿器,我们可以在找到矿石的时候播放一个声音
1 | if (isRightBlock(blockState)) { |
这里我们在找到矿石的时候播放prospector_found_ore
这个声音事件
使用world.playSound
方法,第一个参数是PlayerEntity
(发出声音的实体,可以填null),第二个参数是BlockPos
(位置),第三个参数是声音事件,第四个参数是声音类别,第五个参数是音量,第六个参数是音调
这样当我们扫到矿石的时候,就会播放这个声音
方块音效组
我们可以在方块注册的时候用上我们的方块音效组
1 | public static final Block ICE_ETHER_BLOCK = register("ice_ether_block", new Block(AbstractBlock.Settings.create() |
我们直接加上sounds
这个方法,然后填入我们的音效组就行了
数据文件
语言文件
1 | translationBuilder.add("sounds.tutorialmod.prospector_found_ore", "Prospector Found Ore"); |
这些确切来讲是开启显示字幕
时用到的,不如蹦出来的是一串
sounds.json
这个文件是存放声音事件对应的音频文件的
路径为resources/assets/tutorialmod/sounds.json
而我们的音频文件在resources/assets/tutorialmod/sounds
下面
再说一次!ogg
格式!包括后缀名在内都不能大小!不要直接改文件后缀名!
1 | { |
这里我们可以看到,每个声音事件都有一个subtitle
,这个是用来显示字幕的(要和语言文件中的键一致,不然还是会有问题)
后面的sounds
是命名空间
+音频文件名
,音频文件放对位置就可以了
随后我们就可以进入游戏测试了
题外话
其实在Minecraft中,我们拆包就会发现,根本找不到原版的那些声音文件
这些声音文件是外置的,在我们下载游戏后,.minecraft
下会有一个assets
文件夹,里面有一个objects
文件夹,这里面塞着一堆没有后缀名的文件,其中一些就是我们的声音文件
不过,你也不知道谁是谁,因为那些文件名是加密过的
在assets
文件夹下还有一个indexes
文件夹,里面有一个indexes.json
文件,这个文件是用来记录所有文件的hash
值的
1 | "minecraft/sounds/music/game/a_familiar_room.ogg": |
看其中的一个,我们用这里的这一串c1c3b7233b080ffc17389af8b68a46b2df798ef1
就可以到objects
文件夹下找到对应的文件,而后在这个文件后面加上.ogg
就是这个a_familiar_room
音频文件
不过在各个模组中,我们不需要搞这么复杂,只需要把音频文件放对位置就行了,再有一个sounds.json
文件来对应就行了(这个应该是Minecraft为了资源包制作者和开发者专门留的一个口,从1.7版本开始的)
好了,这只是题外话,如果你想提取音频文件,可以这样找