Minecraft 1.20.1 forge module development – particles

Particles

  • first particle
    • How to generate particles
    • TestParticle–particle class
    • TestOption–Implements the interface ParticleOptions to store and process the data used to create particle effects.
    • TestType–particle type
    • ParticleRegistry–Register particles
    • ParticleFactoryRegistry–Register factory class
    • texture–texture

First particle

I recently had some ideas, so I started learning module development again.
This article is mainly used to record problems in the development process and leave a record of learning.
Referenceforgegemwire1.20.1
Reference boson1.16.5
Different from the above, here we will record the development of particles in a larger length
Instead of just scratching the surface, I will explain it in more detail.
After practicing the particle systems of 1.16.5 and 1.20.1
I found that the particle system didn’t change much
There were pretty much only a few name changes between 1.16.5 and 1.20.1
The development version below is 1.20.1

How to generate particles

  1. About both ends (client/server)
    First of all, you need to know that particles are only displayed on the client side
    But this does not mean that the server has nothing to do with the generation of particles
    If you want to implement some complex customizations in a multiplayer server, some data can only be obtained from the server
    For example, damage events are only triggered on the server side, which requires data synchronization or the use of SendParticles on the server side.

  2. How to generate particles
    server server
    serverLevel.SendParticles
    client client
    clientLevel.addParticle
    Minecraft.getInstance().particleEngine.add(particle);
    The differences between these two methods will be explained in detail later.

  3. About logical ticks and rendering ticks
    Logic engraving in MC is 20 ticks per second. If the computer performance is poor, it will be reduced.
    However, rendering ticks vary according to computer performance, and are generally higher than the frequency of logical ticks.
    Pay attention to what kind of moment you affect particle generation, there is an example below

TestParticle–Particle class

Here we inherit the DustColorTransitionParticle class from the original version

  1. Why should we inherit this class?

    There is nothing special. You can use custom materials as long as you inherit the TextureSheetParticle(1.20.1)/SpriteTexturedParticle(1.16.5) class.
    You can also inherit TextureSheetParticle directly
    The main difference is that DustColorTransitionParticle has set the particle’s maximum life, movement trajectory, etc.
    We directly inherit the expression of dust

  2. Do particles need to be registered?

    Particles need to be registered, but not like Item or Block.
    Instead, register ParticleType, or you can understand ParticleType as Item or Block
    A particle type also needs to register a Provider(1.20.1)/Factory(1.16.5) (there is no difference between the two)
    The Provider class is generally written directly in your Particle class as an internal class.

public class TestParticle extends DustColorTransitionParticle {<!-- -->
//These two member variables come from the parent class and can achieve color changes.
    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);
    }
    //Change color based on the speed of xyz of TestOption
    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_));
    }
//Get the color based on particle age and TestOption speed
    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();
    }
\t
    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 stores the material of your particles. It will be specified when registering the Provider.
    //newAll original particle classes need to specify SpriteSet themselves, except BreakingParticle(1.20.1)
    //It will get the material of the object you specify and achieve the broken effect, so it is very convenient to use.
    //If you want to use the original particles, you must specify the SpriteSet yourself
    //You can use INSTANCE under the UnitTextureAtlasSprite class to create a new SpriteSet
    //But this is a texture set that contains all particle textures and needs to be clipped using uv coordinates, which is more troublesome.
        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);
            //The pickSprite here loads the texture.
            //Check the pickSpirte method, which implements the random selection of materials in sprites
            testParticle.pickSprite(sprites);
            return testParticle;
        }
    }
}

TestOption – implements the interface ParticleOptions to store and process the data used to create particle effects

Just inherit DustColorTransitionOptions directly
Using addParticle will use TestOption, where you can process the incoming parameters and achieve various special effects.

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;
    }
    //Parse the parameters of the /particle command line. You must overwrite them here, otherwise your particles cannot be generated through the command.
    //And only the full-parameter command is implemented here. You must enter all parameters completely to generate particles.
    //Like this/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 {<!-- -->
        //Each expect(' ') here will eat up a command line space
            reader.expect(' ');
            //Simple and easy to understand, data is read in as 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() {<!-- -->
    //Here is the ParticleType you registered
        return modId.TEST.get();
    }
}

TestType–Particle type

public class TestType extends ParticleType implements ParticleOptions{<!-- -->

//The first parameter is used to control whether particles will be rendered when they cannot be seen. The second parameter is the command line parser.
    public TestType(boolean visAble ){<!-- -->
        super(visAble, TestOption.DESERIALIZER);
    }

    @Override
    public Codec codec() {<!-- -->
        return Codec.unit(this::getType);
    }
    
    @Override
    public ParticleType getType() {<!-- -->
        return this;
    }

//Write some information about the particles into the buffer
//This can transmit the speed, size, color and other information of particles to reproduce a particle or other operations
//We don’t use it here
    @Override
    public void writeToNetwork(FriendlyByteBuf p_123732_) {<!-- -->
    }
    
    @Override
    public String writeToString() {<!-- -->
        return BuiltInRegistries.PARTICLE_TYPE.getKey(this).toString();
    }
}

ParticleRegistry–Register particles

Use the DeferredRegister provided by forge
Note that you need to register this register to the mod bus in the main class constructor.

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());

ParticleFactoryRegistry–Registration factory class

@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT)
public class RegisterProvider {<!-- -->
    @SubscribeEvent
    public static void onParticleFactoryRegistration(RegisterParticleProvidersEvent event) {<!-- -->
    //Register ParticleProvider for ParticleType.
    //The particle's json file defines a texture list
//The file name of the particle json must be the same as the particle's registration name
//Otherwise there will be an error of missing texture list when loading particle json
          event.registerSpriteSet(L2Artifacts.TEST.get(), TestParticle.Provider::new);
    }
}

texture–texture

Next we need to create a material assigned to our particle effect
By the way, I added a dynamic effect using mcmeta. The picture was too long, so I placed it at the bottom.

├── META-INF
│ └── mods.toml
├── assets
│ └── modid
│ ├── particles
│ │ └── test.json
│ └── textures
│ └── particle
│ └── hot.png
│ └── hot.png.mcmeta
├── data
└── pack.mcmeta

Create particles folder and textures/particle folder in the directory above.
Then create a json file with the same name as your registration in the particles file, with the following content:

{<!-- -->
  "textures": [
    "modid:hot",
    "modid:hot1"
  ]
}

Here we specify the material of the particle effect.
Multiple materials can be displayed randomly
Then add our material under textures/particles.
Then open hot.png.mcmeta with Notepad and enter the following code to achieve dynamic effects.

{<!-- -->
  "animation": {<!-- -->
  }
}

Start the game and enter the following command to generate our particle effects.

particle modid:test 0 0 0 75 75 75 1 3

Maybe you noticed that your particles are flickering. This is because they inherit from DustColorTransitionParticle.
If you don’t want this effect, please change to another type of inheritance