Zdarzenia klawiatury + Timer

0

Witam,

Moim głównym problemem jest napisanie programu, który wyświetli mi na ekranie małą grafikę i w zależności od przytrzymanego KURSORA, przemieści tą grafikę w odpowiednim kierunku (zgodnie z kursorem). Chcę nadmienić, że problem sam w sobie nie stanowi dla mnie kłopotu - potrafię to napisać. Problem pojawia się, kiedy chcę zlikwidować 'opóźnienie klawiatury'. Już tłumaczę:

Mając taki kod:

private class MyKeyListener implements KeyListener {

    @Override
    public void keyTyped(KeyEvent ke) {

    }

    @Override
    public void keyPressed(KeyEvent ke) {
        if (ke.getKeyCode() == KeyEvent.VK_LEFT) {
            obiekt.x--;
            repaint();
        }
        if (ke.getKeyCode() == KeyEvent.VK_RIGHT) {
            obiekt.x++;
            repaint();
        }
        if (ke.getKeyCode() == KeyEvent.VK_UP) {
            obiekt.x--;
            repaint();
        }
        if (ke.getKeyCode() == KeyEvent.VK_DOWN) {
            obiekt.x++;
            repaint();
        }
    }

    @Override
    public void keyReleased(KeyEvent ke) {
        
    }

}

przytrzymując klawisz strzałki, działa to tak: wykonuje się pierwsze przemieszczenie, następuje chwila przerwy (opóźnienie klawiaturowe), no i wreszcie obiekt przemieszcza się tak jak powinien.

Pomysł nr 1: Zlikwidować opóźnienie klawiaturowe. Wstępnie wydaje się być logiczne, jednak po 1. nie mam pojęcia jak to zrobić, a po 2. na różnych komputerach szybkość powtarzania przycisku może się różnić, a mi zależy na identycznym odstępie czasowym między przemieszczeniem obiektu na każdym komputerze.

Pomysł nr 2: Wykorzystać klasy Timer i TimerTask. I tu pojawia się moja prośba o pomoc, ponieważ siedzę już którąś godzinę z kolei i nie potrafię tego połączyć... Wstępnie kombinuję na jednym przycisku, żeby nie pisać za dużo błędnego kodu i próbuję np. tak:

private class MyKeyListener implements KeyListener {

    @Override
    public void keyTyped(KeyEvent ke) {

    }

    @Override
    public void keyPressed(KeyEvent ke) {
        timer.schedule(new MyTimer(), 0, 50);
    }

    @Override
    public void keyReleased(KeyEvent ke) {
        timer.cancel();
    }

}

private class MyTimer extends TimerTask {

    @Override
    public void run() {
        obiekt.x++;
        repaint();
    }

}

Ale to chwilę się przemieści i się zatrzymuje... Próbowałem też wprowadzić zmienną logiczną 'wcisniety', ale również coś nie działa:

private class MyKeyListener implements KeyListener {

    @Override
    public void keyTyped(KeyEvent ke) {

    }

    @Override
    public void keyPressed(KeyEvent ke) {
        wcisniety = true;
        while (wcisniety) {
            obiekt.x++;
            repaint();
        }
    }

    @Override
    public void keyReleased(KeyEvent ke) {
        wcisniety = false;
    }

}

ale również mi to nie działa. Mam wrażenie, że nie do końca rozumiem jak JAVA realizuje wciśnięcie klawisza. Cały czas byłem przekonany, że dopóki klawisz jest wciśnięty, nie dojdzie do wykonania poleceń zawartych w keyReleased, chociaż wydaje mi się że źle to rozumiem...

Dlatego zwracam się z prośbą o POMYSŁ na zrealizowanie przedstawionego problemu. Jeżeli ktoś mógłby podzielić się wiedzą na temat kiedy java widzi wciśnięty klawisz a kiedy jest on 'puszczony' - również z chęcią skorzystam.

Z góry dziękuję,
Quindy

1

Dokładnie masz to opisane w dokumentacji http://docs.oracle.com/javase/6/docs/api/java/awt/event/KeyEvent.html, ale w wielkim uproszczeniu: np. dla każdej litery wpisanej w JTextFiled zostanie wywołane po kolei keyPressed, keyTyped (is only generated if a valid Unicode character could be generated.), keyReleased. Czyli jak przytrzymasz jakiś klawisz to te metody będą wykonywane w kółko.

Tak więc dodaj Timer działający stale w którym będziesz sprawdzał zmienna wcisniety i jeśli będzie ona ustawiona zmieniasz pozycję. Natomiast z keyPressed wywal pętle while

0

Zrobiłem w końcu... Dużo czasu poświęciłem na to, że kod dla Timera muszę implementować nie w KeyListener, ale w ActionListener :) No i jeszcze jedno... Używałem złego Timera :) Teraz używam tego z javax.swing. W każdym razie zrobiłem to tak:

