Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

1

09.06.2013, 11:34

[SDL] Widgets & Zustandsspeicherung für Eingabe

Hiho,

ich arbeite immer mal weiter an meinem kleinen SDL-Projekt und habe mir bisher eine statische Klasse "um die SDL-Eingabe" herum gebaut, d.h. folgendes:

Input.hpp

C-/C++-Quelltext

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#ifndef INPUT_HPP_INCLUDED
#define INPUT_HPP_INCLUDED

#include <map>
#include <string>
#include <queue>

#include <SDL/SDL.h>

// ein paar andere Sachen - die auftretenden Bezeichner sollten aber hoffentlich selbsterklärend sein :) 
#include <core/geometry.hpp>
#include <core/unicode.hpp>
#include <core/filesystem.hpp>
#include <core/exception.hpp>

namespace core {

    /// ID-Typ für Tasten
    typedef unsigned short KeyID;
    /// ID-Typ für Maustasten
    typedef unsigned short ButtonID;

    /// Zustand einer Taste
    enum KeyState {
        RELEASED = 0,
        PRESSED  = 1,
        HELD     = 2
    };

    namespace keyboard {
        extern KeyID FIRST;
        extern KeyID BACKSPACE;
        extern KeyID TAB;
        extern KeyID CLEAR;
        extern KeyID RETURN;
        extern KeyID PAUSE;
/*
  ... ich kürze das an der Stelle mal ab
*/
        extern KeyID POWER;
        extern KeyID EURO;
        extern KeyID UNDO;
        extern KeyID LAST;
    }

    namespace mouse {
        extern ButtonID FIRST;
        extern ButtonID LEFT;
        extern ButtonID MIDDLE;
        extern ButtonID RIGHT;
        extern ButtonID WHEELUP;
        extern ButtonID WHEELDOWN;
        extern ButtonID LAST;
    }

    /// Zusammenfassung von Maus, Tastatur und Gamepad
    class Input {
        private:
            /// Zuletzt gedrückte Tasten
            static std::queue<KeyID> lastKeys;
            /// Zuletzt gedrückte Maustasten
            static std::queue<ButtonID> lastButtons;
            /// Beschreibt, ob Eingabebehandlung initialisiert wurde
            static bool ready;
            /// Beschreibt, ob die Maus eingefangen wurde
            static bool grab;
            /// Beschreibt, ob die Maus zentriert ist
            static bool drag;
            /// Sensibilität der Maus
            static float sensitivity;
        public:
            /// Initialisiert Eingabebehandlung
            static void init();
            /// Beendet Eingabebehandlung
            static void quit();
            /// Aktualisiert die Eingabebehandlung
            static void update();
            /// Erfasst gedrückte Tasten
            static std::map<KeyID, KeyState> keyboard;
            /// Erfasst gedrückte Maustasten
            static std::map<ButtonID, KeyState> button;
            /// Position des Mauszeigers
            static Vector pos;
            /// Relative Änderung des Mauszeigers
            static Vector diff;
            /// Eingegebenes Zeichen als Unicode-String
            static std::string text;
            /*
            /// Liefert, wie lange eine Taste gehalten wird
            static unsigned short getKeyboardHeld(KeyID const & id);
            /// Liefert, wie lange eine Maustste gehalten wird
            static unsigned short getButtonHeld(ButtonID const & id);
            */
            /// Beschreibt, ob SDL_QUIT ausgelöst wurde
            static bool window_close;
    };

}

#endif // INPUT_HPP_INCLUDED


Input.cpp

C-/C++-Quelltext

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#include <core/input.hpp>

#include <stdexcept>

namespace core {

    namespace keyboard {
        KeyID FIRST         = SDLK_FIRST;
        KeyID BACKSPACE     = SDLK_BACKSPACE;
        KeyID TAB           = SDLK_TAB;
        KeyID CLEAR         = SDLK_CLEAR;
/*
   ... auch hier kürze ich mal ab
*/
        KeyID EURO          = SDLK_EURO;
        KeyID UNDO          = SDLK_UNDO;
        KeyID LAST          = SDLK_LAST;
    }

    namespace mouse {
        ButtonID FIRST     = SDL_BUTTON_LEFT;
        ButtonID LEFT      = SDL_BUTTON_LEFT;
        ButtonID MIDDLE    = SDL_BUTTON_MIDDLE;
        ButtonID RIGHT     = SDL_BUTTON_RIGHT;
        ButtonID WHEELUP   = SDL_BUTTON_WHEELUP;
        ButtonID WHEELDOWN = SDL_BUTTON_WHEELDOWN;
        ButtonID LAST      = SDL_BUTTON_WHEELDOWN;
    }

    bool Input::ready = false;
    std::queue<KeyID> Input::lastKeys;
    std::queue<ButtonID> Input::lastButtons;
    std::map<KeyID, KeyState> Input::keyboard;
    std::map<ButtonID, KeyState> Input::button;
    Vector Input::pos;
    Vector Input::diff;
    std::string Input::text;
    bool Input::grab = false;
    bool Input::drag = false;
    float Input::sensitivity = 1.0;
    bool Input::window_close = false;

    void Input::init() {
        if (Input::ready == true) {
            std::clog << "Input system is already initialized" << std::endl;
            return;
        }
        SDL_EnableUNICODE(true);
        Input::ready = true;
    }

    void Input::quit() {
        if (Input::ready == false) {
            std::clog << "Input system was not initialized" << std::endl;
            return;
        }
        SDL_EnableUNICODE(false);
        Input::ready = false;
    }

    void Input::update() {
        Vector tmp;
        Input::window_close = false;
        Input::text.clear();

        // Tastendruck in -halten umwandeln
        while (!Input::lastKeys.empty()) {
            KeyID buffer = Input::lastKeys.front();
            Input::lastKeys.pop();
            Input::keyboard[buffer] = HELD;
        }
        // Maustastendruck in -halten umwandeln
        while (!Input::lastButtons.empty()) {
            ButtonID buffer = Input::lastButtons.front();
            Input::lastButtons.pop();
            Input::button[buffer] = HELD;
        }

        if (Input::ready == false) {
            throw RuntimeError("Input must be initialized before handling input");
        }
        if (Input::grab) {
            if (SDL_ShowCursor(SDL_QUERY) == SDL_ENABLE) {
                SDL_ShowCursor(SDL_DISABLE);
                SDL_WM_GrabInput(SDL_GRAB_ON);
            }
        } else {
            if (SDL_ShowCursor(SDL_QUERY) == SDL_DISABLE) {
                SDL_ShowCursor(SDL_ENABLE);
                SDL_WM_GrabInput(SDL_GRAB_OFF);
            }
        }

        SDL_Event event;
        short keycode;
        while (SDL_PollEvent(&event)) {
            switch (event.type) {
                case SDL_KEYDOWN:
                    Input::keyboard[event.key.keysym.sym] = PRESSED;
                    Input::lastKeys.push(event.key.keysym.sym);
                    keycode = event.key.keysym.unicode;
                    // Read and Convert Printable Input
                    if ((keycode > 0x800) | (keycode > 0x80) | (keycode > 32 && keycode != 127)) {
                        Input::text.push_back(keycode);
                    }
                    break;
                case SDL_KEYUP:
                    Input::keyboard[event.key.keysym.sym] = RELEASED;
                    break;
                case SDL_MOUSEBUTTONDOWN:
                    Input::button[event.button.button] = PRESSED;
                    Input::lastButtons.push(event.button.button);
                    break;
                case SDL_MOUSEBUTTONUP:
                    Input::button[event.button.button] = RELEASED;
                    break;
                case SDL_MOUSEMOTION:
                    tmp         = Vector(event.motion.x, event.motion.y);
                    Input::diff = Input::pos - tmp;
                    Input::pos  = tmp;
                    break;
                case SDL_QUIT:
                    Input::window_close = true;
                    break;
            }
        }

        if (Input::drag) {
            SDL_WarpMouse(Input::pos.x, Input::pos.y);
        }
    }

}


