我们在做一款2D游戏的时候,移动的角色模型会在移动过程中有各种方向上的变化,下面我会介绍一下如何用传统的方式去判断方向并且分享一下自己对判断方向上的代码优化。
八个方向图示
根据笛卡尔坐标系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有很多的数学函数方法,那么其中一个方法atan2(y,x),功能是返回从原点(0,0)到(x,y)点的线段与x轴正方向之间的平面角度(弧度值)。MDN上是这么介绍的
那么我们可以利用这个方法去求出角度,然后根据角度落在某个范围去判断当前的方向。
代码如下:
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的实现会有大的消耗,那么我下面介绍一种不需要计算角度来判断方向的方法。
首先,我们来看一张图
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值做判断,可以直接得到方向。
如图所示,
令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,文笔不好希望可以谅解一下,如果有错误的地方,还请指正。