HTML5 Canvas - Tap & Rotate Player with Arctangent

One of the more challenging user experiences in a game is the need to move AND aim a player on the screen.  That gets harder with mobile devices, where you have limited controller options.  One way to fix this, is to allow the user to tap where your character should aim than have him turn in that direction.  Think of a gun turret or a spaceship where all you do is tap on enemies and the turret or spaceship turns and fires in that direction.

To do this this you simply need to know what direction your object or "Actor" is currently pointing, and the location of the the object want to point at.  With a single line of Trigonometry it's a simple thing to code.

Here's is a quick Vine video on this approach (roll over your mouse to view)

Below are the steps to do this with code

Step 1. Figure A. : Canvas will rotate in radians.  So rather than 360 degrees in a circle think the radians as and expression of PI. Half way around a circle is 3.14 radians, and all the around is 6.28 radians, or 2PI.  So at any given point you should know your Actors Radian angle in Canvas.  In my code I simply increment (spin) the canvas by +1 or -1 each frame.  Thus it is easier for my "spin' variable to work with 360 degrees for a smooth animation. So I calculate from radians to degrees and back to radians in my code.


 shipRadian=(spin * Math.PI/180); // this will turn 0-360 degrees into a radians from 0-6.28

 context.rotate(shipRadian);    //this will rotate the canvas to a radian

Step 2. Figure B.  To point your Actor at an enemy or a touch event on the screen, you need to calculate the radian angle of that enemy in relation to the Actor.  You can use a simple javascript math expression to do this "Math.atan2(DeltaY, DeltaX)".  In my example, the touch event will place a crosshair symbol on the screen, and we will then fire at that crosshair.  The code to calculate the radian we need looks like this.


 xhairRadian=Math.atan2(touchY-shipy, touchX-shipx);

Step3: Figure C & D:  The next thing to do is to subtract the new radian value by your actors radian.  That delta will give you a number either smaller or larger than PI (3.14).  Now for the best animation we want the actor in our scene to turn in a direction of the shortest path.   Generally the way to consider this is if the delta radian is smaller than PI then the rotation toward the new radian value will be clockwise, if larger than PI the rotation toward the new radian value will need to be counterclockwise

 

 

Code for Figure C & Figure D


 if(xhairRadian<=0){      // The arctangent math calulates a negative radian for half of the radians. This turns the negative radian into is positive counterpart

    xhairRadian=2*Math.PI+xhairRadian;

    }

  deltaRadian=xhairRadian-shipRadian   // Determine the detla between the ship and new radian                        

  if (deltaRadian < -Math.PI || deltaRadian > Math.PI){   // determine if the delta is beyond 3.14 or -.3.14, if so turn right i.e. clockwise

      if(xhairRadian<shipRadian){

    direction="right";

    }

    if(xhairRadian>shipRadian){

      direction="left";

      }

   }

 

  else {       // else if the difference in angle is positive spin toward the right

      if (xhairRadian > shipRadian) {

          direction = "right";

    }

 

    if(xhairRadian<shipRadian){ // if the difference in angls is negative spin toward the left

      direction="left";

      }

   }

  shotstart=1;      // shotstart = 1 means we've finished the calculations and are ready spin and shoot

     }

  }

 

Step4: Figure E: The next thing to do, is to start incrementing the canvas rotation in the proper direction.  Through some testing I found a static rate of movement creates a problem.  Either the ship takes too long to go around, and the action isn't good.  Or the ship moves too quickly in short distances, and it looks choppy. To fix this, I add an accelerated speed to the rotation, where each frame I increase the speed until it his a max speed.  That creates fast and smooth action.


var speedmax =20; // our top rate of speed;

if (shotstart==1){ //if the shot was made start to spin the ship

  if (direction=="left"){ 

      spinspeed--; //if not at top speed then increase the speed of the ship turning in the negative direction

    if (spinspeed<(speedmax*-1)){  

        spinspeed = (speedmax * -1); //if you hit top speed don't increase the speed anymore

    }

  }

  else {

      spinspeed++; //if not at top speed then increase the speed of the ship turning in the positive direction

      if (spinspeed > speedmax) { //if you hit top speed don't increase the speed anymore

          spinspeed = speedmax;

      }

    spin+=spinspeed;  // our spin number increases by the rate of spin

  spinspeed *= 1.6; // increase the spin rate by 60% each frame

}

Step 5 Figure F:  Because our randians and degrees go from 0 to 6.28 and 0 to 360, when you rotate counter clockwise and pass Zero you need to change the math.  Since our"spin" variable is in degrees when we pass Zero we need to shift plus 360 rather than going negative.  Also if going clockwise after you pass 360 you need to go to Zero rather than counting up pass 360.   To manage this you'll need a piece of code that will either subtract or add 360 to the current spin, depending on the direction.


  if (spin >= 360) {  //if you've come all the way around, reset the spin by 360

      spin = spin - 360;

  }

  if (spin <= 0) { //if you've come all the way around, reset the spin by 360

      spin=spin+360;

    }

 

 

Step 6. Figure G.  Ultimately when we get the Actor radian to match the new radian we want to stop spinning.  However when dealing with math of fractional numbers it is hard to get a number to exactly equal another number.  To make this easier all we do is add a buffer amount aound our target radian value.  Thus if our Actor is  close enough to pointing in the right direction we can go ahead and make it equal the target radian.


 if (spinRound >=xhairRadianround-0.5 && spinRound <= xhairRadianround +0.5 || spinRound >Math.PI*2  || spinRound <0) {;

//if the ships close enough to the proper angle no need to animate just point the ship at the cursor

  shipRadian=xhairRadian;

  spinspeed = spindefault;

  shotstart=0; 

    }

    else

  { //if the angle is far enough off start to spin the ship 

   shipRadian=(spin * Math.PI/180)

  }

Step 7. Figure H.  When we've done this we've completed the task of rotating our Actor in the correct direction.  With this done you can trigger the event, which in our case is firing a laser.


 if (shipRadian==xhairRadian){ // we are pointed at the place we tapped, now fire the lasers

      drawShot(); 

      shotprogress=true;  // flag to say we completed the drawing our lasers

     }

That's all there is to this.  Check it out yourself on my public drop box: Launch Example  Open this with any HTML5 compatible device and tap around. View and copy the source to play with your own version.

Follow Bob Duffy on Twitter @bobduffy

Para obter informações mais completas sobre otimizações do compilador, consulte nosso aviso de otimização.
Tags: