Working with the Mercurial Particle Engine

I had some troubles getting Mercurial Particle Engine (MPE) to work in my game and I thought I’d share my experiences, how to avoid my mistakes, and how to get it working in your own project.

First off, I downloaded the binaries from their website, started up a particle testing ground and after a few fast fails (effect not initialised exception, etc.), I got some explosions rendering! I thought I was off to a great start until I noticed that I couldn’t scale it. Oh joy, there’s a Transpose property of the Renderer, but in the binary release, it apparently doesn’t pay attention to that. So I grabbed the “mainline” from their repository, which fixes this issue. Not a problem. There were a couple API differences, but nothing to worry about. First thing I noticed is that the RenderEffect method changed from:

// Old, binary release, 3.1
void RenderEffect(ParticleEffect effect);

to

// Mainline, 4.0
void RenderEffect(ParticleEffect effect, ref Matrix world, ref Matrix view, ref Matrix projection, ref Vector3 camera);

With no overloads. I thought: “Great! This can scale my explosion! I’m set”. I also noticed that I didn’t need to initialize my Effect anymore:

// Old, binary release, 3.1
myEffect = Content.Load<ParticleEffect>("BasicExplosion");
myEffect.Initialise();

vs

// Mainline, 4.0
myEffect = Content.Load<ParticleEffect>("BasicExplosion");

Fair enough. Maybe they found a way to initialize the effect inside the custom XML loader, right? Wrong. In the binary release, when I omitted myEffect.Initialise();, it threw an exception in my face. Wonderful! I added the line and moved on. In the mainline version, you need to go through each of your Effect.Emitters, and manually load the texture for them. If you don’t do this, it will happily draw nothing. Yup. Nothing. It will sit there and go if(veryImportantTexture != null){ draw(veryImportantTexture); } else{ /* fap fap fap */ }. Well there goes 6 hours of my life that I’ll never get back. Since I haven’t written an article on Failing Fast, that will be next (or at least very soon). Developers of a public API, scratch that, of ANY API should write code that makes it hard to make mistakes, and when you do make a glaring error (like a missing texture), make it obvious! Throw an exception! Stop executing! What MPE did here is called Failing Slowly. Instead of presenting me with an obvious error that could have been fixed within minutes, it rolled over this obvious error and did nothing. In short, you need to add this when you load your effects:

foreach(var emitter in myEffect.Emitters)
{
    emitter.ParticleTexture = Content.Load<Texture2D>(emitter.ParticleTextureAssetPath);
    emitter.Initialise();
}

Now that I knew my textures were loading, I continued to play around with the world, view, and projection matrices, in combination with the cameraPosition vector, but nothing drew. Hours went by while I researched matrix math, played around with these variables, looked up (outdated) samples of other people using MPE, and played with these variables some more. I finally started copying out bits out of their test suite. I changed my Renderer from SpriteBatchRenderer to QuadRenderer, and wow! It started to render! Now why doesn’t my SpriteBatchRenderer work? I know can use the QuadRenderer in a 2D game, but I wasn’t using Quads elsewhere in my game and would rather stick to the SpriteBatchRenderer.

In the test suite, they did something interesting with the SpriteBatchRenderer: they assigned a value to the renderer’s Transpose property on initialisation. I tried this, and what do you know! Something was rendering, yet again. Now… why does the SpriteBatchRenderer need 4 matices and a Vector3 to do its rendering? It doesn’t. I messed around with the parameters for the RenderEffect(...) method, and nothing changed. It doesn’t pay any attention to any of the 3 Matrix parameters or the camera position variables. All it cares about is that you set the Transpose property to something, anything. It does not assume the identity matrix and will again, draw absolutely nothing if not set. Here’s what you need at a minimum:

myRenderer.Transformation = Matrix.Identity;

Wow… so MPE does not tell you when you are doing it wrong, at all. If you plan to use the SpriteBatchRenderer, here’s a summary of what you need to do:

  • You must load all ParticleTextures manually for each Emitter in your Effects
  • You must set the Transformation property of the SpriteBatchRenderer to anything other than null
  • Completely ignore the world, view, and projection matrix parameters to the RenderEffect method.

Something like this should work (partial code):

GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
SpriteBatchRenderer myRenderer;
ParticleEffect myEffect;

public void LoadContent()
{
    spriteBatch = new SpriteBatch(GraphicsDevice);

    myRenderer = new SpriteBatchRenderer();
    myRenderer.GraphicsDeviceService = graphics;
    myEffect = Content.Load<ParticleEffect>("EffectLibrary\\BasicExplosion");
    foreach(var emitter in myEffect.Emitters)
    {
        emitter.ParticleTexture = Content.Load<Texture2D>(emitter.ParticleTextureAssetPath);
        emitter.Initialise();
    }
    myRenderer.LoadContent(Content);
}

protected override void Update(GameTime gameTime)
{
    MouseState mouseState = Mouse.GetState();
    if (mouseState.LeftButton == ButtonState.Pressed)
    {
        Vector3 pos = new Vector3(mouseState.X, mouseState.Y, 0f);
        myEffect.Trigger(ref pos);
    }
    myEffect.Update((float)gameTime.ElapsedGameTime.TotalSeconds);
    base.Update(gameTime);
}

protected override void Draw(GameTime gameTime)
{
    GraphicsDevice.Clear(Color.CornflowerBlue);

    Matrix dummyMatrix = Matrix.Identity;
    Vector3 dummyVector3 = Vector3.Zero;
    myRenderer.Transformation = Matrix.Identity; /* set to your own transformation matrix */
    myRenderer.RenderEffect(myEffect, ref dummyMatrix, ref dummyMatrix, ref dummyMatrix, ref dummyVector3);

    base.Draw(gameTime);
}