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

ph4nt0m

Frischling

  • »ph4nt0m« ist der Autor dieses Themas

Beiträge: 81

Beruf: Student

  • Private Nachricht senden

1

07.04.2009, 11:33

Inputmanager nur mit Win32-API (kein DirectInput)

Hallo,
da ich in meinem Projekt auch auf Eingaben reagieren will, habe ich vor, eine Art Inputmanager (Singleton) zu schreiben. Und weil ich keine Unterstützung von Joysticks mit ForceFeedback benötige, scheinen ja normale Win32-API Funktionen ausreichend, oder sogar besser als DirectInput zu sein.

Meine Frage: Wie kann man das am besten umsetzen? Ich möchte ja meinen globalen Inputmanager zu jeder Zeit nach dem Zustand einer Taste, nach der Mausposition, oder auch nach der Veränderung des Mausrads :!: fragen können. Auf der anderen Seite muss ich ihn aber wohl aus meine Fensterprozedur heraus mit Daten füttern (als Reaktion auf WM_KEYDOWN, WM_MOUSEWHEEL usw.).

Das Problem ist, dass ich bisher eigentlich Fensterprozedur, den Teil zum Erzeugen des Fensters usw. recht stark beispielsweise vom Direct3D-Code abgetrennt habe. Jetzt müsste ich aber direkt in der Fensterprozedur auf Eingaben reagieren (außerhalb gehts wohl nur begrenzt, zumindest fällt das Mausrad schonmal weg).

Habt ihr vielleicht irgendwelche Tipps und Vorschläge oder könnt mir etwas Beispielcode zur Verfügung stellen?
;)

2

07.04.2009, 17:55

mach in den inputmanager ne tabelle mit den zuständen aller tasten usw. (am besten private), die von der fensterprozedur aus angepasst wird (stichwort friend), und dann eben über den inputmanager abgefragt werden kann.

Anonymous

unregistriert

3

07.04.2009, 21:05

Also das Trennen von Device und WndProc ist nicht immer eine sehr elegante Lösung, da man das Grafik-Device, sowie das Input-Device als "Extension" ansehen sollte.

Das Beste ist die Fenster-Nachrichten in einen Manager weiterzuleiten und dort verarbeiten zu lassen. Klar kann man mit GetAsnycIrgendwas das zwar umgehen, doch davon rate ich sehr ab, eben weil es Asynchron vonstatten geht.

Joystick ist auch mit WinAPI nativ möglich, nur halt FF nicht. Aber auch hierfür muss man die Multimedia-Nachrichten die auch als Fensternachrichten ankommen abfangen und berarbeiten.

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
void input::update (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    // Nur WM_KEYDOWN und WM_KEYUP wird beachtet.

    if (message != WM_KEYDOWN || message != WM_KEYUP)
        return;
        
    // TODO: verarbeite.

}


LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    // Input-Manager updaten.

    input::get_instance (). update (hwnd, message, wParam, lParam);
    
    // Weitere Nachrichtenverarbeitung.

    switch (message)                                            
    {
        // [...]

    }
    
    return (::DefWindowProc (hwnd, message, wParam, lParam));   
}


Wie du sehen kannst ist das alles recht easy. Am besten ist du schreibst dir einen Task-Manager in denen Tasks (Sound, Input, Grafik, Timer, usw.) eingetragen werden und in der WND-Proc aufgerufen werden anstatt die einzeln reinzuschreiben. :)

ph4nt0m

Frischling

  • »ph4nt0m« ist der Autor dieses Themas

Beiträge: 81

Beruf: Student

  • Private Nachricht senden

4

07.04.2009, 21:48

Danke für die Antworten. Die Vorgehensweise leuchtet mir soweit ein, ich frage mich nur gerade, ob es nicht vielleicht besser wäre, die entsprechende Funktion des Inputmanagers aus den einzelnen case-Zweigen aufzurufen. Bei deinem Vorschlage geschieht der Aufruf ja grundsätzlich und es wird erst in der update-Funktion entschieden, ob passende Nachrichten dabei sein. Andererseits spart man sich sich auf diese Weise ja einiges in der Fensterprozedur, die ja (trotz windowsx.h) häufig schon ohnehin recht lang wird.

