Jump and Run - Grafik,Logik und GUI trennen

Hallo,

Ich programmiere zurzeit ein Jump and Run und versuche es in die Teile GUI, Logik und Grafik aufzuteilen.

Habe zurzeit diese 3 Klassen:

Klasse GUI:
Java:
import javax.swing.JPanel; 
import javax.swing.JFrame;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.ImageIcon;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.Dimension;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import java.io.IOException;
import java.net.URL;

public class GUI extends JPanel implements ActionListener
{       
    KeyCommand key;
    Logik l;
    
    JFrame frame;
    JButton start;
    JLabel luigi;

    Panel p;

    URL uLuigiAnimated;

    ImageIcon luigiAnimated;

    public GUI(Logik l, KeyCommand key){
        this.l = l;
        this.key = key;
        
        loadImages();

        luigi = new JLabel(luigiAnimated);
        luigi.setBounds(70,50,320,240);

        start = new JButton("Start Game!");
        start.setBounds(150,350,150,50);
        start.addActionListener(this);

        setLayout(null);
        setSize(500,550);

        frame = new JFrame();
        frame.setTitle("New Super Luigi Brothers!");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocation(100,100);
        frame.setSize(500,550);
        frame.setResizable(false);

        add(start);
        add(luigi);
        frame.add(this);
        frame.setVisible(true);
    }

    void loadImages() {
        uLuigiAnimated = getClass().getResource("LuigiAnimated.gif");
        luigiAnimated = new ImageIcon(uLuigiAnimated);
    }

    @Override
    public void actionPerformed(ActionEvent e) {  
        if(e.getSource() == start) {
            frame.remove(this);

            p = new Panel(key , l);
            frame.setSize(p.getWidth(),p.getHeight());
            frame.add(p);
        }
    }
    
    class Panel extends JPanel implements Runnable
    {    
        /*Spieler luigi;
        
        URL uLuigiLaeuft;
        URL uLuigiSteht;
        URL uLuigiSpringt;
        URL uLuigiRennt;
        URL uLuigiFaellt;

        BufferedImage luigiLaeuft;
        BufferedImage luigiRennt;
        BufferedImage luigiSteht;
        BufferedImage luigiSpringt;
        BufferedImage luigiFaellt;

        boolean left;
        boolean right;
        boolean up;
        boolean down;
        boolean fast;
        boolean fall;

        long delta;
        long last;
        long fps;
        */
        Logik l;
       

        public Panel(KeyCommand key, Logik l)
        {      
            this.l = l;
            
            setLayout(null);
            setSize(800,500);
   
            addKeyListener(key);
            requestFocus();

            Thread th = new Thread(this);
            th.start();

            l.doInitializations();
        }

        @Override
        public void paintComponent(Graphics g) {   
            super.paintComponent(g); // super Beziehung zur Vaterklasse --> Standard-Implementation des
            // JPanels löscht den kompletten Inhalt des Panels ;)
            l.luigi.paintComponent(g); // Wird direkt erneut gezeichnet ;)
            g.setColor(Color.RED);
            g.drawString("FPS: " + Long.toString(l.fps) ,20,20);
        }

        @Override 
        public void run() {
            while(frame.isVisible()) {
                //calculateFPS();
                //checkKeys();
                
                l.doAll();
                
                repaint();

                try{
                    Thread.sleep(10);
                }
                catch(InterruptedException e) {
                }
            }
        }
Klasse Logik:
Java:
import java.io.IOException;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.net.URL;

public class Logik
{    
    Logik logik;
    
    Spieler luigi;

    boolean left;
    boolean right;
    boolean up;
    boolean down;
    boolean fast;
    boolean fall;

    URL uLuigiLaeuft;
    URL uLuigiSteht;
    URL uLuigiSpringt;
    URL uLuigiRennt;
    URL uLuigiFaellt;

    BufferedImage luigiLaeuft;
    BufferedImage luigiRennt;
    BufferedImage luigiSteht;
    BufferedImage luigiSpringt;
    BufferedImage luigiFaellt;

