2D帧动画模型方向判断方法优化-JS/TS

本文介绍了一种在2D游戏中高效判断角色移动方向的方法。利用笛卡尔坐标系和数学函数,文章详细阐述了如何通过计算角度及优化算法来确定八个基本方向。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我们在做一款2D游戏的时候,移动的角色模型会在移动过程中有各种方向上的变化,下面我会介绍一下如何用传统的方式去判断方向并且分享一下自己对判断方向上的代码优化。

八个方向图示

根据笛卡尔坐标系X,Y轴,f(x)=x,f(x)=-x,四条线分为8个方向如图所示
根据笛卡尔坐标系X,Y轴,f(x)=x,f(x)=-x,四条线分为8个方向如图所示
360°角平均分成8份,那么每一个方向所占的角度范围是45°,下图中每一个用红色画出的角度范围就是一个方向,8个方向是通过四条线分割开来

f1(x) = tan( 22.5 / 180 * PI ) * x (鸡粑粑色的线),
f2(x) = tan( 67.5 / 180 * PI ) * x (黄色线),
f3(x) = - tan( 67.5 / 180 * PI ) * x (绿色线),
f4(x) = - tan( 22.5 / 180 * PI ) * x (蓝色线),

为了看起来更直观,我在图中也有标注。
每个方向所占角度范围

一般方法求方向

根据上面的图示,那么根据两个点去判断方向也变得简单了,令起始点,也就是模型所在位置为坐标系原点,只需要判断出另一个点(目标点)与原点所形成的直线相对于Y轴的夹角,根据这个夹角就可以判断出朝向范围。
要求出角度,就不得不介绍一下JS的标准内置对象Math
下面是MDN对Math的介绍Math的介绍
Math有很多的数学函数方法,那么其中一个方法atan2(y,x),功能是返回从原点(0,0)到(x,y)点的线段与x轴正方向之间的平面角度(弧度值)。MDN上是这么介绍的Math.atan2的介绍
那么我们可以利用这个方法去求出角度,然后根据角度落在某个范围去判断当前的方向。
代码如下:

enum Direction{ //首先我们定义出方向的枚举,这样看起来比较直观
    UP,
    UP_RIGHT,
    RIGHT,
    DOWN_RIGHT,
    DOWN,
    DOWN_LEFT,
    LEFT,
    UP_LEFT
}
static getDirectionByAtan2(curPoint,targetPoint){
        //将当前位置放到原点,转化目标位置相对当前位置为原点时的坐标
        let dx = targetPoint.x - curPoint.x;
        let dy = targetPoint.y - curPoint.y;
        //(dx,dy)点到原点的线段与x轴正方向之间的平面角度(弧度值)
        let radian = Math.atan2(dy,dx);
        //将弧度转换成角度
        let angle = radian / Math.PI * 180;
        if(angle >= -22.5 && angle < 22.5){
            return Direction.RIGHT;
        }else if(angle >= 22.5 && angle < 67.5){
            return Direction.UP_RIGHT;
        }else if(angle >= 67.5 && angle < 112.5){
            return Direction.UP;
        }else if(angle >= 112.5 && angle < 157.5){
            return Direction.UP_LEFT;
        }else if(angle >= -157.5 && angle < -112.5){
            return Direction.DOWN_LEFT;
        }else if(angle >= -67.5 && angle < -22.5){
            return Direction.DOWN_RIGHT;
        }else if(angle >= -112.5 && angle < -67.5){
            return Direction.DOWN;
        }
        //因为Math.atan2(dy,dx)返回的弧度范围时 - PI ~ PI之间。
        //左方向的范围应该是 > 157.5 || < - 157.5;
        return Direction.LEFT;
    }

优化获取方向的方法

个人感觉,Math.atan2的实现会有大的消耗,那么我下面介绍一种不需要计算角度来判断方向的方法。
首先,我们来看一张图0.0
O点作为模型的起点,B点作为目标点,要求出O点到B点的方向
同样的,将模型坐标点置为原点,那么点B(dx,dy)就是目标点相对于原点的位置,我们画一条直线 x = 1,那么 ▲OCD和▲OAB是相似三角形的关系,也就是说,将B点的x分量置为1的时候,D点一定就是转化之后的B点的位置,而且,D点一定是落在x = 1这条直线上的,那么我们可以求出上图中四条直线
f1(x) = tan( 22.5 / 180 * PI ) * x (鸡粑粑色的线),
f2(x) = tan( 67.5 / 180 * PI ) * x (黄色线),
f3(x) = - tan( 67.5 / 180 * PI ) * x (绿色线),
f4(x) = - tan( 22.5 / 180 * PI ) * x (蓝色线),
与x = 1这条直线的交点M,N,P,Q,根据D点的Y值与其四点的Y值做判断,可以直接得到方向。
四条直线与x = 1这条直线的交点的Y值

