EthanH3514 / AL_Yolo

👺 基于Yolov5的Apex Legend游戏 AI 辅瞄外挂
Apache License 2.0
274 stars 46 forks source link

一些疑问和一点点示例代码( #3

Open fatinghenji opened 1 year ago

fatinghenji commented 1 year ago

基于内存的外挂自瞄原理是可以拿到三维坐标,直接修改方向角来瞄准敌人,而基于计算机视觉的外挂只能拿到目标在屏幕上的投影,这是一个二维坐标,要解算出移动的向量很依赖游戏底层的参数(视场角等),目前还没想明白怎么一帧锁敌,也许将来会去实现。

这段话我没太理解。既然已经是一个二维平面上的两点间的直线运动[比如从A(1,1)到B(1,2)仅需要直线移动],为什么要计算三维空间中的滑动角度?

加入PID平滑控制鼠标

class PID:
    def __init__(self, Kp, Ki, Kd):
        self.Kp = Kp
        self.Ki = Ki
        self.Kd = Kd
        self.P = 0
        self.I = 0
        self.D = 0
        self.last_error = 0
        self.last_time = time.time()
        self.first_time = None
        self.first_pos = None
        self.last_pos = None
        self.Kc = None
        self.Tu = None
        self.auto_tune = False

    def pid_control(self, error, dt):
        self.P = error
        self.I += error * dt
        self.D = (error - self.last_error) / dt
        self.last_error = error
        control = self.Kp * self.P + self.Ki * self.I + self.Kd * self.D
        return control

    def auto_tune_pid(self):
        if not self.auto_tune:
            return
        if self.first_time is None:
            self.first_time = time.time()
            self.first_pos = get_current_pos()
            return
        self.last_pos = get_current_pos()
        if self.Kc is None:
            self._find_critical_gain()
        else:
            self._calculate_pid_parameters()
        # 更新鼠标位置
        dt = time.time() - self.last_time
        error = self.first_pos - self.last_pos
        control = self.pid_control(error, dt)
        new_pos = get_current_pos() + control
        set_mouse_pos(new_pos)
        self.last_time = time.time()

    def _find_critical_gain(self):
        Kp = 0.1
        while True:
            self.Kp = Kp
            error = self.first_pos - get_current_pos()
            if self.last_error * error < 0:
                self.Kc = self.Kp
                self.Tu = time.time() - self.first_time
                break
            self.last_error = error
            time.sleep(0.01)
            Kp += 0.1

    def _calculate_pid_parameters(self):
        self.Kp = 0.6 * self.Kc
        self.Ki = 1.2 * self.Kp / self.Tu
        self.Kd = 0.075 * self.Kp * self.Tu
EthanH3514 commented 1 year ago

感谢提问:)

第一个问题是因为当我们在FPS游戏中移动鼠标时,视角也会跟着转动,敌人也会向我们移动的反方向运动,这会产生一个负反馈,导致与正常移动鼠标不同的结果。

示意图

就拿README.md中那个示意图来说,这个示意图是一个从屏幕上方向下的俯视图,点A是敌人真实位置,LR是屏幕,O是准星,A'是敌人A在屏幕上的投影,也就是我们在屏幕上看到的敌人位置。在FPS游戏中鼠标的移动实际距离大小与视角转动的角度成正比,在图中就是正比于 $\theta$ ;而在一般场景中鼠标的移动实际距离与鼠标指针的移动距离成正比,在图中就是正比于 $OA'$ ,这之间有一个 $\tan$ 函数的关系,而比例系数是与FOV和鼠标灵敏度有关的,但是真实的FOV(视场角)与游戏内的FOV并不相同,游戏内的FOV只是一个示意值,它与真实的FOV之间的映射关系我并不清楚,虽然也可以通过实验的方式得出一个拟合式,但感觉方法过于丑陋就暂时放弃了