    long delta;
    long last;
    long fps;
    
    void doAll() {
        calculateFPS();
        checkKeys();
    }
    
    void calculateFPS() {
        delta = System.nanoTime() - last;
        last = System.nanoTime();
        fps = ((long) 1e9/delta);
    }

    void doInitializations() {
        last = System.nanoTime();

        uLuigiLaeuft = getClass().getResource("luigiLaeuft.png");
        uLuigiSteht = getClass().getResource("luigiSteht.png");
        uLuigiRennt = getClass().getResource("luigiRennt.png");
        uLuigiSpringt = getClass().getResource("luigiSpringt.png");

        try {
            luigiLaeuft = ImageIO.read(uLuigiLaeuft);
            luigiSteht = ImageIO.read(uLuigiSteht);
            luigiRennt = ImageIO.read(uLuigiRennt);
            luigiSpringt = ImageIO.read(uLuigiSpringt);
        } catch(IOException e) {
            System.out.println("Fehler " + e.toString());
        }

        luigi = new Spieler(luigiSteht,100,350,1,1);
    }

    void checkKeys() {
        if(left) {
            if(fast) {
                luigi.setDx(-2);
            }
            else {
                luigi.setDx(-1);
            }
            luigi.moveHor();
        }
        if(right) {
            luigi.setImage(luigiLaeuft);
            if(fast) {
                luigi.setDx(2);
            }
            else {
                luigi.setDx(1);
            }
            luigi.moveHor();
        }
        if(up) {
            luigi.moveVert();
            fall = true;
        }
    }
}
Klasse KeyCommand:
Java:
mport java.awt.event.KeyListener;
import java.awt.event.KeyEvent;

public class KeyCommand implements KeyListener
{   
    Logik l;
    
    public KeyCommand(Logik l) {
        this.l = l;
    }
    
    @Override
    public void keyPressed(KeyEvent e) {
        if(e.getKeyCode() == KeyEvent.VK_RIGHT) {
            l.right = true;
            l.left = false;
        }
        else if(e.getKeyCode() == KeyEvent.VK_LEFT) {
            l.left = true;
            l.right = false;
        }
        else if(e.getKeyCode() == KeyEvent.VK_UP) {
            l.up = true;
            l.down = false;
        }
        else if(e.getKeyCode() == KeyEvent.VK_S) {
            l.fast = true;
        }
    }

    @Override
    public void keyReleased(KeyEvent e) {
        if(e.getKeyCode() == KeyEvent.VK_RIGHT) {
            l.right = false;
            l.luigi.setImage(l.luigiSteht);
        }
        else if(e.getKeyCode() == KeyEvent.VK_LEFT) {
            l.left = false;
            l.luigi.setImage(l.luigiSteht);
        }
        else if(e.getKeyCode() == KeyEvent.VK_UP) {
            l.up = false;
        }
        else if(e.getKeyCode() == KeyEvent.VK_S) {
            l.fast = false;
        }
    }

    public void keyTyped(KeyEvent e) {
    }
}
Ich habe mir überlegt, eine Klasse Spiel zu erstellen, in der man das Spiel dann startet. Sollte ungefähr so aussehen:
Klasse Spiel:
Java:
import javax.swing.JPanel; 
import javax.swing.JFrame;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.ImageIcon;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.Dimension;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import java.io.IOException;
import java.net.URL;

public class Spiel
{
    public Spiel() {
        play();
    }

    void play() {
        Logik l = new Logik();
        KeyCommand c = new KeyCommand(l);
        GUI gui = new GUI(l , c);
    }

    class GUI extends JPanel implements ActionListener
    {       
        KeyCommand key;
        Logik l;

        JFrame frame;
        JButton start;
        JLabel luigi;

        Panel p;

        URL uLuigiAnimated;

        ImageIcon luigiAnimated;

