Probleme beim Objekzugriff auf andere Klasse: NullPointerExc

Status
Nicht offen für weitere Antworten.

milchkaffee

Mitglied
Guten Tag allseits,


ich bin ein rechter Java-Neuling und versuche mich derzeit an einem Pong-Klon für ein Projekt - ich habe auch schon ein lauffähiges Applet produziert, mit 3 Klassen:

  • JApplePong (Hauptklasse)
    Ball -> ball1 (zeichnet und bewegt Ball und beinhaltet Kollisionsabfrage)
    Pad -> pad1 (zeichnet Spielpads und bewegt linken Pad mittels Mouse und rechten Pad automatisch)

Leider habe ich das Konzept der Objektorientierung noch nicht so ganz durchdrungen und musste einige 'Krücken' einbauen (Übergabefunktionen in der Hauptklasse), damit ich die Position der Pads an die Klasse Ball übergeben und so eine Kollision ermitteln kann (und umgekehrt!). Die Kollisionsabfrage funktioniert zwar eher schlecht als recht, aber damit möchte ich mich später befassen.

Ich möchte auch noch eine separate Kollisionsklasse schreiben und die Ball-Klasse und die Hauptklasse vereinfachen, doch zunächst wollte ich eine 'Score'-Klasse zur Ermittlung der Punktestände einführen; nur bekomme ich leider 'NullPointerExceptions', wenn ich z.B. bei einer Kollision von einer Klasse heraus (Ball / ball1) eine statische Variable (playerScore1) in einer anderen Klasse (Score / gameScore) mit Hilfe einer Methode dieser Klasse (setGameScore()) um 1 erhöhen will (oder wenn ich aus der Score-Klasse heraus eine Methode der Ball-Klasse aufrufen will, die die Punkte von dort abruft):


Exception in thread "AWT-EventQueue-1" java.lang.NullPointerException
at jApplets.pong.Ball.setPlayerScore(Ball.java:93)
at jApplets.pong.Ball.bewege(Ball.java:471)
at jApplets.pong.JApplePong$gScreen.paintComponent(JApplePong.java:190)
at javax.swing.JComponent.paint(JComponent.java:1027)
at javax.swing.JComponent.paintChildren(JComponent.java:864)
at javax.swing.JComponent.paint(JComponent.java:1036)
.
.

Dieser Fehler tritt immer dann auf, wenn der Ball auf die linke oder rechte Spielgrenze (den Zeichenrahmen) trifft; seltsamerweise nur dann, wenn ich das Applet mit 'run file' direkt in Netbeans starte. Manchmal bleibt der Zeichenbereich dann auch weiß und es hagelt NullPointerExceptions -
nur bei einem view-Aufruf der 'index.html' mit Start im Browser funktionieren Applet und Punktezähler oberflächlich, wenn mir da nicht eine frühere Appletversion aus dem Cache einen Streich spielt, wobei ich den eigentlich gelöscht hatte...
Äußerst seltsam: nachdem ich vorgestern die Score-Klasse eingeführt hatte und der besagte Fehler aufgetreten war, habe ich abgebrochen und am nächsten Tag weitergemacht - wobei dann plötzlich kein Fehler mehr auftrat, aber nur die ersten paar Male mit Start im Browser!


Jedenfalls, hier einige Auszüge aus dem Code (entschuldigt bitte den 'Baustellen-Charakter'):
JApplePong.java (Hauptklasse)
Code:
public class JApplePong extends javax.swing.JApplet {

    /*  Deklaration der Variablen, Objekte und Klassen
     * 
     */
    
    //  Variablen festlegen
    int screenXMax, screenYMax, screenXMin = 0, screenYMin = 0;
    int playerScore1, playerScore2;
    boolean startup = true;
    private boolean debug = false;

    //  Deklaration des Animations-Threadobjekts 'ballAnimeThread'
    AnimeThread ballAnimeThread;
    
    //  Deklaration/Erzeugung der grafischen Klassenobjekte
    Score gameScore;
    Ball ball1;
    Pad pad1 = new Pad(50, 50, 10, 90);
    Pad pad2 = new Pad(310, 50, 10, 90);
    
