[C++] Met XInput een button-release/press herkennen

Pagina: 1
Acties:

Vraag


Acties:
  • 0 Henk 'm!

  • ikt
  • Registratie: Juli 2008
  • Laatst online: 16:49
Ik zit al een middagje te worstelen met een probleem dat ik gewoon niet snap? Ik wil XInput gebruiken in mijn applicatie en de basis-dingen gaan goed: De buttons, de triggers en de analoge controls worden prima uitlezen. Ook heb ik een paar methodes gemaakt die buttons als analoge input kunnen verwerken en analoge inputs na een threshold ook als buttons/digitale knoppen. Dit gaat ook goed.

Dan probeer ik drie methodes te maken:
IsButtonJustPressed
IsButtonJustReleased
WasButtonHeldForMs

Echter werken deze niet goed wanneer ik ze achter elkaar gebruik. Hier de klasse met wat dingen eruit geknipt:

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/* XboxController.hpp */

class XboxController
{
public:
    enum XboxButtons {
        DpadUp,
        etc...,
        SIZEOF_XboxButtons
    };

    int XboxButtonMasks[SIZEOF_XboxButtons] = {
        XINPUT_GAMEPAD_DPAD_UP,
        etc...
    };

private:
    XINPUT_STATE controllerState;
    int controllerNum;
    __int64 pressTime[SIZEOF_XboxButtons];
    __int64 releaseTime[SIZEOF_XboxButtons];

public:
    XboxController(int playerNumber);
    XINPUT_STATE GetState();
    bool IsConnected();
    void Vibrate(int leftval = 0, int rightval = 0);

    bool IsButtonPressed(XboxButtons buttonType, WORD buttonState);
    bool IsButtonJustPressed(XboxButtons buttonType, WORD buttonState);
    bool IsButtonJustReleased(XboxButtons buttonType, WORD buttonState);
    bool WasButtonHeldForMs(XboxButtons buttonType, WORD buttonState, int milliseconds);

    bool XboxButtonCurr[SIZEOF_XboxButtons];
    bool XboxButtonPrev[SIZEOF_XboxButtons];

    // Helper function to convert setting readouts to enums.
    XboxButtons StringToButton(std::string buttonString);

    // Returns a 0.0 to 1.0 value for any button
    float GetAnalogValue(XboxButtons buttonType, WORD buttonState);
};


IsButtonPressed() en GetAnalogValue() werken beide:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
bool XboxController::IsButtonPressed(XboxButtons buttonType, WORD buttonState) {
    if (buttonType == LeftTrigger ||
        buttonType == RightTrigger ||
        buttonType == LeftThumbLeft ||
        buttonType == LeftThumbRight ||
        buttonType == RightThumbLeft ||
        buttonType == RightThumbRight ||
        buttonType == LeftThumbUp ||
        buttonType == LeftThumbDown ||
        buttonType == RightThumbUp ||
        buttonType == RightThumbDown) {
        return(GetAnalogValue(buttonType, buttonState) > 0.75f);
    }
    else {
        return (buttonState & XboxButtonMasks[buttonType]) != 0;
    }
}

float XboxController::GetAnalogValue(XboxButtons buttonType, WORD buttonState) {
    switch (buttonType) {
    case LeftTrigger:
        return (float)controllerState.Gamepad.bLeftTrigger / 255;
    case RightTrigger:
        return (float)controllerState.Gamepad.bRightTrigger / 255;
    case LeftThumbLeft:
        return fmaxf(0, -(float)controllerState.Gamepad.sThumbLX / 32767);
    case LeftThumbRight:
        return fmaxf(0, (float)controllerState.Gamepad.sThumbLX / 32767);
    case RightThumbLeft:
        return fmaxf(0, -(float)controllerState.Gamepad.sThumbRX / 32767);
    case RightThumbRight:
        return fmaxf(0, (float)controllerState.Gamepad.sThumbRX / 32767);
    case LeftThumbUp:
        return fmaxf(0, (float)controllerState.Gamepad.sThumbLY / 32767);
    case LeftThumbDown:
        return fmaxf(0, -(float)controllerState.Gamepad.sThumbLY / 32767);
    case RightThumbUp:
        return fmaxf(0, (float)controllerState.Gamepad.sThumbRY / 32767);
    case RightThumbDown:
        return fmaxf(0, -(float)controllerState.Gamepad.sThumbRY / 32767);
    default:
        return IsButtonPressed(buttonType, buttonState) ? 1.0f : 0.0f;
    }
}


