自定义声音 26.1 Fabric
本篇教程的视频
本篇教程的源代码
GitHub地址:
本篇教程目标
- 搞清楚声音事件和
ogg文件是怎么对上的 - 学会注册声音事件
- 学会写
sounds.json和字幕 - 学会给方块套自定义音效组,以及在代码里主动播放声音
介绍
Minecraft里的声音分很多种,走路的声音、挖方块的声音、村民嘟囔的声音、唱片里的音乐,全都是「声音事件」加「音频文件」凑出来的
我们当然也能加自己的
在开始之前,得先准备ogg格式的音频
这里有两个坑,先说一下:
- 不要直接把
mp3改后缀名,那样是放不出来的,得用格式工厂这类软件真转成ogg - 音频要导出成单声道。立体声的音频在游戏里没有距离衰减,站多远都能听见,听起来会很怪
这一期和 1.21.1 那篇的区别,主要是移除混淆后的类名和方法名,写法上倒是差不多的
另外,26.1 的SoundEvent是个record,里面就两个字段:location(音频位置)和fixedRange(固定的可听距离,可以没有)
所以它有两个工厂方法
createVariableRangeEvent(id)是普通音效,距离随音量走,绝大多数音效都用这个
createFixedRangeEvent(id, range)把可听距离写死,不随音量变化,用在一些特殊场合
这一期我们只用第一个
查看源代码
原版怎么注册声音事件
原版的声音事件都在SoundEvents这个类里
1 | public static final SoundEvent NETHER_BRICKS_BREAK = register("block.nether_bricks.break"); |
注意括号里的字符串是block.nether_bricks.break这种点分格式,这个是资源路径,和json里的键是对应的
再看看它的注册方法
1 | private static SoundEvent register(final String id) { |
常规的,有一堆重载方法,实际上我们只需要关注最后一个就好了
很简单,就是往SOUND_EVENT这个注册表里塞一个SoundEvent
这个方法传了两个id,第一个是注册名,第二个是声音ID。原版为了方便,两个传的一样
SoundType
然后是方块音效组,也就是SoundType
这个类不算注册表,就是个普通的对象,构造函数长这样
1 | public SoundType( |
一共七个参数
前两个是音量和音调
后面五个按顺序是:破坏、踩踏、放置、击打、掉落
掉落那个是指玩家或实体砸在上面的时候,不是方块自己掉下去
原版的下界砖是这样写的
1 | public static final SoundType NETHER_BRICKS = new SoundType(1.0F, 1.0F, |
这五个参数的顺序一定要对上,写反了不会报错,但挖方块的时候放的是踩踏声,很难发现
方块那边就是用sound这个方法把音效组挂上去,比如
1 | BlockBehaviour.Properties.of().strength(2.0F, 6.0F).sound(SoundType.NETHER_BRICKS) |
所以想给方块换音效,思路就是:先攒五个声音事件,拼成一个SoundType,再在方块属性里挂上
动手写
创建 ModSoundEvents
我们新建一个sound包,写ModSoundEvents
1 | public class ModSoundEvents { |
注册方法和原版一样,只是把命名空间换成我们自己的
这里注册了六个事件,一个给探矿器用,五个给方块音效组用
名字都是小写下划线,没有点号
你有多个方块音效要添加时,也可以使用原版那种block.nether_bricks.break,不过注意要和后面sounds.json里的键一致
音效组
接着在同一类里拼一个音效组
1 | public static final SoundType ICE_ETHER_SOUND = new SoundType(1.0F, 1.0F, |
因为SoundType不是注册表里的东西,所以不用注册,直接new就行
初始化方法
1 | public static void register() { |
这个方法里没有正经逻辑,就打了行日志
然后在主类里调用
1 | ModSoundEvents.register(); |
放在方块注册后面就行
给方块用上
找ModBlocks里的ICE_ETHER_BLOCK,在属性后面加上sound
1 | public static final Block ICE_ETHER_BLOCK = register("ice_ether_block", Block::new, |
这样破坏、放置、走上去、打一下、摔在上面,放的都会是我们的声音
其他方块想换也可以照这样加,只要它们共用同一个音效组就行
在代码里播放声音
有些声音不是方块自己触发的,得我们手动播
比如之前写的探矿器,扫到矿石的时候可以响一声
1 | if (found) { |
playSound的参数是:发出声音的实体(可以填null)、位置、声音事件、声音类别、音量、音调
这里声音类别用SoundSource.BLOCKS
填null的意思是「不是某个实体发出来的」,声音就从坐标那里放出来
level在服务端调用没问题,原版会自己同步给附近的玩家,不用我们操心
数据文件
sounds.json
声音事件注册好了,但游戏还不知道该播哪个文件
这个对应关系写在assets/tutorial/sounds.json里
1 | { |
外层这个键,就是我们注册时用的名字,必须一模一样
subtitle是字幕的翻译键,开了「显示字幕」之后,屏幕右下角就会显示这个
sounds里写的是音频文件的位置,格式是命名空间:文件名,不带.ogg
这里有个容易搞错的地方:文件的名字和事件的名字不需要一样
比如探矿器那条,事件叫prospector_found_ore,但音频文件叫1.ogg,所以写的是tutorial:1
sounds.json只是负责「事件 → 文件」的映射,两边怎么起名都行,只要能对上
音频文件放在assets/tutorial/sounds/下面,就是我们的block_break.ogg这些
路径和sounds.json里的写法是对应的:写tutorial:block_break,就找sounds/block_break.ogg
语言文件
字幕也是要翻译的,不然开字幕的时候显示的是一串键名
1 | translationBuilder.add("subtitles.tutorial.prospector_found_ore", "Prospector found ore"); |
中文那份放到ModZhCnLangProvider里
1 | translationBuilder.add("subtitles.tutorial.prospector_found_ore", "探矿器发现了矿石"); |
这个键和sounds.json里的subtitle必须一致
原版用的格式是subtitles.<命名空间>.<名字>,我们照这个来
测试
跑一遍数据生成,把语言文件生成出来,然后进游戏进行测试
常见问题(大肥鱼总结版)
- 完全没有声音:先看
sounds.json的位置对不对,必须是assets/<命名空间>/sounds.json,不是assets/<命名空间>/sounds/sounds.json - 声音事件不认:注册时的名字和
sounds.json外层的键不一致 - 音频找不到:
sounds里写的是命名空间:文件名,不带.ogg,文件本身必须放在sounds/目录下 - 字幕显示成一串键名:语言文件没加,或者
subtitle和语言文件里的键不一致 - 站着不动也能听到:音频导出成立体声了,重新导出成单声道
- 挖方块的声音不对:
SoundType五个参数的顺序错了,顺序是破坏、踩踏、放置、击打、掉落 - 改了音频没反应:客户端有缓存,
F3 + T重载资源包,或者把音频文件名改一下 - 把
mp3改了后缀名放进去:不行,必须真转成ogg