用二维的距离直线移动是可以在几步之内将鼠标准星收敛到目标身上的(因为整个程序只检测屏幕中心640*640像素的区域,这个区域比较小,所以 $\theta$ 也比较小,在这个范围内 $tan(\theta) $ 函数可以近似为 $\theta$ ),也可以用这样的方式实现锁敌,缺陷就是不能一步到位,但是实现了准星吸附的效果。

不知道这样解释是和否能解释清楚

感谢你提供的关于PID的代码,我会在之后的更新中实现它,如果你有兴趣可以尝试提交PR😉

fatinghenji commented 1 year ago

关于FPS游戏FOV、DPI、手感一致的一切,这篇文章介绍的十分清晰。我觉得我们两个的实现思路是不同的。 你希望达到的是:

360度距离感知的是[目标在3D空间里的真实方位],尽可能忽略FOV变化所带来的影响。

你说的:

也可以用这样的方式实现锁敌,缺陷就是不能一步到位,但是实现了准星吸附的效果。

对应的应该是:

对照标准:开启一倍镜,A敌人恰好在屏幕左边缘,鼠标移动X cm后击杀;开启二倍镜,视野变窄,B敌人也在屏幕左边缘,鼠标移动X cm后击杀......缺点:肌肉记忆。因为不同FOV的畸变差异,一次可能拉不到位,依赖二次修正。

那么能否使用里面提到的网站mouse sensitivity来解决呢?

EthanH3514 commented 1 year ago

感谢分享,这些资源我在写这个项目初期都翻阅过,我之前的表达有点简陋,没有表达清楚,我先简单回答你的问题,然后重新表述一遍我之前的回答,然后再回答你的疑问。


你希望达到的是:

360度距离感知的是[目标在3D空间里的真实方位],尽可能忽略FOV变化所带来的影响。

这一句话我没太理解,我是想找到一个鼠标移动函数,这个移动函数的移动向量可以实现一次移动就把准星放在敌人的身上,但是这个移动向量的计算需要涉及到真实的FOV,所以需要找和FOV之间的关系

对应的应该是:

对照标准:开启一倍镜,A敌人恰好在屏幕左边缘,鼠标移动X cm后击杀;开启二倍镜,视野变窄,B敌人也在屏幕左边缘,鼠标移动X cm后击杀......缺点:肌肉记忆。因为不同FOV的畸变差异,一次可能拉不到位,依赖二次修正。

这是我如果实现了依赖FOV的一帧拉枪之后会碰到的问题,但是我目前的处理方法是使用最简单的移动函数 $\vec{arc}=(x_1-x_0,y_1-y_0)\cdot \frac 1{MOUSESENSIVITY}$ ,这个函数并不是很需要考虑FOV的变化,更多的是考虑鼠标灵敏度的变化。

那么能否使用里面提到的网站mouse sensitivity来解决呢?

这个网站只能解决不同游戏间的FOV和鼠标灵敏度对应问题,对于一个游戏内的应该是没什么帮助的


一帧锁敌

首先是一帧锁敌,我想实现的是一次定位,就是能够一次移动鼠标将准星瞄准在敌人的身上,而不是在几次瞄准内逐步逼近敌人最后收敛到敌人的身上

先定义一下 $fov$ 表示游戏设置界面中的FOV,而 $FOV$ 表示真实的视场角,下面都沿用这个定义

在游戏中的 $fov$ 并不是真实的 $FOV$ ,比如关于FPS游戏FOV、DPI、手感一致的一切 这篇文章中有提到:

需要注意的是,游戏设置里的[视野范围值]并不代表真正的”角度”,如APEX 106代表120度水平视野;cai'hong'l 89代表垂直89度,换算为水平120度 作者:灰機Harukidayo https://www.bilibili.com/read/cv15218452/ 出处:bilibili

而我并没有查到关于Apex中设置里的 $fov$ 与真实的视场角( $FOV$ )之间的映射关系,而且可能还有水平FOV和竖直FOV两种FOV,所以对于正比例的系数也就不是很好计算了

