# Problem mit 2D Spiel



## N0__ESCAPE (29. Sep 2016)

Hey! 
Ich hab (zugegeben n bisschen aus nem Tutorial, aber bin ja noch Anfänger) ein 2D spiel gemacht.
Es ist noch nicht fertig, es ist aber ein schwerwiegender Fehler drin. :C
Hier mal der Code:

Code vom Frame:


```
package Fishi;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;

public class frame extends JFrame implements ActionListener {
   private JButton schliessen;
   private JButton einstellung;

   private JButton ende;

   public static void main (String[]args) throws Exception {
     frame frame = new frame("Fishi");
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
     frame.setSize(521, 379);
     frame.setLocation(650, 300);
     frame.setResizable(false);
     frame.setLayout(null);

     JLabel Hintergrund = new JLabel(new ImageIcon(frame.class.getResource("flashlandscape.bx.fl.2007.png")));
     Hintergrund.setSize(521, 379);
     frame.add(Hintergrund);

     frame.setVisible(true);
   }

   public frame(String title) {
     super(title);

     schliessen = new JButton(new ImageIcon(frame.class.getResource("JMneu.jpg")));;
     schliessen.setBounds(170, 40, 160, 45);
     schliessen.addActionListener(this);
     add(schliessen);

     einstellung = new JButton(new ImageIcon(frame.class.getResource("JMneu3.jpg")));;
     einstellung.setBounds(170, 120, 160, 45);
     einstellung.addActionListener(this);
     add(einstellung);


     ende = new JButton(new ImageIcon(frame.class.getResource("JMneu2.jpg")));;
     ende.setBounds(170, 200, 160, 45);
     ende.addActionListener(this);
     add(ende);

     JLabel label = new JLabel("(V 0.1)");
     label.setBounds(475,330,150,20);
     add(label);
   }

   @Override
   public void actionPerformed(ActionEvent e) {
     if (e.getSource() == schliessen) {
       fenster();
     }
     if (e.getSource() == einstellung) {
       //  auswahl();
     }
     if (e.getSource() == ende) {
       System.exit(0);
     }
   }

   public static void fenster() {
     JFrame fenster = new JFrame("Fishi");
     fenster.setSize(812, 512);
     fenster.setLocation(100, 100);
     fenster.setResizable(false);
     fenster.setDefaultCloseOperation(fenster.EXIT_ON_CLOSE);
     fenster.add(new Game());
     fenster.setVisible(true);
   }
}
```

Code vom Game:


```
package Fishi;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

import javax.swing.ImageIcon;
import javax.swing.JPanel;
import javax.swing.Timer;

public class Game extends JPanel implements ActionListener
{

   Image img2;
   Image img;
   int key;
   int lauf;
   int X_bild;
   int nx,nx2;
   int anzahl = 0;
   int anzahl2 = 0;
   int verzögerung = 10;

   int figur_y = 335;

   int left;

   Timer time;

   public Game()
   {
     nx = 0;
     nx2 = 1024;

     key = 0;
     lauf = 0;
     setFocusable(true);
     ImageIcon u = new ImageIcon(Game.class.getResource("game_background.jpg"));
     img = u.getImage();

     ImageIcon i = new ImageIcon(Game.class.getResource("fishi.png"));
     img2 = i.getImage();

     addKeyListener(new al());

     time = new Timer(5, this);
     time.start();

     Sprung sprung = new Sprung();
   }

   public void actionPerformed(ActionEvent e)
   {
     bewegen();
     figur_y = Sprung.sprungposition;
     repaint();
   }

   public void paint(Graphics g){
     super.paint(g);
     Graphics2D f2 = (Graphics2D)g;

     if(getXbild() <= -1024){
       X_bild+= 1024;
     }
     f2.drawImage(img, X_bild, 0, null);
     f2.drawImage(img, X_bild + 1024, 0, null);

     if(X_bild >= -1024){
       X_bild-= 1024;
     }
     f2.drawImage(img2,left, figur_y, null );
   }

   private int getXbild()
   {
     return X_bild;
   }

   public void bewegen()
   {
     if (lauf != -2)
     {
       if (left + lauf <= 230)
       {
         left+= lauf;
       }
       else
       {
         X_bild -= lauf;
         nx -= lauf;
         nx2 -= lauf;
       }   
     }
     else
     {
       if(left - lauf < 0)
       {
         left -= lauf;
       }
     }
   }

   private class al extends KeyAdapter
   {
     public al()
     {
     }

     public void keyPressed(KeyEvent e)
     {
       key = e.getKeyCode();

       if (key == KeyEvent.VK_LEFT)
       {
         lauf = -1;
       }
       if (key == KeyEvent.VK_RIGHT)
       {
         lauf = 1;
       }
       if (key == KeyEvent.VK_SPACE)
       {
         Sprung();
       }
     }
     
     public void keyReleased(KeyEvent e)
     {
       key = e.getKeyCode();
       if (key == KeyEvent.VK_LEFT || key == KeyEvent.VK_RIGHT)
       {
         lauf = 0;
       }
     }
   }

   public void Sprung()
   {
     Sprung SprungAnimation = new Sprung();
     SprungAnimation.start();
   }

   public static void main(String[] args)
   {
   }
}
```

