Tetris Vorgehensweise

MaxJava

Mitglied
Hallo zusammen,
ich habe das Spiel Tetris erfolgreich in JavaScript und HTML programmiert und möchte es jetzt auch in Java programmieren. Leider weiß ich nicht genau wie ich vorgehen soll. Welche Klassen / Komponenten kann ich verwenden, um die einzelnen Blöcke zu erzeugen? Und wie genau erfolgt das Verschieben, also das Neuzeichnen? In HTML habe ich einfach absolut positionierte <div>-Tags verwendet und diese per left,right,top und bottom verschoben. Gibt es sonst Dinge, die ich in Java beachten muss?

Ich habe vor die Logik des Spiels aus meinem JavaScript Spiel zu übernehmen, mir ist schon klar, dass es trotzdem viel Arbeit ist und nahezu alles neu programmiert werden muss.

Kann ich das Spiel mit Swing programmieren? Oder sollte ich da etwas anderes verwenden?
 

lord239123

Bekanntes Mitglied
Du kannst das Spiel durchaus mit Swing programmieren.
Empfehlen kann ich dir JPanels. Du solltest selbst für das zeichnen der Komponenten sorgen.
Bevor du anfängst solltest du dich allerdings erstmal nach Spielschleifen erkundigen.
 

RalleYTN

Bekanntes Mitglied
Java:
public final class Game extends JFrame {
    private static final Game GAME = new Game("Mein Spiel");

    private final Canvas canvas = new Canvas();

    private int fps;
    private int fpsLimit = 60;
    private int delta = 10;

    public static void main(String[] args) {
        GAME.setSize(800, 600);
        GAME.setLocationRelativeTo(null);
        GAME.setResizable(false);
        GAME.setDefaultCloseOperation(EXIT_ON_CLOSE);
        GAME.setContentPane(GAME.canvas);
        GAME.setVisible(true);

        int frames = 0;
        long startTimer = System.currentTimeMillis();
        long updateTimer = 0;

        while(true) {
            //Hier werden alle Sachen des Spiels berechnet
            GAME.canvas.repaint();
            try{Thread.sleep(GAME.delta);}catch(InterruptedException e){e.printStackTrace();}
            frames++;
            updateTimer = System.currentTimeMillis();
      
            if(updateTimer - startTimer >= 1000) {
                GAME.fps = frames;
                frames = 0;
                startTimer = System.currentTimeMillis();

                if(GAME.fps > GAME.fpsLimit) {
                    GAME.delta++;
                } else if(GAME.fps < GAME.fpsLimit && GAME.delta > 0) {
                    GAME.delta--;
                }
            }
        }
    }

    public void setFPSLimit(int limit) {
        this.fpsLimit = limit;
    }

    public static final Game getGame() {
        return Game.GAME;
    }

    public final Canvas getCanvas() {
        return this.canvas;
    }

    public int getFPS() {
        return this.fps;
    }

    public int getFPSLimit() {
        return this.fpsLimit;
    }

    public int getDelta() {
        return this.delta;
    }

    public class Canvas extends JPanel {
        public Canvas() {
            this.setFocusable(true);
            this.setLayout(null);
        }