Echter werken de methodes die veranderingen moeten herkennen niet goed:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
bool XboxController::IsButtonJustPressed(XboxButtons buttonType, WORD buttonState) {
    XboxButtonCurr[buttonType] = IsButtonPressed(buttonType, buttonState);

    // raising edge
    if (XboxButtonCurr[buttonType] && !XboxButtonPrev[buttonType]) {
        XboxButtonPrev[buttonType] = XboxButtonCurr[buttonType];
        return true;
    }

    XboxButtonPrev[buttonType] = XboxButtonCurr[buttonType];
    return false;
}

bool XboxController::IsButtonJustReleased(XboxButtons buttonType, WORD buttonState) {
    XboxButtonCurr[buttonType] = IsButtonPressed(buttonType, buttonState);

    // falling edge
    if (!XboxButtonCurr[buttonType] && XboxButtonPrev[buttonType]) {
        XboxButtonPrev[buttonType] = XboxButtonCurr[buttonType];
        return true;
    }

    XboxButtonPrev[buttonType] = XboxButtonCurr[buttonType];
    return false;
}


Ik zie ook niet echt meer waar het fout gaat? Als ik één keer in de main-loop de knop check, gaat het goed, maar als ik de knop op meerdere dingen check, gaat het fout.

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
XboxController* controller = new XboxController(1);
WORD buttonState;

void update() {
    if (controller && controller->IsConnected()) {
        buttonState = controller->GetState().Gamepad.wButtons;
    }
    else {
        logger.Write("Nope");
    }

    if (controller->IsButtonJustReleased(XboxController::X, buttonState)) {
        logger.Write("Button Released");
    }

    if (controller->IsButtonJustPressed(XboxController::X, buttonState)) {
        logger.Write("Button Pressed");
    }


    if (controller->IsButtonPressed(XboxController::A, buttonState)) {
        showText(0.4, 0.46, 5.0, "A");
    }
    if (controller->IsButtonPressed(XboxController::X, buttonState)) {
        showText(0.4, 0.46, 5.0, "X");
    }
    if (controller->IsButtonPressed(XboxController::LeftThumbDown, buttonState)) {
        showText(0.4, 0.46, 5.0, "LBD");
    }
}

In mijn log, zie ik wanneer ik op X ram nooit "Nope" en "Button Pressed", enkel "Button Released". Als ik de twee aanroepingen omdraai (Dus Pressed voor Released) zie ik enkel "Button Pressed" in de log staan. Op het scherm verschijnt netjes A, X en/of LBD als ik op de knoppen druk en ze verdwijnen weer netjes wanneer ik de knoppen loslaat, idem met LBD als ik het pookje naar beneden duw en weer los laat. De veranderingen zijn echter half te zien in de log - namelijk dus de eerste die komt.

