首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >C# - Joystick灵敏度公式

C# - Joystick灵敏度公式
EN

Stack Overflow用户
提问于 2017-04-05 19:53:08
回答 2查看 1.1K关注 0票数 1

如何计算操纵杆的灵敏度,考虑死区和杆的圆形性质?

我在做一个代表游戏垫的类。我对它的数学有困难,特别是在敏感部分。灵敏度应使操纵杆与中心的距离非线性。我把灵敏度应用在X盒触发器上,没有问题,但由于操纵杆有两个轴(X和Y),我在数学上遇到了麻烦。

我想把圆灵敏度应用到棒上,但我不知道怎么做,特别是考虑到轴上的其他计算(比如死区、距中心的距离等等)。我该怎么做呢?

关于这个问题的更多细节

现在,我已经有了我的临时修复,这不是很好的工作。当操纵杆方向是水平方向或垂直方向时,它似乎在工作,但当我将它移动到对角线方向时,它似乎被破坏了。我的Joystick类有一个Distance属性,它检索棒距中心的距离(值从0到1)。我的Distance属性工作得很好,但是当我应用灵敏度时,在对角线方向上检索到的距离小于1--如果我左右移动,它应该是1,而不管方向如何。

下面,我包含了我的Joystick类的简化版本,其中我删除了大部分无关的代码。由ComputedXComputedY属性反演计算出的轴的X和Y位置。每个属性都应该包括其轴的最终位置(从-1到1),同时考虑到所有的修饰符(死区、饱和、灵敏度等)。

代码语言:javascript
复制
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;
    }

}

我试图使这个问题尽可能简短,但我很难解释这个具体的问题,而不描述它的一些细节。我知道我应该把它写得简短些,但我想至少再写几个字:

感谢您有时间阅读这一切!

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2017-04-06 03:19:46

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

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

我在这里张贴我现在使用的代码。这看起来比我上面问题上的代码要简单得多,也要干净得多:

代码语言:javascript
复制
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. 好了。每一个修饰符现在都是一个圆形的应用,无论我移动操纵杆的方向;

嗯,这个情况花了我一天的时间,因为我在网上没有发现任何与我的问题有关的东西,我也不知道如何去寻找解决方案,但我希望其他人在这个问题上会发现这是有用的。

以下是有关笛卡尔坐标系和极坐标系统的一些参考资料:

system

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

票数 1
EN

Stack Overflow用户

发布于 2021-06-20 21:51:59

下面这些对我来说很好。它接受一个标准抛物线(x^2),并确保结果是签名的。您可能可以调整曲线,使其更接近您的需要,通过使用图形计算器。

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

Mathf.Pow (axes.x,2) *(axes.x<0?-1 : 1)

票数 -1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/43240440

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档