        public GUI(Logik l, KeyCommand key){
            this.l = l;
            this.key = key;

            loadImages();

            luigi = new JLabel(luigiAnimated);
            luigi.setBounds(70,50,320,240);

            start = new JButton("Start Game!");
            start.setBounds(150,350,150,50);
            start.addActionListener(this);

            setLayout(null);
            setSize(500,550);

            frame = new JFrame();
            frame.setTitle("New Super Luigi Brothers!");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setLocation(100,100);
            frame.setSize(500,550);
            frame.setResizable(false);

            add(start);
            add(luigi);
            frame.add(this);
            frame.setVisible(true);
        }

        void loadImages() {
            uLuigiAnimated = getClass().getResource("LuigiAnimated.gif");
            luigiAnimated = new ImageIcon(uLuigiAnimated);
        }

        @Override
        public void actionPerformed(ActionEvent e) {  
            if(e.getSource() == start) {
                frame.remove(this);

                p = new Panel(key , l);
                frame.setSize(p.getWidth(),p.getHeight());
                frame.add(p);
            }
        }

        class Panel extends JPanel implements Runnable
        {    
            /*Spieler luigi;

            URL uLuigiLaeuft;
            URL uLuigiSteht;
            URL uLuigiSpringt;
            URL uLuigiRennt;
            URL uLuigiFaellt;

            BufferedImage luigiLaeuft;
            BufferedImage luigiRennt;
            BufferedImage luigiSteht;
            BufferedImage luigiSpringt;
            BufferedImage luigiFaellt;

            boolean left;
            boolean right;
            boolean up;
            boolean down;
            boolean fast;
            boolean fall;

            long delta;
            long last;
            long fps;
             */
            Logik l;

            public Panel(KeyCommand key, Logik l)
            {      
                this.l = l;

                setLayout(null);
                setSize(800,500);

                addKeyListener(key);
                requestFocus();

                Thread th = new Thread(this);
                th.start();

                l.doInitializations();
            }

            @Override
            public void paintComponent(Graphics g) {   
                super.paintComponent(g); // super Beziehung zur Vaterklasse --> Standard-Implementation des
                // JPanels löscht den kompletten Inhalt des Panels ;)
                l.luigi.paintComponent(g); // Wird direkt erneut gezeichnet ;)
                g.setColor(Color.RED);
                g.drawString("FPS: " + Long.toString(l.fps) ,20,20);
            }

            @Override 
            public void run() {
                while(frame.isVisible()) {
                    //calculateFPS();
                    //checkKeys();

                    l.doAll();

                    repaint();

                    try{
                        Thread.sleep(10);
                    }
                    catch(InterruptedException e) {
                    }
                }
            }
        }
    }

    class Logik
    {    
        Logik logik;

        Spieler luigi;

        boolean left;
        boolean right;
        boolean up;
        boolean down;
        boolean fast;
        boolean fall;

        URL uLuigiLaeuft;
        URL uLuigiSteht;
        URL uLuigiSpringt;
        URL uLuigiRennt;
        URL uLuigiFaellt;

        BufferedImage luigiLaeuft;
        BufferedImage luigiRennt;
        BufferedImage luigiSteht;
        BufferedImage luigiSpringt;
        BufferedImage luigiFaellt;

        long delta;
        long last;
        long fps;

        void doAll() {
            calculateFPS();
            checkKeys();
        }

        void calculateFPS() {
            delta = System.nanoTime() - last;
            last = System.nanoTime();
            fps = ((long) 1e9/delta);
        }

        void doInitializations() {
            last = System.nanoTime();

            uLuigiLaeuft = getClass().getResource("luigiLaeuft.png");
            uLuigiSteht = getClass().getResource("luigiSteht.png");
            uLuigiRennt = getClass().getResource("luigiRennt.png");
            uLuigiSpringt = getClass().getResource("luigiSpringt.png");

            try {
                luigiLaeuft = ImageIO.read(uLuigiLaeuft);
                luigiSteht = ImageIO.read(uLuigiSteht);
                luigiRennt = ImageIO.read(uLuigiRennt);
                luigiSpringt = ImageIO.read(uLuigiSpringt);
            } catch(IOException e) {
                System.out.println("Fehler " + e.toString());
            }

            luigi = new Spieler(luigiSteht,100,350,1,1);
        }

