informa
3 min read
article

“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!

Latest Jobs

Treyarch

Playa Vista, California
6.20.22
Audio Engineer

Digital Extremes

London, Ontario, Canada
6.20.22
Communications Director

High Moon Studios

Carlsbad, California
6.20.22
Senior Producer

Build a Rocket Boy Games

Edinburgh, Scotland
6.20.22
Lead UI Programmer
More Jobs   

CONNECT WITH US

Register for a
Subscribe to
Follow us

Game Developer Account

Game Developer Newsletter

@gamedevdotcom

Register for a

Game Developer Account

Gain full access to resources (events, white paper, webinars, reports, etc)
Single sign-on to all Informa products

Register
Subscribe to

Game Developer Newsletter

Get daily Game Developer top stories every morning straight into your inbox

Subscribe
Follow us

@gamedevdotcom

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