Warum, wieso, weshalb?

Ich würde gern den Zustand er Eingabe speichern, um in der Programmlogik verschiedene Eingabesachen abzufragen, ohne direkt mit SDLEvents arbeiten zu müssen. Sicherlich: das hat dann eher etwas vom "jede Minute gucken, ob die Pizza fertig ist", statt dem eleganteren "ich stelle mir einen Wecker und bekomme eine 'Information' wenn sie fertig ist". Eine "schönere" Lösung ist mir noch nicht eingefallen :hmm:
Effektiv ist das mit der "Zustandsklasse" für die Eingabe für mich eine Frage des Information Hiding und der Kapselung - auch im Hinblick auf eine Migration zu einer neueren SDL-Version. Atm arbeite ich noch mit 1.2, möchte aber in absehbarer Zeit auf 2.0 wechseln.

Ich habe mir ein paar Widget-Klassen geschrieben, die dieses Input-System verwenden. Ja: ich hätte auch eine der fertigen SDL-Widget-Libs nehmen können, aber die bauen alle auf den SDLEvents auf, so dass ich mir die Integration schwer vorstellen kann. Imho ist dieses "Eingabe in das gewünschte Format bringen" (was ich mit der statischen Klasse effektiv mache) kein Teufelszeug :D Zumindest bilde ich mir das ein.

Was suche ich? Was will ich hören? :D

Könnt ihr mir ein besseres Konzept empfehlen? Oder kennt ihr vielleicht sogar eine Lib die auf SDL aufbaut, Widgets zur Verfügung stellt und eine Art Zustandsspeicherung für die Eingaben vornimmt? Die einzige Forderung an ein Widget-System wäre, dass ich das Aussehen der Widgets mit Grafiken modifiziere kann, d.h. ich möchte keine Widgets im üblichen Systemlayout.

Meine aktuelle Lösung der Eingabebehandlung gefällt mir überhaupt nicht. Und bevor ich damit weiterarbeite und mein Projekt zu stark davon abhängig mache, wollte ich mal rumfragen :D

LG Glocke

/EDIT: Sorry, falsches Unterforum erwischt -.-

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »Glocke« (09.06.2013, 11:51)


2

10.06.2013, 13:11

Du leitest von ner Event-Klasse ab und schreibst lauter bunte virtuelle funktionen, die du dann polymorph überschreibst..
http://www.sdltutorials.com/sdl-events
Nur mal so, um ein anderes Konzept zu nennen. Ansonsten kann ich nicht viel dazu sagen, ich benutze SDL Events direkt.

3

10.06.2013, 17:19

Das klingt gut! Ich glaube ich ergänze meine bisherige Struktur darum. D.h. ich fange die SDL-Events ab, aktualisiere meine Zustände (gerade für die Entscheidung "wird mein Button gerade gedrückt" oder "Halte ich diese Taste gerade gedrückt" ist dann damit angenehmer zu arbeiten, für mich zumidnest :D ). Und dann packe ich die SDL-Events in eine Hand voll eigene Event-Klassen. Ich habe ein paar Widgets selber geschrieben, die diese "Tastatur-Maus-Zustände" verwenden. Das auf die Event-Sache umzuschreiben würde den Code aus meinen Augen sehr hässlich machen. Für das Abfragen von Klicks und Tastendrücken (außerhalb er Widget-Logik) in der eigentlichen Programmlogik sollten die Events klar die Nase vorn haben.

Ist diese "Zweiteilung" so auf den ersten Blick sinnvoll?

LG Glocke

4

11.06.2013, 14:49

Aus meiner Sicht spricht nichts dagegen, aber womöglich kannst du das schlußendlich besser beurteilen. Kanns du vielleicht mal zeigen, wie du deine Variante in den Widgets einsetzt. Nur so aus Interesse, bei obigen Code sind bei mir ein paar Fragen offen.

5

11.06.2013, 16:32