        void checkKeys() {
            if(left) {
                if(fast) {
                    luigi.setDx(-2);
                }
                else {
                    luigi.setDx(-1);
                }
                luigi.moveHor();
            }
            if(right) {
                luigi.setImage(luigiLaeuft);
                if(fast) {
                    luigi.setDx(2);
                }
                else {
                    luigi.setDx(1);
                }
                luigi.moveHor();
            }
            if(up) {
                luigi.moveVert();
                fall = true;
            }
        }
    }

    class KeyCommand implements KeyListener
    {   
        Logik l;

        public KeyCommand(Logik l) {
            this.l = l;
        }

        @Override
        public void keyPressed(KeyEvent e) {
            if(e.getKeyCode() == KeyEvent.VK_RIGHT) {
                l.right = true;
                l.left = false;
            }
            else if(e.getKeyCode() == KeyEvent.VK_LEFT) {
                l.left = true;
                l.right = false;
            }
            else if(e.getKeyCode() == KeyEvent.VK_UP) {
                l.up = true;
                l.down = false;
            }
            else if(e.getKeyCode() == KeyEvent.VK_S) {
                l.fast = true;
            }
        }

        @Override
        public void keyReleased(KeyEvent e) {
            if(e.getKeyCode() == KeyEvent.VK_RIGHT) {
                l.right = false;
                l.luigi.setImage(l.luigiSteht);
            }
            else if(e.getKeyCode() == KeyEvent.VK_LEFT) {
                l.left = false;
                l.luigi.setImage(l.luigiSteht);
            }
            else if(e.getKeyCode() == KeyEvent.VK_UP) {
                l.up = false;
            }
            else if(e.getKeyCode() == KeyEvent.VK_S) {
                l.fast = false;
            }
        }

        public void keyTyped(KeyEvent e) {
        }
    }
}
Ich hoffe, dass ich die Aufteilung in Logik,Grafik und GUI richtig gemacht habe. Ich würde jetzt nur gerne wissen wieso sich meine Figur(luigi) nicht bewegt? Ich füge in der Klasse GUI den KeyListener ja hinzu:
Java:
addKeyListener(key);
requestFocus();
Würde mich über Hilfe riesig freuen :)
Vielen Dank für alle die sich die Zeit nehmen und sich das hier wenigstens mal angucken :)

Mfg, Zitrus
 
Hast du schon getestet ob dein KeyListener auch ausgelöst wird? (Konsolenausgabe bzw. Debugausgabe?)

Ansonsten finde ich die Klasse GUI etwas eigentartig aufgebaut.
Einerseits leitet diese von JPanel ab und erzeugt sich selber dann ein JFrame auf welchem diese "GUI" angezeigt werden kann. Beim Starten entfernst du das GUI Objekt vom JFrame und fügst ein Panel Objekt hinzu.
Somit gibt es zwischem dem JFrame und der GUI Klasse keinen direkten Zusammenhang. Daher würde ich die JFrame Erstellung etc. auslagern. auch die Klasse Panel würde ich in eine eigene Datei geben und den Namen ändern.

Beispiel:
MainWindow - erstellt das JFrame (nicht davon ableiten) und fügt JPanels zu diesem hinzu oder nimmt sie weg
MenuPanel - enthält den Start Button usw.
GamePanel - zeigt das eigentliche Spiel

Des Weiteren haben die Bilder in der Logik nichts verloren, das es sich hier um eine UI Sache handelt.

Warum sich deine Figur nicht bewegt kann ich dir nicht sagen, ich sehe aber das du noch eine Klasse Spieler hast.
 
Vielleicht ist dir mit dem Entwurfsmuster MVC (Modell View Controller) geholfen. :) Dadrüber kann man noch einen Front-Controller stellen. Vielleicht hilft dir das ja weiter.
 
@Joose

Zuallererst möchte ich mich bedanken für deine ausführliche Antwort :)