        @Override
        public void paintComponent(Graphics graphics) {
            super.paintComponent(graphics);

            graphics.setColor(Color.BLACK);
            graphics.fillRect(0, 0, getWidth(), getHeight());
            graphics.setColor(Color.WHITE);
            graphics.setFont(new Font(Font.MONOSPACED, 0, 14);

            // Hier wird gerendert
        }
    }
}
Das ist ein von mir geschriebenes Muster für Die Hauptklasse eines Javaspiels. Kannst sie haben, ich progge gerade eine 2D Engine und dann brauch ich es nicht mehr.
 

lord239123

Bekanntes Mitglied
Warum hälst du den Thread in der Klasse an?
Normalerweise freut man sich über eine möglichst hohe FPS Zahl und macht alle Bewegungen von diesen abhängig.
Wenn du z.B. eine Geschwindigkeit von 5 px/s vorgesehen hast dann updatest du in jedem Frame die entsprechende Position um Speed * Zeit pro Frame. So erhälst du bei jeder FPS-Zahl die richtige Geschwindigkeit und musst den Thread nicht anhalten. Du musst dann natürlich in jedem Durchlauf die Zeit berechnen. Wenn ich das richtig gesehen habe berechnest du aber schon deine FPS.
Sowas solltest du bei deiner 2dGameEngine beachten.
Am Anfang der Spielschleife werden Tastendrücke erkannt. Danach werden Positionen und anderes geupdated und zum Schluss wird alles gerendert.
 

RalleYTN

Bekanntes Mitglied
Wie du sehen kannst wird erst berechnet, dann gerendert und dann der Thread für ein paar Millisekunden pausiert damit die CPU keine Epilepsie bekommt und durchbrennt. Zudem ist das Anhalten für "delta"-Millisekunden wichtig weil sonst das Spiel nach ein paar Sekunden eine andere geschwindigkeit fürs Rendern hat. das heisst ungefähr dass wenn ein ball da wäre und er bewegt sich von links nach rechts dann tut er das in unterschiedlichen geschwindigkeiten. Natürlich wären das dann immer 5px die er sich bewegt allerdings könnte er sich entweder 5px 60 Mal in einer Sekunde bewegen und in der nächsten 180 Mal. Ich sorge damit dafür, dass alles gleichmäßig auf 60FPS läuft.
höhere FPS bringen dir sowieso nichts da dein Bildschirm die nicht zeigen kann.
 

lord239123

Bekanntes Mitglied
Wenn du die Geschwindigkeit mit der Time per Frame multiplizierst bewegt sich der Ball bei jeder FPS Zahl gleich schnell.
So wird das in der professionellen Spieleentwicklung auch gemacht.
Vielleicht solltest du dich mal ein wenig mit JMonkey beschäftigen. Viele Sachen sind in 3d ähnlich wie in 2d.
Der Code davon ist Open Source.
 
Zuletzt bearbeitet:

RalleYTN

Bekanntes Mitglied
Meine Methode funktioniert auch und ich hab sie mir angewöhnt also bleibe ich auch dabei. Kann man bei deiner Methode eigentlich eine FPS begrenzung setzen oder ist die einfach nur unbeschränkt?
 

lord239123

Bekanntes Mitglied
Wofür sollte man eine FPS Begrenzung brauchen?
Bei größeren Spieleprojekten freut man sich über jeden Frame pro Sekunde.
Eine Drosselung auf 60 FPS ist meiner Meinung nach sinnlos und die meisten modernen Displays können auch höhere Raten darstellen.
Es funktioniert zwar bei hohen FPS Zahlen, was ist aber bei weniger FPS?
Dann würde dein Ball sich wieder langsamer bewegen da du nur nach unten drosseln kannst.
Kannst du wirklich ausschließen, dass die Rate nie unter 60 fällt?
 

RalleYTN

Bekanntes Mitglied
Wie gesagt man sollte drosseln weil die CPU sonst nen Überschlag macht. Ausschliessen kann man das nie. Wenn jemand nen schlechten PC hat dann fällt die auch schon mal auf unter 60FPS.
 

lord239123

Bekanntes Mitglied
Dann musst du eine Mischung aus meiner und deiner Version machen, damit Bewegungen immer gleich schnell sind.
Der CPU wird dabei aber nichts passieren. Da brauchst du keine Sorgen zu haben.
 

RalleYTN

Bekanntes Mitglied
könntest du den Code der von mir gepostet wurde mal nehmen und deine Methode statt meiner einbauen. Dann kann ich mal gucken, wie ich das mischen kann und dann in meine Engine einbauen. Natürlich poste ich das Ergebnis dann auch hier.
 

lord239123

Bekanntes Mitglied
Java:
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Game extends JFrame{

    private final static Game GAME = new Game();
    private Canvas canvas = new Canvas();
  
    private double tps = 0;
    private boolean isGame = true;
  
    public static void main(String[] args) {  
        GAME.setSize(800, 600);
        GAME.setLocationRelativeTo(null);
        GAME.setResizable(false);
        GAME.setDefaultCloseOperation(EXIT_ON_CLOSE);
        GAME.setContentPane(GAME.canvas);
        GAME.setVisible(true);
      
        GAME.gameLoop();
    }
  
    public void gameLoop(){
        long time1 = System.nanoTime();
        long time2 = 0;
      
        while(isGame){
            testForInput();
            update(tps);
            render();
          
            time2 = System.nanoTime();
            tps = (double)(time2 - time1)/1000000000;
            time1 = time2;
            System.out.println("FPS: " + (1/tps));
        }
    }
  
    public void testForInput(){
        //Hier wird auf Tastendrücke geprüft
    }
  
    public void update(double tps){
        //Objekte werden geupdatet(z.B. Positionen, Rotationen)
        //x += 5 * tps sorgt z.B. dafür, dass sich die x Koordinate um 5 Einheiten pro Sekunde nach rechts verschiebt
    }
  
    public void render(){
        //Hier werden alle Objekte gerendert
    }
  
    public class Canvas extends JPanel {
        public Canvas() {
            this.setFocusable(true);
            this.setLayout(null);
        }
    }

}
 

RalleYTN

Bekanntes Mitglied
Ich habe mal ein paar Test ausgeführt........ Ich hatte dabei den TaskManager offen und naja....... deine Methode hatte bei mir eine CPU Auslastung von knapp 50%....... bei meiner Methode waren es 7%. Allerdings habe ich einen Fehler bei meiner Methode entdeckt: Bei meiner Methode konnte man die Begrenzung nicht wegnehmen ohne dass sich alles ungleichmäßig bewegt. Durch diese nette kleine Lösung geht das jetzt auch:
Java:
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;

import javax.swing.JFrame;
import javax.swing.JPanel;

public final class Game extends JFrame {
  
