记录一下Fabric高版本模组开发的注意事项

内容目录

渲染层

由于我闲的没事干所以就捡起了前年的老项目,让他复活了, 这个项目就是在游戏内播放音乐的模组,
但是由于版本更新导致了很多地方需要修改首先就是渲染的问题,我之前写的项目不需要手动指定渲染层RenderLayer, 然后又好久没有开动fabric的模组开发所以导致我不知道怎么获取到这个渲染层, 于是我翻了一下MC的源代码翻到了渲染层是从一个抽象类RenderLayer的静态方法中获取, 并且需要使用lambda获取
下面是kotlin的获取方法

val renderLayer = RenderLayer::getGuiTexturedOverlay

这里获取到的是GUI的材质层,就是跟血量条、饥饿值和物品栏同一个层,这样的话在按下ESC暂停的时候
会自动将其模糊化处理

渲染

Minecraft的渲染机制是依靠整个程序的生命周期所以渲染不能暂停,但是有一种方法可以让渲染暂停: 使用条件语句来控制渲染, 下面是kotlin的示例

HudRenderCallback.EVENT.register { context, _ ->
    if (!loadLyric) return@register  // 这里判断条件
    /// 在这里渲染
}

当然这个return@register并不会真的停止而是一直循环判断条件, loaderLyric这个变量需要设为可变var然后在某个地方达成某个条件的时候改变值(Boolean)

当然由于register内的语句会被循环执行你也可以让渲染出来的文字变色就像下面这样

var colorCycle = 0f
val colorChangeSpeed = 0.01f
HudRenderCallback.EVENT.register { context, tickDeltaManager ->
    if (!loadSongDetail) return@register
    colorCycle += colorChangeSpeed * tickDeltaManager.lastDuration
    if (colorCycle > 1f) colorCycle = 0f
    val red = (sin(colorCycle * Math.PI * 2) * 0.5 + 0.5) * 255
    val green = (sin((colorCycle + 0.33) * Math.PI * 2) * 0.5 + 0.5) * 255
    val blue = (sin((colorCycle + 0.66) * Math.PI * 2) * 0.5 + 0.5) * 255
    val color = ((255 shl 24) or (red.toInt() shl 16) or (green.toInt() shl 8) or blue.toInt())
    context.drawText(
        minecraftClient.textRenderer,
        Text.literal("↓ 正在播放 ↓"),
        5, 73, color, true
    )
}

需要在循环体外声明一个可变一个不可变的变量可变的就是rgb颜色的基本值,不可变的变量就是颜色循环的速度,设置成0.01是我比较接受的变色速度, 然后在下面drawText方法中传入颜色值就能绘制出渐变色的字体了

对渲染的材质做出改变

fabric官方文档并没有细讲具体是怎么改变的我是从Discord中询问出来的, 这个操作可以让渲染出的材质进行变换,例如: 旋转、放大、缩小。 下面是Kotlin的示例

var rotationAngle = 0f
val defaultCoverId: Identifier = Identifier.of("rmusic", "texture/default.png")
HudRenderCallback.EVENT.register { context, _ ->
    if (!loadCover) return@register
    rotationAngle += 1f
    if (rotationAngle >= 360f) rotationAngle = 0f
    val matrix = context.matrices
    matrix.push()
    matrix.translate(5f + 24f, 20f + 24f, 0f)
    val quaternion = Quaternionf().apply {
        rotateZ(Math.toRadians(rotationAngle.toDouble()).toFloat())
    }
    matrix.multiply(quaternion)
    matrix.translate(-(5f + 24f), -(20f + 24f), 0f)
    context.drawTexture(
        renderLayer, defaultCoverId, 5,
        20, 0f, 0f, 48,
        48, 48, 48
    )
    matrix.pop()
}

使用matrix的push pop方法来改变材质的行为, 这里是让这个渲染出来的图片旋转起来, 同样需要在循环体外定义一个可变的变量rotationAngle用于保存旋转的角度
这里的defaultCoverId是一个注册过的材质, 如果想要动态的改变这个材质可以看下面的示例。

运行时注册材质

我开发的模组是在游戏内听音乐所以需要动态的注册专辑的封面到游戏的注册表内所以就有了下面的代码

