九.CANVAS Math.atan和Math.atan2的区别

在cavans中,凡是涉及角度都用“弧度”表示,例如180度就应该写成Math.PI,e而360度就该写成Math.PI*2,

在实际开发中,推荐一下写法:

度数*Math.PI/180

CANVAS使用的是W3C坐标系:

三角函数公式:

Math.atan的弊端:

tan度=X/Y;

坐标轴有四个像限:

X/Y=-X/-Y

-X/Y=X/Y

这种情况会导致一个tan度对应两个角度的情况,如图(图1

tan(A)=-0.5(-1/2);

tan(B)=0.5(1/2);

tan(C)=-0.5(-1/2);

tan(D)=-0.5(-1/-2);

Math.atan无法区分26.57对应的是哪个夹角

使用Math.atan2,可以求出俩边之间夹角的度数并且判断该度数对应的是哪个夹角

Math.atan2接收两个参数,Math.atan接受的是一个参数【度数】

Math.atan2(1,2)=26.56 ,对应图1角B

Math.tan2(-1,-2)=-153.34...,对应图1角D

-153.34这个角度是从X轴正方形以逆方形计算的,这样就区分开了两个角度,如下图(图2)

由于Math.atan低弊端导致比较扫用,Math.atan2用的比较多

在线demo:http://runjs.cn/code/wsu1pqg6

源码:

绘制箭头:

function Arrow(x, y, color, angle) {
    this.x = x || 0;
    this.y = y || 0;
    this.color = color || "#FF0099";
    this.angle = angle || 0;
}
Arrow.prototype = {
    stroke: function(cxt) {
        cxt.save();
        cxt.translate(this.x, this.y);
        cxt.strokeStyle = this.color;
        cxt.beginPath();
        cxt.moveTo(-20, -10);
        cxt.lineTo(0, -10);
        cxt.lineTo(0, -20);
        cxt.lineTo(20, 0);
        cxt.lineTo(0, 20);
        cxt.lineTo(0, 10);
        cxt.lineTo(-20, 10);
        cxt.closePath();;
        cxt.stroke();
        cxt.restore();
    },
    fill: function(cxt,cnv) {

        cxt.save();
        //cnv.clearReact(cnv.width,cnv.height)
        cxt.translate(this.x, this.y);
        cxt.rotate(this.angle);
        cxt.beginPath();//切记,不要遗留这个
        cxt.moveTo(-20, -10);
        cxt.lineTo(0, -10);
        cxt.lineTo(0, -20);
        cxt.lineTo(20, 0);
        cxt.lineTo(0, 20);
        cxt.lineTo(0, 10);
        cxt.lineTo(-20, 10);
        cxt.closePath();
        cxt.fill();
        cxt.restore();

    }
}


提供鼠标位置:

window.tools = {};
window.tools.getMouse = function(element) {
    var mouse = { x: 0, y: 0 };
    element.addEventListener("mousemove", function(e) {
        var x, y;
        var e = e || window.event;
        if (e.pageX || e.pageY) {
            x = e.pageX;
            y = e.pageY;
        } else {
            //IE8以下,以及混子模式下的chrome和safari
            x = e.clientX + document.body.scrollLeft || document.documentElement.scrollLeft;
            y = e.clientY + document.body.scrollTop || document.documentElement.scrollTop;
        }
        //将当前的坐标减去canvas元素的偏移量,则为x、y为是鼠标在canvas中的相对坐标
        x -= element.offsetLeft;
        y -= element.offsetTop;
        mouse.x = x;
        mouse.y = y;
        
    });
     return mouse;
}

核心代码:

在线demo

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="800" height="800"></canvas>
    <script src="arrow.js"></script>
    <script src="tools.js"></script>
    <script>
    function $$(id) {
        return document.getElementById(id);
    }

    window.onload = function() {
        var cnv = $$("canvas");
        var cxt = cnv.getContext('2d'); 
        //实例化一个箭筒,中心坐标为画布的中心坐标
        var arrow = new Arrow(cnv.width / 2, cnv.height / 2);

        var mouse = window.tools.getMouse(cnv);;
        function drawFrame() {
            window.requestAnimationFrame(drawFrame, cnv);
            cxt.clearRect(0, 0, 800, 800);
            //获取鼠标坐标
            var dx = mouse.x - cnv.width / 2;
            var dy = mouse.y - cnv.height / 2;
            //获取鼠标和箭头的夹角
            arrow.angle = Math.atan2(dy, dx);

            arrow.fill(cxt);
        };
        drawFrame();

    }
    </script>
</body>

</html>

======更新2024/04/30========

Math.atan2拿到的是弧度

需要注意的是,它的取值范围是[-PI, PI]。

  • 当 (x1, y1) 在第一象限, 0 < θ < PI/2
  • 当 (x1, y1) 在第二象限 PI/2 < θ≤PI
  • 当 (x1, y1) 在第三象限, -PI < θ < -PI/2
  • 当 (x1, y1) 在第四象限, -PI/2 < θ < 0

弧度和角度转换公式

角度 = 弧度 * 180 / Math.PI;
弧度= 角度 * Math.PI / 180;

参考:https://www.cnblogs.com/webhmy/p/9700079.html

请注意,当使用JavaScript的Math.atan2函数来获取角度时,该函数返回的是一个弧度值,范围在-π到π之间(即-180度到180度),并且它考虑了符号(即方向),而且计算出来的角度是符合css transform rotate旋转规范的

 图解

 

atan2计算出来的角度是符合css transform rotate旋转规范的 

在线demo:JS Bin - Collaborative JavaScript Debugging 

代码

 

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
        }

        .box {
            width: 50px;
            height: 50px;
            position: fixed;
            top: 40px;
            right: 40px;
            background: red;
            opacity: .5;
            z-index: 10;
        }

        .box::after {
            content: "";
            width: 10px;
            height: 10px;
            background-color: black;
            right: 0;
            top: 0;
            position: absolute;
            display: block;
        }


        .tip {
            position: fixed;
            top: 10;
            right: 10px;
            z-index: 20;
        }
    </style>