private class MyKeyListener implements KeyListener {

    @Override
    public void keyTyped(KeyEvent ke) {

    }

    @Override
    public void keyPressed(KeyEvent ke) {
        wcisniety = true;
        key = ke.getKeyCode();            
    }

    @Override
    public void keyReleased(KeyEvent ke) {
        wcisniety = false;
    }

}

@Override
public void actionPerformed(ActionEvent ae) {
    if (key == KeyEvent.VK_RIGHT) {
        poruszPrawo();
    }
    if (key == KeyEvent.VK_LEFT) {
        poruszLewo();
    }
    if (key == KeyEvent.VK_UP) {
        poruszGora();
    }
    if (key == KeyEvent.VK_DOWN) {
        poruszDol();
    }
}

Teraz śmiga jak ta lala :) W każdym razie - wyczytałem gdzieś, że przytrzymanie klawisza tylko pod Linuksem sprawia problemy?? Ja właśnie działam na Linuksie. Wyczytałem, że pod Linuksem przytrzymując klawisz, Java widzi to jako naprzemienne wciskanie i puszczanie klawisza (KeyPressed, KeyReleased, KeyPressed, KeyReleased...). Natomiast pod Windowsem przytrzymując klawisz, Java odczytuje to jako ciągłe wciskanie klawisza, a dopiero po rzeczywistym puszczeniu wywoływany jest JEDEN KeyReleased (KeyPressed, KeyPressed, KeyPressed, KeyPressed... KeyReleased). Czy to prawda?

2

Sprawdziłem na XP, położyłem kota na klawiaturze, a po godzinie go obudziłem i przepędziłem. Metody keyPressed i keyReleased wykonały się raz.

1

Tak naprawdę powinieneś mieć kilka zmiennych boolean, którymi będziesz rejestrował wciśnięcie wszystkich klawiszy które Cię interesują (Najprościej użyć klasy BitSet lub EnumSet). Wtedy będzie możliwe jednoczesne przesuwanie po skosie (np. lewo i góra, albo -22,5 stopnia od pionu w przypadku naciśniecia 7 i 8 na klawiaturze numerycznej). Natomiast actionPerformed pełni u ciebie rolę takiej biednej procedury zegarowej bo metody porusz(...) są wywoływane w tempie w jakim JVM odbiera zdarzenia od systemu. Metoda actionPerformed wymusza oczywiście istnienie focusa na komponencie GUI, który takie zdarzenie obsługuje. Tak więc czysty panel (gry) odpada.

Żeby uniezależnić się od tego należy zrobić własny timer w którego procedurze obsługi testowane będą te zmienne i wywoływanie procedur porusz(...) właśnie stamtąd. Ewentualnie robi się to w pętli aktywnego renderingu (podczas fazy odczytywania urządzeń wejściowych), która też działa jak stabilizowany timer. Tak to działa w 99% gier.

0
Olamagato napisał(a)

Tak naprawdę powinieneś mieć kilka zmiennych boolean, którymi będziesz rejestrował wciśnięcie wszystkich klawiszy które Cię interesują (Najprościej użyć klasy BitSet lub EnumSet). Wtedy będzie możliwe jednoczesne przesuwanie po skosie (np. lewo i góra, albo -22,5 stopnia od pionu w przypadku naciśniecia 7 i 8 na klawiaturze numerycznej). Natomiast actionPerformed pełni u ciebie rolę takiej biednej procedury zegarowej bo metody porusz(...) są wywoływane w tempie w jakim JVM odbiera zdarzenia od systemu. Metoda actionPerformed wymusza oczywiście istnienie focusa na komponencie GUI, który takie zdarzenie obsługuje. Tak więc czysty panel (gry) odpada.

Żeby uniezależnić się od tego należy zrobić własny timer w którego procedurze obsługi testowane będą te zmienne i wywoływanie procedur porusz(...) właśnie stamtąd. Ewentualnie robi się to w pętli aktywnego renderingu (podczas fazy odczytywania urządzeń wejściowych), która też działa jak stabilizowany timer. Tak to działa w 99% gier.

Jeżeli chodzi o poruszanie się po skosie, to tak naprawdę nie jest mi to potrzebne... Nie rozumiem o co chodzi, że czysty panel odpada - stworzyłem ramkę, w niej panel - ustawiłem na niego focus i cała gra pojawia się właśnie w tym panelu...

Swoją drogą, że dopiero raczkuję w Javie, także nie wszystko jeszcze rozumiem. Można powiedzieć że raczej łopatologicznie staram się skleić coś mądrego...

Także nie miałbym pojęcia jak zrobić własny timer, a tym bardziej co masz na myśli mówiąc 'pętla aktywnego renderingu' :) W każdym razie dzięki za odpowiedź :)

1 użytkowników online, w tym zalogowanych: 0, gości: 1