赞
踩
最近有了些想法,于是乎,重新开始了模组开发的学习。
本文主要用于记录开发过程的问题,留下学习的记录。
参考forgegemwire1.20.1
参考boson1.16.5
与以上不同的是,这里将更大篇幅的记录粒子开发
而不是浅尝辄止,并且会解释的更加详细
在实操了1.16.5和1.20.1的粒子系统后
我发现粒子系统的改变并不大
1.16.5和1.20.1之间几乎只有一些名称的变化
下面的开发版本为1.20.1
关于两端(客户端/服务端)
首先你要知道粒子只在客户端显示
但这并不意味着服务器与粒子的生成毫无关系
如果你要在多人服务器中实现一些复杂的自定义时,有些数据只能从服务器获得
比如伤害事件,只在服务端触发,这就需要数据同步或者在服务端使用SendParticles
生成粒子的方法
服务端server
serverLevel.SendParticles
客户端client
clientLevel.addParticle
Minecraft.getInstance().particleEngine.add(particle);
在后面会再详细的说明这两种方法的区别
关于逻辑刻和渲染刻
逻辑刻在mc中是每秒20tick,若电脑性能较差也会有所降低
但渲染刻则根据电脑性能不同有所区别,一般都会高于逻辑刻的频率
要注意你影响粒子生成的是哪种刻,下面会有一个例子
这里我们继承的是原版中的DustColorTransitionParticle类
为什么要继承这个类?
并没有什么特别的,只要继承了TextureSheetParticle(1.20.1)/SpriteTexturedParticle(1.16.5)类即可使用自定义材质
你也可以直接继承TextureSheetParticle
主要的区别是DustColorTransitionParticle已经设置了粒子的最大生命、运动轨迹等等
我们直接继承灰尘的表现形式
粒子需不需要注册?
粒子需要注册,但不是像Item或者Block一样去注册Particle
而是注册ParticleType,或者你可以把ParticleType理解成Item、Block
一个粒子类型还需要注册一个Provider(1.20.1)/Factory(1.16.5)(这两者并没有什么区别)
Provider类一般直接写在你的Particle类里做内部类
public class TestParticle extends DustColorTransitionParticle { //这两个成员变量来至父类,可以实现颜色的转变 private final Vector3f fromColor; private final Vector3f toColor; protected TestParticle(ClientLevel p_172053_, double p_172054_, double p_172055_, double p_172056_, double p_172057_, double p_172058_, double p_172059_, TestOption p_172060_, SpriteSet p_172061_) { super(p_172053_, p_172054_, p_172055_, p_172056_, p_172057_, p_172058_, p_172059_, p_172060_, p_172061_); float f = this.random.nextFloat() * 0.4F + 0.6F; this.fromColor = this.randomizeColor(p_172060_.getFromColor(), f); this.toColor = this.randomizeColor(p_172060_.getToColor(), f); } //根据TestOptition的xyz的速度更改颜色 private Vector3f randomizeColor(Vector3f p_254318_, float p_254472_) { return new Vector3f(this.randomizeColor(p_254318_.x(), p_254472_), this.randomizeColor(p_254318_.y(), p_254472_), this.randomizeColor(p_254318_.z(), p_254472_)); } //根据粒子age、TestOption速度获取颜色 private void lerpColors(float p_172070_) { float f = ((float)this.age + p_172070_) / ((float)this.lifetime + 1.0F); Vector3f vector3f = (new Vector3f((Vector3fc)this.fromColor)).lerp(this.toColor, f); this.rCol = vector3f.x(); this.gCol = vector3f.y(); this.bCol = vector3f.z(); } public void render(VertexConsumer p_172063_, Camera p_172064_, float p_172065_) { this.lerpColors(p_172065_); super.render(p_172063_, p_172064_, p_172065_); } //Provider @OnlyIn(Dist.CLIENT) public static class Provider implements ParticleProvider<TestOption> { //SpriteSet存储着你粒子的材质,在注册Provider的时候会一起指定它 //new所有的原版粒子类都需要自己指定SpriteSet,除了BreakingParticle(1.20.1) //它会获取你指定的物体的材质,实现破碎效果,所以使用起来很方便 //如过你要使用原版的粒子,就要自己指定SpriteSet //可以使用UnitTextureAtlasSprite类下的INSTANCE去new 一个 SpriteSet //但这是一个纹理集,包含所有的粒子纹理,需要使用uv坐标裁剪,较为麻烦 private final SpriteSet sprites; public Provider(SpriteSet p_172073_) { this.sprites = p_172073_; } public Particle createParticle(TestOption p_172075_, ClientLevel p_172076_, double p_172077_, double p_172078_, double p_172079_, double p_172080_, double p_172081_, double p_172082_) { TestParticle testParticle = new TestParticle(p_172076_, p_172077_, p_172078_, p_172079_, p_172080_, p_172081_, p_172082_, p_172075_, this.sprites); //这里的pickSprite加载纹理。 //查看一下pickSpirte方法,它实现了随机选取sprites内的材质 testParticle.pickSprite(sprites); return testParticle; } } }
直接继承DustColorTransitionOptions即可
使用addParticle会使用到TestOption,在这里你可以处理传入的参数,实现各种特效
public class TestOption extends DustColorTransitionOptions { private final Vector3f toColor; public TestOption(Vector3f color, Vector3f toColor, float scale) { super(color, toColor, scale); this.toColor = toColor; } public Vector3f getToColor() { return this.toColor; } //解析/particle命令行的参数,这里必须复写,否则你的粒子无法通过指令生成 //并且这里只实现了全参数的命令,你必须完整输入全部参数才可以生成粒子 //像这样/particle test 0 0 0 75 75 75 10 public static final Deserializer<TestOption> DESERIALIZER = new Deserializer<TestOption>() { @Override public TestOption fromCommand(ParticleType<TestOption> p_123733_, StringReader reader) throws CommandSyntaxException { //这里的每一个expect(' ')都将吃掉一个命令行的空格 reader.expect(' '); //简单易懂,数据作为float读入 float speedX = reader.readFloat(); reader.expect(' '); float speedY = reader.readFloat(); reader.expect(' '); float speedZ = reader.readFloat(); reader.expect(' '); float red = reader.readInt( ); reader.expect(' '); float green = reader.readInt(); reader.expect(' '); float blue = reader.readInt(); reader.expect(' '); float diameter = reader.readFloat(); return new TestOption(new Vector3f(speedX, speedY, speedZ), new Vector3f(red, green, blue), diameter); } @Override public TestOption fromNetwork(ParticleType<TestOption> p_123735_, FriendlyByteBuf buffer) { float speedX = buffer.readFloat(); float speedY = buffer.readFloat(); float speedZ = buffer.readFloat(); int red = buffer.readInt(); int green = buffer.readInt(); int blue = buffer.readInt(); float diameter = buffer.readFloat(); return new TestOption(new Vector3f(speedX, speedY, speedZ), new Vector3f(red, green, blue), diameter); } }; @Override public ParticleType getType() { //这里是你注册的ParticleType return modId.TEST.get(); } }
public class TestType extends ParticleType implements ParticleOptions{ //第一个参数是用来控制粒子在看不到的时候会不会渲染。第二个参数就是命令行解析器。 public TestType(boolean visAble ){ super(visAble, TestOption.DESERIALIZER); } @Override public Codec codec() { return Codec.unit(this::getType); } @Override public ParticleType getType() { return this; } //把粒子的一些信息写入buffer //这样可以传输粒子的速度、大小、颜色等信息,以此来复现一个粒子,或者别的操作 //这里我们不使用 @Override public void writeToNetwork(FriendlyByteBuf p_123732_) { } @Override public String writeToString() { return BuiltInRegistries.PARTICLE_TYPE.getKey(this).toString(); } }
使用forge提供的DeferredRegister
注意,这里需要在主类构造方法里,将这个注册器注册到mod总线中
public class ParticleRegistry {
public static final DeferredRegister<ParticleType<?>> PARTICLES = DeferredRegister.create(ForgeRegistries.PARTICLE_TYPES, MODID);
public static final RegistryObject<ParticleType<TestOption>> TEST= PARTICLES.register("test", () -> new TestType(false));
}
ParticleRegistry.PARTICLE_TYPES.register(FMLJavaModLoadingContext.get().getModEventBus());
@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT)
public class RegisterProvider {
@SubscribeEvent
public static void onParticleFactoryRegistration(RegisterParticleProvidersEvent event) {
//为ParticleType注册ParticleProvider。
//粒子的json文件定义了一个纹理列表
//粒子json的文件名必须与粒子的注册名相同
//否则加载粒子json时将出现缺少纹理列表的错误
event.registerSpriteSet(L2Artifacts.TEST.get(), TestParticle.Provider::new);
}
}
接下来我们需要创建为我们粒子效果指定材质
顺便加入一个动态效果用mcmeta来实现,图片太长,放在了最下面
├── META-INF
│ └── mods.toml
├── assets
│ └── modid
│ ├── particles
│ │ └── test.json
│ └── textures
│ └── particle
│ └── hot.png
│ └── hot.png.mcmeta
├── data
└── pack.mcmeta
像如上的目录创建particles文件夹,和textures/particle文件夹。
然后在particles文件里创建和你注册名相同的json文件,里面内容如下:
{
"textures": [
"modid:hot",
"modid:hot1"
]
}
这里我们指定里粒子效果的材质。
多个材质可以随机显示
然后在textures/particle下添加我们的材质。
再在hot.png.mcmeta中用记事本打开输入下面的代码即可实现动态效果
{
"animation": {
}
}
启动游戏,输入如下命令,就可以生成我们的粒子效果了。
particle modid:test 0 0 0 75 75 75 1 3
也许你注意到你的粒子在闪烁,这是因为继承的是DustColorTransitionParticle
如果你不想要这种效果,请换别的类型继承
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。