    //  Rendering auf Antialiasing setzen
    RenderingHints rh = new RenderingHints(
                    RenderingHints.KEY_TEXT_ANTIALIASING,
                    RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
    
    //  Thread-Klasse 'AnimeThread' - für Animation
    private class AnimeThread extends Thread {
        @Override
        public void run() {
            //  Neuzeichnen der Zeichenobjekte in einer Endlosschleife 
            //  mit Verzögerung von (x)ms 
            while (true) {
                if (Thread.interrupted()) break;
                repaint();
                pause(18);
            }
        }
    }


@Override
    public void init() {
        try {
            java.awt.EventQueue.invokeAndWait(new Runnable() {
                public void run() {
                    initComponents();
                    gScreen.requestFocus();
                }
            });
            
            //  'ballAnimeThread'-Threadobjekt erzeugen starten 
            //  (neuer Thread mit minimaler Priorität für min. CPU-Last)
            ballAnimeThread = new AnimeThread();
            ballAnimeThread.setPriority(Thread.MIN_PRIORITY);
            ballAnimeThread.start();
        } 
        catch (Exception ex) { ex.printStackTrace(); }
    }
    
    //  Deklaration der erweiterten JPanel-Klasse 'gScreen' (incl. Maus-Listener)
    private class gScreen extends JPanel implements MouseMotionListener {
        @Override
        /** automatisches Auffrischen der graphischen Komponenten 
         *  mittels Aufruf der Funktion 'paintComponent' bzw. 
         *  durch Überschreiben ihrer Superklasse mit 'g'
         */
        public void paintComponent(Graphics g) {
            Graphics2D g2 = (Graphics2D) g;
            //  Rendering mit Antialiasing für bessere Zeichenqualität und flüssige Animation 
            g2.setRenderingHints(rh);
            super.paintComponent(g2);
            
            //  Initilisierung
            if ( startup == true ) {
                
                //  Hintergrundfarbe setzen und Grenzen des Zeichenrahmens ermitteln
                //  des Zeichenrahmens mit Einrechnung des Ballradius + 1
                gScreen.setBackground(Color.darkGray);
                screenXMax = getWidth();
                screenYMax = getHeight();
                
                //  Erzeuge Ball-Objekt 1
                ball1 = new Ball((int)screenXMax/2, (int)screenYMax/2, 30);
                //ball1.resetBall();
                gameScore = new Score(screenXMin, screenYMin, screenXMax, screenYMax);
                
                //  Hinzufügen von Mausbewegungs-Listener für aktuelle Klasse
                addMouseMotionListener(this);
            
                startup = false;
            }
            
            //  Anzeige der Spielerpunkte
            //playerScore1 = ball1.getPlayerScore(1);
            //playerScore2 = ball1.getPlayerScore(2);
            gameScore.countScores(g2);           [b][color=red]<<<<<<<<<<< Fehlerquelle[/color][/b]                                                            

            //  Textmeldung in graphische Komponente 'g2' schreiben
            g2.drawString("\nPick and drag the ball via mouse - \n" +
                "Hit SPACE to exit.", (int)screenXMax/2 - 150, (int)screenYMax - 20);

            //  Zeichnen der Objekte
            pad1.setPadStops(screenYMin,screenYMax);
            pad2.setPadStops(screenYMin,screenYMax);
            pad2.setPadStartPos(screenXMax - 50, (int)screenYMax/2);
            pad1.zeichne(g2);
            pad2.zeichne(g2);
            
            ball1.setBorders(screenXMin, screenXMax, screenYMin, screenYMax);
            ball1.zeichne(g2);
            ball1.bewege(g2);
            
            //  Krücken: 
            //  ball1.receivePad (hole aktuelles Pad aus pad1 und reiche an ball1 weiter)
            //  ball1.receivePad(pad1.getPadLB(), pad1.getPadUB(), pad1.getPadW(), pad1.getPadH());
            ball1.receivePad1(pad1.getPad()[0], pad1.getPad()[1], pad1.getPad()[2], pad1.getPad()[3]);
            ball1.receivePad2(pad2.getPad()[0], pad2.getPad()[1], pad2.getPad()[2], pad2.getPad()[3]);
            
            //pad1.receiveBall(ball1.getBall()[0], ball1.getBall()[1], ball1.getBall()[2], ball1.getBall()[3], ball1.getBall()[4]);
            pad2.receiveBall(ball1.getBall()[0], ball1.getBall()[1], ball1.getBall()[2], ball1.getBall()[3], ball1.getBall()[4]);
            //pad1.receivePadBounce(ball1.getPadBounce()[0], ball1.getPadBounce()[1]);
            //pad2.receivePadBounce(ball1.getPadBounce()[0], ball1.getPadBounce()[1]);
            pad1.receivePadBounce(ball1.isCollision(1)[2], ball1.isCollision(1)[3]);
            pad2.receivePadBounce(ball1.isCollision(2)[2], ball1.isCollision(2)[3]);
            
            pad2.botPlayer();
.
.

Score.java
Code:
public class Score {
    int screenXMax, screenYMax, screenXMin = 0, screenYMin = 0;
    int playerScore1, playerScore2;
    Ball ball1;
    Pad pad1;
    
    Score(int screenXMin, int screenYMin, int screenXMax, int screenYMax) {
        this.screenXMin = screenXMin;
        this.screenYMin = screenYMin;
        this.screenXMax = screenXMax;
        this.screenYMax = screenYMax;
    }

    void countScores( Graphics2D g2 ) {
        //playerScore1 = ball1.getPlayerScore(1);
        //playerScore2 = ball1.getPlayerScore(2);
        playerScore1 = ball1.getPlayerScore(1);                      [b][color=darkred]<<<<<<<<<<<<<< Fehlerquelle[/color][/b]
        playerScore2 = ball1.getPlayerScore(2);                      [b][color=red]<<<<<<<<<<<<<< Fehlerquelle[/color][/b]
        
        g2.setColor(Color.WHITE);
        g2.drawString("Punkte Spieler: " + playerScore1, screenXMin + 10, screenYMin + 50);
        g2.drawString("Punkte Computer: "+ playerScore2, screenXMax - 120, screenYMin + 50);
        //g2.drawString("Sie haben verloren.", ((int)screenXMax/2) - 50, (int)screenYMax/2);
        //System.out.println(Thread.currentThread().getName());
        if (playerScore1 == 5 || playerScore2 == 5) {
            //try { 
                if (playerScore1 == 5) {
                    g2.drawString("Punkte Spieler: " + playerScore1, screenXMin + 10, screenYMin + 50);
                    g2.drawString("Sie haben gewonnen!", ((int)screenXMax/2) - 50, (int)screenYMax/2);
                    //pause(1500);
                    System.out.println("P1scored");
                }
                if (playerScore2 == 5) {
                    g2.drawString("Punkte Computer: " + playerScore2, screenXMax - 120, screenYMin + 50);
                    g2.drawString("Sie haben verloren.", ((int)screenXMax/2) - 50, (int)screenYMax/2);
                    //pause(1500);
                    System.out.println("P2scored");
                }
                //Thread.sleep(1500);
                //pause(1500);

                ball1.resetPlayerScores();
                ball1.resetBall();
            //}
            //catch (InterruptedException e) {}
        }
    }

In dieser Version lasse ich die Punkte noch in der Ballklasse zählen und hole sie von dort ab, mit :
Ball.java
Code:
.
.
class getPlayerScore {

        public int getPlayerScore(int ply) {
            int ps = 0;
        if (ply == 1) ps = playerScore1;
        else if (ply == 2) ps = playerScore2;
        return ps;
        }
 }
.
.
In einer neueren Version rufe ich bei Erhöhung der Spielpunkte in der Ballklasse nur eine Methode in der Scoreklasse auf:
'setPlayerScore(gameScore,1);'
[/code]
Wobei ich im Deklarationsteil der Ballklasse ein Referenzvariable auf das Exemplar 'gameScore' der Scoreklasse Bezug nehme:
Code:
Score gameScore;



Und ich nehme an, dass genau hier mein Denkfehler liegt; jedenfalls scheint bei beiden Versionen ein Verweis auf eine leere Referenz stattzufinden, wenn ich das richtig sehe - meine Funktionsaufrufe zeigen nicht auf das richtige Exemplar der Klasse...?

Die zentrale Frage: wie schaffe ich es, dass ich von anderen Klassen auf das richtige Exemplar meiner Nebenklassen (ball1, pad1, gameScore,...) und dort auf die gewünschten Objekte / Methoden zugreifen kann?


Weitere Problemchen:

Ich dachte mir, es wäre vielleicht im Sinne der Performance besser, alle Zeichen-Funktionen aus den Nebenklassen ganz herauszunehmen und nur relevante Werte für die Hauptklasse bereitzustellen - dann könnte ich mir u.a. die Übermittlung der 'Graphics2D'-Objkte sparen, doch ich glaube für den besagten Fehler oben ist eher eine fehlerhafte Zugriffsmethode auf klassenfremde Objekte verantwortlich...?

Derzeit kann ich keine 'gewonnen/verloren'-Meldung auf dem Bildschirm darstellen, während der Ball mit Thread.sleep() eingefroren wird (weil auch die Text-Zeichnung angehalten wird?), außerdem wird Punktestand '5' nicht mehr angezeigt.

Wenn ich das richtig erkannt habe, wäre mit der 'Shape'-Klasse eine weitaus sicherere Kollisionserkennung möglich; derzeit muss ich sogar zweimal Prüfen, in der Ball- und in der Padklasse (falls die Maus schneller als der Ball ist). Aber mit einer gesonderten Kollisionsklasse wäre das vielleicht auch nicht das Thema.

Momentan springt der Ball vom oberen und unteren Padrand manchmal in gleicher Richtung zurück und 'gleitet' vor allem an der Ober- und Unterkante der Pad entlang, statt abzuprallen.

Beim Start im Browser hat die System.exit()-Funktion keine Wirkung, bzw. das Spiel bricht nicht ab.

Der Mauszeiger bleibt immer sichtbar (auf Höhe des oberen Padrandes), was sehr irritierend wirkt und 'Hänger' beim Ein- und Austreten des Zeigers aus dem Zeichebereich zur Folge hat



Ich bin für jede Hilfe dankbar, vor allem zu meinem zentralen Problem des richtigen Objektzugriffs auf klassenfremde Objekte und Variablen!!

Vielen Dank im Voraus,

milchkaffee[/quote]
 
P.S. ich habe mir auch bereits die Tutorials in den FAQs durchgelesen, z.B.
Zugriff: von einem Objekt zum Anderen
Arten von Klassen
Konstruktoren, Dekonstruktoren, Rückgabewerte
Das Problem nur: ich bin mir absolut nicht im Klaren darüber, welcher dieser Ansätze für meinen Zweck am geeignetsten oder überhaupt anwendbar ist... bitte helft!

milchkaffee [/quote]
 
du musst dein Programm entschlanken, um den Fehler genau zu identifizieren,

hunderte Zeilen unnötiger Code wie
> g2.drawString("Punkte Spieler: " + playerScore1, screenXMin + 10, screenYMin + 50);
> g2.drawString("Punkte Computer: "+ playerScore2, screenXMax - 120, screenYMin + 50);

sorgen höchstens dafür, dass keiner hier liest

----

schlecht ist auch Beschreibung a la 'vor zwei Tagen hatte ich Fehler x, gestern Fehler y, seit dem noch 4 Sachen verändert,
wenn man es auf Weis z startet dann passiert a usw'

poste ein aktuelles Programm mit Anleitung zum Fehler und die aktuellen Probleme

---

Applets sind immer schlecht zu testen, wenn möglich versuche erstmal JFrames,

-------


ganz grob vermutet:
in Zeile 46 startest du einen Thread, der irgendwas initialisiert,
in 55 einen Thread, der auf die Komponenten zugreift? zumindest über repaint?

du verwendest zwar invokeAndWait(), aber das hat vielleicht keine Wirkung, wenn du schon im AWT-Event-Thread bist

baue in jedem Fall in dein Programm an richtigen Stellen Ausgaben ein a la
System.out.println("führe nun initComponents() durch");
System.out.println("setzt Ball x in y");

System.out.println("greife gleich auf Ball x in y");
NullPointerException


wenn die Zugriffs-Ausgabe VOR der setze-Ausgabe erscheint, bzw. die setzt-Ausgabe gar nicht kommt, da Exceptionen,
dann weiß man vielleicht etwas eher, worum es geht
 
Hallo SlaterB, erst einmal herzlichen Dank für deine Antwort!


Du hast recht, die genannte Version ist eine einzige Baustelle..! Prinzipiell hatte das Grundgerüst mit den 3 Klassen ja schon gut funktioniert - bis versucht hatte, von den ausgelagerten Nebenklassen (Score, Ball, Pad) aus untereinander Zugriff zu erhalten.
Wie gesagt, daher die 'Krücken' (auch als solche benannt).

Auch wenn ich es gerne mit ausgelagerten Klassen geschafft hätte, momentan komme ich so einfach nicht vorwärts, wie du richtig angemerkt hast, daher habe ich gestern Abend noch einmal alle Klassen zurück in die Hauptklasse verpflanzt und haufenweise alten bzw. experimentellen Code entsorgt!
Das Ergebnis ist ein einigermaßen kompaktes Programm (für meine bescheidenen Begriffe 🙂), was auch soweit ganz gut läuft - mit internen Nebenklassen, bei denen der gegenseitige Zugriff keinerlei Probleme bereitet.

Was den neuen AnimeThread(meintest du den?) angeht, da habe ich mich an den Rat meines Dozenten gehalten; der meinte, dass in diesem Teil:
Code:
public void init() {
        try {
            java.awt.EventQueue.invokeAndWait(new Runnable() {
                public void run() {
                    initComponents();
                    gScreen.requestFocus();
                }
            });
            
            //  'ballAnimeThread'-Threadobjekt erzeugen starten 
            //  (neuer Thread mit minimaler Priorität für min. CPU-Last)
            ballAnimeThread = new AnimeThread();
            ballAnimeThread.setPriority(Thread.MIN_PRIORITY);
            ballAnimeThread.start();
        } 
        catch (Exception ex) { ex.printStackTrace(); }
    }
zuerst die graphischen Komponenten initialisiert werden (initComponents()) und der Thread dann anschießend nichts mehr zu tun hat, weshalb man dort den repaint-Thread starten und im Hintergrund laufen lassen kann - wenn ich das richtig verstanden habe. Da ich mit Threads noch nicht soo viel Erfahrung habe, ließ ich andere Tutorials, die etwas komplizierter aufgebaut sind, zunächst außen vor.

Wenn ich richtig liege, halte ich mittels der wait()-Methode den besagten repaint-Thread 'AnimeThread' an, was erklären würde, warum keine 'gewonnen/verloren'-Meldung angezeigt wurden - wenn ich die wait()-Methode vor und nicht nach der g2.drawString-Anweisung aufgerufen hätte... zwischenzeitlich habe ich das Problem anders gelöst, indem ich mit 'Schaltern' zu zeichnende Objekte an-/und ausschalte. Obwohl es schon ganz praktisch wäre, zu verstehen wie man die Threads 'richtig' anhält 🙂

Was mir jetzt nicht so ganz klar ist: wo und wie wird der eigentliche Programm-Thread gestartet (gScreen-Klasse), mit initComponents()?

Vielleicht kannst du mir diese Frage beantworten, und auch, wie man von (ausgelagerten) Nebenklassen gleicher Ebene gegenseitig aufeinander zugreifen kann, à la:
Code:
Ball.java:
{
     gameScore.scoreCount(1);
}

Score.java:
void scoreCount(int pl) {
     if (pl=1) playerScore1++;  
     else if (pl=2) playerScore2++;
}
[code]

Im Grunde habe ich eigentlich nichts anderes zu machen versucht und mit Nebenklassen innerhalb der Hauptklasse funktioniert das ja auch problemlos.

Wegen der zunächst unlösbaren Schwierigkeiten mit der Technik ausgelagerter Klassen und wegen besserer Übersichtlichkeit, starte ich einen neuen Thread, wo ich (mit dem 'ausgemisteten' Programm) einige andere Probleme ansprechen möchte...

schöne Grüße,


milchkaffee
 
Ich habe mittlerweile mein Programm um etliche Funktionen erweitert und arbeite, wie beschrieben, nur noch mit internen Klassen, was die Null-Pointer-Exceptions eliminiert hat. Wenn ich auch nicht weiß, warum ein Auslagern der Nebenklassen nicht funktionierte (evtl. wg. mehrfacher übergabe der Grafikkomponente g2?), das Programm läuft nun soweit recht gut.

milchkaffee
 
Status
Nicht offen für weitere Antworten.

Neue Themen


Zurück
Oben