Minecraft Forge Modding: (1.8 and earlier) Particles/EntityFX

Background And Introduction

If you are modding for 1.9 or later versions, you should look at Jabelar's Particle 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 basically entities (EntityFX to be precise) that are used to create display effects. They are entities because they exist independent of the player, they have position and movement, and can even collide and such. For example, the hearts that appear above an EntityAnimal when it is in love, the rain, the sparks above lava, even footprints, are all done with such particle EntityFX.

There are a fair number of diverse built-in "vanilla" particles available as listed here: MinecraftGamePedia Particles Entry. These particles can be spawned when you want, and given some additional customization in terms of motion, alpha transparency, and additional color blending. 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) and there isn't a way to register new ones. 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 are entities that 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 a reasonable amount, like just one every few ticks or alternatively one big burst.

When you spawn the particle, the further motion, lifespan, etc. is controlled by the associated EntityFX class. 

Tip: Usually you want to add some randomness to the initial position and motion of the particles. The World class as well as Entity class provides a rand field of type Random so you can use those to generate random numbers easily, otherwise you can create your own new Random instance. Familiarize yourself with Java's random number generation.

Lastly, with vanilla particles, the class represents the general behavior and several actual effects may use the same EntityFX child class even if the name doesn't seem exactly matching. For example, the angryVillager effect uses the EntityHeartFX class because of the way they appear and hover is similar to the hearts.

Spawning Vanilla Particles


Spawning a vanilla particle is pretty easy, just uses the WorldServer#spawnParticle() method which takes the name of the particle (as a string) plus the initial position and the initial motion.

Tip: Initiate the spawn of vanilla particles on the server side,. Even though EntityFX is only a client side class, the server method doesn't instantiate it but rather sends a packet to all the clients (which then instantiate the class). Since you usually want all players to see the particles, the method to initiate the spawn should be invoked on the server side (world.obj.isRemote should be false). 

Okay, so putting this all together here is an example where I wanted to continuously generate a modest number of happyVillager particles (I was making a custom villager).

if (!worldObj.isRemote && ticksExisted%40==0)
{
    double motionX = rand.nextGaussian() * 0.02D;
    double motionY = rand.nextGaussian() * 0.02D;
    double motionZ = rand.nextGaussian() * 0.02D;
    worldObj.spawnParticle(
          "happyVillager", 
          posX + rand.nextFloat() * width * 2.0F - width, 
          posY + 0.5D + rand.nextFloat() * height, 
          posZ + rand.nextFloat() * width * 2.0F - width, 
          motionX, 
          motionY, 
          motionZ);
}

This code was in the onUpdate() method of my custom villager.  You can see that first I check that we're on the server side.

I also only want to spawn occasionally so I use the modulo (%) operator with the built-in ticksExisted (all Entity have this, it can be very useful for timing purposes) so that I will only run this code once every 40 ticks (remember a tick is 1/20th of second so this will process every 2 seconds). Familiarize yourself with the modulo operator as it is useful for such spaced timing.

I create some Gaussian random numbers to provide variety to the motion.  Gaussian distribution provide a natural variety.

For the initial positions, remember I'm generating these around custom villager entity.  So the posX, and width fields are for the villager entity, not the particle entity. The rand.nextFloat() returns a number between 0.0F and 1.0F so if you think through the math, I'm generating the particles up to 1/2 the villager's width away from the villager.

Tip: It is okay if the particles generate inside another entity. The collisions aren't impactful for the particles. However, you don't want to generate them inside a block as that may kill the particles.

List Of Vanilla Particle Textures


Sometimes you may want to create a custom particle that reuses a vanilla texture. Even with same texture you can customize it by changing the color, scale and motion.

If you want to use a vanilla texture, in the constructor of your custom class that extends EntityFX, you would call the setParticleTextureIndex() method. The int parameter to pass to the method maps to vanilla particles like this:

  • 0 = EntitySmokeFX, EntityAuraFX, EntitySuspendFX, EntityReddustFX, EntitySnowShovelFX and also EntityExplodeFX
  • 0 to 7 = EntityPortalFX and also EntityCloudFX
  • 19 to 22 = EntityRainFX and also EntityFishWake
  • 32 = EntityBubbleFX
  • 48 = EntityFlameFX
  • 49 = EntityLavaFX
  • 64 = EntityNoteFX
  • 65 = EntityCrit2FX
  • 80 = EntityHeartFX
  • 81 = EntityAngryVillagerFX
  • 82 = EntityHappyVillagerFX
  • 144 = EntitySpellParticleFX.InstantFactory and also EntitySpellParticleFX.WitchFactory
  • 112, 113 = EntityDropParticleFX
  • 128 to 135 = EntitySpellParticleFX
  • 160 to 167 = EntityFireworksFX
  • 225 to 250 = EntityEnchantingTableParticleFX
