一句话总结:自瞄的核心不是读写内存,是一点三角函数——用自己与目标的两套坐标算出方向向量,再换算成游戏视角的偏航角 yaw 和俯仰角 pitch,本篇把这个最底层的函数写出来并验证,全程在虚构靶场《星陨 Online》上进行。
绘制系列四篇收尾时留了个口子:框架能看见人了,但「看得见」和「打得中」之间还差一个自动转向。这个进阶系列就补这块,依然是四篇:角度数学(本篇)→ 目标选择与平滑 → 写视角与骨骼瞄准 → 速度预判与边界讨论。
设定沿用前作:靶场是虚构的《星陨 Online》(StarFall.exe),所有偏移继续编,练习请用 Pwn Adventure 或自己的程序。绘制系列的代码全部可以直接复用,没看过的话建议先看(一)透明窗口和(二)读内存。
先约定视角的坐标系
不同游戏对视角角的定义五花八门,动手前必须把靶场的约定弄清。星陨靶场采用最常见的 FPS 约定:
- yaw(偏航角):绕竖直轴左右转,范围 -180° 到 +180°,0° 朝向 +X 方向
- pitch(俯仰角):抬头低头,范围 -89° 到 +89°,抬头为正
- 视角数据存放在引擎模块:
engine.dll + 0x6ABC30(编的),两个 float,顺序 pitch、yaw
这个约定跟多数 Source 系引擎一致,学一遍到处能用。
数学:两步三角函数
设自己的眼睛位置是 eye(本地玩家坐标 z 加视高 64),目标胸口是 target(脚底 z 加 48)。方向向量:
Vec3 delta = target - eye; // dx, dy, dz
yaw 是方向向量在水平面上的投影角。画个俯视图,X 朝右 Y 朝上,atan2 天生就是干这个的:
float CalcYaw(const Vec3& delta) {
return atan2f(delta.y, delta.x) * 57.2957795f; // 180/π
}
pitch 是垂直面上的角,邻边是水平距离(用勾股),对边是高度差:
float CalcPitch(const Vec3& delta) {
float horiz = sqrtf(delta.x * delta.x + delta.y * delta.y);
return atan2f(delta.z, horiz) * 57.2957795f;
}
注意靶场约定「抬头为正」,所以 pitch 直接取正号即可;有些引擎是「低头为正」,那时要加个负号。别背结论,用自己的靶场打一发看视角往哪转,比查资料快。
图 1:yaw 在俯视图里算,pitch 在侧视图里算,各是一次 atan2
归一化:处理跨 ±180° 的坑
算出来的 yaw 是「绝对角」,但当前视角可能是 170°,目标在 -170°。直接拿 340° 去转,视角会绕远路还可能触发角速度检测。正确的做法是求「最短角差」,把差值归一化到 -180° ~ +180°:
float NormalizeAngle(float a) {
while (a > 180.0f) a -= 360.0f;
while (a < -180.0f) a += 360.0f;
return a;
}
// 当前视角 curYaw,目标角 targetYaw → 需要转的量
float delta = NormalizeAngle(targetYaw - curYaw);
这个函数短小到不像话,但它是整个自瞄里最容易写错的地方。检验方法很土:把当前角设成 179、目标角设成 -179,正确输出是 +2(右转 2 度),写错的版本会输出 -358。写完先跑这组用例再往下走。
组装:CalcAngle 完整版
struct Angles { float pitch, yaw; };
bool CalcAngle(const Vec3& eye, const Vec3& target, Angles& out) {
Vec3 delta = { target.x - eye.x,
target.y - eye.y,
target.z - eye.z };
float horiz = sqrtf(delta.x * delta.x + delta.y * delta.y);
if (horiz < 1.0f) return false; // 目标在头顶正上方,水平距离为零
out.yaw = atan2f(delta.y, delta.x) * 57.2957795f;
out.pitch = atan2f(delta.z, horiz) * 57.2957795f;
out.yaw = NormalizeAngle(out.yaw);
out.pitch = fmaxf(-89.0f, fminf(89.0f, out.pitch));
return true;
}
在靶场里放两个静止靶位验证:自己站在 (100, 100, 64),目标放 (500, 100, 112)——正右方偏上,算出来 yaw 应为 0、pitch 应约为 +4.4°。对上了,数学层就稳了。
三个值得提前知道的东西
每帧重算,不要缓存角度。视角是连续变化的,上一帧算的角度这一帧就是错的。缓存谁都可以,唯独别缓存角度。
眼睛高度别拍脑袋。视高每个游戏不一样,靶场是 64,错了整条弹道整体偏高或偏低。确定方法很朴素:站平地读一次 z,站箱子上再读一次,差值就是视高修正。
atan2 的参数顺序。是 atan2(y, x) 不是 atan2(x, y),这个错不会报编译警告,只会让你对着 45 度偏差调一下午。上面代码里我特意写全了注释。
下一篇预告
角度会算了,但「打谁」还没定——场上 32 个人总不能全瞄。下一篇写目标筛选:视场角(FOV)内才参与、按角度和血量加权排序,再加一层平滑插值,让转向不一步到位。这些都是「像不像人」的关键,也是这个系列技术含量最高的部分。

发表评论
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。