Skip to content

Latest commit

 

History

History
555 lines (443 loc) · 14.2 KB

Algorithm.md

File metadata and controls

555 lines (443 loc) · 14.2 KB

游戏逆向各功能绘制算法

介绍

  1. 本文档仅对游戏逆向中某些绘制功能的数学原理进行分析,不做游戏逆向方面的教学。
  2. 需要掌握一定的数学知识,注:仅触及高中数学知识。
  3. 文档内会用伪代码进行对算法的演示计算。
  4. 仅数学计算原理分析,无侵害游戏本身行为。
  5. 作者名:Liv,QQ: 1319923129。

2D方框绘制

所需数据

  1. 敌人坐标 (X,Y,Z)

  2. 矩阵数据

算法分析

方框绘制需要获取敌人在屏幕上的坐标点,然后计算得出矩形信息,进行绘制。

因为获取的敌人坐标是敌人在世界内的相对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);

血条绘制

所需数据2

  1. 血量数据
  2. 敌人屏幕坐标

算法分析2

敌人当前血量/敌人初始血量,计算出敌人当前血量的占比。

血条绘制初始的宽度*占比,得到当前对应敌人血量的血条宽度。

伪代码2

float 敌人初始血量;
float 敌人当前血量;
float 血条绘制宽度;

血条绘制宽度=血条绘制宽度*敌人当前血量/敌人初始血量;

3D方框绘制

所需数据3

  1. 敌人坐标
  2. 敌人鼠标X(角度)

算法分析3

以人物鼠标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转换为屏幕坐标,然后八个点互相连线即可。

伪代码3

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点坐标);
// 其余点同理

人物视线绘制

所需数据4

  1. 敌人坐标
  2. Yaw,Pitch

算法分析4

水平

以世界平面建立平面直角坐标系。

人物视线首先要实现水平方向的旋转,α角在这里代表的即敌人鼠标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点坐标转成屏幕坐标,进行连线即可绘制出人物视线。

伪代码4

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点屏幕坐标);

旋转雷达绘制

所需数据5

  1. 敌人坐标
  2. 本人坐标
  3. 本人鼠标X(角度)

算法分析5

黑色以本人坐标为原点的世界平面直角坐标系,红色为以本人视线为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)

伪代码5

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);

雷达人物视线绘制

所需数据6

  1. 本人鼠标X
  2. 敌人鼠标X
  3. 敌人雷达坐标
  4. 雷达坐标

算法分析6


以本人视线为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)

伪代码6

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方框

算法分析7

通过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

伪代码7

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)
    {
        绘制直线(前一个底点,目前底点);
        绘制直线(前一个顶点,目前顶点);
    }
    前一个底点=目前底点;
    前一个顶点=目前顶点;
}