【问题标题】:Navigate according to degrees in circle根据圆圈的度数导航
【发布时间】:2023-06-14 00:31:01
【问题描述】:

我尝试用 javascript 和 nodejs 构建一个 2d 画布游戏。 在游戏中,玩家是一艘可以在大海中航行的船。 船对象是从 0 到 360 度开始的具有 16 个船位置(evrey 框架为 22.5 度)的精灵表。

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

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

船必须在每个检查度数中向左或向右移动1个精灵,并且必须是最佳方式(最佳方式是指船度数为0且度数从 Nipplejs 是 335 船应该向右而不是向左航行..)。

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

为了相互理解,我们称ship degree为ship.degree,来自Nipplejs的degree我们称之为nipplejs.degree,ship sprite将由ship.sprite[0-16]表示(0 =右方向)。

请帮忙。

【问题讨论】:

    标签: javascript canvas geometry html5-canvas 360-degrees


    【解决方案1】:

    要沿最小角度转动,下面的函数将以每 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>

    【讨论】: