feat: expose aim smooth as 0-100

This commit is contained in:
kedou 2026-08-07 21:50:05 +08:00
parent fcb1f599a4
commit 6fc8d8c16b
4 changed files with 107 additions and 21 deletions

View File

@ -36,6 +36,38 @@ namespace
return a;
}
float Clamp01(float value)
{
if (value < 0.f) return 0.f;
if (value > 1.f) return 1.f;
return value;
}
float FrameScale()
{
float deltaTime = ImGui::GetIO().DeltaTime;
if (!(deltaTime > 0.f) || deltaTime > 0.2f)
deltaTime = 1.f / 60.f;
float scale = deltaTime * 60.f;
if (scale < 0.5f) scale = 0.5f;
if (scale > 2.f) scale = 2.f;
return scale;
}
// Distance-adaptive response:
// far target -> catch up; near target -> settle without a large final snap.
float AimResponse(float error, float smooth, float referenceError)
{
smooth = Clamp01(smooth * 0.01f);
const float nearResponse = 0.06f + (1.f - smooth) * 0.10f;
const float farResponse = 0.28f + (1.f - smooth) * 0.26f;
const float weight = Clamp01(error / (referenceError > 0.001f ? referenceError : 1.f));
const float perFrame = nearResponse + (farResponse - nearResponse) * weight;
return 1.f - powf(1.f - perFrame, FrameScale());
}
// IsKeyDown は Framework/Input/Input.h のグローバル関数を使用
// (GetAsyncKeyState はマウスボタンを検知できない環境があるため、WH_MOUSE_LL フックで捕捉)
@ -80,7 +112,12 @@ void CFramework::AimAll()
// ---- 自瞄 ----
if (!g.g_Aim)
{
m_aimLastTarget = 0;
m_aimResidualX = 0.f;
m_aimResidualY = 0.f;
return;
}
// FOV 円はキーを押していなくても表示
Vector2 Center = Vector2(GameSize.x * 0.5f, GameSize.y * 0.5f);
@ -91,7 +128,12 @@ void CFramework::AimAll()
// ※ GetAsyncKeyState / 低级钩子 在本环境(管理员+RawInput独占)都检测不到按键,
// g_AimForce 是绕过按键检测的替代方案
if (!g.g_AimForce && !IsKeyDown(g.g_AimKey0) && !IsKeyDown(g.g_AimKey1) && !IsKeyDown(g.g_AimKey2))
{
m_aimLastTarget = 0;
m_aimResidualX = 0.f;
m_aimResidualY = 0.f;
return;
}
// カメラ位置 (ローカル頭部を優先、無ければ origin)
local.UpdateBone();
@ -179,13 +221,31 @@ void CFramework::AimAll()
m_diagBestDist = bestDist;
if (bestAddr == 0)
{
m_aimLastTarget = 0;
m_aimResidualX = 0.f;
m_aimResidualY = 0.f;
return;
}
if (bestAddr != m_aimLastTarget)
{
m_aimLastTarget = bestAddr;
m_aimResidualX = 0.f;
m_aimResidualY = 0.f;
}
// 2. 照準適用
if (g.g_AimMethod == 0)
{
m_aimResidualX = 0.f;
m_aimResidualY = 0.f;
AimMemory(bestPoint, localPos);
}
else
{
AimMouse(bestScreen, Center);
}
// 3. ターゲット線
if (g.g_AimDrawTarget)
@ -268,32 +328,54 @@ Vector3 CFramework::GetAimBone(CPlayer* pEntity)
// Mouse モード: 画面中心とターゲットの差分をマウス移動量に変換
void CFramework::AimMouse(const Vector2& target, const Vector2& center)
{
float dx = target.x - center.x;
float dy = target.y - center.y;
const float dx = target.x - center.x;
const float dy = target.y - center.y;
const float dist = sqrtf(dx * dx + dy * dy);
float dist = sqrtf(dx * dx + dy * dy);
if (dist < 1.f) // 既に当たっている
if (dist < 0.5f)
{
m_aimResidualX = 0.f;
m_aimResidualY = 0.f;
return;
}
// smooth: 0=即時追従, 1=ゆっくり追従
float scale = g.g_AimSens * (1.f - g.g_AimSmooth);
float mx = dx * scale;
float my = dy * scale;
const float referenceError = g.g_AimFov > 32.f ? g.g_AimFov * 0.5f : 32.f;
const float response = AimResponse(dist, g.g_AimSmooth, referenceError);
// 1フレームの最大移動量でクランプ (なめらか + 暴走防止)
float maxSpeed = g.g_AimMaxSpeed;
float len = sqrtf(mx * mx + my * my);
float sensitivity = g.g_AimSens;
if (sensitivity < 0.1f) sensitivity = 0.1f;
if (sensitivity > 5.f) sensitivity = 5.f;
// Never allow the smoothing slider to turn a small error into a one-frame snap.
float gain = response * sensitivity;
if (gain > 0.85f) gain = 0.85f;
float mx = dx * gain;
float my = dy * gain;
// MaxSpeed is expressed at 60 FPS; keep the behavior stable across frame rates.
float maxSpeed = g.g_AimMaxSpeed * FrameScale();
if (maxSpeed < 1.f) maxSpeed = 1.f;
const float len = sqrtf(mx * mx + my * my);
if (len > maxSpeed)
{
mx = mx / len * maxSpeed;
my = my / len * maxSpeed;
}
// デッドゾーン (細かいジッター防止)
if (fabsf(mx) < 0.5f && fabsf(my) < 0.5f)
// Preserve fractional movement instead of truncating every frame to LONG.
const float moveX = mx + m_aimResidualX;
const float moveY = my + m_aimResidualY;
const LONG sendX = static_cast<LONG>(lroundf(moveX));
const LONG sendY = static_cast<LONG>(lroundf(moveY));
m_aimResidualX = moveX - static_cast<float>(sendX);
m_aimResidualY = moveY - static_cast<float>(sendY);
if (sendX == 0 && sendY == 0)
return;
SendMouseMove((LONG)mx, (LONG)my);
SendMouseMove(sendX, sendY);
}
// Memory モード: MovementContext._rotation (Vector2 x=yaw y=pitch) へ書く
@ -319,13 +401,12 @@ void CFramework::AimMemory(const Vector3& target, const Vector3& localPos)
yaw = ClampAngle(yaw);
pitch = ClampAngle(pitch);
// 平滑化: 現在値 → 目標値へ補間 (yaw は短い方へ回す)
float t = 1.f - g.g_AimSmooth;
if (t <= 0.f)
return;
// Distance-adaptive exponential smoothing. It is never 0 or 1, so the
// slider cannot produce either a one-frame snap or a permanently lagging aim.
float dyaw = ClampAngle(yaw - rot.x);
float dpitch = ClampAngle(pitch - rot.y);
float angleError = sqrtf(dyaw * dyaw + dpitch * dpitch);
float t = AimResponse(angleError, g.g_AimSmooth, 0.75f);
Vector2 newRot{
ClampAngle(rot.x + dyaw * t),

View File

@ -92,6 +92,11 @@ private:
uintptr_t m_diagBestAddr = 0; // 最近一帧锁定的目标地址
float m_diagBestDist = 0.f;// 最近一帧目标到屏幕中心的距离
// 鼠标自瞄的亚像素累积,避免小误差因 LONG 截断而永远到不了目标。
uintptr_t m_aimLastTarget = 0;
float m_aimResidualX = 0.f;
float m_aimResidualY = 0.f;
void DrawLine(ImVec2 a, ImVec2 b, ImColor color, float width)
{
ImGui::GetBackgroundDrawList()->AddLine(ImVec2((int)a.x, (int)a.y), ImVec2((int)b.x, (int)b.y), color, width);

View File

@ -57,7 +57,7 @@ void CFramework::RenderMenu()
ImGui::Checkbox("启用", &g.g_Aim);
ImGui::Combo("方式", &g.g_AimMethod, AimMethodList, IM_ARRAYSIZE(AimMethodList));
ImGui::CustomSliderFloat("锁定范围", "##AimFov", &g.g_AimFov, 10.f, 500.f);
ImGui::CustomSliderFloat("平滑", "##AimSmooth", &g.g_AimSmooth, 0.f, 1.f);
ImGui::CustomSliderFloat("平滑(0-100)", "##AimSmooth", &g.g_AimSmooth, 0.f, 100.f);
ImGui::CustomSliderFloat("最大距离", "##AimDist", &g.g_AimMaxDist, 50.f, 2000.f);
ImGui::Combo("瞄准部位", &g.g_AimBone, AimBoneList, IM_ARRAYSIZE(AimBoneList));

View File

@ -35,7 +35,7 @@ struct Globals
bool g_Aim = false;
int g_AimMethod = 1; // 0 = Memory(写旋转), 1 = Mouse(SendInput)
float g_AimFov = 150.f; // FOV 半径 [px]
float g_AimSmooth = 0.45f; // 0=瞬移 1=最慢
float g_AimSmooth = 45.f; // 0-100: 自适应平滑,0=快但有限幅,100=慢但不会瞬移
float g_AimSens = 1.0f; // Mouse 模式灵敏度倍率
float g_AimMaxSpeed = 80.f; // Mouse 模式单帧最大移动 [px]
float g_AimMaxDist = 500.f; // 最大锁定距离 [m]