粒子
粒子是通常通过对应粒子类型生成的视觉效果。客户端和服务端两侧都可以生成粒子,但由于它主要用于视觉表现,关键部分只存在于物理(以及逻辑)客户端。
本页介绍粒子类型与粒子描述的构建和使用。渲染方面的详细信息,见配套的客户端粒子文档。
注册 ParticleType
粒子通过 ParticleType 注册,与 EntityType、BlockEntityType 类似:每个生成的粒子是 Particle 类的实例,而 ParticleType 保存公共信息,用于注册。粒子类型属于注册表对象,因此应像其他对象一样使用 DeferredRegister:
public class MyParticleTypes {
// Assuming that your mod id is examplemod
public static final DeferredRegister<ParticleType<?>> PARTICLE_TYPES =
DeferredRegister.create(BuiltInRegistries.PARTICLE_TYPE, "examplemod");
// The easiest way to add new particle types is reusing vanilla's SimpleParticleType.
// Implementing a custom ParticleType is also possible, see below.
public static final Supplier<SimpleParticleType> MY_QUAD_PARTICLE = PARTICLE_TYPES.register(
// The name of the particle type.
"my_quad_particle",
// The supplier. The boolean parameter denotes whether setting the Particles option in the
// video settings to Minimal will affect this particle type or not; this is false for
// most vanilla particles, but true for e.g. explosions, campfire smoke, or squid ink.
() -> new SimpleParticleType(false)
);
}
说明 只有需要在服务端处理粒子时才必须提供
ParticleType。客户端也可以直接使用Particle。
自定义 ParticleType
SimpleParticleType 能满足大多数需求,但有时需要在服务端为粒子附加数据。这时需要自定义 ParticleType 及对应的 ParticleOptions。先创建实际存储信息的 ParticleOptions:
public class MyParticleOptions implements ParticleOptions {
// A map codec defining additional information for the particle, used e.g. in commands.
// Since there is no information in our type, use a unit map codec;
// this corresponds to using an empty string in a command.
public static final MapCodec<MyParticleOptions> CODEC = MapCodec.unit(new MyParticleOptions());
// Read and write information to the network buffer.
public static final StreamCodec<ByteBuf, MyParticleOptions> STREAM_CODEC = StreamCodec.unit(new MyParticleOptions());
// Does not need any parameters, but may define any fields necessary for the particle to work.
public MyParticleOptions() {}
@Override
public ParticleType<?> getType() {
// Return the registered particle type
}
}
然后在自定义 ParticleType 中使用该实现:
public class MyParticleType extends ParticleType<MyParticleOptions> {
// The boolean parameter again determines whether to limit particles at lower particle settings.
// See implementation of the MyParticleTypes class near the top of the article for more information.
public MyParticleType(boolean overrideLimiter) {
// Pass the deserializer to super.
super(overrideLimiter);
}
@Override
public MapCodec<MyParticleOptions> codec() {
return MyParticleOptions.CODEC;
}
@Override
public StreamCodec<? super RegistryFriendlyByteBuf, MyParticleOptions> streamCodec() {
return MyParticleOptions.STREAM_CODEC;
}
}
并在注册时引用它:
public static final Supplier<MyParticleType> MY_CUSTOM_PARTICLE = PARTICLE_TYPES.register(
"my_custom_particle",
() -> new MyParticleType(false)
);
最后在 ParticleOptions#getType 中返回已注册的类型:
public class MyParticleOptions implements ParticleOptions {
// ...
@Override
public ParticleType<?> getType() {
return MY_CUSTOM_PARTICLE.get();
}
}
粒子描述
粒子描述是位于 assets/<namespace>/particles 下的 JSON 文件,与对应的粒子类型同名,内容为相对于 assets/<namespace>/textures/particles 的纹理列表。
示例如下:
{
// A list of textures that will be played in order. Will loop if necessary.
// Texture locations are relative to the textures/particle folder.
"textures": [
// Points to `assets/examplemod/textures/particle/my_particle_0.png`
"examplemod:my_particle_0",
"examplemod:my_particle_1",
"examplemod:my_particle_2",
"examplemod:my_particle_3"
]
}
资源重载时,ParticleResources 会读取所有粒子描述,并把纹理拼接到 TextureAtlas#LOCATION_PARTICLES 图集中,再为每个描述创建包含对应 TextureAtlasSprite 列表的 SpriteSet。
使用粒子描述
若要让粒子使用描述,必须通过客户端 Mod 事件总线上的 RegisterParticleProvidersEvent 事件,将 ParticleType 与接收 SpriteSet 的 ParticleProvider 关联:
public class MyParticleProvider implements ParticleProvider<SimpleParticleType> {
private final SpriteSet spriteSet;
// Take in the sprite set provided by the `ParticleResources`.
public MyParticleProvider(SpriteSet spriteSet) {
this.spriteSet = spriteSet;
}
// ...
}
// In some client-only event handler
@SubscribeEvent // on the mod event bus only on the physical client
public static void registerParticleProviders(RegisterParticleProvidersEvent event) {
// #registerSpriteSet MUST be used when dealing with particle descriptions.
event.registerSpriteSet(MyParticleTypes.MY_PARTICLE.get(), MyParticleProvider::new);
}
警告 如果某个粒子类型有描述文件,却没有通过
RegisterParticleProvidersEvent#registerSpriteSet关联ParticleProvider,日志会出现“Redundant texture list”(多余纹理列表)消息。
数据生成
继承 ParticleDescriptionProvider 并重写 #addDescriptions(),即可生成粒子定义文件:
public class MyParticleDescriptionProvider extends ParticleDescriptionProvider {
// Get the parameters from `GatherDataEvent.Client`.
public MyParticleDescriptionProvider(PackOutput output) {
super(output);
}
// Assumes that all the referenced particles actually exists. Replace "examplemod" with your mod id.
@Override
protected void addDescriptions() {
// Adds a single sprite particle definition with the file at
// assets/examplemod/textures/particle/my_single_particle.png.
spriteSet(MyParticleTypes.MY_SINGLE_PARTICLE.get(), Identifier.fromNamespaceAndPath("examplemod", "my_single_particle"));
// Adds a multi sprite particle definition, with a vararg parameter. Alternatively accepts an iterable.
spriteSet(MyParticleTypes.MY_MULTI_PARTICLE.get(),
Identifier.fromNamespaceAndPath("examplemod", "my_multi_particle_0"),
Identifier.fromNamespaceAndPath("examplemod", "my_multi_particle_1"),
Identifier.fromNamespaceAndPath("examplemod", "my_multi_particle_2")
);
// Alternative for the above, appends "_<index>" to the base name given, for the given amount of textures.
spriteSet(MyParticleTypes.MY_ALT_MULTI_PARTICLE.get(),
// The base name.
Identifier.fromNamespaceAndPath("examplemod", "my_multi_particle"),
// The number of textures.
3,
// Whether to reverse the list, i.e. start at the last element instead of the first.
false
);
}
}
别忘了将提供器添加到 GatherDataEvent.Client:
@SubscribeEvent // on the mod event bus
public static void gatherData(GatherDataEvent.Client event) {
event.createProvider(MyParticleDescriptionProvider::new);
}
生成粒子
如前所述,服务端只认识 ParticleType 和 ParticleOptions,客户端则直接操作由与类型关联的 ParticleProvider 提供的 Particle。因此,不同侧的生成方式差异很大。
- 通用代码:调用
Level#addParticle或Level#addAlwaysVisibleParticle,这是创建所有人都能看见的粒子的首选方式。 - 客户端代码:使用通用方式;或者创建所需粒子类的
new Particle()实例,再调用Minecraft.getInstance().particleEngine#add(Particle)。这种方式仅对当前客户端显示,其他玩家看不到。 - 服务端代码:调用
ServerLevel#sendParticles,原版的/particle命令使用此方法。
来源: NeoForged/Documentation(26.1)。中文翻译依据提交
4a680cdd4d15。