</head>

<body>
    <div class="box">
    </div>
    <div class="tip">Math.atan2计算出来的角度是符合css规范的</div>
    <canvas />
    <script>
        const canvas = document.querySelector("canvas");
        var ctx = canvas.getContext("2d");
        const wWidth = document.documentElement.clientWidth;
        const wheight = document.documentElement.clientHeight;
        const xAxiosCenter = wWidth / 2;
        const yAxiosCenter = wheight / 2;
        canvas.width = wWidth;
        canvas.height = wheight;
        const pointR = 10;

        const baseLayout = function () {
            //中心园点
            ctx.beginPath();
            ctx.arc(xAxiosCenter, yAxiosCenter, pointR, 0, 2 * Math.PI);
            ctx.fillStyle = "#ff0";//设置填充颜色
            ctx.fill();//开始填充
            ctx.stroke();
            //水平中心线
            ctx.moveTo(0, yAxiosCenter);
            ctx.lineTo(wWidth, yAxiosCenter);
            ctx.stroke();
            //垂直中心线
            ctx.moveTo(xAxiosCenter, 0);
            ctx.lineTo(xAxiosCenter, wheight);
            ctx.stroke();

        }
        baseLayout();
        const box = document.querySelector(".box");
        document.onmousedown = (e) => {
            console.log(e.clientX, e.clientY)
            ctx.clearRect(0, 0, wWidth, wheight);
            baseLayout();
            //绘制鼠标位置远点
            ctx.beginPath();
            ctx.arc(e.clientX, e.clientY, pointR, 0, 2 * Math.PI);
            ctx.fillStyle = "red";//设置填充颜色
            ctx.fill();//开始填充
            ctx.stroke();
            //绘制交叉点到鼠标的位置
            ctx.moveTo(xAxiosCenter, yAxiosCenter);
            ctx.lineTo(e.clientX, e.clientY);
            ctx.stroke();

            ctx.font = "15px serif";
            //拿到的是弧度
            const tan2Value = Math.atan2(e.clientY - yAxiosCenter, e.clientX - xAxiosCenter)
            //角度要转
            const rotate = tan2Value * 180 / Math.PI;
            ctx.strokeText("弧度:" + tan2Value + ",角度:" + rotate, e.clientX + pointR, e.clientY + pointR);
            box.style.transform = `rotate(${rotate}deg)`;

        }


    </script>
</body>

</html>

function spawnEnemy(n) { setInterval(() => { for (let idx = 0; idx < n; idx++) { let radius = Math.random() * (20 - 4) + 8; let x = Math.random() * canvas.width - radius; let y = Math.random() * canvas.height + radius; let color = hsl(${Math.floor(Math.random() * 360)},70%,50%); let angle = Math.atan2(player.y - y, player.x - x); let vector = { x: Math.cos(angle) * 2, y: Math.sin(angle) * 2, }; let enemyObj = new Enemy(x, y, radius, color, vector); enemyArrays.push(enemyObj); } }, 1000); } function animate() { if (!gameStarted) { // 如果游戏未开始,不执行游戏逻辑 return; } player.draw(ctx); ctx.fillStyle = "rgba(0,0,0,0.2)"; // ctx.clearRect(0,0,canvas.width,canvas.height); ctx.fillRect(0, 0, canvas.width, canvas.height); enemyArrays.forEach((enemy, idx) => { enemy.draw(ctx); enemy.update(); // 判断,从enemyArrays数组中删除 if (enemy.x <= 0 || enemy.x >= canvas.width || enemy.y <= 0 || enemy.y >= canvas.height) { enemyArrays.splice(idx, 1); } }); //shell shellArrays.forEach((shell, idx) => { shell.draw(ctx); shell.update(); enemyArrays.forEach((enemy, j) => { // 碰撞判断 let distance = Math.hypot(shell.x - enemy.x, shell.y - enemy.y); if (distance < 20) { enemyArrays.splice(j, 1); shellArrays.splice(idx, 1); createParticle(particleNum, enemy.x, enemy.y, enemy.color); score += 10; scoreText.innerHTML = 'Score: ' + score; } }); // 范围判断 if (shell.x <= 0 || shell.x >= canvas.width || shell.y <= 0 || shell.y >= canvas.height) { shellArrays.splice(idx, 1); } }); // 粒子 particleArrays.forEach((particle, idx) => { particle.draw(ctx); particle.update(); if (particle.alpha <= 0.1) { particleArrays.splice(idx, 1); } }); window.requestAnimationFrame(animate); } // 创建粒子 function createParticle(n, x, y, color) { for (let i = 0; i < n; i++) { let vector = { x: Math.random() * 3 - 1.5, y: Math.random() * 3 - 1.5, }; particleArrays.push(new Particle(x, y, 3, color, 1, vector)); } } function handleClick() { window.addEventListener("click", (e) => { let x = e.clientX; let y = e.clientY; let color = "white"; let radius = 10; let radian = Math.atan2(y - player.y, x - player.x); let vector = { x: Math.cos(radian) * 6, y: Math.sin(radian) * 6, }; let shell = new Shell(player.x, player.y, radius, color, vector); shellArrays.push(shell); }) } 子弹和敌人的运动方向是怎么产生的
06-02
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陆康永

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值