Emitters and Particle Effects in Phaser.io – Building a Gnome Wizard

I play a gnome wizard on an emulated EverQuest server titled Project 1999 (p99). One of the spells I can cast in the game is called Harvest, which let’s me gather back some mana on my character. I’ve always been fond of how this spell looks, so I decided to recreate a crude version of it. I used the JavasScript framework Phaser.io and now have a strong grasp of how to create and control emitters and particle effects with the framework.

Here is how the spell looks in-game

Here is my version

100% accurate, right? 🙂

Particle Effects

Particle effects are essentially a group of graphics that, individually, have some set of randomized behavior. A single particle (or sprite in the case of Phaser) doesn’t do much on its own, yet when one of these graphics are emitted with a large number of other graphics, the whole scene starts to look really cool. They are typically used for temporary events like explosions, movement interactions, or in our case: the visual effect of casting a spell.

I’ve always known particle effects as “those things that look pretty but hog your GPU memory if your settings are too high”. This is true because each particle in the effect takes up memory space: the graphic itself, position, visible duration, etc. A particle effect with a ton of independently randomized graphics will look better than one with only a few, but it will also be exponentially more taxing on the graphics processing unit (GPU).

Emitters

An emitter could be considered the parent of a particle effect. It has properties controlling the effect like the maximum number of graphics to use, the scaling of each, and much more. The Phaser emitter constructor takes in coordinates, as well as the maximum number of particles the emitter can “own” on the game at a single point in time.

var emitter = this.add.emitter(x, y, 200);

In my case, I wanted to create a bunch of emitters in a circle paturn, and then have them produce pulses of graphics in certain intervals. Here is my helper method that registers my emitters with Phaser:

buildEmitter:function(x, y) {
	var emitter = this.add.emitter(x, y, 200);

	emitter.minParticleSpeed.setTo(-30, 30);
	emitter.maxParticleSpeed.setTo(30, -30);
	emitter.makeParticles('sparkle');
	emitter.minParticleScale = 0.01;
	emitter.maxParticleScale = 0.1;
	
	return emitter;
}

And the function that calls the code above. This uses some math to get x/y coordinates of a circle with defined radius and position based on the first little ring graphic that appears when the spell starts.

var numChops = 40;
var modifier = 40; //still trying to figure out how not to have to do this
var radius = ring.body.halfWidth - modifier;
for (var i = 0; i < numChops; i++) {
	var x = (ring.body.center.x + radius * Math.cos(2 * Math.PI * i / numChops)) - modifier;
	var y = (ring.body.center.y + radius * Math.sin(2 * Math.PI * i / numChops)) - modifier;
	emitters.push(this.buildEmitter(x, y));
}

And finally, the function that gets called when the game area is clicked or touched. This recursively calls itself a given number of times in an attempt to simulate the duration of the spell cast.

doSpellEffects: function(listener, listenerContext, priority, counter) {
	this.ringGroup.visible = true;
	var counter = counter || 0;
	this.spellEffects.forEach(function(e){ 
	   e.start(true, 2000, null, 20); 
	});
	
	var game = this;
	if (counter < 6)
	{
		setTimeout(function(){ 
			game.doSpellEffects(listener, listenerContext, priority, counter+1);
		}, 500);
	}
	else
	{
		this.ringGroup.visible = false;
	}
}

The Full Game Code

Download my project files here.

var Harvest = { };

Harvest.Game = function(game) {
    this.gnome;
    this.spellEffects;
    this.ringGroup = [];
    this.text;
};

Harvest.Game.prototype = {
    preload: function() {
        this.load.image('gnome', 'images/gnome.png');
        this.load.image('sparkle', 'images/sparkle.png');
        this.load.image('ring', 'images/ring.png');
    },
    create: function() {
        this.physics.startSystem(Phaser.Physics.ARCADE);
        
        this.gnome = this.add.image(this.world.centerX - 20, this.world.centerY - 20, 'gnome');
        this.gnome.scale.x = .1;
        this.gnome.scale.y = .1;
        
        this.scale.minWidth = 270;
		this.scale.minHeight = 480;

		this.input.addPointer();
		this.stage.backgroundColor = '#000000';
        
        this.buildRings();
        this.buildEmitters();
        this.input.onDown.add(this.doSpellEffects, this);
        
        var style = { font: "20px Arial", fill: "#ff0044", wordWrap: false, align: "center",  backgroundColor: "#ffff00" };
        this.text = this.add.text(this.world.centerX, 20, "Click Anywhere To Cast", style);
        this.text.anchor.set(0.5);
    },
    buildRings:function() {
        this.ringGroup = this.add.group();
        this.ringGroup.enableBody = true;
        
        var r1 = this.ringGroup.create(this.gnome.x - 80, this.gnome.y -80, 'ring');
        r1.scale.x = .7;
        r1.scale.y = .7;
        
        var r2 = this.ringGroup.create(this.gnome.x + 80, this.gnome.y -70, 'ring');
        r2.scale.x = .2;
        r2.scale.y = .7;
        r2.angle = 45;
        
        var r3 = this.ringGroup.create(this.gnome.x - 80, this.gnome.y -20, 'ring');
        r3.scale.x = .2;
        r3.scale.y = .7;
        r3.angle = -45;
        
        this.ringGroup.visible = false;
    },
    buildEmitters:function() {
        
        var emitters = [];
        var game = this;
        
        var ring = this.ringGroup.getFirstExists();
        
        var numChops = 40;
        var modifier = 40; //still trying to figure out how not to have to do this
        var radius = ring.body.halfWidth - modifier;
        for (var i = 0; i < numChops; i++) {
            var x = (ring.body.center.x + radius * Math.cos(2 * Math.PI * i / numChops)) - modifier;
            var y = (ring.body.center.y + radius * Math.sin(2 * Math.PI * i / numChops)) - modifier;
            emitters.push(this.buildEmitter(x, y));
        }
        
        this.spellEffects = emitters;
    },
    buildEmitter:function(x, y) {
        var emitter = this.add.emitter(x, y, 200);

        emitter.minParticleSpeed.setTo(-30, 30);
        emitter.maxParticleSpeed.setTo(30, -30);
        emitter.makeParticles('sparkle');
        emitter.minParticleScale = 0.01;
        emitter.maxParticleScale = 0.1;
        
        return emitter;
    },
    doSpellEffects: function(listener, listenerContext, priority, counter) {
        this.ringGroup.visible = true;
        var counter = counter || 0;
        this.spellEffects.forEach(function(e){ 
           e.start(true, 2000, null, 20); 
        });
        
        var game = this;
        if (counter < 6)
        {
            setTimeout(function(){ 
                game.doSpellEffects(listener, listenerContext, priority, counter+1);
            }, 500);
        }
        else
        {
            this.ringGroup.visible = false;
        }
    },
};

4 thoughts on “Emitters and Particle Effects in Phaser.io – Building a Gnome Wizard

  1. Very interesting work! I redid that particle effect for p99 and loved the original version of the spells from 1999-2002.

    1. Hello!

      I’m curious to hear more information about your comment. What do you mean you redid the particle effect for p99? Any details to share?

  2. You can find the old forums I maintained while working on updating the new particle system that the titanium client uses to make it look as classic as possible. I manipulated hex code and graphical images of the original sprites to achieve a more classic look.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.