如图所示,
令D(ddx,ddy),则ddx = dx / dx = 1;ddy = dy / dx;
当ddx >0时,
ddy >= 2.414,---------------------------方向为 ↑ (D落在M上方);
ddy >= 0.414 && ddy < 2.414,------方向为↗(D落在MN之间);
ddy >= - 0.414 && ddy < 0.414,----方向为→(D落在NP之间);
ddy >= - 2.414 && ddy < -0.414,—方向为↘(D落在PQ之间);
ddy < - 2.414,---------------------------方向为 ↓ (D落在Q下方);
当ddx < 0时,
ddy>= 2.414,---------------------------方向为 ↑ (D落在M上方);
ddy>=0.414 && ddy < 2.414,-------方向为↖(D落在MN之间);
ddy>=-0.414&&ddy < 0.414,--------方向为←(D落在NP之间);
ddy>=-2.414&&ddy < -0.414,-------方向为↙(D落在PQ之间);
ddy<-2.414,------------------------------方向为↓ (D落在Q下方);

特殊的
当ddx == 0时:
ddy> 0,为↑;
ddy<=0,为↓;

优化后的代码实现

static getDirection(curPoint,targetPoint){
        //先得到当前位置与目标位置形成的向量(dx,dy);
        let dx = targetPoint.x - curPoint.x;
        let dy = targetPoint.y - curPoint.y;
        //当向量的X分量为0时,y分量 > 0则为上方向,反之为下方向,
        //因为下方向是正对准屏幕的方向,所以Y分量等于0时也是下方向。让玩家可以直接看到正脸
        if(dx === 0){
            return dy > 0 ? Direction.UP : Direction.DOWN;
        }
        //当向量的Y分量为0时,X分量 >= 0则为右向(因为个人觉得右向看起来比较舒服,所以在为0时也是右向)
        //反之为左向
        if(dy === 0){
            return dx >= 0 ? Direction.RIGHT : Direction.LEFT;
        }

        let ddy = dy / dx;
        if(ddy >= 2.414){
            return Direction.UP;
        }else if(ddy >= 0.414 && ddy < 2.414){
            return dx > 0 ? Direction.UP_RIGHT : Direction.UP_LEFT;
        }else if(ddy >= -2.414 && ddy < 0.414){
            return dx > 0 ? Direction.DOWN_RIGHT : Direction.DOWN_LEFT;
        }
        return Direction.DOWN;
    }

上述完整代码


enum Direction{
    UP,
    UP_RIGHT,
    RIGHT,
    DOWN_RIGHT,
    DOWN,
    DOWN_LEFT,
    LEFT,
    UP_LEFT
}
class Utils{

    static getDirection(curPoint,targetPoint){
        //先得到当前位置与目标位置形成的向量(dx,dy);
        let dx = targetPoint.x - curPoint.x;
        let dy = targetPoint.y - curPoint.y;
        //当向量的X分量为0时,y分量 > 0则为上方向,反之为下方向,
        //因为下方向是正对准屏幕的方向,所以Y分量等于0时也是下方向。让玩家可以直接看到正脸
        if(dx === 0){
            return dy > 0 ? Direction.UP : Direction.DOWN;
        }
        //当向量的Y分量为0时,X分量 >= 0则为右向(因为个人觉得右向看起来比较舒服,所以在为0时也是右向)
        //反之为左向
        if(dy === 0){
            return dx >= 0 ? Direction.RIGHT : Direction.LEFT;
        }

        let ddy = dy / dx;
        if(ddy >= 2.414){
            return Direction.UP;
        }else if(ddy >= 0.414 && ddy < 2.414){
            return dx > 0 ? Direction.UP_RIGHT : Direction.UP_LEFT;
        }else if(ddy >= -2.414 && ddy < 0.414){
            return dx > 0 ? Direction.DOWN_RIGHT : Direction.DOWN_LEFT;
        }
        return Direction.DOWN;
    }

    static getDirectionByAtan2(curPoint,targetPoint){
        //将当前位置放到原点,转化目标位置相对当前位置为原点时的坐标
        let dx = targetPoint.x - curPoint.x;
        let dy = targetPoint.y - curPoint.y;
        //(dx,dy)点到原点的线段与x轴正方向之间的平面角度(弧度值)
        let radian = Math.atan2(dy,dx);
        //将弧度转换成角度
        let angle = radian / Math.PI * 180;
        if(angle >= -22.5 && angle < 22.5){
            return Direction.RIGHT;
        }else if(angle >= 22.5 && angle < 67.5){
            return Direction.UP_RIGHT;
        }else if(angle >= 67.5 && angle < 112.5){
            return Direction.UP;
        }else if(angle >= 112.5 && angle < 157.5){
            return Direction.UP_LEFT;
        }else if(angle >= -157.5 && angle < -112.5){
            return Direction.DOWN_LEFT;
        }else if(angle >= -67.5 && angle < -22.5){
            return Direction.DOWN_RIGHT;
        }else if(angle >= -112.5 && angle < -67.5){
            return Direction.DOWN;
        }
        //因为Math.atan2(dy,dx)返回的弧度范围时 - PI ~ PI之间。
        //左方向的范围应该是 > 157.5 || < - 157.5;
        return Direction.LEFT;
    }
}

虽然简单,但是这也是分享的第一步,后面我会继续努力学习积累,分享更多的东西。

emmm,文笔不好希望可以谅解一下,如果有错误的地方,还请指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值