Common类库新增摇杆控件。

This commit is contained in:
QI Mingxuan
2026-04-22 20:48:39 +08:00
parent 1279885924
commit be03bdf34e
5 changed files with 976 additions and 0 deletions
+87
View File
@@ -0,0 +1,87 @@
using System;
namespace XP.Common.Controls
{
/// <summary>
/// 虚拟摇杆核心计算逻辑(纯函数,无副作用)| Virtual joystick core calculation logic (pure functions, no side effects)
/// </summary>
public static class JoystickCalculator
{
/// <summary>
/// 将位移限制在圆形区域内 | Clamp displacement within circular radius
/// </summary>
/// <param name="dx">X 轴偏移量 | X axis displacement</param>
/// <param name="dy">Y 轴偏移量 | Y axis displacement</param>
/// <param name="radius">最大半径(必须大于 0| Maximum radius (must be greater than 0)</param>
/// <returns>限制后的 (clampedDx, clampedDy) 元组 | Clamped (clampedDx, clampedDy) tuple</returns>
public static (double clampedDx, double clampedDy) ClampToRadius(double dx, double dy, double radius)
{
if (radius <= 0)
throw new ArgumentOutOfRangeException(nameof(radius), "半径必须大于 0 | Radius must be greater than 0");
var distance = Math.Sqrt(dx * dx + dy * dy);
// 如果距离在半径内,直接返回原值 | If within radius, return original values
if (distance <= radius)
return (dx, dy);
// 归一化到半径上 | Normalize to radius boundary
var scale = radius / distance;
return (dx * scale, dy * scale);
}
/// <summary>
/// 计算归一化输出,包含死区映射 | Calculate normalized output with dead zone mapping
/// </summary>
/// <param name="dx">X 轴偏移量 | X axis displacement</param>
/// <param name="dy">Y 轴偏移量 | Y axis displacement</param>
/// <param name="radius">最大半径(必须大于 0| Maximum radius (must be greater than 0)</param>
/// <param name="deadZone">死区比例,范围 [0.0, 1.0) | Dead zone ratio, range [0.0, 1.0)</param>
/// <param name="mode">摇杆模式 | Joystick mode</param>
/// <returns>归一化输出 (outputX, outputY) 元组,范围 [-1.0, 1.0] | Normalized output tuple, range [-1.0, 1.0]</returns>
public static (double outputX, double outputY) CalculateOutput(
double dx, double dy, double radius, double deadZone, JoystickMode mode)
{
if (radius <= 0)
throw new ArgumentOutOfRangeException(nameof(radius), "半径必须大于 0 | Radius must be greater than 0");
if (deadZone < 0.0 || deadZone >= 1.0)
throw new ArgumentOutOfRangeException(nameof(deadZone), "死区比例必须在 [0.0, 1.0) 范围内 | Dead zone must be in range [0.0, 1.0)");
// 步骤 1-2:将位移限制在半径内 | Step 1-2: Clamp displacement within radius
var (clampedDx, clampedDy) = ClampToRadius(dx, dy, radius);
// 步骤 3:计算比例值 | Step 3: Calculate ratio
var ratioX = clampedDx / radius;
var ratioY = clampedDy / radius;
// 步骤 4:对每个轴分别应用死区 | Step 4: Apply dead zone to each axis independently
var outputX = ApplyDeadZone(ratioX, deadZone);
var outputY = ApplyDeadZone(ratioY, deadZone);
// 步骤 5SingleAxisY 模式下强制 OutputX = 0 | Step 5: Force OutputX = 0 in SingleAxisY mode
if (mode == JoystickMode.SingleAxisY)
outputX = 0.0;
return (outputX, outputY);
}
/// <summary>
/// 对单轴比例值应用死区映射 | Apply dead zone mapping to a single axis ratio
/// </summary>
/// <param name="ratio">轴比例值,范围 [-1.0, 1.0] | Axis ratio, range [-1.0, 1.0]</param>
/// <param name="deadZone">死区比例,范围 [0.0, 1.0) | Dead zone ratio, range [0.0, 1.0)</param>
/// <returns>死区映射后的输出值 | Output value after dead zone mapping</returns>
internal static double ApplyDeadZone(double ratio, double deadZone)
{
var absRatio = Math.Abs(ratio);
// 在死区内,输出为 0 | Within dead zone, output is 0
if (absRatio < deadZone)
return 0.0;
// 死区外线性映射:sign(ratio) × (absRatio - D) / (1 - D) | Linear mapping outside dead zone
return Math.Sign(ratio) * (absRatio - deadZone) / (1.0 - deadZone);
}
}
}