游戏逆向各功能绘制算法
- 本文档仅对游戏逆向中某些绘制功能的数学原理进行分析,不做游戏逆向方面的教学。
- 需要掌握一定的数学知识,注:仅触及高中数学知识。
- 文档内会用伪代码进行对算法的演示计算。
- 仅数学计算原理分析,无侵害游戏本身行为。
- 作者名:Liv,QQ: 1319923129。
敌人坐标 (X,Y,Z)
矩阵数据
方框绘制需要获取敌人在屏幕上的坐标点,然后计算得出矩形信息,进行绘制。
因为获取的敌人坐标是敌人在世界内的相对3D坐标,而绘制需要的是敌人在屏幕上的2D坐标,所以需要通过敌人坐标+矩阵数据,使用WorldToScreen(W2S)函数进行转换坐标。
WorldToScreen算法这里不展开讲,对于矩阵数据可能算法不同,如竖矩阵和横矩阵有两种不同对应算法。
将3D坐标通过矩阵计算转换成屏幕的2D坐标,才能进行进一步的绘制。
struct Pos{ // 3D坐标数据
float x,y,z;
}
struct Point{ // 2D坐标数据
float x,y;
}
Pos 敌人坐标;
float 矩阵[4][4];
Point 屏幕坐标;
屏幕坐标=WorldToScreen(矩阵,敌人坐标);
绘制(屏幕坐标.x,屏幕坐标.y);
- 血量数据
- 敌人屏幕坐标
敌人当前血量/敌人初始血量,计算出敌人当前血量的占比。
血条绘制初始的宽度*占比,得到当前对应敌人血量的血条宽度。
float 敌人初始血量;
float 敌人当前血量;
float 血条绘制宽度;
血条绘制宽度=血条绘制宽度*敌人当前血量/敌人初始血量;
- 敌人坐标
- 敌人鼠标X(角度)
以人物鼠标X方向为X轴建立平面直角坐标系。
这个为人物3D方框底面4点的平面图形。点0为人物的坐标点,做为原点。
需要计算出会随人物旋转而旋转的3D方框,就必须在人物的鼠标X的基础上进行计算。
需要计算出A、B、C、D共计4点的坐标。
以B点为例:
OB为自定义的一个长度。
需要求出B点的平面坐标 (x,y),即需要求出BE和OE的长度。
这里有一个定角α,45°。
由于是在人物鼠标X为X轴的坐标系上,所以实际计算中 α=鼠标X+45°。
通过sin、cos函数和OB长度即可计算出BE和OE长度。
sin和cos这里用α角的弧度进行计算。(弧度=角度*PI/180)
BE推导:
sin α=BE/OB
sin(α弧度)=sin α=BE/OB
sin(α弧度)=BE/OB
BE=sin(α弧度)*OB
OE推导:
cos α=OE/OB
cos(α弧度)=cos α=OE/OB
cos(α弧度)=OE/OB
OE=cos(α弧度)*OB
结果:
BE=sin(α弧度)*OB
OE=cos(α弧度)*OB
那么对应B点的(x,y)就是(BE,OE)。
其余点同理,A点角为鼠标X+135°,C点角为鼠标X+225°,D点角为鼠标X+315°。
通过同样计算方法,得出四点在此平面直角坐标系中的x、y坐标,因为原点是人物的坐标点,所以要在(x,y)基础上都加上(敌人x,敌人y),才能得到这四个点在游戏世界中的坐标点,四个点的z坐标就是人物的z坐标,即可使用W2S转换为屏幕坐标,即可进行绘制。
顶点四点仅是把z坐标加上方框高度,同样使用W2S转换为屏幕坐标,然后八个点互相连线即可。
struct Pos{ // 3D坐标数据
float x,y,z;
}
struct Point{ // 2D坐标数据
float x,y;
}
Pos 敌人坐标;
float 敌人鼠标X;
float 矩阵[4][4];
float 斜边;
Pos A点坐标;
Point A点屏幕坐标;
A点坐标.x=敌人坐标.x+斜边*cos((敌人鼠标X+45)*PI/180);
A点坐标.y=敌人坐标.y+斜边*sin((敌人鼠标X+45)*PI/180);
A点坐标.z=敌人坐标.z;
A点屏幕坐标=WorldToScreen(矩阵,A点坐标);
// 其余点同理
- 敌人坐标
- Yaw,Pitch
以世界平面建立平面直角坐标系。
人物视线首先要实现水平方向的旋转,α角在这里代表的即敌人鼠标X角度,是一个动角。
人物头部坐标为线段起始点,B点坐标为线段结束点
OB是自定义长度。
需要求出B点在此坐标系中的(x,y),即需要求出OA,BA长度。
通过sin、cos函数和OB长度计算出OA,BA数据。
和3D方框同理,用弧度制计算,推导过程一样。(弧度=角度*PI/180)
结果:
BA=sin(α弧度)*OB
OA=cos(α弧度)*OB
那么对应B点坐标就是(OA,BA)
此平面直角坐标系是以人物头部坐标为原点。
所以要在B点坐标基础上加上人物头部坐标。
得到最终B点三维坐标是(OA+人物头部.x,BA+人物头部.y,人物头部.z)
这样就实现了水平方向实现绘制视线,但视线不会在纵向方向发生变换,所以需要<算法2>。
需要视线纵向方向的移动,就需要用到敌人鼠标Y角度数据进一步完善坐标计算。
以人物侧视图建立平面直角坐标系,原点为头部坐标点。
β角在这里代表敌人鼠标Y角度,也是动角。
OB是自定义长度。
因为B点在水平平面上的(x,y)坐标已经求出,这里仅需要计算出B点的z坐标,即此坐标系中的y坐标。
需要求出B点在此坐标系中的y坐标,即需要求出BC长度。
通过sin函数和OB长度计算出BC数据。
与之前算法同理,弧度制计算。(弧度=角度*PI/180)
结果:
BC=sin(β弧度)*OB
那么对应B点在此坐标系中的y坐标就是BC,
由于是侧视图,所以对于在游戏中的三维坐标来说,算出来的y坐标即是游戏中的z坐标高度。
由于原点是头部坐标点,所以B点的z坐标为(BC+人物头部.z)
最终算出来B点坐标为(OA+人物头部.x,BA+人物头部.y,BC+人物头部.z)
通过W2S函数将人物头部坐标点和B点坐标转成屏幕坐标,进行连线即可绘制出人物视线。
struct Pos{ // 3D坐标数据
float x,y,z;
}
struct Point{ // 2D坐标数据
float x,y;
}
Pos 敌人头部坐标;
Point 敌人头部屏幕坐标;
float 敌人鼠标X,敌人鼠标Y;
float 矩阵[4][4];
float 绘制长度;
float 平面绘制长度;
Pos B点坐标;
Point B点屏幕坐标;
平面绘制长度=cos(Pitch*M_PI/180)*绘制长度;
B点坐标.x=人物头部.x+cos(Yaw*M_PI/180)*平面绘制长度;
B点坐标.y=人物头部.y+sin(Yaw*M_PI/180)*平面绘制长度;
B点坐标.z=人物头部.z+sin(Pitch*M_PI/180)*绘制长度;
B点屏幕坐标=WorldToScreen(矩阵,B点坐标);
敌人头部屏幕坐标=WorldToScreen(矩阵,敌人头部坐标);
连线(敌人头部屏幕坐标,B点屏幕坐标);
- 敌人坐标
- 本人坐标
- 本人鼠标X(角度)
黑色以本人坐标为原点的世界平面直角坐标系,红色为以本人视线为y轴,以本人坐标为原点,建立平面直角坐标系。
OA是敌人与本人之间的距离,通过勾股定理即可计算,计算完需要进行缩放距离。
目标是计算出∠AOC,然后通过三角函数算出A点相对于红色坐标系中的坐标。
α角是敌人与世界x轴之间的夹角,这里不能使用atan直接进行计算,需要利用更完备,支持象限符号的atan2(反正切2)函数计算。(角度=弧度*180/PI)
计算:
α=atan2(AB/OB)*180/PI;
β角就是本人鼠标X角度和α角之间的夹角
计算:
β=本人鼠标X角度-α
这里拓展一下sin和cos两个函数:
如果sin处理的角度大于180,则需要 角度-360。
若角度大于90小于180,则需要 180-角度
sin(100)=sin(180-100)=sin(80)
sin(190)=sin(190-360)=sin(-170)
cos角度处理和sin一样,不过计算出来的结果符号是相反的。
比如设α为30°,本人鼠标X为250°,
那么β=250°-30°=220°
sin(β)=sin(β-360)=sin(-140)=-sin(180-140)=-sin(40)
∠AOC即是40°,和之前算法一样,通过sin、cos函数和OA,计算出点A在红色坐标系中的(x,y)即(AC,OC)
点O是雷达绘制在屏幕上的坐标,而且sin函数内部处理β角最后算出来的∠AOC是[-]的,所以是用雷达的x坐标加上A点x坐标才能使敌人绘制在第三象限,cos函数结果是相反的,所以是雷达的y坐标减去A点的y坐标。
A点相对于红色坐标系的坐标为(雷达.x+AC,雷达.y-OC)
struct Pos{ // 3D坐标数据
float x,y,z;
}
struct Point{ // 2D坐标数据
float x,y;
}
Pos 敌人坐标;
Pos 本人坐标;
Point 雷达中心坐标;
float 雷达半径
float 距离;
float 本人鼠标X;
float 角度;
Point A点坐标;
距离=sqrt(pow(敌人坐标.x-本人坐标.x,2),pow(敌人坐标.y-本人坐标.y,2));
// 距离缩放 [3000]这个常数可自行调整
距离=距离/3000*雷达半径*2;
角度=atan2(敌人坐标.y-本人坐标.y,敌人坐标.x-本人坐标.x);
角度=本人鼠标X-角度;
A点坐标.x=雷达中心坐标.x+距离*sin(角度*PI/180);
A点坐标.y=雷达中心坐标.y-距离*cos(角度*PI/180);
- 本人鼠标X
- 敌人鼠标X
- 敌人雷达坐标
- 雷达坐标
以本人视线为y轴建立的红色平面直角坐标系。
以雷达中心点坐标为原点。
B点即为计算完的敌人在雷达上的坐标点。
α为本人鼠标X,β为敌人鼠标X。
AB为自定义视线绘制长度。
∠ABC=β-α
通过sin、cos函数计算得出A点坐标。
结果:
AC=sin(∠ABC*PI/180)*AB
BC=cos(∠ABC*PI/180)*AB
由于是需要以敌人雷达点为原点,所以需要在敌人雷达坐标的基础上减去A的坐标。
所以A的坐标(敌人雷达坐标.x-AC,敌人雷达坐标.y-BC)
struct Pos{ // 3D坐标数据
float x,y,z;
}
struct Point{ // 2D坐标数据
float x,y;
}
float 敌人鼠标X,本人鼠标X;
Point 敌人雷达坐标;
Point 雷达坐标;
Point A点坐标;
float 角度;
float 视线长度;
角度=敌人鼠标X-本人鼠标X;
A点坐标.x=敌人雷达坐标.x-sin(角度*PI/180)*视线长度;
A点坐标.y=敌人雷达坐标.y-cos(角度*PI/180)*视线长度;
绘制直线(敌人雷达坐标,A点坐标);
通过3D方框绘制可知,当绘制的为正方形底面的3D矩形时,
底面四个点对应的角度是45,135,225,315
推导
4边形->45°
然后向后递增90
那么可以推测 45=360/(2*4),90=360/4。
设边数为n
可以得出一个通用结论,第一个角度=360/2n,
每次递增的角度=360/n
即可计算出任意边数的角度,
即等于360/2n+360/n*i
化简得180(1+2i)/n
struct Pos{ // 3D坐标数据
float x,y,z;
}
struct Point{ // 2D坐标数据
float x,y;
}
float 敌人鼠标X;
float 斜边长度;
float 角度;
float 矩形高度;
Pos 敌人坐标;
Pos 底点[n+1];
Pos 顶点[n+1];
Point 前一个底点;
Point 前一个顶点;
for(int i=0;i<n+1;i++)
{
角度=敌人鼠标X+180(1+2i)/n;
底点[i].x=顶点[i].x=敌人坐标.x+cos(角度*PI/180)*斜边长度;
底点[i].y=顶点[i].y=敌人坐标.y+sin(角度*PI/180)*斜边长度;
底点[i].z=敌人坐标.z;
顶点[i].z=敌人坐标.z-矩形高度;
Point 目前底点=WorldToScreen(底点[i]);
Point 目前顶点=WorldToScreen(顶点[i]);
绘制直线(底点屏幕,顶点屏幕);
if(i)
{
绘制直线(前一个底点,目前底点);
绘制直线(前一个顶点,目前顶点);
}
前一个底点=目前底点;
前一个顶点=目前顶点;
}