FPS游戏(UE4,U3D引擎)方框绘制,骨骼透视,BT功能的原理 和反外挂策略 |
作者: 佚名 日期:2023-07-19 13:23:07 来源: 本站整理
|
朝向值 和角度差的计算过程,根据上面已经得到的朝向数据
我们编写如下代码,为了让理解更简单 这里我分成了4个象限来讲解
[C++] 纯文本查看 复制代码
void 周围对象::计算朝向_Cs(坐标结构_3 目标, 朝向结构_2& 角度, 朝向结构_2& 角度差)
{
FLOAT FOV_x = *( FLOAT *)(( DWORD )GetModuleHandleA( "hl.exe" ) + 0x195fe58);
FLOAT FOV_y = *( FLOAT *)(( DWORD )GetModuleHandleA( "hl.exe" ) + 0x195fe5C);
FLOAT FOV_z = *( FLOAT *)(( DWORD )GetModuleHandleA( "hl.exe" ) + 0x195fe60);
FLOAT 水平朝向 = *( FLOAT *)(( DWORD )GetModuleHandleA( "hl.exe" ) + 0x19E10C8);
FLOAT 高低朝向 = *( FLOAT *)(( DWORD )GetModuleHandleA( "hl.exe" ) + 0x19E10C4);
if (目标.x > FOV_x && 目标.y >= FOV_y) //第一象限
{
角度.水平朝向 = ( FLOAT )(( double ) atan2 (目标.y - FOV_y, 目标.x - FOV_x) * 180 / 3.1415);
}
if (目标.x <= FOV_x && 目标.y > FOV_y) //第二象限
{
角度.水平朝向 = 180 - ( FLOAT )(( double ) atan2 (目标.y - FOV_y, FOV_x - 目标.x) * 180 / 3.1415);
}
if (目标.x < FOV_x && 目标.y <= FOV_y) //第三象限
{
角度.水平朝向 = 180 + ( FLOAT )(( double ) atan2 (FOV_y - 目标.y, FOV_x - 目标.x) * 180 / 3.1415);
}
if (目标.x >= FOV_x && 目标.y < FOV_y) //第四象限
{
角度.水平朝向 = 360 - ( FLOAT )(( double ) atan2 (FOV_y - 目标.y, 目标.x - FOV_x) * 180 / 3.1415);
}
FLOAT 平面距离 = sqrt ((目标.x - FOV_x) * (目标.x - FOV_x) + (FOV_y - 目标.y) * (FOV_y - 目标.y));
if (目标.z > FOV_z) //上方
{
角度.高低朝向 = ( FLOAT )(-( double ) atan2 (目标.z - FOV_z, 平面距离) * 180 / 3.1415);
}
if (目标.z < FOV_z) //下方
{
角度.高低朝向 = ( FLOAT )(( double ) atan2 (FOV_z - 目标.z, 平面距离) * 180 / 3.1415);
}
角度差.水平朝向 = 水平朝向 - 角度.水平朝向;
if (角度差.水平朝向 <= -180) //跨0轴的两种情况
{
角度差.水平朝向 += 360;
}
if (角度差.水平朝向 >= 180)
{
角度差.水平朝向 -= 360;
}
角度差.高低朝向 = 角度.高低朝向 - 高低朝向;
}
绘制类
万事俱备,我们来写个绘制类
数据全部完毕我们开始写代码绘制了,这部分是固定的代码,很简单
完全中文编程,代码已经很清晰了
大家跟着我敲写一遍即可
当然第一种 是最粗糙的, 由奢入简易 由俭入奢易难 我们先来粗糙的
[C++] 纯文本查看 复制代码
struct 坐标结构_2
{
float x, y;
};
struct 坐标结构_3
{
float x, y, z;
};
struct 坐标结构_4
{
float x, y, z, w;
};
class 绘制
{
public :
HWND m_窗口句柄;
RECT m_窗口;
int m_分辨率宽;
int m_分辨率高;
RECT m_外窗口;
int m_外窗口宽;
int m_外窗口高;
float m_矩阵[16];
DWORD m_矩阵地址;
public :
绘制( HWND 窗口句柄, DWORD 矩阵地址);
绘制()
{
}
private :
void 取窗口信息();
public :
void 绘制矩形( HDC HDC 句柄, HBRUSH 画刷句柄, int x, int y, int w, int h);
void 画框( HDC HDC 句柄, HBRUSH 画刷句柄, int x, int y, int w, int h, int 厚度);
void 画线( HDC HDC 句柄, int X, int Y);
void 绘制字符串( HDC HDC 句柄, int x, int y, COLORREF color, const char * text);
//还没讲矩阵绘制 这个函数请忽略
//bool 世界坐标转屏幕坐标(坐标结构_3 游戏坐标, 坐标结构_2& 屏幕坐标);
bool 世界坐标转屏幕坐标_非矩阵(坐标结构_2& 屏幕坐标, FLOAT 水平角度差, FLOAT 高低角度差); //传递真实角度
};
绘制::绘制( HWND 窗口句柄, DWORD 矩阵地址)
{
m_窗口句柄 = 窗口句柄;
m_矩阵地址 = 矩阵地址;
memcpy (&m_矩阵, ( PBYTE *)(m_矩阵地址), sizeof (m_矩阵));
取窗口信息();
}
void 绘制::取窗口信息()
{
GetClientRect(m_窗口句柄, &m_窗口);
m_分辨率宽 = m_窗口.right - m_窗口.left;
m_分辨率高 = m_窗口.bottom - m_窗口.top;
GetWindowRect(m_窗口句柄, &m_外窗口); //含有边框及全屏幕坐标
m_外窗口宽 = m_外窗口.right - m_外窗口.left;
m_外窗口高 = m_外窗口.bottom - m_外窗口.top;
}
void 绘制::绘制矩形( HDC HDC 句柄, HBRUSH 画刷句柄, int x, int y, int w, int h)
{
RECT 矩形 = { x,y,x + w,y + h };
FillRect( HDC 句柄, &矩形, 画刷句柄); //绘制矩形
}
void 绘制::画框( HDC HDC 句柄, HBRUSH 画刷句柄, int x, int y, int w, int h, int 厚度)
{
绘制矩形( HDC 句柄, 画刷句柄, x, y, w, 厚度); //顶边
绘制矩形( HDC 句柄, 画刷句柄, x, y + 厚度, 厚度, h - 厚度); //左边
绘制矩形( HDC 句柄, 画刷句柄, (x + w) - 厚度, y + 厚度, 厚度, h - 厚度); //右边
绘制矩形( HDC 句柄, 画刷句柄, x + 厚度, y + h - 厚度, w - 厚度 * 2, 厚度); //底边
}
void 绘制::画线( HDC HDC 句柄, int X, int Y)
{
取窗口信息();
MoveToEx( HDC 句柄, m_分辨率宽 / 2, m_分辨率高, NULL);
LineTo( HDC 句柄, X, Y);
}
HFONT 字体属性;
void 绘制::绘制字符串( HDC HDC 句柄, int x, int y, COLORREF color, const char * text)
{
SetTextAlign( HDC 句柄, TA_CENTER | TA_NOUPDATECP);
SetBkColor( HDC 句柄, RGB(0, 0, 0));
SetBkMode( HDC 句柄, TRANSPARENT);
SetTextColor( HDC 句柄, color);
SelectObject( HDC 句柄, 字体属性);
TextOutA( HDC 句柄, x, y, text, strlen (text));
DeleteObject(字体属性);
}
//还没讲矩阵绘制 这个函数请忽略
/*bool 绘制::世界坐标转屏幕坐标(坐标结构_3 游戏坐标, 坐标结构_2& 屏幕坐标)
{
取窗口信息();
memcpy(&m_矩阵, (PBYTE*)(m_矩阵地址), sizeof(m_矩阵));
坐标结构_4 裁剪坐标;
裁剪坐标.x = 游戏坐标.x * m_矩阵[0] + 游戏坐标.y * m_矩阵[4] + 游戏坐标.z * m_矩阵[8] + m_矩阵[12];
裁剪坐标.y = 游戏坐标.x * m_矩阵[1] + 游戏坐标.y * m_矩阵[5] + 游戏坐标.z * m_矩阵[9] + m_矩阵[13];
裁剪坐标.z = 游戏坐标.x * m_矩阵[2] + 游戏坐标.y * m_矩阵[6] + 游戏坐标.z * m_矩阵[10] + m_矩阵[14];
裁剪坐标.w = 游戏坐标.x * m_矩阵[3] + 游戏坐标.y * m_矩阵[7] + 游戏坐标.z * m_矩阵[11] + m_矩阵[15];
if (裁剪坐标.w < 0.0f)
return false;
坐标结构_3 NDC;
NDC.x = 裁剪坐标.x / 裁剪坐标.w;
NDC.y = 裁剪坐标.y / 裁剪坐标.w;
NDC.z = 裁剪坐标.z / 裁剪坐标.w;
屏幕坐标.x = (m_分辨率宽 / 2 * NDC.x) + m_分辨率宽 / 2;
屏幕坐标.y = -(m_分辨率高 / 2 * NDC.y) + m_分辨率高 / 2;
return true;
}*/
bool 绘制::世界坐标转屏幕坐标_非矩阵(坐标结构_2& 屏幕坐标, FLOAT 水平角度差, FLOAT 高低角度差)
{
取窗口信息();
FLOAT 高低可视角度 = ( FLOAT )(( double ) atan2 (m_分辨率高, m_分辨率宽)*180/3.1415);
if ( fabs (水平角度差) > 45 || fabs (高低角度差) > 高低可视角度)
{
return false ; // 不在屏幕范围内
int 水平差 = ( int )( tan (水平角度差 * 3.1416 / 180) * ((m_分辨率宽) / 2));
屏幕坐标.x = ( float )(m_分辨率宽 / 2 + 水平差);
// 哪里不懂可以v hdlw312
int 高度差 = ( int )( tan (高低角度差 * 3.1416 / 180) * ((m_分辨率宽) / 2));
屏幕坐标.y = ( float )(m_分辨率高 / 2 + 高度差);
return true ;
}
到这里 非矩阵绘制的所有准备工作都已经完毕了 ,就看你要怎么调用了
非矩阵绘制方框
编写循环绘制代码
通过上面的编写 我们终于可以调用观看效果了
[C++] 纯文本查看 复制代码
HBRUSH 画刷句柄;
HDC HDC 句柄;
COLORREF 文本颜色_亮红 = RGB(255, 0, 0);
COLORREF 文本颜色_红 = RGB(128, 0, 0);
COLORREF 文本颜色_黄 = RGB(200, 200, 0);
COLORREF 文本颜色_黑 = RGB(0, 0, 0);
COLORREF 文本颜色_白 = RGB(255, 255, 255);
COLORREF 文本颜色_常用 = RGB(158, 255, 0);
坐标结构_2 屏幕坐标_j;
坐标结构_2 屏幕坐标_H;
周围对象 周围;
int mainThread()
{
绘制 FPS_绘制(FindWindow(L "Valve001" , 0), ( DWORD )GetModuleHandleA( "hl.exe" ) + 0x1820100);
while ( true )
{
HDC 句柄 = GetDC(FPS_绘制.m_窗口句柄);
周围.刷新周围数据_Cs();
for ( int i = 0; i < ( int )周围.对象数量; i++)
{
if (周围.对象列表[i].死亡标志位 == 1)
{
continue ;
}
if (FPS_绘制.世界坐标转屏幕坐标_非矩阵(屏幕坐标_j,
周围.对象列表[i].角度差_j.水平朝向, 周围.对象列表[i].角度差_j.高低朝向))
{
if (FPS_绘制.世界坐标转屏幕坐标_非矩阵(屏幕坐标_H,
周围.对象列表[i].角度差_H.水平朝向, 周围.对象列表[i].角度差_H.高低朝向))
{
if (周围.对象列表[i].阵营 == 2)
{
画刷句柄 = CreateSolidBrush(文本颜色_常用);
}
else
{
画刷句柄 = CreateSolidBrush(文本颜色_亮红);
}
float 高度 = 屏幕坐标_j.y - 屏幕坐标_H.y;
float 宽度 = 高度 / 2;
FPS_绘制.画框( HDC 句柄, 画刷句柄, ( int )(屏幕坐标_H.x - 宽度/2),
( int )屏幕坐标_H.y-2, ( int )宽度, ( int )(高度+4), 1);
DeleteObject(画刷句柄);
char healthChar[255];
sprintf_s(healthChar, sizeof (healthChar), "%d" , 周围.对象列表[i].Hp);
FPS_绘制.绘制字符串( HDC 句柄, ( int )屏幕坐标_j.x, ( int )屏幕坐标_j.y,
文本颜色_常用, healthChar);
if (GetKeyState(VK_F2) & 1)
{
FPS_绘制.画线( HDC 句柄, ( int )屏幕坐标_j.x, ( int )屏幕坐标_j.y);
}
}
}
}
Sleep(1);
DeleteObject( HDC 句柄);
}
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
DisableThreadLibraryCalls(hModule);
CreateThread(0, 0, (LPTHREAD_START_ROUTINE)mainThread, 0, 0, 0);
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break ;
}
return TRUE;
}
if (GetKeyState(VK_F2) & 1)
这里面学习一个快捷键指令 GetKeyState
每次按下切换他的状态 大于0 或则小于0
从而可以实现开关的功能
微调
发现效果是有了,但是并不精准
优点是:不需要矩阵 ,完全的数学公式可以计算出来,而且公式是固定的,
缺点:
但是不精准
有可视角的关系,运算的误差等等导致的,算出来的误差虽然不是特别大
而且也可以通过调整的方式让其精确,但是不够完美.
我们可以选择微调可视角度,微调屏幕X,Y坐标等等方式来处理
反正这种方法想要完全的精密是不太可能的
但是达到基本效果是没问题的,虽然达到精密没有那么的重要,但是完美主义者
是一定会使用矩阵来计算的
那么我们继续学习矩阵,用矩阵的方式 绘制方框
绘制的几种方法和反外挂建议
我们非矩阵的方式绘制 是没有那么的精确的
在学习矩阵绘制之前,我们先来了解下绘制的几种方法
第一种 hook d3d/opengl
优点:不闪 ,代码简单
缺点:非常容易被检测
第二种 窗口上自行绘制,但是会闪
优缺点适中
第三种 自建透明窗口,覆盖游戏窗口,透明窗口上绘制
优点:稳定
确定:代码复杂,会闪
反外挂:无非就是针对外挂使用的函数进行检测
第四种,分屏透视,一个窗口正常游戏,另外一个窗口显示透视信息,主播一般用的
不要说很少一部分,我对陪玩代练主播等了解较多,大部分都是这样.
反外挂思路,同样是针对实现原理,很轻松的应对
只是后2种 属于外部绘制,检测比较困难
下面我们对几种方法一一了解
深入学习矩阵 (总结所有游戏矩阵特点 找到矩阵)
对象的世界坐标列向量
x
y
z
w(w为了兼容4*4矩阵以及可以NDC坐标转化而设计存在的,大家可以暂且不管)
可以通过被一个游戏矩阵(以后我们就叫他游戏矩阵吧) 乘 从而获得剪辑坐标,也可以叫裁剪坐标,都是翻译而来(暂且当成屏幕坐标也没问题,因为他到屏幕坐标转换及其简单)
在学习这个矩阵之前呢我们先来了解
行主序和列主序
行主序就是拿4*4矩阵的行来* 后面的矩阵,我们前面的例子 都是这样
a1 a2 a3 a4 * x = a1 *x+ a2*y+ a3*z + a4*w
b1 b2 b3 b4 y b1 *x+ b2*y+ b3*z + b4*w
c1 c2 c3 c4 z c1 *x+ c2*y+ c3*z + c4*w
d1 d2 d3 d4 w d1 *x+ d2*y+ d3*z + d4*w
而列主序就是 拿4*4 矩阵的列 来* 后面的矩阵,这个前面没有出现过
a1 b1 c1 d1 * x = a1 *x+ a2*y+ a3*z + a4*w
a2 b2 c2 d2 y b1 *x+ b2*y+ b3*z + b4*w
a3 b3 c3 d3 z c1 *x+ c2*y+ c3*z + c4*w
a4 b4 c4 d4 w d1 *x+ d2*y+ d3*z + d4*w
那么我们现在了解下这个游戏矩阵都能对我们干嘛,给他拆分成几个功能
最后他是这几个功能的结合体而已
缩放位移矩阵
如果是行主序矩阵的话就是
(1不是固定的 应该写成Tw )
如果是列主序矩阵的话就是
Sx 0 0 0
0 Sy 0 0
0 0 Sz 0
Tx Ty Tz Tw
乘
x
y
z
w
等于
x*Sx+w*Tx
y*Sy+w*Ty
z*Sz+w*Tz
w*Tw
对矩阵 x y z w 进行了 缩放和位移
所以我们在这里知道 矩阵移动影响的主要是
Tx Ty Tz Tw
结论一: 我们在走路做位移的情况下,行主序最后一列 列主序最后一行 Tx Ty Tz Tw 会改变
当然不一定只有走路的时候会变
原因很简单,这个矩阵是各种操作的结合体如以下例子:只改变Y的情况下
只改变X的情况下
只改变Z的情况下
XYZ混合改变的情况下
旋转矩阵
简单证明下结论
假设旋转45度
用极限验证法,验证结果正确
围绕Z轴 转动
结论二:只转动水平朝向的时候 行主序第三列不变 ,列主序第三行 不变化
列主序如下
围绕X 轴转动
结论三:只转动高低朝向的时候 行主序第一行不变 ,列主序第一列 不变化
列主序如下
围绕Y 轴转动 我们没有这种情况的时候
开倍镜
总结矩阵6条结论
第一结论: 行主序最后一列 列主序最后一行 走路 跳的状态会改变,不代表别的动作不改变
第二结论: 水平转动的情况 行主序第三列不变 列主序的话第三行不变
第三结论: 高低朝向改变的时候 行主序第一行不变 列主序的第一列不变
第四结论: 矩阵第一个值 -1 到1 的, 这个绝对吗 不绝对!
第五结论: 行主序 第一个行第3个元素 是固定的0 列主序 第一列的第三个元素是0 不绝对!
第六结论: 我们开倍镜 第一个值 会* 相应的倍数 不绝对!
通过找到的矩阵可以进一步完善结论
通过以上的规律 CE 搜到如下矩阵地址
矩阵地址
hl.exe+1820100
这个过程文字很啰嗦,只能视频里见了,无非根据以上结论 扫描而已
如果这个都扫不到,说明CE基础薄弱,需要加强练习
世界坐标 ->剪辑坐标->NDC坐标->屏幕坐标
那么我们来了解下坐标的转换过程
通过以上的矩阵
世界坐标 ----> 剪辑坐标
矩阵乘法
a0 a1 a2 a 3
a4 a5 a6 a7
a8 a9 a10 a11
a12 a13 a14 a15
*
x
y
z
w
得到:
剪辑坐标 x = a0*x +a4*y + a8*z + a12*w
剪辑坐标 y = a1*x +a5*y + a9*z + a13*w
剪辑坐标 z = a2*x +a6*y + a10*z + a14*w
剪辑坐标 w = a3*x +a7*y + a11*z + a15*w
世界坐标按照我们之前的矩阵法则转换成了平面的剪辑坐标
但是剪辑坐标是和分辨率没有直接关系的 ,需要我们进一步转换
剪辑坐标坐标系如下 正中心为0,0
剪辑坐标---->NDC坐标
矩阵的设计中w 是可以让剪辑坐标范围到-1和1的 也就成了NDC坐标
所以NDC坐标很好理解,就是 -1到1的平面坐标系 中心点为0,0
NDC .x = 剪辑坐标 x/剪辑坐标 w
NDC.y =剪辑坐标y/剪辑坐标 w
NDC.z =剪辑坐标z/剪辑坐标 w
DNC坐标---->屏幕坐标
有了DNC坐标我们可以很容易转换成屏幕坐标了
当然屏幕坐标不止一种形式
我们现在只以CS为例
后面会有其他类型
DNC.x / 1 = 屏幕坐标差.x / 分辨率_宽 /2
屏幕坐标差.x = (分辨率_宽 /2) * DNC.x
屏幕坐标.x = (分辨率_宽 /2) * DNC.x + 分辨率_宽/2
DNC.y / 1 = 屏幕坐标差.y / 分辨率_高 /2
屏幕坐标差.y = -(分辨率_高 /2)*DNC.y
屏幕坐标.y= -(分辨率_高 /2)*DNC.y + 分辨率_高 /2
矩阵绘制
最终代码如下
再用之前的代码 只把世界坐标转屏幕坐标_非矩阵() 改成世界坐标转屏幕坐标() 就可以实现功能了
其实也是我们之前的代码设计的比较合理
这样 只要替换一句命令,其他指令完全不影响
[C++] 纯文本查看 复制代码
bool 绘制::世界坐标转屏幕坐标(坐标结构_3 游戏坐标, 坐标结构_2& 屏幕坐标)
{
取窗口信息();
memcpy (&m_矩阵, ( PBYTE *)(m_矩阵地址), sizeof (m_矩阵));
坐标结构_4 裁剪坐标;
裁剪坐标.x = 游戏坐标.x * m_矩阵[0] + 游戏坐标.y * m_矩阵[4] + 游戏坐标.z * m_矩阵[8] + m_矩阵[12];
裁剪坐标.y = 游戏坐标.x * m_矩阵[1] + 游戏坐标.y * m_矩阵[5] + 游戏坐标.z * m_矩阵[9] + m_矩阵[13];
裁剪坐标.z = 游戏坐标.x * m_矩阵[2] + 游戏坐标.y * m_矩阵[6] + 游戏坐标.z * m_矩阵[10] + m_矩阵[14];
裁剪坐标.w = 游戏坐标.x * m_矩阵[3] + 游戏坐标.y * m_矩阵[7] + 游戏坐标.z * m_矩阵[11] + m_矩阵[15];
if (裁剪坐标.w < 0.0f)
return false ;
坐标结构_3 NDC;
NDC.x = 裁剪坐标.x / 裁剪坐标.w;
NDC.y = 裁剪坐标.y / 裁剪坐标.w;
NDC.z = 裁剪坐标.z / 裁剪坐标.w;
屏幕坐标.x = (m_分辨率宽 / 2 * NDC.x) + m_分辨率宽 / 2;
屏幕坐标.y = -(m_分辨率高 / 2 * NDC.y) + m_分辨率高 / 2;
return true ;
}
这次不用微调就已经 精确了吧?
所以证明矩阵确实算的比较准确而且帮我们省掉了大量计算的时间
到这里,你的基础算过了
迎接后面真正的学习吧!
单机口袋西游和单机突袭进一步熟悉相关数据,矩阵,以及绘制
口袋西游:
用到的数据很简单
这是一款普通的RPG类游戏,不设计到射击相关的功能,我们只是拿来做画框绘制练习。
人物数据:
[[[单机版基地址]+1C]+28]+3C X
[[[单机版基地址]+1C]+28]+40 Y
[[[单机版基地址]+1C]+28]+44 Z
[[[单机版基地址]+1C]+28]+560 X中心点
[[[单机版基地址]+1C]+28]+564 Y中心店
[[[单机版基地址]+1C]+28]+568 Z中心点
[[[单机版基地址]+1C]+28]+56C X中心点到对顶点的X差
[[[单机版基地址]+1C]+28]+570 Y中心点到对顶点的Y差
[[[单机版基地址]+1C]+28]+574 Z中心点到对顶点的Z差
[[[单机版基地址]+1C]+28]+578 立方体下顶点X坐标
[[[单机版基地址]+1C]+28]+57C 立方体下顶点Y坐标
[[[单机版基地址]+1C]+28]+580 立方体下顶点Z坐标
[[[单机版基地址]+1C]+28]+584 立方体上顶点X坐标
[[[单机版基地址]+1C]+28]+588 立方体上顶点Y坐标
[[[单机版基地址]+1C]+28]+58C 立方体上顶点Z坐标
周围遍历
[[[[[单机版基地址]+1C]+8]+20]+5C] 对象数量
[[[[[[[单机版基地址]+1C]+8]+20]+58]+N*4]+4]+138 周围对象血量(未被攻击过是0)
[[[[[[[单机版基地址]+1C]+8]+20]+1C]+N*4]+4]+3C 周围对象X
[[[[[[[单机版基地址]+1C]+8]+20]+1C]+N*4]+4]+40 周围对象Z
[[[[[[[单机版基地址]+1C]+8]+20]+1C]+N*4]+4]+44 周围对象Y
[[[[[[[单机版基地址]+1C]+8]+20]+1C]+N*4]+4]+ED 死亡标志1为死亡BYTE
[[[[[[[单机版基地址]+1C]+8]+20]+1C]+N*4]+4]+2C4 X中心点
[[[[[[[单机版基地址]+1C]+8]+20]+1C]+N*4]+4]+2C8 Z中心店
[[[[[[[单机版基地址]+1C]+8]+20]+1C]+N*4]+4]+2CC Y中心点
[[[[[[[单机版基地址]+1C]+8]+20]+1C]+N*4]+4]+2D0 X中心点到对顶点的X差
[[[[[[[单机版基地址]+1C]+8]+20]+1C]+N*4]+4]+2D4 Y中心点到对顶点的Y差
[[[[[[[单机版基地址]+1C]+8]+20]+1C]+N*4]+4]+2D8 Z中心点到对顶点的Z差
[[[[[[[单机版基地址]+1C]+8]+20]+1C]+N*4]+4]+2DC 立方体下顶点X坐标
[[[[[[[单机版基地址]+1C]+8]+20]+1C]+N*4]+4]+2E0 立方体下顶点Y坐标
[[[[[[[单机版基地址]+1C]+8]+20]+1C]+N*4]+4]+2E4 立方体下顶点Z坐标
[[[[[[[单机版基地址]+1C]+8]+20]+1C]+N*4]+4]+2E8 立方体上顶点X坐标
[[[[[[[单机版基地址]+1C]+8]+20]+1C]+N*4]+4]+2EC 立方体上顶点Y坐标
[[[[[[[单机版基地址]+1C]+8]+20]+1C]+N*4]+4]+2F0 立方体上顶点Z坐标
突袭:
这是一款类似于CS的单机游戏,在设计上相对于CS更加简单一些,数据也比较简单,适合新手练习。
这款游戏也可以联网,有一些数据需要在联网状态下才能精确的分析出来,在新课程中也做了详细的讲解。
人物数据:
[0x00587C0C]+28 X
[0x00587C0C]+2C Y
[0x00587C0C]+30 Z
[0x00587C0C]+34 水平朝向顺时针递增360
[0x00587C0C]+38 竖直朝向-90到90 从下到上
[0x00587C0C]+EC 角色血量
周围遍历
00587C18 玩家数量
[[00587C10]+n*4]+4 X_head
[[00587C10]+n*4]+8 Y_head
[[00587C10]+n*4]+C Z_head
[[00587C10]+n*4]+28 X
[[00587C10]+n*4]+2C Y
[[00587C10]+n*4]+30 Z
[[00587C10]+n*4]+50 身高
[[00587C10]+n*4]+34 水平朝向顺时针递增360
[[00587C10]+n*4]+38 竖直朝向-90到90 从下到上
[[00587C10]+n*4]+EC 角色血量
在周围遍历中我们发现多了一个头部坐标,这也是其相对于CS设计的更加完善的地方。
游戏不分类型
RPG也好,回合制也好,FPS也好。
其实只是一个操作的区别而已,本质没有任何区别。
FPS的自瞄相当于Rpg的朝向怪物。
FPS的绘制相当于RPG的计算碰撞体 以方便躲避 或则自写寻路等等。
通过已有的数据,我们可以实现以下效果。
看到RPG游戏这样意外吗?
因为游戏很古老,引擎也很古老,所以能够得到资源只允许我们做一个3D的立方体模型,
但是不管如何,RPG和FPS的本质是一样的,没有任何区别,只是我们研究的角度不同,要达到的效果也不同。
矩阵
下面我们来看看这两款游戏的矩阵数据
口袋西游矩阵 [D2E5D0]+E8
突袭矩阵 57AFE0
这都是标准的4*4矩阵,具体的分析方法在上面我们已经提到了,下面我们来看看封装数据的过程。
数据封装
[C++] 纯文本查看 复制代码
DWORD 口袋西游_周围基地址 = 0xD0DF1C;
void 周围对象::刷新周围数据_口袋西游()
{
DWORD Temp = *( DWORD *)(*( DWORD *)(*( DWORD *)(*( DWORD *)口袋西游_周围基地址 + 0x1C)
+ 0x8) + 0x20);
对象数量 = *( DWORD *)(Temp + 0x5C);
DWORD 怪物基地址 = *( DWORD *)(Temp + 0x58);
for ( int i = 0; i < ( int )对象数量; i++)
{
对象列表[i].X_j = *( FLOAT *)(*( DWORD *)(怪物基地址 + 0x4 * i) + 0x2DC);
对象列表[i].Y_j = *( FLOAT *)(*( DWORD *)(怪物基地址 + 0x4 * i) + 0x2E0);
对象列表[i].Z_j = *( FLOAT *)(*( DWORD *)(怪物基地址 + 0x4 * i) + 0x2E4);
对象列表[i].X_H = *( FLOAT *)(*( DWORD *)(怪物基地址 + 0x4 * i) + 0x2E8);
对象列表[i].Y_H = *( FLOAT *)(*( DWORD *)(怪物基地址 + 0x4 * i) + 0x2EC);
对象列表[i].Z_H = *( FLOAT *)(*( DWORD *)(怪物基地址 + 0x4 * i) + 0x2F0);
对象列表[i].阵营 = 1;
对象列表[i].Hp = *( DWORD *)(*( DWORD *)(怪物基地址 + 0x4 * i) + 0x138);
对象列表[i].死亡标志位 = 0;
}
对象数量++;
对象列表[对象数量-1].X_j = *( FLOAT *)(*( DWORD *)(*( DWORD *)(*( DWORD *)0xD0DF1C + 0x1C)
+ 0x28) + 0x578);
对象列表[对象数量-1].Y_j = *( FLOAT *)(*( DWORD *)(*( DWORD *)(*( DWORD *)0xD0DF1C + 0x1C)
+ 0x28) + 0x57C);
对象列表[对象数量-1].Z_j = *( FLOAT *)(*( DWORD *)(*( DWORD *)(*( DWORD *)0xD0DF1C + 0x1C)
+ 0x28) + 0x580);
对象列表[对象数量-1].X_H = *( FLOAT *)(*( DWORD *)(*( DWORD *)(*( DWORD *)0xD0DF1C + 0x1C)
+ 0x28) + 0x584);
对象列表[对象数量-1].Y_H = *( FLOAT *)(*( DWORD *)(*( DWORD *)(*( DWORD *)0xD0DF1C + 0x1C)
+ 0x28) + 0x588);
对象列表[对象数量-1].Z_H = *( FLOAT *)(*( DWORD *)(*( DWORD *)(*( DWORD *)0xD0DF1C + 0x1C)
+ 0x28) + 0x58C);
对象列表[对象数量-1].阵营 = 2;
对象列表[对象数量-1].Hp = *( DWORD *)(*( DWORD *)(*( DWORD *)(*( DWORD *)0xD0DF1C + 0x1C)
+ 0x28) + 0x288);
对象列表[对象数量-1].死亡标志位 = 0;
}
DWORD 突袭_周围基地址 = 0x00587C10;
void 周围对象::刷新周围数据_突袭()
{
对象数量 = *( DWORD *)0x00587C18;
DWORD 怪物基地址 = *( DWORD *)突袭_周围基地址;
for ( int i = 1; i < ( int )对象数量; i++)
{
对象列表[i].X_j = *( FLOAT *)(*( DWORD *)(怪物基地址 + 0x4 * i) + 0x28);
对象列表[i].Y_j = *( FLOAT *)(*( DWORD *)(怪物基地址 + 0x4 * i) + 0x2C);
对象列表[i].Z_j = *( FLOAT *)(*( DWORD *)(怪物基地址 + 0x4 * i) + 0x30);
对象列表[i].X_H = *( FLOAT *)(*( DWORD *)(怪物基地址 + 0x4 * i) + 0x4);
对象列表[i].Y_H = *( FLOAT *)(*( DWORD *)(怪物基地址 + 0x4 * i) + 0x8);
对象列表[i].Z_H = *( FLOAT *)(*( DWORD *)(怪物基地址 + 0x4 * i) + 0xC);
对象列表[i].阵营 = 2;
对象列表[i].Hp = 100;
对象列表[i].死亡标志位 = 0;
}
}
上一页 [1] [2]
| |
|
|