Das Komische ist, dass wenn ich mit dem Debugger durchgegangen bin (Ja der KeyListener wurde ausgelöst/zugewiesen) hat sich die Figur auch bewegt! Ohne Debugger jedoch nicht.

Habe mal versucht deine Vorschläge umzusetzen. Nur weiß ich nicht wie ich das genau machen soll. Habe mal deine 3 vorgeschlagenen Klassen erstellen:

Klasse MainWindow:
Java:
import javax.swing.JFrame;
import javax.swing.JPanel;

public class MainWindow
{
    JFrame frame;
    JPanel aktPanel;

    public MainWindow()
    {        
        frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocation(100,100);
        frame.setVisible(true);
    }

    public void addPanel(JPanel panel) {
        if(aktPanel != null) {
            frame.remove(aktPanel);
        }

        this.aktPanel = panel;

        frame.add(aktPanel);
        frame.setSize(aktPanel.getWidth(),aktPanel.getHeight());
    }
}
Klasse MenuPanel
Java:
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JButton;
import javax.swing.ImageIcon;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.KeyListener;
import java.awt.event.KeyEvent;
import java.net.URL;

public class MenuPanel extends JPanel implements ActionListener
{    
    JButton start;
    JLabel luigi;

    URL uLuigiAnimated;

    ImageIcon luigiAnimated;
    
    MainWindow w;
    Logik l;
    KeyListener key;

    public MenuPanel(MainWindow w, Logik l, KeyListener key){
        this.w = w;
        this.l = l;
        this.key = key;
        
        loadImages();

        luigi = new JLabel(luigiAnimated);
        luigi.setBounds(70,50,320,240);

        start = new JButton("Start Game!");
        start.setBounds(150,350,150,50);
        start.addActionListener(this);

        setLayout(null);
        setSize(500,550);

        add(start);
        add(luigi);
        w.addPanel(this);
    }

    void loadImages() {
        uLuigiAnimated = getClass().getResource("LuigiAnimated.gif");
        luigiAnimated = new ImageIcon(uLuigiAnimated);
    }

    @Override
    public void actionPerformed(ActionEvent e) {  
        if(e.getSource() == start) {
            w.addPanel(new GamePanel(w,l,key)); 
        }
    }
}
Klasse GamePanel
Java:
import javax.swing.JPanel;
import java.awt.image.BufferedImage;
import java.awt.event.KeyListener;
import java.awt.event.KeyEvent;
import java.awt.Graphics;
import java.awt.Color;
import java.net.URL;

public class GamePanel extends JPanel implements Runnable
{    
    URL uLuigiLaeuft;
    URL uLuigiSteht;
    URL uLuigiSpringt;
    URL uLuigiRennt;
    URL uLuigiFaellt;

    BufferedImage luigiLaeuft;
    BufferedImage luigiRennt;
    BufferedImage luigiSteht;
    BufferedImage luigiSpringt;
    BufferedImage luigiFaellt;

    Logik l;
    KeyListener key;
    MainWindow w;

    public GamePanel(MainWindow w, Logik l, KeyListener key)
    {      
        this.l = l;
        this.key = key;
        this.w = w;

        this.addKeyListener(key);
        this.setFocusable(true);
        this.requestFocus();

        setLayout(null);
        setSize(800,500);

        Thread th = new Thread(this);
        th.start();

        l.doInitializations();
    }

    @Override
    public void paintComponent(Graphics g) {   
        super.paintComponent(g); // super Beziehung zur Vaterklasse --> Standard-Implementation des
        // JPanels löscht den kompletten Inhalt des Panels ;)
        l.luigi.paintComponent(g); // Wird direkt erneut gezeichnet ;)
        g.setColor(Color.RED);
        g.drawString("FPS: " + Long.toString(l.fps) ,20,20);
    }

    @Override 
    public void run() {
        while(w.frame.isVisible()) {
            l.doAll();

            repaint();

            try{
                Thread.sleep(10);
            }
            catch(InterruptedException e) {
            }
        }
    }
}
Klasse Spieler:

Java:
import javax.swing.JComponent;
import java.awt.Graphics;
import java.awt.image.BufferedImage;

