Windowsの「ゲームデバイスキャリブレーション」は実際に何をしますか?

1
2019.09.26

このウィザードは実際に何をしますか?デッドゾーンを作成してレジストリに保存しますか?生データをどこかの上限と下限にマッピングしますか?

これはもっぱらソフトウェアキャリブレーションですか、それともデータをデバイスに送り返しますか?

このウィザードの出力を識別して、アプリケーション内からキャリブレーションを利用できるようにすることができるかどうか疑問に思っています。

回答
1
2022.01.13

Windowsの組み込みジョイスティックコントローラーキャリブレーションツールの最近の調査から、PS4ジョイスティックで次の動作を確認できます(結果は他のコントローラーでも同様であるはずです)。

  • キャリブレーション・ウィザードを通過した後、以下のレジストリは、<NUM>が軸番号で較正されたデータ、HKEY_CURRENT_USER\System\CurrentControlSet\Control\MediaProperties\PrivateProperties\DirectInput\<DEVICE_ID>\Calibration\0\Type\Axes\<NUM>、と書かれています。

  • PS4ジョイスティックの場合、Windowsは6軸を読み取るため、6つのレジストリキー値が作成されます。軸の番号は次のとおりです。

    • 0(x)->左アナログスティックの水平方向の動き。
    • 1(y)->左アナログスティックの垂直方向の動き。
    • 2(z)->右アナログ水平運動。
    • 3(Rx)-> L2トリガー。
    • 4(Ry)-> R2トリガー。
    • 5(Rz)->右アナログ垂直運動。
  • レジストリキーのフォーマットは、Calibrationが(<MIN> <MID> <MAX>としてエンコード)12バイトのバイナリ値にマッピングされます。たとえば、私のAxis 0(x)の値は次のとおりです。<00 00 00 00> <80 00 00 00> <ff 00 0000>これは次のように変換されます。

    • 軸0の最小値= <00 00 00 00> = 0(10進数)
    • 中軸0の値= <80 00 00 00> = 128(10進数)
    • 最大軸0の値<ff00 00 00> = 255(10進数)
  • Windowsが行うキャリブレーションは、物理値をキャリブレーションされた範囲(上からの最小値、中間値、最大値)に変換することです。たとえば、私の欠陥のある左アナログスティックが0から255の範囲であるはずなのに、水平方向の動き(x軸)について10から100まで読み取ったとします。次に、最小/最大値をそれぞれ10と100に設定して、欠陥のあるアナログスティックを調整できます。

  • デッドゾーンの特定の設定はないようです。したがって、これは上記のアプリケーションに残された実装の詳細であると思います(たとえば、ゲームの場合、ゲームロジックコードの一部として定義されます)。

  • キャリブレーション設定はレジストリに保存されるため、キャリブレーションは異なるマシン間で永続的ではありません。

特定のケースでは、Windows APIを使用して値を読み取ることを検討することをお勧めします(XInput、UWP APIなど)。ボーナスとして、Windows XInputAPIを使用してコントローラー入力を読み取る私のバギーの一部を次に示します。

 #pragma comment(lib,"XInput.lib")
#pragma comment(lib,"Xinput9_1_0.lib")

#include <iostream>
#include <roapi.h>
#include <Xinput.h>


XINPUT_STATE fetchAConnectedJoystick() 
{
    DWORD dwResult;    
    XINPUT_STATE state;

    for (DWORD i=0; i < XUSER_MAX_COUNT; i++)
    {
        ZeroMemory(&state, sizeof(XINPUT_STATE));

        // Simply get the state of the controller from XInput.
        dwResult = XInputGetState(i, &state);

        // Controller is connected
        if(dwResult == ERROR_SUCCESS)
        {
            return state;
        }
    }

    std::exit;
}


void printLeftAnalogStickReadings(XINPUT_STATE state) 
{
    float LX = state.Gamepad.sThumbLX;
    float LY = state.Gamepad.sThumbLY;
    std::cout << "(X=" << LX << ", Y=" << LY << ")\n";
}


int computeAdjustedMagnitude(XINPUT_STATE state) 
{
    int INPUT_DEADZONE = 42;

    float LX = state.Gamepad.sThumbLX;
    float LY = state.Gamepad.sThumbLY;

    float magnitude = sqrt(LX*LX + LY*LY);              // Determine how far the controller is pushed

    // Determine the direction the controller is pushed
    float normalizedLX = LX / magnitude;
    float normalizedLY = LY / magnitude;

    if (magnitude > INPUT_DEADZONE)                     // Check if the controller is outside a circular dead zone
    {
        if (magnitude > 32767) magnitude = 32767;       // Clip the magnitude at its expected maximum value
        magnitude -= INPUT_DEADZONE;                    // Adjust magnitude relative to the end of the dead zone

    }
    else                                                // If the controller is in the deadzone zero out the magnitude
    {
        magnitude = 0.0;
    }
    return magnitude;
}


int main()
{
    XINPUT_STATE state;
    int sleepDuration = 100;                            // Milliseconds
    int adjustedMag = 0;

    while (true) {
        state = fetchAConnectedJoystick();
        printLeftAnalogStickReadings(state);
//      adjustedMag = computeAdjustedMagnitude(state);
//      std::this_thread::sleep_for(std::chrono::milliseconds(sleepDuration));
    }
}