informa
2 MIN READ
Blogs

“This is just HTML5/JS, are you kidding me?”

When it comes to canvas-based HTML5-Games the possibilities are limited, but I want to show you some amazing technics!

(Original Article: http://vempiregame.com/allgemein/this-is-just-html5js-are-you-kidding-me/)

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:

Screen Shot 2016-10-29 at 17.03.25

Cool isn't it?

So how is this made, you ask?
OK, here you have it.
It is primarily about these things:

  1. A proper image, we'll focus on this below!
  2. Scaling
  3. Rotating
  4. Moving
  5. Using of canvas.globalCompositeOperation = 'lighter'
  6. 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:

effectdust7

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,
scaling:45,

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!

CONNECT WITH US

Explore the
Advertise with
Follow us

Game Developer Job Board

Game Developer

@gamedevdotcom

Explore the

Game Developer Job Board

Browse open positions across the game industry or recruit new talent for your studio

Browse
Advertise with

Game Developer

Engage game professionals and drive sales using an array of Game Developer media solutions to meet your objectives.

Learn More
Follow us

@gamedevdotcom

Follow us @gamedevdotcom to stay up-to-date with the latest news & insider information about events & more