Kyselin taannoin säikeistä ohjelmoinnin harjoitustyötä tehdessäni. Kuten tuossa kerron, minulla oli reaaliaikaisen pelin runko, joka muistutti pitkälti yksisäikeisen pelin runkoa. Suoritus siis pyörii yhdessä pääsilmukassa, tapahtumat napattiin talteen niiden sattuessa ja ne voitiin käsitellä silmukassa oikealla hetkellä. Liian yksinkertaista ollakseen totta. Käytännössä silmukka vain pyöri pyörimistään ja kesti useita sekunteja ennen kuin käyttöliittymä reagoi syötteisiin. Harjoitustyön ohjaaja ei osannut kuin todea, että "kontrollin pitäisi olla swingillä", mitä tuo sitten tarkoittaakin.
Nyt olen oma-alotteisesti kahlannut Googlea läpi päivätolkulla löytääkseni jonkinlaisen tutoriaalin aiheesta, mutta toistaiseksi vastaan ei ole tullut edes toimivaa esimerkkiä yksinkertaisesta pongista tai tetriksestä, ei ainakaan sellaista, jonka suoritus ei olisi tökkinyt jatkuvasti.
Summa summarum: Miten reaaliaikaisen pelin runko yleensä toteutaan Javalla? En usko, että hakemani systeemi voi juurikaan poiketa esimerkkinä mainitsemastani Pongista: Pelin kappaleet (pallo) etenevät jatkuvasti, grafiikka päivittyy aina, kun uudet liikkeet tapahtuvat ja tapahtumiin (nuolinäppäimet) reagoidaan oikealla hetkellä.
Edit: Ja tietenkin heti, kun olen saanut aiheen aloitettua, löydänkin saman tien etsimäni. Thread.sleep() näkyy olevan se, mitä kaipasin.
Nyt ei satu olemaan javakääntäjää käsillä, mutta tässä on kuitenkin jotain tynkää mistä voi yrittää lähteä.
Tämän olisi tarkoitus luoda tyhjä ikkuna, joka sulkeutuu ja ohjelman suoritus päättyy kun joko ikkuna suljetaan tai painetaan esciä.
// Lisää tähän tarvittavat importit... public class Peli extends JFrame implements KeyListener, Runnable { protected boolean loppu; public static void main(String[] args) { new Peli(); } public Peli() { loppu = false; // Käynnistää pääloopin uudessa säikeessä, sinne voi lykätä ainakin tarvittavat laskemiset ja ruudunpäivitykset tietyin väliajoin. new Thread(this).start(); // Ikkunan asetukset ja näppäimistökuuntelijan lisäys. setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setSize(new Dimension(400, 300)); addKeyListener(this); setVisible(true); } public void run() { while(!loppu) { // Päälooppi // Varmistetaan että muille säikeille annetaan edes hieman aikaa suorittaa omaa tehtäväänsä joka kierroksella. Thread.yield(); } // Sulkee ikkunan. dispose(); } public void keyTyped (KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { loppu = true; } } public void keyPressed (KeyEvent e) {} public void keyReleased (KeyEvent e) {} }
Toivottavasti tuo nyt toimii edes jotenkuten tai antaa ideaa siitä kuinka säikeitä ja ikkunoita voi käyttää. :)
Illemmalla tai huomenissa voin yrittää pistää jotain parempaa esimerkkiä, kunhan saan tuossa vielä tuon tentin suoritettua.
Kiitoksia! Tuo ainakin näyttää hyvin pitkälle siltä, mitä olen yrittänytkin, eli en siis liene aivan väärillä jäljillä. Lisäsin esimerkkiisi Canvas-olion, jolle piirrän pääloopissa aina mustan nelikulmion. Lisäsin myös keyTyped()-metodiin rivin, joka liikuttaa nelikulmiota minkä tahansa näppäimen painalluksesta. Kokonaisuutena koodi on nyt tällainen, lisäsin kommentit kohtiin, joihin tein muutoksia:
import java.awt.event.KeyEvent; import javax.swing.*; import java.awt.event.KeyListener; import java.awt.Dimension; // <MUUTOS> import java.awt.Canvas; import java.awt.Graphics; // </MUUTOS> public class Peli extends JFrame implements KeyListener, Runnable { protected boolean loppu; // <MUUTOS> private Graphics g; private double x = 200.0, y = 200.0; // </MUUTOS> public static void main(String[] args) { new Peli(); } public Peli() { loppu = false; // Käynnistää pääloopin uudessa säikeessä, sinne voi lykätä ainakin tarvittavat laskemiset ja ruudunpäivitykset tietyin väliajoin. // Ikkunan asetukset ja näppäimistökuuntelijan lisäys. setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setSize(new Dimension(400, 300)); addKeyListener(this); setVisible(true); // <MUUTOS> Canvas canvas = new Canvas(); canvas.setBounds(0, 0, 400, 400); this.getContentPane().add(canvas); this.g = canvas.getGraphics(); // </MUUTOS> new Thread(this).start(); } public void run() { while(!loppu) { // Päälooppi // <MUUTOS> this.g.fillRect((int)this.x, (int)this.y, 10, 10); // </MUUTOS> // Varmistetaan että muille säikeille annetaan edes hieman aikaa suorittaa omaa tehtäväänsä joka kierroksella. Thread.yield(); } // Sulkee ikkunan. dispose(); } public void keyTyped (KeyEvent e) { // <MUUTOS> this.x -= 1; // </MUUTOS> } public void keyPressed (KeyEvent e) {} public void keyReleased (KeyEvent e) {} }
Törmään kuitenkin taas vanhaan ongelmaani: Neliökulmion liike tuntuu olevan varsin hidasta ja tökkivää enkä saa asialle oikein mitään. Voisiko syy olla käyttämässäni järjestelmässä tai tulkissa, vai pitäisikö tuo toteuttaa aivan muulla tavalla?
Edit: Olen nimittäin kokeillut muitakin vastaavanlaisia esimerkkejä ja kaikissa tuntuu olevan sama vika.
hunajavohveli kirjoitti:
Törmään kuitenkin taas vanhaan ongelmaani: Neliökulmion liike tuntuu olevan varsin hidasta ja tökkivää enkä saa asialle oikein mitään. Voisiko syy olla käyttämässäni järjestelmässä tai tulkissa, vai pitäisikö tuo toteuttaa aivan muulla tavalla?
Ajoin sen Sunin Java-toteutuksella, jossa on Hotspot-kääntäjä. Tökkimistä en havainnut, paitsi sen takia, miten tuota näppäimistöä luetaan. Java tekee sen tässä niin, että kun näppäin on painettu pohjaan, lähetetään ensin yksi keyTyped-tapahtuma ja pidetään pieni tauko. Jos näppäin on yhä pohjassa, silloin tapahtumia lähetetään useita peräkkäin tämän pienen alkutauon jälkeen.
Se on tehty niin, koska yleisen käyttö näppäimistölle on tekstin kirjoittaminen. Varmaankin nuo keyPressed ja keyReleased antavat paremmin peliin sopivaa käytöstä. Kirjastoissa voi olla myös muuta, joka sopii vielä paremmin. (En osaa Javasta kuin ihan perusteet.)
Toinen tärkeä asia oikeassa pelissä on jonkinlainen kaksoispuskurointi, joka piilottaa piirtämisen silloin kun se tapahtuu. Valmiiksi puskuriin piirretty kuva sitten laitetaan kerralla näkyviin, mikä vähentää välkettä ja tekee liikkumisesta tasaisemman näköistä.
Tuplapuskuroinnin tai vastaavan ominaisuuden toteuttamisen saisi joku Java-osaaja neuvoa, koska en tunne oikeaa tapaa. Peliin sopivan näppäimenlukutavankin hän voi tietää, joten kertokoon. Jos odotellessasi haluat googletella, se on double buffering in English.
Äkkiä en keksi muuta Java-toimintapeliä kuin melko kevytgrafiikkaisen Turbotanksin. Pätkiikö sekin systeemissäsi? Se on selainpeli: http://turbotanks.com/
Omassa koneessani liike on tosi tasaista ja peli on täysin pelattava Firefoxissa. Ulkoasusta tulee kyllä mieleen jokin historiallinen vektorigrafiikkapelikonsoli, josta olen kuullut tarinoita :-)
Javakin pystyy 3D-grafiikkaan, varsinkin jos käyttää sopivaa kirjastoa. En vain tähän hätään tiedä esimerkkiä, minkä tähän pätkäisisin.
Tässä on tuosta edellisestä esimerkistä viety hieman eteenpäin siten, että nyt siinä voi ohjata mustaa viivaa nuolinäppäimillä.
Tosin tämä tapa hoitaa kaksoispuskurointi on erittäin hidas tapa ja varsinkin suuremmalla ikkunan koolla se aiheuttaa merkittävää tökkimistä.
import java.awt.Dimension; import java.awt.Graphics; import java.awt.Image; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.geom.Point2D; import java.util.Vector; import javax.swing.JFrame; public class Peli extends JFrame implements KeyListener, Runnable { protected boolean loppu; protected float suunta; protected float nopeus; protected Point2D.Float paikka; protected Vector<Integer> painetut; public static void main(String[] args) { new Peli(); } public Peli() { loppu = false; nopeus = 0; suunta = 0; paikka = new Point2D.Float(200, 150); painetut = new Vector<Integer>(); // Käynnistää pääloopin uudessa säikeessä, sinne voi lykätä ainakin tarvittavat laskemiset ja ruudunpäivitykset tietyin väliajoin. new Thread(this).start(); // Ikkunan asetukset ja näppäimistökuuntelijan lisäys. setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setSize(new Dimension(400, 300)); addKeyListener(this); setVisible(true); } public void run() { while(!loppu) { // Päälooppi try { Thread.sleep(20); } catch (InterruptedException e) { } kasitteleNappaimet(); paikka.x += nopeus * Math.sin(suunta); paikka.y += nopeus * Math.cos(suunta); repaint(); } // Sulkee ikkunan. dispose(); } public void kasitteleNappaimet() { for (int nappain : painetut) { if (nappain == KeyEvent.VK_LEFT) { suunta += Math.toRadians(3); } else if(nappain == KeyEvent.VK_RIGHT) { suunta -= Math.toRadians(3); } else if (nappain == KeyEvent.VK_UP) { nopeus += 0.1; } else if (nappain == KeyEvent.VK_DOWN) { nopeus -= 0.1; } else if (nappain == KeyEvent.VK_ESCAPE) { loppu = true; } } } public void paint(Graphics g) { Image bufferImage = createImage(this.getWidth(), this.getHeight()); Graphics buffer = bufferImage.getGraphics(); buffer.drawLine( (int)(paikka.x - 5 * Math.sin(suunta)), (int)(paikka.y - 5 * Math.cos(suunta)), (int)(paikka.x + 5 * Math.sin(suunta)), (int)(paikka.y + 5 * Math.cos(suunta))); buffer.drawString("Sijainti: (" + (int)paikka.x + ", " + (int)paikka.y + ")", 10, 40); g.drawImage(bufferImage, 0, 0, null); } public void keyPressed(KeyEvent e) { if (!painetut.contains(e.getKeyCode())) { painetut.add(e.getKeyCode()); } } public void keyReleased(KeyEvent e) { painetut.remove((Integer)e.getKeyCode()); } public void keyTyped(KeyEvent e) {} }
Kaksoispuskuroinnin hallitsen kyllä, en vain jaksanut lisätä sitä vielä tuohon, ja se sinänsä ei ole vaikuttanut tökkimiseen. Olen kokeillut myös keyPressed()-metodia keyTyped()-metodin sijaan, mutta samalla tuloksella. Minulla ei ole juuri nyt Java-kääntäjää tässä, mutta kokeilen vielä tuota Samin täydennettyä esimerkkiä heti, kun pystyn.
Olin muuttanut tuohon myös näppäinpainallusten luvun siten, että keyPressed kerää vektoriin listan näppäimistä, jotka on painettu pohjaan ja vastaavasti keyReleased poistaa näppäimen listalta kun sen vapauttaa. Tällä tavoin saadaan rekisteröityä jokainen pohjassa oleva näppäin ja lisäksi suoritettua näppäimestä tapahtuva toiminto jokaisella pääsilmukan kierroksella.
Sami kirjoitti:
Tosin tämä tapa hoitaa kaksoispuskurointi on erittäin hidas tapa ja varsinkin suuremmalla ikkunan koolla se aiheuttaa merkittävää tökkimistä.
Itse olen aina tehnyt kakosispuskuroinnin tuolla tavalla ja nyt vähän kiinnostaisi tietää miten se tehdään nopeammin? Pitäisikö käyttää jotakin kirjastoa, jolla pääsee näytönohjaimen muistiin käsiksi vai onko vielä joku muukin tapa?
Eipä ole pahemmin tullut tehtyä mitään sellaisia sovelluksia, missä tarvitsisi tehdä kovinkaan reaaliaikaista piirtoa suurelle näytölle. Asia kuitenkin kiinnosti sen verran, että piti ottaa selvää ja löytyihän siihen vastaus: createCompatibleVolatileImage joka luo näyttölaitteelle optimoidun kuvan. Tuon avulla edellinen yksinkertainen esimerkkiohjelma lähti pyörimään 1680*1050 tilassa siististi jopa 500 FPS nopeudella (kunhan vaan ei luo sitä uutta puskurikuvaa jokaisessa silmukassa, vaan ainoastaan jos kuvan koko muuttuu, koska muuten gc joudutaan ajamaan vähän väliä ja se hidastaa todella paljon).
Nyt pääsin viimeinkin kääntämään tuon viimeisimmän koodin ja näyttäisi käyttäytyvän tällä koneella (sekä kaksoispuskuroinnin kanssa että ilman) siten, että kun annan viivan liikkua vapaasti, FPS on karkeasti arvioiden alle 10. Kun taas pidän jotain näppäintä pohjassa, liike on keskimäärin sulavaa, mutta tökkii lievästi. Vika on siis ilmeisesti jossain muussa kuin koodissa?
Edit: Tuo käyttäytyy itse asiassa täsmälleen samoin kuin aiemmin omassa koodissani ja syy näkyisi olevan Thread.sleep(), parametrin arvosta riippumatta. Kuitenkaan ilman sitä tapahtumienkäsittelylle ei taas tunnu jäävän juuri ollenkaan resursseja, ja reagointi tapahtuu ehkä viiden sekunnin kuluttua näppäimen pohjaan painamisesta. Pelkkä Thread.yield() ei tunnu auttavan asiaa.
Edit 2: No nyt alkaa ongelman luonne selvitä. Kummastelin äsken, minkä vuoksi viiva etenee vaihtelevasta FPS:stä riippumatta keskimäärin samalla nopeudella, vaikka koodista puuttuu liikkeen suhteutus kuluneeseen aikaan. Tulostus konsoliin pääsilmukan jokaisella kierroksella paljasti, että silmukka etenee kuin eteneekin koko ajan varsin hyvällä FPS:llä. Sen sijaan repaint()-metodi ei näemmä hoidakaan hommaansa jokaisella kierroksella. Repaintin FPS näkyisi siis olevan normaalisti luokkaa 10 ja suurempi, kun jokin näppäin on pohjassa. Miksiköhän näin?
Voipi johtua siitä, että repaint() suorittaa piirtämisen toisessa säikeessä, jolloin pääsilmukan suoritus jatkuu normaalisti välittömästi repaint-metodin kutsumisen jälkeen.
Kokeilin vaihtaa repaintin tilalle suoraan paint(this.getGraphics()), mutta tulos on sama. Onko tuo siis riippuvainen Javan toteutuksessa, vai tökkiikö kuvan päivitys myös sinulla?
Suurella resoluutiolla tökkii, mutta syynä näyttäisi pääasiassa olevan se, että puskurikuva luodaan joka piirtokierroksella uusiksi, jolloin ilmeisesti gc joudutaan ajamaan vähän väliä.
Kuinkahan pahasti se tökkii vielä jos et luokaan uutta puskurikuvaa joka kierroksella, vaan ainoastaan jos kuvan koko on muuttunut?
Image bufferImage; public void paint(Graphics g) { if (bufferImage == null || bufferImage.getWidth(null) != getWidth() || bufferImage.getHeight(null) != getHeight()) { bufferImage = createImage(this.getWidth(), this.getHeight()); } Graphics buffer = bufferImage.getGraphics(); buffer.clearRect(0, 0, getWidth(), getHeight());
Kokeilin jo aiemmin ilman kaksoispuskurointia, jolloin poistin puskurikuvan luomisen kokonaan. Kokeilin myös siten, että piirsin puskuriin, mutta loin sen ainoastaan kerran. Kumpikaan ei vaikuttanut tökkimiseen mitenkään. Jos sinulla kerran toimii sulavasti, näyttäisi siltä, että vika ei tosiaan ole koodissa, vaan jossain aivan muualla. Kiitoksia kuitenkin avusta! Ainakin nyt tiedän, ettei vika ole käyttämässäni rakenteessa ja voin huoletta jatkaa varsinaisen toiminallisuuden koodaamista. Itse ohjelmahan tosiaan pyörii mainiosti, vaikka kuvan päivitys vähän takkuaakin.
Palaan vielä grafiikan piirtoon. Kaikki näytti toimivan hienosti, kunnes vähän aikaa sitten päätin lisätä ikkunaan JMenuBar-valikon. Syystä tai toisesta pelimaailma kuitenkin piirtyy valikon päälle, mikä on minusta jokseekin omituinen ilmiö. Yo. esimerkkikoodissa valikko ei tule näkyviin ollenkaan, joten piirrän pelimaailman JFramen sijaan ikkunaan lisätylle Canvas-oliolle. Olen kokeillut Canvasin sijaan myös JPanelia, mutta olio kuin olio tuntuu aina peittävän valikon. Millähän asian saisi korjattua?
Pelien kanssa kannattaa käyttää BufferStrategyä:
http://java.sun.com/javase/6/docs/api/java/awt/
http://www.javalobby.org/forums/thread.jspa?
Ratkaiseeko tuo valikon peittymisen vai auttaako se esim. aiemmin mainittuun tökkimiseen? Panen tuon kyllä muistiin tulevaisuuden varalle, mutta harjoitustyön deadline lähestyy ja haluaisin juuri nyt keskittyä selviämään olennaisimmista ongelmista, mieluiten muuttamatta mitään, mitä ei ole pakko muuttaa.
BufferStrategyllä saa tehtyä kaksoispuskuroinnin ja grafiikan hardware-kiihdytyksen (kuva säilytetään näytönohjaimen muistissa). VolatileImagella saa tehtyä saman asian, mutta ymmärtääkseni sen käyttö on BufferStrategyä vaikeampaa.
Pelin piirtyminen valikkojen päälle johtuu siitä, että olet sekoittanut ns. heavyweight ja lightweight -komponentteja. http://java.sun.com/products/jfc/tsc/articles/
Canvas on heavyweight-komponentti ja JMenuBar on lightweight-komponentti, jolloin Canvas piirretään aina JMenuBar:n päälle. Yksinkertaisin korjaus on käyttää java.awt.MenuBar:ia javax.swing.JMenuBar:n sijaan.
Kaikki muut käyttämäni komponentit ovat swing-komponentteja, joten voisiko ongelman yhtenäisyyden vuoksi korjata myös siten, että Canvasin vastaavasti vaihtaisi johonkin swing-komponenttiin? JCanvasta ei näy olevan, joten kokeilin JPanelia, mutta se näkyy käyttäytyvän samoin kuin Canvas.
Laitapa tuotoksesi näkyville, jotta muutkin voivat lukea, kääntää ja suorittaa koodiasi. Debuggaus näkemättä koodia on vaikeaa...
Koodi on perusrakenteeltaan tällainen. Kaikki ongelman kannalta epäolennainen on karsittu pois. Metodi drawWorld() sisältää kokeilemani tavat sekä suoralle että kaksoispuskuroinnin kautta tapahtuvalle piirrolle.
import javax.swing.*; import java.awt.*; import java.awt.event.*; public class Engine { // Ikkunan ja peliruudun koot private static final int WINDOW_WIDTH = 750; private static final int WINDOW_HEIGHT = 650; private static final int GAME_SCREEN_WIDTH = 600; private static final int GAME_SCREEN_HEIGHT = 600; // Komponentit private JFrame window; // Ikkuna private Container contents; // Ikkunan sisältöpaneeli private Image image_buffer; // Puskuri, jolle pelimaailma piirretään private Graphics buffer_g; // Puskurin grafiikat private JPanel panel; // Komponentti, jolle piirretty maailma kopioidaan private Graphics panel_g; // Pinnan grafiikat // Konstruoidaan moottori public Engine() { // Luodaan JFrame-ikkuna this.window = new JFrame(); this.window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.window.setSize(WINDOW_WIDTH, WINDOW_HEIGHT); this.contents = this.window.getContentPane(); // Lisätään JFrameen valikko JMenuBar menubar = new JMenuBar(); JMenu menu = new JMenu("Valikko"); JMenuItem start_game = new JMenuItem("Aloita"); this.window.setJMenuBar(menubar); menubar.add(menu); menu.add(start_game); // Lisätään JFrameen JPanel this.panel = new JPanel(); this.contents.add(this.panel); // Koko muuttumattomaksi ja ikkuna näkyviin this.window.setResizable(false); this.window.setVisible(true); // Luodaan puskuri, jolle piirretään. Otetaan talteen sen ja JPanelin grafiikat. this.panel_g = this.panel.getGraphics(); this.image_buffer = this.window.createImage(GAME_SCREEN_WIDTH, GAME_SCREEN_HEIGHT); this.buffer_g = this.image_buffer.getGraphics(); } // Päivitetään pelin tilaa private void nextFrame() { } // Piirretään maailma private void drawWorld() { // Piirto suoraan JPanelille //this.panel_g.fillRect(0, 0, 40, 40); // Piirto puskurin kautta this.buffer_g.fillRect(0, 0, 40, 40); this.panel_g.drawImage(this.image_buffer, 0, 0, this.window); } // Pääsilmukka public void run() { while(true) { this.nextFrame(); // Päivitetään this.drawWorld(); // Piirretään try { Thread.sleep(10); } catch (InterruptedException e) { } } } public static void main(String[] args) { Engine e = new Engine(); // Luodaan uusi moottori e.run(); // Ajetaan pääsilmukka } }
Näillä muutoksilla valikko näkyy:
MenuBar menubar = new MenuBar(); Menu menu = new Menu("Valikko"); MenuItem start_game = new MenuItem("Aloita"); this.window.setMenuBar(menubar);
Kokeilin tuota jo aiemman viestisi perusteella, ja se toimii hyvin. Ajattelin vain, että voisi olla tyylipuhtaampaa, jos kaikki komponentit olisivat lightweight-komponentteja, mutta eiköhän tälläkin pärjätä. Kiitos avusta!
Vielä näppäinten lukemisesta, toivon mukaan viimeinen murhe tämän harjoitustyön kanssa. Käytän lukemiseen siis Samin esimerkissä esitettyä tapaa. Ongelmana on se, että kuuntelija kutsuu keyPressed()-ja keyTyped()-metodeja jatkuvasti, vaikka näppäintä ainoastaan pidetään pohjassa. Tarvitsisin tiedon nimenomaan siitä, kun näppäin painetaan pohjaan. Samoin keyReleased()-metodia kutsutaan koko ajan näppäimen ollessa pohjassa, ei silloin kun näppäin nostetaan, mistä myös tarvitsisin tiedon. API:sta löysin mainintoja keyDown()- ja keyUp()-metodeista, mutta kaikkia väitettiin vanhentuneiksi ja paheksuttaviksi. Löytyisikö hakemaani siis "oikeaoppisempaa" tapaa?
keyPressed-metodia kutsutaan vähän väliä, jos näppäintä pidetään pohjassa, mutta keyReleased-metodia kutsutaan ainoastaan silloin kun näppäin vapautetaan (tai ainakin näin se itselläni toimii), eli tuon haluamasi toiminnallisuuden kanssa ei pitäisi olla mitään ongelmaa.
Esimerkiksi näin voit kutsua haluamiasi metodeita vain jos näppäin painetaan pohjaan tai vapautetaan:
public void keyPressed(KeyEvent e) { if (!painetut.contains(e.getKeyCode())) { nappainPainettu(e.getKeyCode()); painetut.add(e.getKeyCode()); } } public void keyReleased(KeyEvent e) { nappainVapautettu(e.getKeyCode()); painetut.remove((Integer)e.getKeyCode()); } public void nappainPainettu(int code) { System.out.println("Painettu: " + code); } public void nappainVapautettu(int code) { System.out.println("Vapautettu: " + code); }
Sami kirjoitti:
Esimerkiksi näin voit kutsua haluamiasi metodeita vain jos näppäin painetaan pohjaan tai vapautetaan:
Niin kieltämättä voin, mutta sekä keyPressed()- että keyReleased()-metodia kutsutaan minulla jatkuvasti näppäimen ollessa pohjassa. Näppäin siis vapautuu koko ajan ollessaan pohjassa, mutta keyPressed() kirjaa näppäimen aina uudelleen vapautumisen jälkeen, joten useimmiten systeemi toimii oikein. Joskus taas käy niin, että jossain framessa näppäimen pohjassa oleminen jää rekisteröimättä kokonaan.
private int laskuri = 0; private class KeyHandler extends KeyAdapter { public void keyPressed(KeyEvent e) { System.out.println("PRESSED " + e.getKeyCode() + " " + laskuri++); } public void keyReleased(KeyEvent e) { System.out.println("RELEASED " + e.getKeyCode() + " " + laskuri++); } }
Ylläoleva koodi tuottaa seuraavanlaisen tulosteen silloin, kun pidän oikeaa nuolinäppäintä pohjassa:
PRESSED 39 0 RELEASED 39 1 PRESSED 39 2 RELEASED 39 3 PRESSED 39 4 RELEASED 39 5 PRESSED 39 6 RELEASED 39 7 PRESSED 39 8 RELEASED 39 9 PRESSED 39 10 RELEASED 39 11 PRESSED 39 12 RELEASED 39 13 PRESSED 39 14 RELEASED 39 15
Aihe on jo aika vanha, joten et voi enää vastata siihen.