Kanns du vielleicht mal zeigen, wie du deine Variante in den Widgets einsetzt. Nur so aus Interesse, bei obigen Code sind bei mir ein paar Fragen offen.


Hier ein Beispiel aus meiner Button-Klasse:

C-/C++-Quelltext

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
    bool Widget::mouseOver() {
        return this->collideRect(Input::pos);
    }

    bool Widget::leftClick() {
        if (this->mouseOver()) {
            return Input::button[mouse::LEFT] == PRESSED;
        }
        return false;
    }

    bool Widget::leftHeld() {
        if (this->mouseOver()) {
            return Input::button[mouse::LEFT] == HELD;
        }
        return false;
    }

    bool Widget::wheelUp() {
        if (this->mouseOver()) {
            return Input::button[mouse::WHEELUP] != RELEASED;
        }
        return false;
    }

    bool Widget::wheelDown() {
        if (this->mouseOver()) {
            return Input::button[mouse::WHEELDOWN] != RELEASED;
        }
        return false;
    }


C-/C++-Quelltext

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
    void Button::logic() {
        Widget::logic();

        if (this->state == DISABLED) {
            return;
        }
        this->clicked = false;

        bool mouseOver = this->mouseOver();
        bool leftClick = this->leftClick();
        bool leftHeld  = this->leftHeld();
        if (leftClick || leftHeld) {
            // Handle Press
            if (mouseOver && this->state != DOWN) {
                this->state = DOWN;
            }
            // Handle Release
            if (!mouseOver && this->state == DOWN) {
                this->state = DEFAULT;
            }
        }
        if (!leftHeld && !leftClick) {
            // Handle Focus
            if (mouseOver && this->state == DEFAULT) {
                this->state = HOVER;
            }
            // Handle Unfocus
            if (!mouseOver && this->state == HOVER) {
                this->state = DEFAULT;
            }
            // Handle Release
            if (this->state == DOWN) {
                this->state = DEFAULT;
            }
        }
        leftClick = Input::button[mouse::LEFT] == PRESSED;
        bool leftFree = Input::button[mouse::LEFT] == RELEASED;
        if (leftClick && mouseOver) {
            this->held = true;
            // Eat Click
            Input::button[mouse::LEFT] = HELD;
        } else if (leftFree && this->held) {
            this->held = false;
            this->clicked = mouseOver;
        }
    }


Das ist zwar nicht besonders "schön", aber vielleicht verständlich. Ich habe noch eine Hand voll Enums usw. in Verwendung, deren Semantik ergibt sich hoffentlich aus den Bezeichnern :)

LG Glocke

6

12.06.2013, 00:26

Oh danke, ich glaub ich habs halbwegs kapiert. Ich hatte mich unter anderem gefragt, wo du die Map komplett auffüllst bevor du (z.B. Input::button[mouse::LEFT] == PRESSED) prüfst. Das sehe ich immer noch nicht, aber vielleicht bin ich auch ganz blind.

Dann zeig ich auch noch meine 'Idee'.

C-/C++-Quelltext

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
54
55
56
57
58
59
60
//Anwendung

while(SDL_PollEvent(&e))
{
    if(btn.processEvent(e) == 2)
    {
        //Bombenangriff
    }
}

if(btn.isMouseDown())
{
    //Bombenangriff
}

if(btn.isHover())
{
    //Bombenangriff
}


//aus der Button Klasse..

int Button::processEvent(SDL_Event& e)
{
    if(!_isVisible) return 0;
    int result = 0;

    if(_isClickable)
    {
        switch(e.type)
        {
        case SDL_MOUSEBUTTONDOWN:
            if(e.button.button == SDL_BUTTON_LEFT)
            {
                if(_isHover)
                {
                    _isMouseDown = true;
                    _isActive = true;
                    result = 1;
                }
                else
                    _isActive = false;
            } break;
        case SDL_MOUSEBUTTONUP:
            if(e.button.button == SDL_BUTTON_LEFT)
            {
          if(_isHover && _isMouseDown)
             result = 2;
          _isMouseDown = false;
         }
            break;
     }
    }

    if(_isActive)
        checkReturn(e, &result);

    return result;
}