Code von der Sprung animation:


```
package Fishi;

public class Sprung extends Thread
{
   static boolean fertig = true;
   static boolean hochpunkt = false;

   int sprunghoehe = 85;
   static int ursprungY = 335;
   static int sprungposition = ursprungY;

   public Sprung()
   {
   }

   public void run()
   {
     fertig = false;
     int verzögerung = 4;
     while(fertig == false)
     {
       Sprung();
       try
       {
         Thread.sleep(verzögerung);
       }
       catch(Exception e)
       {
       }
     }
     hochpunkt = false;
   }

   public void Sprung()
   {
     if(hochpunkt == false)
     {
       sprungposition--;
     }

     if(sprungposition == (ursprungY - sprunghoehe))
     {
       hochpunkt = true;
     }
     if(hochpunkt == true && sprungposition <= ursprungY)
     {
       sprungposition++;
       if(sprungposition == ursprungY)
       {
         fertig = true;
       }
     }
   }
}
```



Wenn man das alles zsm laufen lässt, funktioniert allles, wenn man aber die Leertaste gedrückt hält, buggt die figur total rum.... hat jmd ne idee? ich verzweifel schon xD

VG
N0__ESCAPE


----------



## Joose (29. Sep 2016)

N0__ESCAPE hat gesagt.:


> .... wenn man aber die Leertaste gedrückt hält, buggt die figur total rum.... hat jmd ne idee? ich verzweifel schon xD


"buggt .. total rum" ist keine Fehlerbeschreibung.
Was sollte die Figur machen? Was macht sie stattdessen? Was hast du bisher versucht um hinter die Ursache zu kommen?


----------



## N0__ESCAPE (29. Sep 2016)

Also wenn man normal die Leertaste drückt, springt die Figur wie sie soll.
aber nach ungefähr 3 bis 5 sekunden dauerhaftem halten der leertaste springt die figur zwar immer noch aber viel schneller und unkontrollierter.

Bisher habe ich versucht nach den listener den thread sleepen zu lassen, dass man nicht leertaste spammen kann, etwas anderes ist mir nicht eingefallen :C


----------



## Joose (29. Sep 2016)

Wenn ich mich richtig erinnere löst dein KeyAdapter sehr oft auf wenn du dauerhaft auf einer Taste bleibt. 
Bei der Leertaste wird dann jedesmal ein neues Threadobjekt erstellt, du hast dann einfach x Threads parallel laufen. Jeder führt seine Berechnung durch und da du statische Variablen verwendest überschreiben sich die Werte immer gegenseitig -> unkontrolliertes hüpfen.

Mögliches Lösung: Erstelle immer nur ein Threadobjekt für den Sprung, sofern das noch aktiv ist kein neues erstellen.

Tipp: Lies dir mal Informationen zur GameLoop durch

Anmerkungen zu deinem Code: Man sollte nicht von JFrame erben siehe java - Why shouldn't you extend JFrame and other components? - Stack Overflow
Klassennamen sollten in UpperCamelCase geschrieben werden
Anstatt deine Klasse Sprung von Thread erben zu lassen solltest du stattdessen das Runnable Interface implementieren, hier gilt ähnliches wie beim Erben vom JFrame -> du erweiterst nicht die Funktionalität der Klasse
Eine Methode sollte niemals so heißen wie die Klasse in der sie sich befindet (siehe Klasse Sprung)
Wozu brauchst du deine statische Methode "fenster"? Tausche doch einfach den Content am vorhanden JFrame aus
Vermeide statische Variablen  bereitet mehr Probleme als sie lösen


----------



## N0__ESCAPE (29. Sep 2016)

Danke, ich versuch deine antwort erstmal zu verstehen! xD


----------



## Major_Sauce (29. Sep 2016)

