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 *