public class Spieler extends JComponent implements Movable
{
    BufferedImage i;
    int x;
    int y;
    double dx;
    double dy;
    
    public Spieler(BufferedImage i, int x, int y, double dx, double dy)
    {
        this.i = i;
        this.x = x;
        this.y = y;
        this.dx = dx;
        this.dy = dy;
    }
    
    @Override
    public void paintComponent(Graphics g) {
        g.drawImage(i,x,y,this);
    }
    
    public BufferedImage getImage() {
        return i;
    }
    
    public void setImage(BufferedImage i) {
        this.i = i;
    }
    
    public int getX() {
        return x;
    }
    
    public void setX(int x) {
        this.x = x;
    }
    
    public int getY() {
        return y;
    }
    
    public void setY(int y) {
        this.y = y;
    }
    
    public double getDx() {
        return dx;
    }
    
    public void setDx(double dx) {
        this.dx = dx;
    }
    
    public double getDy() {
        return dy;
    }
    
    public void setDy(double dy) {
        this.dy = dy;
    }
    
    @Override
    public void moveHor() {
        if(dx != 0) {
            x += dx;
        }
    }
    
    @Override 
    public void moveVert() {
        if(dy != 0) {
            y += -dy;
        }
    }
}
Zu guter Letzt noch die "Spiel" Klasse in der ich das Spiel starte. (Nicht wundern keine main(String args[]) vorhanden, da ich mit
der Entwicklungsumgebung BlueJ programmiere)

Klasse Spiel:
Java:
public class Spiel
{
    public Spiel() {
        play();
    }
    
    void play() {
        Logik l = new Logik();
        KeyCommand key = new KeyCommand(l);
        MainWindow w = new MainWindow();
        MenuPanel p = new MenuPanel(w,l,key);
    }
}

Ich hoffe, dass es so wenigstens etwas in Ordnung ist :)
Das Problem:
Die Figur bewegt sich immernoch nicht..

PS: Vor der Aufteilung in Logik,Grafik(bzw.GUI) und Listener hatte ich alles in einer Klasse. Damals funktionierte die Bewegung aber! Da dies schnell unübersichtlich habe ich mir eben überlegt alles zu trennen. Nun klappt aber die Bewegung nichtmehr^^

Damals sah meine checkKey() Methode so aus:
Java:
void checkKeys() {
void checkKeys() {
        if(left) {
            if(fast) {
                luigi.setDx(-2);
            }
            else {
                luigi.setDx(-1);
            }
            luigi.moveHor();
        }
        if(right) {
            luigi.setImage(luigiLaeuft);
            if(fast) {
                luigi.setDx(2);
            }
            else {
                luigi.setDx(1);
            }
            luigi.moveHor();
        }
        if(up) {
            luigi.moveVert();
            fall = true;
        }
        this.requestFocus();
    }
Da die checkKey()-Methode im GamePanel lag konnte ich einfach den focus direkt requesten. Nun geht das aber nichtmehr sogut^^

Über Verbesserungsvorschläge und Hilfe würde ich mich freuen :)

@TheDirtyClown
Das MVC schau ich mir mal morgen an :) Hab heute zwar bissl reingeguckt muss aber noch für die Schule lernen :)


Mfg, Zitrus
 
Zuletzt bearbeitet:
Also zunächst glaube ich, dass du einen guten game loop brauchst (hier ein guter Artikel).
Wenn du das hast, kannst du deine Steuerung überarbeiten: Wenn eine Taste gedrückt/losgelassen wird, wird der Bewegungsvektor von Luigi verändert (mithilfe von Vektoraddition/Subtraktion). Pro Tick bewegt sich Luigi dann entsprechend seines Bewgungsvektors (wichtig: Luigi bewegt sich, nicht das Spiel bewegt ihn!). Und da Luigi nun auch selbst weiss, wann er läuft und wann nicht, kann er auch selbst sein Image verändern.
 
Passende Stellenanzeigen aus deiner Region:

Neue Themen

Oben