Es gibt einen sehr viel einfacheren Weg. Nutze einfach statt dem "KeyAdapter" den "Key Listener".
Achtung, der Key Listener wird per implements, nicht per extends angebunden, da dies ein Interface ist.
Dieser enthält 3 Methoden, keyPressed(), keyReleased() und keyTyped().
KeyTyped kannst du komplett außer acht lassen.
KeyPressed wird ausgeführt sobald der Knopf den Zustand auf "gedrückt" ändert, dann kannst du deine Jump methode ausführen und fertig. Du solltest auch noch eine Art Begrenzung einbauen. Dann brauchst du eigentlich nur noch einen indikator ob der Charakter bereits Springt, denn wenn du gerade den Sprung ausführst, magst du ja nicht nochmal springen können. In der Sprung klasse setzt du zum Bleistift einen boolean auf true wenn der charakter gerade springt, wenn der Sprung vollendet ist setzt du diesen boolean wieder auf false. Dann frägst du in der KeyPressed methode ab, ob der boolean auf false ist, falls ja, führst du einen Sprung aus, falls nein, machst du einfach gar nichts.
Hier mal ein wenig "pseudo code", habe das gerade mal direkt in dem Editor geschrieben, könnte also sein dass da ein paar fehler drin sind, aber in etwa so könnte es aussehen:

```
private class InputHandler implements KeyListener {
   
    private boolean isCurrentlyJumping = false;
   
    public void KeyPressed(KeyEvent e){
        if(!isCurrentlyJumping){
            //Löse einen Sprung aus, wie auch immer das bei dir geht. Bsp:
            character.jump();
        }
    }

    public void setCurrentlyJumping(boolean currentlyJumping){
        this.isCurrentlyJumping = currentlyJumping;
    }

    //lassen wir einfach mal so
    public void KeyReleased(KeyEvent e){
    }

    //lassen wir einfach mal so, wir handeln alles über die anderen zwei
    public void KeyTyped(KeyEvent e){}

}
```

Das einzige was du noch machen müsstest, wäre zu Beginn des Sprungs die "setCurrentlyJumping(true)" auszuführen und dann beim Beenden des Sprungs "setCurrentlyJumping(false)". Das wärs.

Das ist definitiv nicht die eleganteste Lösung, aber ich glaube alles kompliziertere würde dich vorerst nicht weiterbringen weil man dann nicht mehr weiß wie man das bei sich einfügt. Ging mir zumindest immer so...

Mfg Major


----------



## N0__ESCAPE (30. Sep 2016)

hey 
danke, dass du dir so viel zeit genommen hast, wenn ich aber oben statt dem Action den KEy listener implementiere, so geht der Timer nicht mehr...

mfg


----------



## Joose (30. Sep 2016)

Was soll denn der Timer machen?


----------



## N0__ESCAPE (30. Sep 2016)

Der is für die Bewegungs geschwindigkeit des charakters da. deszu höher der is deszu langsamer der charakter.


----------



## Joose (30. Sep 2016)

Ok wie schon oben geschrieben, solltest du dir vielleicht mal etwas zur GameLoop durchlesen 
Und statt dem Timer diese verwenden


----------



## N0__ESCAPE (30. Sep 2016)

Joose hat gesagt.:


> Mögliches Lösung: Erstelle immer nur ein Threadobjekt für den Sprung, sofern das noch aktiv ist kein neues erstellen



okay, das versuche ich jetzt.
Ich hab in der Game Methode den boolean nureinsprung = false
erstellt.
dann hab ich vor

```
if (key == KeyEvent.VK_SPACE)
            {
                    Sprung();              
            }
```

zu

```
if (key == KeyEvent.VK_SPACE)
            {
                if (nureinsprung == true)
                {
                    Sprung();
                }              
            }
```

umgewandelt. problem: ich dachte mir,beim ersten mal kommt die anwendung durch den boolean,
dann gehts in die sprung methode und dort wird am anfang der boolean auf false gesetzt, am ende auf true. nur leider geht das nicht so übergreifend. :C

und mir fällt grad was auf:
in der sprung methode

```
package Fishi;

public class Sprung extends Thread
{
     static boolean fertig = true;
     static boolean hochpunkt = false;
   
     int sprunghoehe = 85;
     static int ursprungY = 335;
     static int sprungposition = ursprungY;
   
    public Sprung()
    {          
    }
  
    public void run()
    {      
        fertig = false;      
      
        int verzögerung = 4;
        while(fertig == false)
        {
            Sprung();          
            try
            {
                Thread.sleep(verzögerung);
            }
            catch(Exception e)
            {
              
            }
        }  
        hochpunkt = false;
    }
  
    public void Sprung()
    {
        if(hochpunkt == false)
        {
            sprungposition--;          
        }
      
        if(sprungposition == (ursprungY - sprunghoehe))
        {
            hochpunkt = true;
        }
        if(hochpunkt == true && sprungposition <= ursprungY)
        {
            sprungposition++;
            if(sprungposition == ursprungY)
            {
                fertig = true;              
            }
        }
    }
}
```

habe ich ja eigentlich mit dem boolean fertig genau das gemacht oder? die anwendung kann den sprung nur nochmal durchlaufen, wenn der sprung fertig ist.?


----------



## Joose (30. Sep 2016)