设准星的坐标为 $(x_0,y_0)$ ,敌人位置在屏幕上的投影为 $(x_1,y_1)$ ,那么最直接的想法就是把鼠标移动的向量设置为 我曾经设想的鼠标移动方向向量为 $\vec{arc} = (x_1-x_0,y_1-y_0)\cdot \frac 1{MOUSE_SENSIVITY}$ ,但是这并不能将准星直接锁定到敌人的身上,因为当移动鼠标的时候,由于实际上移动的是人物的视角,所以随着视角的转动,敌人会和准星“双向奔赴”,实际需要移动的距离应该是小于 $|\vec{arc}|$ 的

而这个移动的向量需要考虑上这一点,所以我将这个问题放到三维中去考虑了。还是用那个示意图,因为鼠标移动实际上是在转动人物的视角,而当鼠标匀速移动时视角也是匀速转动的,可以认为视角变化的角度是正比于鼠标移动的距离的。

可以认为水平方向和竖直方向的移动是独立的,因为当水平移动鼠标的时候,敌人的投影对应的纵坐标应该是不变的,竖直移动鼠标也是这样。所以我将这个问题分开考虑了,分成了水平的移动和竖直的移动。

设 $|OC|=d$ ,对于一个方向上的移动,解三角形有 $\theta = \arctan \frac{|OA'|}{|OC|}=\arctan \frac{x_1-x_0}d$ 。现在需要解算 $d$ ,解三角形可以知道 $d=|OR|\cdot \cot \frac{FOV}2=\frac{WIDTH}2\cdot \frac{FOV}2$ ,这里 $WIDTH$ 是指横向的屏幕分辨率,发现 $d$ 和 $FOV$ 之间是有个关系的,这个关系与屏幕分辨率有关。

在游戏中经过实验发现 $fov$ 变化时水平和竖直方向上的可视范围都会变化,有如下两个猜测:

我个人倾向于第二种猜测,毕竟这是简洁的,并且对于不同的屏幕比例都可以适应

其实到这里已经可以实验验证了,但是后续是因为鼠标驱动的dll文件是从网上扒的(自己不会写,github上开源的没有微软驱动证书,过不了Apex检测),我并不是很懂里面的鼠标移动函数的原理。我在游戏中实验的时候会发现一些奇怪的问题,比如一次向右移动3000个像素甚至不如向右移动5次200像素移动的远(排除了移动太远而导致转过整数圈的情况),所以实验就中止了 。

如果这些都解决了,就会碰到你所说的那个问题:

对照标准:开启一倍镜,A敌人恰好在屏幕左边缘,鼠标移动X cm后击杀;开启二倍镜,视野变窄,B敌人也在屏幕左边缘,鼠标移动X cm后击杀......缺点:肌肉记忆。因为不同FOV的畸变差异,一次可能拉不到位,依赖二次修正。

这就需要对于每个倍镜都去验证对应的FOV和鼠标灵敏度,相当于使用不同的倍镜下,需要使用不同的鼠标移动向量

后来用 $\vec{arc}=(x_1-x_0,y_1-y_0)\cdot \frac 1{MOUSESENSIVITY}$ 这个移动向量试了一下,发现效果比想象中的好很多,虽然不能一次定位到敌人身上,但是由于代码的主循环是很快的,一秒内会移动很多次鼠标,锁敌也是很迅速的,不会很受不能一帧锁敌这个问题的影响。同时因为并不会一下子锁死而实现了类似“准星吸附”一样的效果,而且也不用考虑不同倍镜的FOV和鼠标灵敏度变化问题了。


对于这个网站mouse sensivity,它更像是一个跨游戏的 $fov$ 对应转换计算器,比如要从 CSGO 转到 Apex,而这两个游戏的相同设置下的 $fov$ 可能对应的真实视场角( $FOV$ )并不一样,这就会导致转了游戏之后手感的差异,而这个网站就是去计算两个游戏间 $fov$ 和鼠标灵敏度的对应关系,在我的观察下对于同一个游戏并没有什么帮助。我尝试过通过这个网站导出 Apex的 $fov$ 与FOV的对应关系,但是没成功


不知道能否回答你的疑问

fatinghenji commented 1 year ago

非常感谢。理解了。 编辑,如果真的做到一帧拉枪,应该需要相当不错的显卡,即使以90帧来运行游戏,1帧的时间约为1/90≈11ms,本项目采用的截图方式按readme中所述已经有5ms。留给模型的时间仅剩6ms,应当需要相当好的显卡才能做到这么短的推理时间吧。

EthanH3514 commented 1 year ago

非常感谢。理解了。 编辑,如果真的做到一帧拉枪,应该需要相当不错的显卡,即使以90帧来运行游戏,1帧的时间约为1/90≈11ms,本项目采用的截图方式按readme中所述已经有5ms。留给模型的时间仅剩6ms,应当需要相当好的显卡才能做到这么短的推理时间吧。

目前并不能实现真正的一帧拉枪(游戏的一帧)是因为在原理上的不足而不是硬件上的不足,就算硬件非常的强大仍然不能实现真正的一帧拉枪。目前的方案是能够在几次推理内锁定敌人,而推理的帧率的上限就是游戏的帧率,所以就算能够达到代码一次循环时间小于游戏一帧的刷新时间,在作出鼠标移动命令之后仍然需要等待下一帧的刷新来看到鼠标移动的反馈,所以仍然需要几帧来实现锁敌。

这是因为目前的鼠标移动函数并不能一次移动就将准星对准敌人,需要几次来逼近。目前的方案能实现的是能够在比较短的时间内锁定目标,而这个时间并没有短到人感受不到,但是可以感受到准星的比较强的吸附感(可能跟手柄的辅助瞄准一样)。要实现真正的一帧拉枪,就只能在原理上优化。


如果不在原理上优化,那么能够争取的就是感受上的一帧拉枪(可以称为伪一帧拉枪),如果能通过优化模型和并行加速来达到锁敌时间小于人的反应时间,那么也可以认为是一帧拉枪,当然这样的情况下屏幕刷新率也要比较高。

EthanH3514 commented 1 year ago

非常感谢。理解了。 编辑,如果真的做到一帧拉枪,应该需要相当不错的显卡,即使以90帧来运行游戏,1帧的时间约为1/90≈11ms,本项目采用的截图方式按readme中所述已经有5ms。留给模型的时间仅剩6ms,应当需要相当好的显卡才能做到这么短的推理时间吧。

其实6ms的推理并不是很难达到的,目前的时间主要消耗在主要功能没有并行上(每次推理的开始都需要等待截图)

我的显卡是RTX 3050Laptop,在我的显卡上单独跑代码的话推理时间稳定在10ms,我用的是Yolov5s模型,而Yolov5n的模型计算量是v5s的四分之一,如果换成v5n的话理论上推理会快四倍,推理时间也许可以挺进3ms,换个好点的显卡1ms也是可能的。

如果并行和模型都优化了剩下的优化空间就是python的龟速了,那时候可能会尝试用C++重写

fatinghenji commented 1 year ago

突然意识到一个逻辑问题。

所以随着视角的转动,敌人会和准星“双向奔赴”。

可是“双向奔赴”发生在下一帧,而不是已经推理完毕的本帧。而做到本帧的一帧拉枪是可以的。如果要解决你提到的问题,是否需要引入预测算法呢?如果需要引入,那么应当预测未来多少帧呢?

EthanH3514 commented 1 year ago

突然意识到一个逻辑问题。

所以随着视角的转动,敌人会和准星“双向奔赴”。

可是“双向奔赴”发生在下一帧,而不是已经推理完毕的本帧。而做到本帧的一帧拉枪是可以的。如果要解决你提到的问题,是否需要引入预测算法呢?如果需要引入,那么应当预测未来多少帧呢?

本帧的一帧拉枪是很难实现的,因为之前的回答,而设计预测算法其实就是去解决一帧拉枪的问题,要么就是找到了一帧拉枪的公式,要么就是通过大量数据训练或者实验找到一个拟合公式,而前者需要有游戏的 $fov$ 与FOV的映射关系,后者要做的话过于复杂,我是难以实现的

fatinghenji commented 1 year ago

image 似乎网站有计算方法。示例 好像是付费才能使用的功能,不清楚为啥我发的这个链接可以直接看。注意:只需要调整箭头所指的部分即可,调整转换到的部分会蹦出来付费提示。 有一个更好的计算?Apex Legends Calculator,网址里面的note部分很有意思:

Notes Apex Legends' FOV is 4:3, but the in-game value is not accurate. The actual rendered 4:3 FOV is cl_fovScale * 70, however the conversion from the ingame slider to cl_fovScale is a mess. If you set the FOV to 110, it will become 1.55 cl_fovScale, resulting in 108.5° instead of 110°. Apex Legends' ADS sensitivity scales by the amount zoomed in, maintaining a 1:1 tracking speed across all FOVs. However the calculation uses the rounded hipfire FOV value which makes the scaling slightly inaccurate. The weapon optics actually zoom in the amount they say they do, but only at 70 FOV, cl_fovScale 1. The zoom will be higher than what it says it is at higher FOVs, with a 10x optic zooming in ~12.77x at 110 FOV. For best results, set the fov using the config file since you will know 100% what your value is. Set your ADS sensitivity to the corrected multipliers for the correct conversion. Alternatively you can match the rotation distance, but it will get increasingly more sensitive the more you zoom in. ADS 1x is for SMG, SG, and Pistol ironsights (except RE-45). ADS 1x is identical to Optic 1x. ADS 2x is for AR, LMG, and Sniper ironsights.

更详细的说明:Apex Legends Calculator blog

EthanH3514 commented 1 year ago

image 似乎网站有计算方法。示例 好像是付费才能使用的功能,不清楚为啥我发的这个链接可以直接看。注意:只需要调整箭头所指的部分即可,调整转换到的部分会蹦出来付费提示。 有一个更好的计算?Apex Legends Calculator,网址里面的note部分很有意思:

Notes Apex Legends' FOV is 4:3, but the in-game value is not accurate. The actual rendered 4:3 FOV is cl_fovScale * 70, however the conversion from the ingame slider to cl_fovScale is a mess. If you set the FOV to 110, it will become 1.55 cl_fovScale, resulting in 108.5° instead of 110°. Apex Legends' ADS sensitivity scales by the amount zoomed in, maintaining a 1:1 tracking speed across all FOVs. However the calculation uses the rounded hipfire FOV value which makes the scaling slightly inaccurate. The weapon optics actually zoom in the amount they say they do, but only at 70 FOV, cl_fovScale 1. The zoom will be higher than what it says it is at higher FOVs, with a 10x optic zooming in ~12.77x at 110 FOV. For best results, set the fov using the config file since you will know 100% what your value is. Set your ADS sensitivity to the corrected multipliers for the correct conversion. Alternatively you can match the rotation distance, but it will get increasingly more sensitive the more you zoom in. ADS 1x is for SMG, SG, and Pistol ironsights (except RE-45). ADS 1x is identical to Optic 1x. ADS 2x is for AR, LMG, and Sniper ironsights.

更详细的说明:Apex Legends Calculator blog

非常感谢你的分享!我之后会在更新中试图实现之前的一帧拉枪想法

CXUtk commented 10 months ago

一帧拉枪如果知道相机参数(FOV)等,应该可以直接用迭代方法求出鼠标移动距离,这个问题不一定有解析解,但是近似总是可以做到很精确的

EthanH3514 commented 10 months ago

一帧拉枪如果知道相机参数(FOV)等,应该可以直接用迭代方法求出鼠标移动距离,这个问题不一定有解析解,但是近似总是可以做到很精确的

因为不会搞(悲),最近也一直没时间去学习qaq