【问题标题】:C# - Joystick sensitivity formulaC# - 操纵杆灵敏度公式
【发布时间】:2017-04-05 19:53:08
【问题描述】:

如何计算摇杆灵敏度,同时考虑死区和摇杆的圆形特性?

我正在研究一个代表游戏手柄棒的类。我在数学上遇到了麻烦,特别是在敏感性部分。灵敏度应该使操纵杆与中心的距离非线性。我在 X-Box 触发器上应用了灵敏度没有问题,但是因为操纵杆有两个轴(X 和 Y),所以我在涉及的数学运算上遇到了麻烦。

我想对摇杆应用圆形灵敏度,但我真的不知道该怎么做,特别是考虑到轴上的其他计算(如死区、距中心的距离等)。我是如何做到这一点的?


有关问题的其他详细信息

现在,我已经有我的临时修复,但效果不太好。当操纵杆方向为水平或垂直时,它似乎正在工作,但是当我将它移动到对角线方向时,它似乎被窃听了。我的 Joystick 类有一个 Distance 属性,它检索棒到中心的距离(从 0 到 1 的值)。我的Distance 属性运行良好,但是当我应用灵敏度时,如果我四处移动我的操纵杆,则检索到的距离在对角线方向上小于 1,无论方向如何,它都应该是 1。

下面,我包含了我的Joystick 类的简化版本,其中我删除了大部分不相关的代码。计算得到的轴的 X 和 Y 位置由ComputedXComputedY 属性检索。考虑到所有修改器(死区、饱和度、灵敏度等),每个属性都应包括其轴最终位置(从 -1 到 1)。

public class Joystick
{

    // Properties

    // Physical axis positions
    public double X { get; set;}
    public double Y { get; set; }
    // Virtual axis positions, with all modifiers applied (like deadzone, sensitivity, etc.)
    public double ComputedX { get => ComputeX(); }
    public double ComputedY {get => ComputeY(); }
    // Joystick modifiers, which influence the computed axis positions 
    public double DeadZone { get; set; }
    public double Saturation { get; set; }
    public double Sensitivity { get; set; }
    public double Range { get; set; }
    public bool InvertX { get; set; }
    public bool InvertY { get; set; }
    // Other properties
    public double Distance
    {
        get => CoerceValue(Math.Sqrt((ComputedX * ComputedX) + (ComputedY * ComputedY)), 0d, 1d);
    }
    public double Direction { get => ComputeDirection(); }


    // Methods

    private static double CoerceValue(double value, double minValue, double maxValue)
    {
        return (value < minValue) ? minValue : ((value > maxValue) ? maxValue : value);
    }


    protected virtual double ComputeX()
    {
        double value = X;
        value = CalculateDeadZoneAndSaturation(value, DeadZone, Saturation);
        value = CalculateSensitivity(value, Sensitivity);
        value = CalculateRange(value, Range);
        if (InvertX) value = -value;
        return CoerceValue(value, -1d, 1d);
    }


    protected virtual double ComputeY()
    {
        double value = Y;
        value = CalculateDeadZoneAndSaturation(value, DeadZone, Saturation);
        value = CalculateSensitivity(value, Sensitivity);
        value = CalculateRange(value, Range);
        if (InvertY) value = -value;
        return CoerceValue(value, -1d, 1d);
    }


    /// <sumary>Gets the joystick's direction (from 0 to 1).</summary>
    private double ComputeDirection()
    {
        double x = ComputedX;
        double y = ComputedY;
        if (x != 0d && y != 0d)
        {
            double angle = Math.Atan2(x, y) / (Math.PI * 2d);
            if (angle < 0d) angle += 1d;
            return CoerceValue(angle, 0d, 1d);
        }
        return 0d;
    }


    private double CalculateDeadZoneAndSaturation(double value, double deadZone, double saturation)
    {
        deadZone = CoerceValue(deadZone, 0.0d, 1.0d);
        saturation = CoerceValue(saturation, 0.0d, 1.0d);

        if ((deadZone > 0) | (saturation < 1))
        {
            double distance = CoerceValue(Math.Sqrt((X * X) + (Y * Y)), 0.0d, 1.0d);
            double directionalDeadZone = Math.Abs(deadZone * (value / distance));
            double directionalSaturation = 1 - Math.Abs((1 - saturation) * (value / distance));

            double edgeSpace = (1 - directionalSaturation) + directionalDeadZone;
            double multiplier = 1 / (1 - edgeSpace);
            if (multiplier != 0)
            {
                if (value > 0)
                {
                    value = (value - directionalDeadZone) * multiplier;
                    value = CoerceValue(value, 0, 1);
                }
                else
                {
                    value = -((Math.Abs(value) - directionalDeadZone) * multiplier);
                    value = CoerceValue(value, -1, 0);
                }
            }
            else
            {
                if (value > 0)
                    value = CoerceValue(value, directionalDeadZone, directionalSaturation);
                else
                    value = CoerceValue(value, -directionalSaturation, -directionalDeadZone);
            }
            value = CoerceValue(value, -1, 1);
        }

        return value;
    }


