This was one of the greatest compliments that we’ve received for VEmpire – The Kings of Darkness.

There are a lot of things that makes a game look polished.

But when it comes to canvas-based HTML5-Games the possibilities are limited, it is not Unity!

To cut a long story short, here is what makes VEmpire look as it does:

– Great art!

– Smart animations!

– A simple particle-engine!

– Neat effects!

I can’t do the art for you and i want to talk about the particle engine in the next post, but today I want to focus on one particular effect, it is this one:

Cool isn’t it?

So how is this made, you ask?

OK, here you have it.

It is primarily about these things:

A proper image, we’ll focus on this below! Scaling Rotating Moving Using of canvas.globalCompositeOperation = ‘lighter’ Using of canvas.globalAlpha for Fading in and out

So let’s figure it out step by step.

The first thing you need is a proper image with transparency, a png.

Here you have it:

Consider using a typical HTML 5 game-engine you have an update and a draw method to overwrite for your objects!

Both are triggered every frame, the draw method after the update method.

In the update-method the position of our “dust” is calculated, we are moving it from the initial position towards a target-point and

are increasing the rotation-angle. Further we have a little “state-machine” for handling fading in and fading out.

In the draw-method our dust-image is drawn onto the canvas with the proper settings.

To strengthen the effect we are drawing the image twice while letting the second one rotate reverse!

We are using Impact/JS but it will be similar for any other engine out there.

isReady:false, gaSpeed:0.01, state:0, diffFric:0.001, angleSpeed:0.004, update:function(){ this.parent(); // Calculating the Position, where 'to' is the Target-Point, diffPosX & diffPosY are the distances if (this.pos.x!=this.to.x) { var ldiffX=this.diffPosX*(this.diffFric); if(Math.abs( ldiffX )<this.posTolX)ldiffX=ldiffX<0? this.posTolX*-1:this.posTolX; this.pos.x+=ldiffX;//this.diffPosX; if ((this.pos.x.round(1)==this.to.x.round(1)) || (this.diffPosX<0 && this.pos.x<this.to.x) || (this.diffPosX>0 && this.pos.x>this.to.x)) { this.pos.x=this.to.x; } } if (this.pos.y!=this.to.y) { var ldiffY=this.diffPosY*(this.diffFric); if(Math.abs( ldiffY )<this.posTolY)ldiffY=ldiffY<0? this.posTolY*-1:this.posTolY; this.pos.y+=ldiffY;//this.diffPosX; if ((this.pos.y.round(1)==this.to.y.round(1)) || (this.diffPosY<0 && this.pos.y<this.to.y) || (this.diffPosY>0 && this.pos.y>this.to.y)) { this.pos.y=this.to.y; } } // Rotating... this.currentAnim.angle+=this.angleSpeed; // Fading in and Fading out... if (this.state==0) { this.currentAnim.alpha+=this.gaSpeed; if (this.currentAnim.alpha>=this.switchAlpha) { this.currentAnim.alpha=this.switchAlpha; this.state=2; } }else if (this.state==1 && this.pos.x==this.to.x && this.pos.y==this.to.y) { this.state=2; }else if(this.state==2 && this.gaFadeOut){ this.currentAnim.alpha-=this.gaSpeed; if ( this.currentAnim.alpha<=0) { this.currentAnim.alpha=0; this.state=3; this.isReady=true; } } }, draw:function(){ ig.system.context.save(); if( this.currentAnim.alpha != 1) { ig.system.context.globalAlpha = this.currentAnim.alpha; } ig.system.context.globalCompositeOperation = "lighter"; ig.system.context.translate( ig.system.getDrawPos(this.pos.x + this.currentAnim.pivot.x), ig.system.getDrawPos(this.pos.y + this.currentAnim.pivot.y) ); this.pos.x/=this.scaling; this.pos.y/=this.scaling; this.size.x/=this.scaling; this.size.y/=this.scaling; ig.system.context.rotate(this.currentAnim.angle); ig.system.context.scale(this.scaling,this.scaling); ig.system.context.drawImage(this.currentAnim.sheet.image.data,ig.system.getDrawPos(-this.currentAnim.pivot.x),ig.system.getDrawPos(-this.currentAnim.pivot.y)); ig.system.context.rotate( -this.currentAnim.angle*2.5 ); // Drawing a second one to make the effect much more effective ig.system.context.drawImage(this.currentAnim.sheet.image.data,ig.system.getDrawPos(-this.currentAnim.pivot.x),ig.system.getDrawPos(-this.currentAnim.pivot.y)); ig.system.context.restore(); this.pos.x*=this.scaling; this.pos.y*=this.scaling; this.size.x*=this.scaling; this.size.y*=this.scaling; },

The effect is simple but truly effective, hopefully you can try it out and enjoy it.

If you have any question, just ask me below!

Wolfgang!