  private static final Game GAME = new Game("Mein Spiel");
  private final Canvas canvas = new Canvas();

  private double tps;
  private int  fps;
  private int  delta;
  private int  fpsLimit = 60;

  public Game(String title) {
    
     super(title);
   }

   public static void main(String[] args) {
  GAME.setSize(800, 600);
  GAME.setLocationRelativeTo(null);
  GAME.setResizable(false);
  GAME.setDefaultCloseOperation(EXIT_ON_CLOSE);
  GAME.setContentPane(GAME.canvas);
  GAME.setVisible(true);

  int frames = 0;
  long startTimer = System.currentTimeMillis();
  long updateTimer = 0;
  long timer1 = System.nanoTime();
  long timer2 = 0;

  while(true) {
  
  GAME.canvas.repaint();
  timer2 = System.nanoTime();
  GAME.tps = (double)(timer2 - timer1) / 1000000000;
  timer1 = timer2;
  
  if(GAME.fpsLimit != 0) {
    
     try{Thread.sleep(GAME.delta);}catch(InterruptedException e){e.printStackTrace();}
  frames++;
  updateTimer = System.currentTimeMillis();
  
  if(updateTimer - startTimer >= 1000) {
  GAME.fps = frames;
  frames = 0;
  startTimer = System.currentTimeMillis();

  if(GAME.fps > GAME.fpsLimit) {
  GAME.delta++;
  } else if(GAME.fps < GAME.fpsLimit && GAME.delta > 0) {
  GAME.delta--;
  }
  }
  
  } else {
    
     GAME.fps = (int)(1 / GAME.tps);
  }
  
  System.out.printf("FPS: %d | DELTA: %d | TPS: %f\n", GAME.fps, GAME.delta, GAME.tps);
GAME.update(tps);
  }
  }
  
   public void update(double tps) {
    
     //Hier werden alle Sachen des Spiels berechnet
   }

  public void setFPSLimit(int limit) {
    
  this.fpsLimit = limit;
  }

  public static final Game getGame() {
    
  return Game.GAME;
  }

  public final Canvas getCanvas() {
    
  return this.canvas;
  }

  public int getFPS() {
    
  return this.fps;
  }

  public int getFPSLimit() {
    
  return this.fpsLimit;
  }

  public int getDelta() {
    
  return this.delta;
  }

  public class Canvas extends JPanel {
    
  public Canvas() {
    
      this.setFocusable(true);
      this.setLayout(null);
  }

  @Override
  public void paintComponent(Graphics graphics) {
    
      super.paintComponent(graphics);

      graphics.setColor(Color.BLACK);
      graphics.fillRect(0, 0, getWidth(), getHeight());
      graphics.setColor(Color.WHITE);
      graphics.setFont(new Font(Font.MONOSPACED, 0, 14));

      // Hier wird gerendert
    }
  }
}

Lass mal den TaskManager nebenbei offen und mach beim ersten Durchlauf das FPS Limit auf 0 und beim zweiten auf 60. Dann siehst du den klar erkennbaren Unterschied unserer Methoden.
Danke für das Beispiel. Wie gesagt du hast mir geholfen einen Bug zu beseitigen ;)

EDIT: Ehm was zur Hölle? Warum erkennt das Forum keinen Tabulator?
 
Zuletzt bearbeitet:

lord239123

Bekanntes Mitglied
Du musst aber bedenken, dass dabei noch nichts gerendert wird. Anderenfalls sinken die FPS drastisch und auch bei deiner Methode würde die CPU-Auslastung steigen.
Am besten entscheidest du selber, ob du eine Drosselung haben möchtest oder nicht.
Bei mir brauchte das Programm nur 10% der CPU Leistung.
 

Ähnliche Java Themen


Oben