Rafael
Rafael

Reputation: 157

Navigate according to degrees in circle

I try to build a 2d canvas game in javascript and nodejs. In the game the player is a ship that can navigate his way in the sea. The ship object is sprite sheet with 16 ship positions (22.5 degree for evrey frame) start from 0 to 360 degrees.

To navigate the ship i use NippleJS, its give me the degree in 0-360 (for example: 328.9051138238949).

Now, i need algorethem that check which sprite from the 16 should be display in evrey moment.

The ship must to move 1 sprite left or right in every check degree and its must be the best way (best way mean if ship degree is 0 and the degree from Nipplejs is 335 the ship should navigae right instead of left..).

I start to think about it and i understand that i need to display image frame if degree is between -(22.5/2) and (22.5/2) but i got alot of problems when i over 360 and more things..

To understand each other lets call ship degree ship.degree and the degree from Nipplejs we will call nipplejs.degree, and the ship sprite will represented by ship.sprite[0-16] (0 = right direction).

Please help.

circle 360 degrees

Upvotes: 0

Views: 747

Answers (1)

Blindman67
Blindman67

Reputation: 54089

To turn along the smallest angle the function below will step the angle by the number of steps angleMoveSteps per 360 deg. Eg 90 steps will step in 4 deg steps.

It will stop stepping if the angle difference is less than this step.

const angleSteps = 16;
const angleMoveSteps = 90;

function getNextAngle(newDirection, currentDirection) {
    // normalize both directions to 360 
    newDirection %= 360;
    currentDirection %= 360;

    // if new direction is less than current move it ahead by 360
    newDirection = newDirection < currentDirection ? newDirection + 360 : newDirection;

    // get the difference (will always be positive)
    const dif = newDirection - currentDirection;

    // if the difference is greater than 180 and less than 360 - step
    // turn CCW
    if (dif > 360 / 2 && dif < 360 - (360 / angleMoveSteps)) {
         return currentDirection - (360 / angleMoveSteps);
    }

    // if the difference is greater than step and less than 180
    // turn CW
    if (dif > (360 / angleMoveSteps) && dif < 360 / 2){
        return currentDirection + (360 / angleMoveSteps);
    }

    // do nothing if within step angle of target
     return currentDirection
}

To convert from deg to image index the following function will do that. See demo for compact versions.

function getStepAngle(dir) {  // dir in degrees

    // normalise the angle
    dir = (dir % 360 + 360) % 360;

    // scale to angleSteps 0 to 16
    dir /= 360 / angleSteps;

    // offset by half a step
    dir += 0.5;

    // floor the result to get integer
    dir = Math.floor(dir);

    // Get remainder to ensure value between 0 and 16 not including 16
    return dir % angleSteps;
}

Demo

The demo shows the two functions being used. Click on the dial to set a new target direction and the current direction will move to the new direction, showing the moving angle (green) and the image index (blue) as it moves.

const angleSteps = 16;
const angleMoveSteps = 90;
var currentDir = 0;
var shipDir = 0;
var targetAngle = 0;


function getNextAngle(newDirection, currentDirection) {
    const step = 360 / angleMoveSteps
    newDirection %= 360;
    currentDirection %= 360;
    newDirection = newDirection < currentDirection ? newDirection + 360 : newDirection;
    const dif = newDirection - currentDirection;

    if (dif > 360 / 2 && dif < 360 - step) { return currentDirection - step }
    if (dif > step && dif < 360 / 2) { return currentDirection + step }
     return currentDirection
}

function getStepAngle(dir) {
    return Math.floor(((dir % 360 + 360) % 360) / (360 / angleSteps) + 0.5) % angleSteps;
}






/* Demo code from here down */
Math.TAU = Math.PI * 2;
const ctx = canvas.getContext("2d")
const w = canvas.width;
const h = canvas.height;
var seeking = false;
const speed = 100; // milliseconds

update();
canvas.addEventListener("click", event => {
   const bounds = canvas.getBoundingClientRect();
   const x = event.pageX - bounds.left - scrollX;
   const y = event.pageY - bounds.top  - scrollY;
   targetAngle = Math.atan2(y - w / 2, x - h / 2) * 180 / Math.PI;
   if(!seeking){ render() }
});
function render() {
   requestAnimationFrame(update);
   var newDir = getNextAngle(targetAngle, currentDir);
   if(newDir !== currentDir) {
       currentDir = newDir;
       seeking = true;
       setTimeout(render, speed);
   } else {
       currentDir = targetAngle;
        setTimeout(()=>requestAnimationFrame(update), speed);
       seeking = false;
   }
   
}

function update() {
   shipDir = getStepAngle(currentDir);
   clear();
   drawCompase();
   drawTargetAngle(targetAngle);
   drawCurrentAngle(currentDir);
   drawStepAngle(shipDir);

}

function clear() { ctx.clearRect(0,0,w,h) }

function angleText(text,x,y,angle,size = 12, col = "#000") {
    const xAX = Math.cos(angle);
    const xAY = Math.sin(angle);
    ctx.fillStyle = col;
    ctx.font = size + "px arial";
    ctx.textAlign = "right";
    ctx.textBaseline = "middle";
    if(xAX < 0) {
        ctx.setTransform(-xAX, -xAY, xAY, -xAX, x, y);
        ctx.textAlign = "left";
    
    } else {
        ctx.setTransform(xAX, xAY, -xAY, xAX, x, y);
        ctx.textAlign = "right";
    }
    ctx.fillText(text,0,0);
}
function drawCompase() {
    var i;
    const rad = h * 0.4, rad1 = h * 0.395, rad2 = h * 0.41;
    ctx.lineWidth = 1;
    ctx.strokeStyle = "#000";
    ctx.beginPath();
    ctx.arc(w / 2, h / 2, rad, 0, Math.TAU);
    ctx.stroke();

    ctx.lineWidth = 2;
    ctx.beginPath();
    for (i = 0; i < 1; i += 1 / angleSteps) {
         const ang = i * Math.TAU;
         ctx.moveTo(Math.cos(ang) * rad1 + w / 2, Math.sin(ang) * rad1 + h / 2);
         ctx.lineTo(Math.cos(ang) * rad2 + w / 2, Math.sin(ang) * rad2 + h / 2);
    }
    ctx.stroke();

    for (i = 0; i < 1; i += 1 / angleSteps) {
         const ang = i * Math.TAU;
         angleText(
             (ang * 180 / Math.PI).toFixed(1).replace(".0",""), 
             Math.cos(ang) * (rad1 - 2) + w / 2, 
             Math.sin(ang) * (rad1 - 2) + h / 2,
             ang
         );
    }
    ctx.setTransform(1,0,0,1,0,0);
}
function drawTargetAngle(angle) { // angle in deg
    const rad = h * 0.30, rad1 = h * 0.1, rad2 = h * 0.34;
    const ang = angle * Math.PI / 180;
    const fromA = ang - Math.PI / (angleSteps * 4);
    
    const toA = ang + Math.PI / (angleSteps * 4);
    ctx.linewidth = 2;
    ctx.strokeStyle = "#F00";
    ctx.beginPath();       
    
    ctx.moveTo(Math.cos(fromA) * rad + w / 2, Math.sin(fromA) * rad + h / 2);
    ctx.lineTo(Math.cos(ang) * rad2 + w / 2, Math.sin(ang) * rad2 + h / 2);
    ctx.lineTo(Math.cos(toA) * rad + w / 2, Math.sin(toA) * rad + h / 2);       
    ctx.stroke();  
    angleText(
         angle.toFixed(1).replace(".0",""), 
         Math.cos(ang) * (rad - 4) + w / 2, 
         Math.sin(ang) * (rad - 4) + h / 2,
         ang,
         12, "#F00"
    );      
    ctx.setTransform(1,0,0,1,0,0);

}
function drawCurrentAngle(angle) { // angle in deg
    const rad = h * 0.14, rad2 = h * 0.17;
    const ang = angle * Math.PI / 180;
    const fromA = ang - Math.PI / (angleSteps * 2);
    
    const toA = ang + Math.PI / (angleSteps * 2);
    ctx.linewidth = 2;
    ctx.strokeStyle = "#0A0";
    ctx.beginPath();       
    
    ctx.moveTo(Math.cos(fromA) * rad + w / 2, Math.sin(fromA) * rad + h / 2);
    ctx.lineTo(Math.cos(ang) * rad2 + w / 2, Math.sin(ang) * rad2 + h / 2);
    ctx.lineTo(Math.cos(toA) * rad + w / 2, Math.sin(toA) * rad + h / 2);       
    ctx.stroke();  
    angleText(
         angle.toFixed(1).replace(".0",""), 
         Math.cos(ang) * (rad - 4) + w / 2, 
         Math.sin(ang) * (rad - 4) + h / 2,
         ang,
         12, "#0A0"
    );      
    ctx.setTransform(1,0,0,1,0,0);

}
function drawStepAngle(angle) { // ang 0 to angleSteps cyclic
    var ang = angle % angleSteps;
    ang *= Math.PI / angleSteps*2;
    const fromA = ang - Math.PI / angleSteps;
    const toA = ang + Math.PI / angleSteps;
    
    const rad = h * 0.4, rad1 = h * 0.35, rad2 = h * 0.44;
    const rad3 = h * 0.34, rad4 = h * 0.45;
    ctx.linewidth = 1;
    ctx.strokeStyle = "#08F";
    ctx.beginPath();   
    ctx.arc(w / 2, h / 2, rad1, fromA, toA);
    ctx.moveTo(w / 2 + Math.cos(fromA) * rad2, h / 2 + Math.sin(fromA) * rad2, 0, Math.TAU);
    ctx.arc(w / 2, h / 2, rad2,  fromA, toA);
    ctx.stroke();
    
    ctx.linewidth = 2;
    ctx.beginPath();       
    
     ctx.moveTo(Math.cos(fromA) * rad3 + w / 2, Math.sin(fromA) * rad3 + h / 2);
     ctx.lineTo(Math.cos(fromA) * rad4 + w / 2, Math.sin(fromA) * rad4 + h / 2);
     ctx.moveTo(Math.cos(toA) * rad3 + w / 2, Math.sin(toA) * rad3 + h / 2);
     ctx.lineTo(Math.cos(toA) * rad4 + w / 2, Math.sin(toA) * rad4 + h / 2);       
     ctx.stroke();     
     
     angleText(
         angle, 
         Math.cos(ang + 0.1) * (rad - 2) + w / 2, 
         Math.sin(ang + 0.1) * (rad - 2) + h / 2,
         ang,
         16, "#08F"
     );     
     
    ctx.setTransform(1,0,0,1,0,0);
}
body { font-family: arial }
canvas {
 position: absolute;
 top: 0px;
 left: 0px;
}
<span> Click to set new target direction</span>
<canvas id="canvas" width="400" height="400"></canvas>

Upvotes: 1

Related Questions