按圆度导航

Navigate according to degrees in circle

我尝试在 javascript 和 nodejs 中构建一个 2d canvas 游戏。 在游戏中,玩家是一艘可以在大海中航行的船。 船对象是 sprite sheet 有 16 个船位置(每帧 22.5 度)从 0 到 360 度开始。

为了导航我使用 NippleJS 的船,它给我 0-360 的度数(例如:328.9051138238949)。

现在,我需要算法来检查 16 个中的哪个精灵应该在每个时刻显示。

船必须在每个检查度数向左或向右移动1个精灵,并且它必须是最佳方式(最佳方式意味着如果船舶度数为 0 且 Nipplejs 的度数为 335,则船舶应向右导航而不是向左导航..)。

我开始考虑它并且我知道如果度数在 -(22.5/2) 和 (22.5/2) 之间我需要显示图像帧但是当我超过 360 度和更多东西时我遇到了很多问题..

为了相互理解,我们将船舶度数称为 ship.degree,我们将 Nipplejs 的度数称为 nipplejs.degree,船舶精灵将由 ship.sprite[0-16] 表示( 0 = 正确的方向)。

请帮忙。

要沿最小角度转动,下面的函数会将角度按每 360 度的步数 angleMoveSteps 步进。例如,90 步将以 4 度步进。

如果角度差小于此步,将停止步进

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
}

要将 deg 转换为图像索引,可以使用以下函数。请参阅精简版演示。

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;
}

演示

演示显示了正在使用的两个函数。 点击转盘设置新的目标方向,当前方向会向新方向移动,移动时显示移动角度(绿色)和图像索引(蓝色)。

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>