Wie genau meinst du das mit dem "Task-Manager"? Eigentlich habe ich bei Grafik und Timer bisher die Fensterprozedur nie gebraucht, deshalb ja nun beim Input auch die Nachfrage. Könntest du auch hier ein Beispiel und/oder Code nennen? :)

Und als letztes noch: Den internen Aufbau des Inputmanagers würdest du auch in etwa so gestalten, wie PCShadow es bereits vorschlug? Also die Zustände in Feldern und Variablen speichern, die dann wiederum mit Hilfe einiger Methoden aus anderen Teilen des Programms abgefragt werden können?
;)

Anonymous

unregistriert

5

07.04.2009, 22:39

Hi,

also so ne Art Task-Manager oder Service-Manager ist ein "Hauptverwalter" für die Nachrichtenverarbeitung. Beispiel von deinem Device: Du hast es nicht in die WndProc drin, aber du musst auf Nachrichten wie WM_PAINT (neuzeichnen), WM_SIZE (Restore) und paar weitere Nachrichten reagieren. Wie bekommt dein Device das also mit?

Die Antwort: Gar nicht.

Was kann man also machen? Da diese Nachrichten systemspezifisch sind muss das Device damit auch eigentlich nix zu tun haben und diese nicht handhaben, aber ein anderer kann als "Proxy" fungieren. Dieser Proxy fängt die Nachrichten auf und ruft die notwendigen Funktionen zum Verarbeiten des Devices auf.

Beispiel: Bei WM_SIZE (besondere Fälle hiervon!) wird invalidate vom device aufgerufen und dann restore. Deine Device-Klasse muss die Nachrichten also nicht verarbeiten, es bekommt nicht mal mit, dass die existieren. :)

Hier mal Pseudo-Code ausm Kopf:

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
// Basisklasse für alle Services

class service
{
public:
    virtual void update (HWND hwnd, UINT message, WPARAM wParam, lParam) = 0;
    
    void set_done (bool done) { done_ = done; }
    bool is_done (void) { return done_; }
private:
    bool done_; // = false

};

// Service für die Grafikausgabe, NICHT DAS DEVICE!!! Diese Klasse benutzt nur das Device!

class graphic : 
    public service
{
public:
    void update (HWND hwnd, UINT message, WPARAM wParam, lParam)
    {
        if (message == WM_SIZE)
        {
            // WM_SIZE kommt u.A. wenn ein Restore vollzogen werden muss, drum hier Sachen für Restore einfügen,

        }
        
        
        if (message == WM_PAINT)
        {
            // Bei WM_PAINT 1x Zeichnen.

        }
        
        // Andere Messages die zum Zeichnen benötigt oder relevant sind verarbeiten.

    }
};

// Service für die Input-Verarbeitung. KEIN INPUT-MANAGER! Die Klasse benutzt den Input-Manager.

class input : 
    public service
{
public:
    void update (HWND hwnd, UINT message, WPARAM wParam, lParam)
    {
        if (message == WM_KEYDOWN || message == WM_KEYUP)
        {
            // Tastatur-Daten an den Input-Manager weiterleiten.

            // z. B.

            // this->manager_.set_key (taste aus params, wert aus params);

        }
        
        // Messages für Joystick, Maus usw verarbeiten.

    }
};

// Service für die Timer-Verarbeitung. KEIN INPUT-MANAGER! Die Klasse benutzt den Framework-weiten Timer.