Note: The way Minecraft uses the index is to separate it into x and y texture offsets as follows:
  • particleTextureIndexX = particleTextureIndex % 16;
  • particleTextureIndexY = particleTextureIndex / 16;

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.

Spawning Custom Particles


As mentioned in the introduction above, the problem with the spawnParticle() method is that it relies on a string to reference the particle type, but there is no way to easily register new particles.  So if you create a custom EntityFX child class you'll have to spawn it more directly.

However, you need to handle the fact that EntityFX is client side only. If you want all the clients synced to show the particles you have three choices:

  1. Server sends packets: Follow the practice of the vanilla particles and have the server decide when to spawn particles and send packets to all the clients. The advantage of this method is that the particles can be exactly synced (same number, position, etc.) on all clients.
  2. Spawn the particles within a client-side method or class: The advantage to this method is that you don't need to mess with packets. The disadvantage is that the methods you often want to spawn from are usually both sides (like onUpdate()). Also, if you've got randomness then each client will see slightly different particles (but this often doesn't matter).
  3. Make a method in your proxy for spawning the particles: (My preferred method). You'd code it such that when that method is called on the server it does nothing, and on the client it instantiates the particles.The advantage is you don't have to mess with custom packets, and also you can invoke the method from anywhere without worrying about side. The only drawback is that if you've got randomness then each client will see slightly different particles (but this often doesn't matter).

Here is an example using a proxy method. If you need more understanding of proxies, check out my explanation here. It requires the following steps:

  • Create a custom particle class that extends EntityFX.
  • Create a method in your CommonProxy class that does nothing.
  • @Override that method in your ClientProxy class such that it instantiates the particles.
  • Wherever in your code you want to generate the particles, just invoke the proxy method.

In this example, I have a special villager called a "mysterious stranger" that I want to emit particles continuously.

Create Custom Particle Class That Extends EntityFX


Here is my particle class (just a purple version of the EntityAuraFX). I chose to extend EntityAuraFX because it already had the motion and lifespan suitable for my needs.:

public class EntityParticleFXMysterious extends EntityAuraFX
{
    public EntityParticleFXMysterious(World parWorld,
            double parX, double parY, double parZ,
            double parMotionX, double parMotionY, double parMotionZ) 
    {
        super(parWorld, parX, parY, parZ, parMotionX, parMotionY, parMotionZ);
        setParticleTextureIndex(82); // same as happy villager
        particleScale = 2.0F;
        setRBGColorF(0x88, 0x00, 0x88);
    }
}

Create A Method In Your CommonProxy


So in my CommonProxy class I simply put this empty method:

public void generateMysteriousParticles(Entity theEntity) { }


Note that for the parameters you should pass whatever information you need to generate the particles. In my case I just need the entity, since I can calculate the position from the entity's position and I'm going to randomize the particles so don't need more info. But if this was meant for generating particles from a tile entity, you might need to pass the tile entity position instead.

@Override The Method In Your ClientProxy


And in my ClientProxy I put the actual particle instantiation code:

@Override
public void generateMysteriousParticles(Entity theEntity)
{
    double motionX = theEntity.worldObj.rand.nextGaussian() * 0.02D;
    double motionY = theEntity.worldObj.rand.nextGaussian() * 0.02D;
    double motionZ = theEntity.worldObj.rand.nextGaussian() * 0.02D;
    EntityFX particleMysterious = new EntityParticleFXMysterious(
          theEntity.worldObj, 
          theEntity.posX + theEntity.worldObj.rand.nextFloat() * theEntity.width 
                * 2.0F - theEntity.width, 
          theEntity.posY + 0.5D + theEntity.worldObj.rand.nextFloat() 
                * theEntity.height, 
          theEntity.posZ + theEntity.worldObj.rand.nextFloat() * theEntity.width 
                * 2.0F - theEntity.width, 
          motionX, 
          motionY, 
          motionZ);
    Minecraft.getMinecraft().effectRenderer.addEffect(particleMysterious);        
}