fun registerTexture(textureResource: ByteArray, id: Identifier) {
    destroyTexture(qrcodeId)
    val nativeImage = NativeImage.read(textureResource)
    val nativeImageTexture = NativeImageBackedTexture(nativeImage)
    minecraftClient.execute { minecraftClient.textureManager.registerTexture(id, nativeImageTexture) }
}

textureResource是png图片的字节数组, id是需要注册的id

网络通信

Mojang在1.20.5之后重构了网络通信的代码导致之前的代码完全不可用, 新版本的网络通信需要手动指定CustomPayload在这个Payload类里面定义CODEC来指定编码和解码的过程, 下面是Kotlin的示例

@JvmRecord
data class RMusicCustomPayload(val payload: String) : CustomPayload {
    override fun getId(): CustomPayload.Id<out CustomPayload> {
        return ID
    }

    companion object {
        val ID = CustomPayload.Id<RMusicCustomPayload>(networkingId)
        val CODEC: PacketCodec<PacketByteBuf, RMusicCustomPayload> = PacketCodec.of({ value, buf ->
            buf.writeString(value.payload)
        }, { buf -> return@of RMusicCustomPayload(buf.readString()) })
    }
}

ID就是你网络通信的ID, CODEC内的第一个lambda是编码的过程, 第二个是解码的过程, 这里只是最简单的一个CustomPayload就是使用纯文本来通信, 后期可以使用gson或者其他json来序列化和反序列化接收到的数据

接收和发送数据包

客户端从服务端接收

ClientPlayNetworking.registerGlobalReceiver(RMusicCustomPayload.ID) { payload, context ->
    println(payload.payload) // 这里的payload.payload中的第二个payload就是上面定义的CustomPayload数据类构造器中的payload
}

客户端发送到服务端

ClientPlayNetworking.send(RMusicCustomPayload("xxxxxx"))

服务端发送到客户端

ServerPlayNetworking.send(player, RMusicCustomPayload("xxxx"))

服务端从客户端接收

fun registerServerReceiver() {
    ServerPlayNetworking.registerGlobalReceiver(RMusicCustomPayload.ID) { payload, context ->
        println(payload.payload)
    }
}

注册网络通信ID

这是最重要的一部分!!!!

  1. 如果你的模组只需要向服务端发送数据包而不需要从服务端接收数据包那就在ModInitializer块中执行以下代码
class RMusicServer : ModInitializer {
    override fun onInitialize() {
        PayloadTypeRegistry.playC2S().register(RMusicCustomPayload.ID, RMusicCustomPayload.CODEC)
    }
}
  1. 如果你的模组只需要从服务端接收数据包而不需要向服务端发送数据包那就在ModInitializer块中执行以下代码
class RMusicServer : ModInitializer {
    override fun onInitialize() {
        PayloadTypeRegistry.playS2C().register(RMusicCustomPayload.ID, RMusicCustomPayload.CODEC)
    }
}
  1. 如果是双向数据包那就要执行这两个代码就像下面这样
class RMusicServer : ModInitializer {
    override fun onInitialize() {
        PayloadTypeRegistry.playC2S().register(RMusicCustomPayload.ID, RMusicCustomPayload.CODEC)
        PayloadTypeRegistry.playS2C().register(RMusicCustomPayload.ID, RMusicCustomPayload.CODEC)
    }
}

最最最重要的是必须要在ModInitializer块中注册不能再Client或者Server块中注册

Text类的使用

高版本的Minecraft将Text类全部采用懒加载的形式来提高性能, 使用了java的工具类Supplier
这里给出一个拓展函数用来快速创建Supplier的Text

fun Text.supplier(): Supplier<Text> {
    return Supplier { this }
}

直接对一个Text 或者 MutableText使用.supplier()就能创建出Supplier<Text>

温馨提示:

1.本站大部分内容均收集于网络!若内容若侵犯到您的权益,请发送邮件至:[email protected],工作室将第一时间处理!

2.资源所需价格并非资源售卖价格,是收集、整理、编辑详情以及本站运营的适当补贴,并且本站不提供任何免费技术支持。

3.所有资源仅限于参考和学习,版权归原作者所有。

Kotlin学习技术默认

使用ROneBot框架开发一个机器人,

2024-9-7 13:28:50

Kotlin默认

Kotlin中解析QQ音乐和网易云音乐歌词的工具类

2025-1-2 15:33:43

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索