N0__ESCAPE hat gesagt.:


> ```
> public void run()
> {
> fertig = false;
> ...


Nein denn wie schon oben gesagt löst dein KeyAdapter öfters aus und erstellt jedes mal ein neues Sprung Objekt. Direkt am Anfang der run Methode setzt du fertig auf false, womit die while-Schleife durchlaufen kann. Und da die Variable "fertig" statisch ist verwenden alle Threads die gleiche Variable, obwohl der eine Thread meint er ist fertig kann es passieren dass er dann trotzdem weiterläuft.

Und noch eine kleine Anmerkung: Du solltest einen catch-Block niemals leer lassen! Sonst entgehen dir wahrscheinlich Fehler die dir viel Arbeit ersparen könnten.


----------



## N0__ESCAPE (30. Sep 2016)

aaaah okay ich habs verstanden ! 
gäbe es irgendne einfache möglichkeit dass der adapter nur einmal ausgelöst werden kann?
(also bis der sprung zu ende ist, so wie in mario halt xD)


----------



## Joose (30. Sep 2016)

Nein das kannst du nicht beeinflussen.
Aber ich habe weiter oben schon geschrieben was du stattdessen machen könntest. Mit der Variable "fertig" lagst du schon irgendwie richtig  aber du machst das ganze an der falschen Stelle und das static machts sowieso kaputt. Du solltest schon in der Klasse "Game" dafür sorgen das es nur einen Sprung geben kann.

Am Ende würde ich immer noch zur GameLoop raten


----------



## N0__ESCAPE (30. Sep 2016)

GameLoop hab ich gegoogelt, ist mir aber noch zu kompliziert (ist mein erstes Spiel, programmiere seit 2 wochen mit java)
dann werd  ich noch die richtige stelle suchen


----------



## Joose (30. Sep 2016)

N0__ESCAPE hat gesagt.:


> GameLoop hab ich gegoogelt, ist mir aber noch zu kompliziert


Eine simple GameLoop ist nichts anderes als eine simple while Schleife die in einem Thread läuft 

```
while(running) {
    handleInput(); // vearbeitet klicks und tasten
    doLogic(); // berechnet alles mögliche
    render(); // zeichnet den neuen status des Spiels
}
```



N0__ESCAPE hat gesagt.:


> (ist mein erstes Spiel, programmiere seit 2 wochen mit java)


Nach 2 Wochen Java ist ein Spiel vielleicht nicht gerade die beste Art zu üben, da hier so viel unterschiedliche Themen gefragt sind welche man noch nicht alle beherrschen kann.
Auch wenn es heißt "learning by doing" sollte man sich langsam rantasten und Thema für Thema behandeln nicht alle gleichzeitig.



N0__ESCAPE hat gesagt.:


> dann werd  ich noch die richtige stelle suchen


Die richtige Klasse habe ich dir ja schon genannt


----------



## N0__ESCAPE (30. Sep 2016)

Joose hat gesagt.:


> Nach 2 Wochen Java ist ein Spiel vielleicht nicht gerade die beste Art zu üben, da hier so viel unterschiedliche Themen gefragt sind welche man noch nicht alle beherrschen kann.
> Auch wenn es heißt "learning by doing" sollte man sich langsam rantasten und Thema für Thema behandeln nicht alle gleichzeitig.



hab genau das auch gemacht. habe schon viele kleine programme selbst (OHNE google) geschrieben, unter anderem einen kleinen Rechner, BubbleSort, usw.


----------



## Major_Sauce (3. Okt 2016)

Trotzem macht eine Loop sinn.
Wie gesagt, es ist im Grunde nur eine While schleife die dann von mir aus erstmal nur zwei methoden aufruft, "update" und "render", kannst diese natürlich auch umbenennen, zum Bleistift "do_logic" und "draw". Wichtig ist, dass beides von einander separiert wird. Es gibt einen part, in dem Berechnungen stattfinden, dann gibt es den Part in dem gezeichnet wird. 
Und für den Sprung einen eigenen Timer, nicht ganz elegant und ich würde sogar mal behaupten dass eine Loop einfacher ist. Hier ein Beispiel:

Du hast 5 verschiedene Objekte welche sich alle bewegen sollen. Für jede Bewegung baust du einen Timer. 
Mal ganz davon abgesehen, dass die Timer nicht immer ganz genau gleichschnell sind, müsstest du das ganze fünf mal machen.

Wenn du für die 5 Objekte eine Loop baust, musst du in der Loop nur sagen dass dieses Objekt nun bewegt werden soll (ich nenne es Update). In der Update-Methode des Objekts steht dann fürs erste mal nur, dass sich das Objekt von mir aus um 5 Pixel nach rechts bewegt. Das wars.

Mfg Major


----------