class input : 
    public service
{
public:
    void update (HWND hwnd, UINT message, WPARAM wParam, lParam)
    {
        // Normaler Timer wird aktuallisiert.

        if (message == WM_TIMER)
        {
            // [...]

        }

        // Interessanter Fall und generell Schwer zu handhaben in Spielen, 

        // aber so sehr easy: WM_TIMECHANGE (Systemzeit wurde 

        // geändert z. B. automatische Umstellung von Sommer auf Winterzeit.

        if (message == WM_TIMECHANGE)
        {
            // Alle Timer mit der Zeitdifferenz aktuallisieren.

        }
    }
};


// kernel

class kernel
{
public:
    // hinzufügen eines services.

    void add_service (service* my_service)
    { service_.push_back (my_service); }
    
    // aktuallisieren des kernels.

    void update (HWND hwnd, UINT message, WPARAM wParam, lParam)
    {
        for (std::vector<service*>::iterator it = service_.begin (); it != service_.end (); )
        {
            // Service aktuallisieren.

            (*it)->update (hwnd, message, wParam, lParam);
            
            // freigegebene Services löschen.

            if ((*it)->is_done ())
                it = service_.erase (it);
            else
                ++it;
        }
    }
};


void init (void)
{
    // Services hinzufügen.

    kernel::get_instance ().add_service (new graphic ());
    kernel::get_instance ().add_service (new input ());
    kernel::get_instance ().add_service (new timer ());
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    // Services aktuallisieren.

    kernel::get_instance (). update (hwnd, message, wParam, lParam);
   
    // Weitere Nachrichtenverarbeitung.

    switch (message)                                           
    {
        // [...]

    }
   
    return (::DefWindowProc (hwnd, message, wParam, lParam));   
}


Also man kann hierdrüber viel mehr Nachrichten effektiv verarbeiten. Auch so Sondernachrichten wie z. B. das der Bildschirmschoner gestartet wird, der Bildschirm deaktiviert wurde, die Internet-Verbindung unterbrochen wurde usw. also alle Systemnachrichten und co!

Zu deinem Input-Problem: Mach dir ein bool-Array von 255-Zeichen und fertig. :)

ph4nt0m

Frischling

  • »ph4nt0m« ist der Autor dieses Themas

Beiträge: 81

Beruf: Student

  • Private Nachricht senden

6

08.04.2009, 14:34

Danke für deine Hilfe. Dein vorschlagenes Klassendesign für den Service-Manager ist durchaus "ansprechend". :D Im Moment wäre jedoch wie gesagt der Input der einzige Service, der Daten aus der Fensterprozedur empfangen muss. Sollte sich das in Zukunft ändern, werde ich aber versuchen, deinen Manager zu implementieren. Für den Moment habe ich daher jedoch erstmal nur die update-Funktion des Inputmanagers direkt aus der WndProc heraus aufgerufen, wie du es in deiner ersten Antwort vorgeschlagen hast.

Allerdings gibt es ein Problem: Als ersten Test habe ich versucht, die Richtung eines sich bewegenden Sprites durch Druck der Leertaste umzukehren. Tippe ich die Taste nur kurz an, bleibt das Sprite ganz kurz stehen und bewegt sich dann entweder in die neue oder in die alte Richtung weiter (scheint mehr oder weniger zufällig zu sein). Halte ich die Leertaste gedrückt, bleibt es ca. eine halbe Sekunde stehen und bewegt sich dann aber wenigstens in die richtige (also umgekehrte) Richtung weiter.

Eventuell kannst du daran schon erkennen, um welchen Fehler es sich handelt (auch, wenn das jetzt eben ein sehr bildliches Beispiel ist). Ansonsten werde ich umgehend mehr Details liefern, ich kann nur nicht einschätzen, was genau du eventuell noch wissen musst.
;)

Anonymous

unregistriert

7

08.04.2009, 14:39

Details!

Nur Input? Wie löst du den Restore-Effekt auf?

ph4nt0m

Frischling

  • »ph4nt0m« ist der Autor dieses Themas

Beiträge: 81

Beruf: Student

  • Private Nachricht senden

8

08.04.2009, 14:57