7

12.06.2013, 06:52

Oh danke, ich glaub ich habs halbwegs kapiert. Ich hatte mich unter anderem gefragt, wo du die Map komplett auffüllst bevor du (z.B. Input::button[mouse::LEFT] == PRESSED) prüfst. Das sehe ich immer noch nicht, aber vielleicht bin ich auch ganz blind.


Das geschieht in Input::update beim switchen des Event-Typs.

Dann zeig ich auch noch meine 'Idee'.

C-/C++-Quelltext

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
54
55
56
57
58
59
60
//Anwendung

while(SDL_PollEvent(&e))
{
    if(btn.processEvent(e) == 2)
    {
        //Bombenangriff
    }
}

if(btn.isMouseDown())
{
    //Bombenangriff
}

if(btn.isHover())
{
    //Bombenangriff
}


//aus der Button Klasse..

int Button::processEvent(SDL_Event& e)
{
    if(!_isVisible) return 0;
    int result = 0;

    if(_isClickable)
    {
        switch(e.type)
        {
        case SDL_MOUSEBUTTONDOWN:
            if(e.button.button == SDL_BUTTON_LEFT)
            {
                if(_isHover)
                {
                    _isMouseDown = true;
                    _isActive = true;
                    result = 1;
                }
                else
                    _isActive = false;
            } break;
        case SDL_MOUSEBUTTONUP:
            if(e.button.button == SDL_BUTTON_LEFT)
            {
          if(_isHover && _isMouseDown)
             result = 2;
          _isMouseDown = false;
         }
            break;
     }
    }

    if(_isActive)
        checkReturn(e, &result);

    return result;
}


Wie handelst du mehrere Buttons: bekommen alle Buttons das Event und bestimmen anhand der Zeigerposition wer geklickt wurde? Oder gibst verwendest du das Ergebnis der Handle-Methode, um die Weitergabe an andere Buttons ggf. zu unterbinden?

LG Glocke

8

12.06.2013, 12:59

"Das geschieht in Input::update beim switchen des Event-Typs."

Was mir nicht klar ist: Kann es denn nicht vorkommen, dass du z.B. auf leftClick prüfst, obwohl das SDL Event (=SDL_MOUSEBUTTONDOWN) noch gar nicht eingetreten ist und somit auch kein map-key dafür existiert?

"Wie handelst du mehrere Buttons: bekommen alle Buttons das Event und bestimmen anhand der Zeigerposition wer geklickt wurde? Oder gibst verwendest du das Ergebnis der Handle-Methode, um die Weitergabe an andere Buttons ggf. zu unterbinden?"

_isHover zeigt ja Objektspezifisch an, ob der Cursor überm Steuerelement schwebt. Nur dann gibts 1 oder 2 als Rückgabe, sonst halt die 0. Damit lassen sich mehrere Buttons unabhängig voneinander behandeln.

9

12.06.2013, 13:20

Was mir nicht klar ist: Kann es denn nicht vorkommen, dass du z.B. auf leftClick prüfst, obwohl das SDL Event (=SDL_MOUSEBUTTONDOWN) noch gar nicht eingetreten ist und somit auch kein map-key dafür existiert?


Wenn kein ButtonDown Event eingetreten ist, gibt es in der map keinen Key, richtig. Suche ich danach erhalte ich den default wert (afaik false).

_isHover zeigt ja Objektspezifisch an, ob der Cursor überm Steuerelement schwebt. Nur dann gibts 1 oder 2 als Rückgabe, sonst halt die 0. Damit lassen sich mehrere Buttons unabhängig voneinander behandeln.


Okay, macht Sinn :)

LG Glocke

10

13.06.2013, 00:01

"Wenn kein ButtonDown Event eingetreten ist, gibt es in der map keinen Key, richtig. Suche ich danach erhalte ich den default wert (afaik false)."

Interessant, ich dachte, das wär dann undefiniert oder so was. Ich merke mir das.

Werbeanzeige