    private double CalculateSensitivity(double value, double sensitivity)
    {
        value = CoerceValue(value, -1d, 1d);

        if (sensitivity != 0)
        {
            double axisLevel = value;
            axisLevel = axisLevel + ((axisLevel - Math.Sin(axisLevel * (Math.PI / 2))) * (sensitivity * 2));
            if ((value < 0) & (axisLevel > 0))
                axisLevel = 0;
            if ((value > 0) & (axisLevel < 0))
                axisLevel = 0;
            value = CoerceValue(axisLevel, -1d, 1d);
        }

        return value;
    }


    private double CalculateRange(double value, double range)
    {
        value = CoerceValue(value, -1.0d, 1.0d);
        range = CoerceValue(range, 0.0d, 1.0d);
        if (range < 1)
        {
            double distance = CoerceValue(Math.Sqrt((X * X) + (Y * Y)), 0d, 1d);
            double directionalRange = 1 - Math.Abs((1 - range) * (value / distance));
            value *= CoerceValue(directionalRange, 0d, 1d);
        }
        return value;
    }

}

我试图让这个问题尽可能简短,但如果不描述一些细节,我很难解释这个具体问题。我知道我应该保持简短,但我想至少再写几句话:

感谢您抽出宝贵时间阅读所有内容!

【问题讨论】:

    标签: c# .net math joystick


    【解决方案1】:

    在网上搜索了一下几何数学后,我终于找到了解决问题的方法。我数学真的很差,但现在我知道它实际上很简单。

    我应该将它们应用于操纵杆半径,而不是单独为每个轴应用死区和灵敏度。因此,为此,我只需要将操纵杆的笛卡尔坐标(X 和 Y)转换为极坐标(半径和角度)。然后,我在半径坐标上应用死区灵敏度和我想要的所有修饰符,并将其转换回笛卡尔坐标。

    我在这里发布我现在使用的代码。这看起来比我上面问题中的代码更简单、更干净:

    private void ComputeCoordinates()
    {
        // Convert to polar coordinates.
        double r = CoerceValue(Math.Sqrt((X * X) + (Y * Y)), 0d, 1d);  // Radius;
        double a = Math.Atan2(Y, X);  // Angle (in radians);
    
        // Apply modifiers.
        double value = ComputeModifiers(r);
    
        // Convert to cartesian coordinates.
        double x = value * Math.Cos(a);
        double y = value * Math.Sin(a);
    
        // Apply axis independent modifiers.
        if (InvertX) x = -x;
        if (InvertY) y = -y;
    
        // Set calculated values to property values;
        _computedX = x;
        _computedY = y;
    }
    
    
    private double ComputeModifiers(double value)
    {
        // Apply dead-zone and saturation.
        if (DeadZone > 0d || Saturation < 1d)
        {
            double edgeSpace = (1 - Saturation) + DeadZone;
            if (edgeSpace < 1d)
            {
                double multiplier = 1 / (1 - edgeSpace);
                value = (value - DeadZone) * multiplier;
                value = CoerceValue(value, 0d, 1d);
            }
            else
            {
                value = Math.Round(value);
            }
        }
    
        // Apply sensitivity.
        if (Sensitivity != 0d)
        {
            value = value + ((value - Math.Sin(value * (Math.PI / 2))) * (Sensitivity * 2));
            value = CoerceValue(value, 0d, 1d);
        }
    
        // Apply range.
        if (Range < 1d)
        {
            value = value * Range;
        }
    
        // Return calculated value.
        return CoerceValue(value, 0d, 1d);
    }
    

    上面代码的解释

    1. 将物理操纵杆的 X 和 Y 坐标转换为极坐标;
    2. 将死区、饱和度、灵敏度和范围修改器应用于半径坐标;
    3. 使用原始角度和修改后的半径转换回笛卡尔坐标(X 和 Y);
    4. 可选:对每个新轴应用与轴无关的修饰符(在这种情况下,如果用户希望轴反转,我只是反转每个轴);
    5. 完成。现在,无论我移动操纵杆的方向如何,每个修改器都以循环方式应用;

    嗯,这种情况花了我一天的工作时间,因为我在互联网上没有找到与我的问题相关的任何内容,我也不知道如何搜索解决方案,但我希望其他人得到对这个问题可能会觉得这很有用。

    这里有一些关于笛卡尔和极坐标系的参考资料:

    https://en.wikipedia.org/wiki/Cartesian_coordinate_system

    https://en.wikipedia.org/wiki/Polar_coordinate_system

    https://social.msdn.microsoft.com/Forums/vstudio/en-US/9f120a35-dcac-42ab-b763-c65f3c39afdc/conversion-between-cartesian-to-polar-coordinates-and-back?forum=vbgeneral

    【讨论】:

      【解决方案2】:

      以下内容对我来说效果很好。它采用标准抛物线 (x^2) 并确保结果是有符号的。您可以使用图形计算器调整曲线,使其更接近您的需要。

      事实上,f(-1) = -1, f(0) = 0, f(1) = 1 并且两者之间的曲线不太敏感。

      Mathf.Pow(axes.x, 2) * (axes.x

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多