Also meine Update-Funktion sieht bisher so aus (nur Tastatur):


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
void Input::update(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    DEBUG_STACK;

    if(message == WM_KEYDOWN)
    {
            // Bereichsüberschreitung verhindern

        if(wParam > 255)
            EXCEPTION(TEXT("Virtual Key Code außerhalb des erlaubten Bereichs."));

        currentKeys_[wParam] = true;
        oldKeys_[wParam] = lParam & (1<<30);
    }
    else if(message == WM_KEYUP)
    {
            // Bereichsüberschreitung verhindern

        if(wParam > 255)
            EXCEPTION(TEXT("Virtual Key Code außerhalb des erlaubten Bereichs."));

        currentKeys_[wParam] = false;
        oldKeys_[wParam] = true;
    }
}


currentKeys_ und oldKeys_ sind halt zwei Arrays von jeweils 256 bool-Werten. Die Funktion, die ich dann von außerhalb zur Abfrage aufrufe:


C-/C++-Quelltext

1
2
3
4
bool Input::keyDown(unsigned int key, bool checkFirst)
{
    return currentKeys_[key] && (!checkFirst || !oldKeys_[key]);
}


Was genau meinst du mit dem Restore-Effekt? [Alt]+[Tab], also Lost Devices?
;)

Anonymous

unregistriert

9

08.04.2009, 15:18

Sieht recht anständig aus.

Jop meinte Lost-Device.

ph4nt0m

Frischling

  • »ph4nt0m« ist der Autor dieses Themas

Beiträge: 81

Beruf: Student

  • Private Nachricht senden

10

08.04.2009, 15:34

Also auf ein Lost Device reagiere ich bisher einfach in der Hauptschleife (intern halt über IDirect3DDevice9::TestCooperativeLevel()). Und das hat auch immer gut funktioniert:


C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
    // Wenn das Direct3D-Device resettet werden muss und kann

if(Direct3D::getInstance().deviceLost())
{
    if(Direct3D::getInstance().readyToReset())
    {
        StateManager::getInstance().prepareReset(); // Reset vorbereiten (Ressourcen freigeben)

        Direct3D::getInstance().reset();            // Direct3D-Device resetten

        StateManager::getInstance().reset();        // Ressourcen wieder neu laden

    }
}


Naja, darum geht es ja jetzt auch eigentlich gar nicht. Kannst du dir denn erklären, wodurch es zu dem beschriebenen Phänomen kommt? Dass das Sprite stehenbleibt, spricht ja im Prinzip dafür, dass die if-Abfrage (ob die Leertaste gedrückt wurde) mehrfach true ergibt, das Sprite somit in jedem Frame immer wieder die Richtung ändert und sich kaum vom Fleck bewegt.


Edit: Nachdem ich mal Winspector (ähnlich Spy++, aber kostenlos) auf mein Programm losgelassen hatte, stellte folgenden Sachverhalt (und wohl auch Grund für das Problem) fest: Es wird unmittelbar nach Tastendruck einmal WM_KEYDOWN gesendet. Hält man die Taste aber weiter durchgehend gedrückt, so fasst Windows nach dem Senden der ersten Nachricht für etwa eine halbe Sekunde alle weiteren zusammen und sendet erst nach Ablauf der halben Sekunde ein weiteres WM_KEYDOWN, bei dem dann eben (wie in der MSDN beschrieben) das untere Wort von lParam die Anzahl der zusammengefassten Nachrichten enthält. Man kann dieses Verhalten übrigens auch im Notepad beobachten, wenn man eine Taste gedrückt hält: Es wird sofort ein einzelner Buchstabe ausgegeben, dann folgt eine halbe Sekunde Pause und die weiteren Buchstaben folgen.

Ich bräuchte in meinem Programm natürlich eine umgehende Benachrichtung, sonst taugt der ganze Inputmanager nichts. Da aber WM_KEYDOWN an mehreren Stellen für diesen Zweck genannt wurde, muss es doch auch eine Lösung für dieses Problem geben. :(
;)

Werbeanzeige