Minecraft Modding: Particles

Background And Introduction


Warning: If you are modding for 1.8 or later versions, you should look at Jabelar's Particle / EntityFX Tips instead. (In 1.8 and earlier, particles are essentially simple entities and use the EntityFX class; whereas, in 1.9 and later there is new class called Particle.)

Particles are essentially a simple rendered effect that is only instantiated client-side. The base class for particles is the Particle class. They are essentially a texture that can move as well as a few settings like whether they can collide and whether they are affected by gravity.

Examples of particles are the hearts that appear above an EntityAnimal when it is in love, the rain, the sparks above lava, even footprints, etc.

There are a fair number of diverse built-in "vanilla" particles available as listed here: MinecraftGamePedia Particles Entry. You can also view all the types by looking at the EnumParticleTypes class. These particles can be spawned when you want, and given some additional customization in terms of motion, alpha transparency, and additional color blending.

Key Point: However, all the built-in particles use a single texture resource (particles.png) with offsets to pick off the specific texture for each particle. For example, the angryVillager particle is offset 81 and happyVillager particle is offset 81 (the offsets are multipled by 16 to find actual offset inside the texture).  So this means that you cannot change the texture of a vanilla particle easily. 

Furthermore, vanilla particles are spawned by referencing their name (as a string constant) and there isn't a way to register new ones. Note this is likely to change in 1.13 where they should be fully name-spaced. Therefore, the spawning for custom particles needs a different approach (as explained below) than vanilla.

While it may be fun to create a lot of particles, they do use up memory and processing power and so can affect performance. So each particle has a particleMaxAge field after which it will be "killed". You'll also want to control the spawning to keep the total number to a reasonable amount, like just one every few ticks or alternatively one big burst.  Lastly you generally only want to spawn them when they would be in view of the player -- for example the Block#randomDisplayTick() method only activates if the player is nearby.

Tip: Usually you want to add some randomness to the initial position and motion of the particles. The World class (a world field is available in the Particle instance) provides a rand field of type Random so you can use those to generate random numbers easily. Familiarize yourself with Java's random number generation.


Cancelling Vanilla Particles


There is no event for particles spawning. So cancelling vanilla particles is actually kinda tricky.

One possible way, as suggested by diesieben07, is as follows:
  • Make a new class that implements IWorldAccess and delegates all methods to Minecraft#renderGlobal, except the particle spawns, which you only delegate if you want the particle to actually spawn.
  • Replace the world access. Subscribe to EntityJoinWorldEvent and check if the entity is Minecraft#thePlayer. If it is, do this (pseudocode):
       world.removeWorldAccess(Minecraft#renderGlobal);
       world.addWorldAccess(new ClassFromStepOne());

Warning: The above method will only work if one mod does it. I think that is generally okay though as it is rare in modding to mess with the IWorldAccess, but definitely something to think about if compatibility is a concern.

The ParticleManager and EnumParticleType


So the vanilla code has a ParticleManager which registers all the particle types and assigning an "ID". The vanilla particles are registered based on the EnumParticleType. However, there isn't an easy way to add modded values to the EnumParticleType.

The ParticleManager has two methods related to spawning particles. The spawnEffectParticle() method requires an ID. Therefore, you can use that method for vanilla particles. there is also the addEffect() method takes a Particle parameter directly. You can use that for modded particles (i.e. that extend Particle class).

Key Point: It is difficult to extend the particle type ID, so instead for custom particles don't worry about "registering" the particle -- you can spawn it directly with the addEffect()method.

Server-Side Versus Client-Side Spawning


Warning: The particles only exist on the client side. The related classes like Particle and ParticleManager are not even loaded on the server.

When spawning particles then, you need to figure out where the logic is running that "decides" to spawn the particle. For example, if you already have access to a client-side function like a block's randomDisplayTick() method you can then spawn the particles directly on the client.

However, maybe the logic is decided on the server-side -- like perhaps you want to spawn particles when an entity dies. In that case, the server will need to send a packet to the client to tell the client to spawn the packet. If you're spawning vanilla particles there are already methods to create those packets for you. Otherwise you will need a custom packet.

Key Point: When you're "spawning" a particle on the client side, you're not really instantiating it on the server -- instead you're just telling client to spawn it. This is different from spawning an entity where an instance is created on both sides.

Key Point: If you need to spawn a custom particle based on server-side logic, you will need a custom packet sent from server to client.

Spawning Vanilla Particles


If you just want your mod to spawn existing particles, check out Jabelar's Spawning Vanilla Particles tutorial.

Fully Custom Particles


Important: This is probably the part you really want to know -- how to make fully custom particles. If you want fully custom textures, including possibility for advanced animations, check out Jabelar's Spawning Custom Particles tutorial.

Spawning Particles Randomly Or Regularly


In most cases you need to control the frequency of the particle generation. This is important partly to make the visual effect and also partly to help avoid creating performance problems.

The most common thing is to have a regular interval that has a bit of randomness to it. So first you need to get the regular interval.

Set The Regular Interval

There are a few ways to do this:

  1. Count ticks. Entities and TileEntities, as well as related things like EntityItem all have a ticksExisted field which is already counting ticks for you. So you can simply check (in a tick update method) whether the ticksExisted is a multiple of the interval you want. For example you can do a check like if (ticksExisted % 20 == 0) and that will be true once per second (since there are 20 ticks per second).
  2. Hook into existing methods for particle purposes. For example, the Block class already has a method called randomDisplayTick() and you can also "schedule" tick updates for blocks on regular intervals.

Familiarize yourself with the modulo operator as it is useful for such spaced timing.

Adding Randomness

There are many types of random distribution functions. Gaussian distribution provide a natural variety because most results will be near the nominal but some rare results will be more varied.

I suggest you consider adding randomness to "everything". The lifespan of the particle should be a bit random, the initial position should be a bit random, the motion should be a bit random and so forth.

You can create your own Random instance, but there are already ones available in all the major classes, such as World, Entity (and everything derived from that like EntityPlayer), Block, and so forth. It is good practice to just use an existing Random generator to avoid creating spurious memory garbage.

If you're not already familiar you should look at the documentation for the Random class.

TipIt is okay if the particles generate inside an entity. The collisions aren't impactful for the particles.

Warning: You don't want to generate particles inside a block as that may kill the particles, unless you turn off the canCollide value. 

No comments:

Post a Comment