Note that the Minecraft class is also client side only, so that is yet another reason to put this code in the proxy.

You you can see I now create an instance of my custom effect class, then I add it through the effect renderer.

Spawn The Particles In Your Code


So I wanted my mysterious stranger entity to emit these particles continuously. So I put the following code in the onUpdate() method of my custom entity:

if (ticksExisted % 5 == 0)
{
    MagicBeans.proxy.generateMysteriousParticles(this);
}

In this case I'm generating the particles every 5 ticks by simply calling the proxy method. I don't have to worry about what side the code is running on (onUpdate() runs on both client and server) because I used the proxy.

11 comments:

  1. Hey, when I try to spawn vanilla particles using

    worldObj.isRemote() == false

    they don't show up! Although if I omit the server-side check they will show. The problem is that I am concerned other players on a server will not be able to see these particles.. Help!

    ReplyDelete
    Replies
    1. EntityFX are client side only, it even has @SideOnly(Side.CLIENT) on the class.

      Delete
    2. You're right I need to update this. The correct way is to make a method in the proxy that does nothing on the server and generates the particles on all the clients.

      Delete
    3. Note that I'm only talking about the custom particles when I talk about the proxy. To spawn vanilla particles you DO spawn them on the server, but you'll notice that the spawn code does not include the actual EntityFX class but instead maps it to a string. So even though the EntityFX is client side only, the spawning happens on the server which sends out a packet to all the clients that tells them the EntityFX to instantiate.

      Delete
    4. Okay, I finally got around to correcting the tutorial using a proxy method approach.

      Delete
  2. Wouldn't you still be able to provide float values to the proxy function to sync all the particles anyway?

    ReplyDelete
    Replies
    1. Assuming you wanted the particles to be random, syncing them would be tricky unless you generated the values on the server and sent packets with the information (this is certainly doable). Because the way the proxy works is that the server and all the clients will call it separately and they would all need the float values synced up with packets in order to pass that to the proxy method.

      But yes you can make more complicated proxy methods that take in more information. It's just that doing that to sync things isn't likely.

      One trick that can be used though is to use a fixed seed for the random numbers so that all the clients will produce the same "random" number sequence.

      In the end though, in most Minecraft cases no one cares about seeing the particles exactly the same as other client. That is why the particles are client-side entities.

      Delete
  3. I have two questions :D
    I have done everything like you, but how can I add textures to the particles? I don't see them anyhow o_O
    2nd question (Not related to this): How can I add textures to a class that extends EntityThrowable?

    ReplyDelete
  4. I struggled to get vanilla particles to spawn with these instructions (modding 1.8).

    The part that wasn't clear was how to use "WorldServer#spawnParticle()". Calling worldIn.spawnParticle(args...) with the arguments found in source code examples calls the World version, not the WorldServer version.

    I stumbled on the definition of WorldServer.spawnParticle() while working on making particles spawn directly using vanilla packets. That method in WorldServer does all the packet handling for you. You just have to call it specifically and provide the appropriate arguments, which are different from the World version.

    I did it this way and it worked:
    if(!worldIn.isRemote)
    {
    WorldServer worldserver = (WorldServer) worldIn;
    worldserver.spawnParticle(EnumParticleTypes.SMOKE_LARGE, false, pos.getX() + 0.5D, pos.getY()+ 1.0D, pos.getZ()+ 0.5D, 1, 0.0D, 0.0D, 0.0D, 0.0D, new int[0]);
    }

    The arguments are:
    EnumParticleTypes,
    boolean (I think this sets whether to spawn the particles even if no player is nearby, false seems best),
    double x coordinate, double y coordinate, double z coordinate,
    Int (number of particles),
    double x offset, double y offset, double z offset,
    double (particle speed - 0.0D seems to be normal speed; 1.0D is crazy fast),
    int[] (no idea what this does; new int[0] works)

    You can probably also just use worldIn.spawnParticle(args...) with the proper arguments for the WorldServer version rather than casting worldIn as a WorldServer. I haven't tried that.

    This works on a dedicated server as well as in SP.


    I hope this is helpful to others who are struggling with this part of the tutorial (I've been reading your posts).


    Thanks for the information Jabelar!

    ReplyDelete