Ik snap het niet. De buttonState wordt enkel beschreven door controller->GetState().Gamepad.wButtons en wordt gewoon netjes meegestuurd als argument in plaats van dat die uit de klasse wordt gehaald, dus die zal geen onverwachte veranderingen krijgen. IsButtonPressed returnt gewoon een bool, dus zou per aanroep XboxButtonCurr[buttonType] gewoon keihard een waarde moeten geven. Elke wordt XboxButtonPrev[buttonType] = XboxButtonCurr[buttonType]; ook aangeroepen, dus de if (XboxButtonCurr[buttonType] && !XboxButtonPrev[buttonType]) { check zou meteen true moeten returnen bij een verandering, ook al -


Wacht even! Ik snap het nu. Tijdens het schrijven van de vorige zin begon er een lampje te branden.

Omdat beide methoden van dezelfde variabelen gebruik maken is natuurlijk de eerste functie goed, maar daarna krijgt meteen de tweede functie de geüpdatete "previous" en zijn curr en prev voor de tweede aangeroepene altijd hetzelfde :(

Nu ik dit snap, hoe pas ik dit aan? Elke functie zijn eigen previous/current (voor elke knop) bij laten houden lijkt me niet echt een goede oplossing, deze state variabelen in de hoofdklasse updaten lijkt me ook niet gewenst?

Edit - Ik heb even een snelle methode in elkaar geflanst om toch extern de dingen te updaten -
code:
1
2
3
4
5
6
void XboxController::UpdateButtonChangeStates()
{
    for (int i = 0; i < SIZEOF_XboxButtons; i++) {
        XboxButtonPrev[i] = XboxButtonCurr[i];
    }
}


Deze wordt dus elke update() aangeroepen. Hoe vreselijk slecht is deze oplossing? Het werkt op zich goed, maar het ligt me toch niet lekker.

Beste antwoord (via ikt op 27-02-2016 19:09)


  • farlane
  • Registratie: Maart 2000
  • Laatst online: 11-10 14:49
Ben niet bekend met XInput, maar volgens mij is het zo slecht niet : je zult toch op een of andere manier moeten bepalen wat "de vorige state" en "de huidige state" is. Je vorige methode, afgezien van het feit dat ze van dezelfde state gebruikt maakten zouden ook maar 1 aanroep werken, daarna zou de state zijn bijgewerkt.

Dwz, dit zou dus bij de tweede aanroep ook niet werken:
C:
1
2
3
4
5
6
7
if (controller->IsButtonJustReleased(XboxController::X, buttonState)) {
    logger.Write("Button Released 1");
}

if (controller->IsButtonJustReleased(XboxController::X, buttonState)) {
    logger.Write("Button Released 2");
}


Een wat geavanceerdere methode zou zijn een lijst van event listeners bijhouden en bij het vaststellen van een event al deze listeners aanroepen.

Polling voelt misschien niet goed, maar het is wel een doeltreffende methode en simpel als wat.

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.

Alle reacties


Acties:
  • Beste antwoord
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 11-10 14:49
Ben niet bekend met XInput, maar volgens mij is het zo slecht niet : je zult toch op een of andere manier moeten bepalen wat "de vorige state" en "de huidige state" is. Je vorige methode, afgezien van het feit dat ze van dezelfde state gebruikt maakten zouden ook maar 1 aanroep werken, daarna zou de state zijn bijgewerkt.

Dwz, dit zou dus bij de tweede aanroep ook niet werken:
C:
1
2
3
4
5
6
7
if (controller->IsButtonJustReleased(XboxController::X, buttonState)) {
    logger.Write("Button Released 1");
}

if (controller->IsButtonJustReleased(XboxController::X, buttonState)) {
    logger.Write("Button Released 2");
}


Een wat geavanceerdere methode zou zijn een lijst van event listeners bijhouden en bij het vaststellen van een event al deze listeners aanroepen.

Polling voelt misschien niet goed, maar het is wel een doeltreffende methode en simpel als wat.

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Acties:
  • 0 Henk 'm!

  • ikt
  • Registratie: Juli 2008
  • Laatst online: 16:49
Bij de tweede oplossing heb ik ook IsButtonJustPressed/Released stateloos gemaakt, deze lezen alleen. De vorige states worden dus om de hele scriptloop geüpdate in plaats van per aanroep, wat de problemen verhelpt.

Ik houd het dus bij polling, bedankt!

Acties:
  • 0 Henk 'm!

  • epic007
  • Registratie: Februari 2004
  • Laatst online: 07-10 10:46
Nog een alternatieve oplossing, met een enum die de state van de button bijhoud. Per button kan je dus een ButtonState bij gaan houden.

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <iostream>
#include <string>

using namespace std;

enum ButtonState {
    Released = 0x00,
    Pressing = 0x01,
    Releasing = 0x02,
    Pressed = 0x03
};

ButtonState updateButton(ButtonState prevState, bool isPressed)
{
    return static_cast<ButtonState>(((prevState << 1) + isPressed) & Pressed);
}

void printButtonState(ButtonState state)
{
    switch(state)
    {
        case Released: cout << "Released" << endl; break;
        case Pressing: cout << "Pressing" << endl; break; 
        case Releasing: cout << "Releasing" << endl; break;
        case Pressed: cout << "Pressed" << endl; break;
        default: cout << "Unknown" << endl; break;
    }
}

int main()
{
    ButtonState state = Released;
    
    printButtonState(state);
    
    state = updateButton(state, true);
    printButtonState(state);

    state = updateButton(state, true);
    printButtonState(state);

    state = updateButton(state, true);
    printButtonState(state);

    state = updateButton(state, false);
    printButtonState(state);

    state = updateButton(state, false);
    printButtonState(state);

    state = updateButton(state, false);
    printButtonState(state);
}

[ Voor 3% gewijzigd door epic007 op 01-03-2016 09:05 ]


Acties:
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 11-10 14:49
Ik zie even niet hoe Releasing en Pressing button *states* zijn.

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.