2D Plattformer - Springen / Physik

Blender3D

Top Contributor
Dies geschieht eigentlich mittels der drawImage Methode.
Schön und gut, nur braucht diese ein Graphics Objekt.
Und genau da fängt mein Problem an.
In der Render Methode sollte das Graphics Objekt bekannt sein.
Die Objekte die Du zeichnen willst sollten dafür eine Schnittstelle implementieren.
z.B.
Java:
interface DrawAble{
    public void draw(Graphics g);
}
Deine Spieleklasse erweitert Deine Display Klasse und verlangt die abstrakte Methode render();
Java:
public class Game extends Display{
    private DrawAble [] objects;
    
    @Override
    protected  void render(Graphics g){
        for( DrawAble o : objects )
            o.draw( g );       
    }
}
 

Hag2bard

Bekanntes Mitglied
Danke nochmal, irgendwie erinnert mich das hier stark an Linux. Man trifft auf ein Problem, findet die Lösung und schon folgt das nächste Problem.
Nur dass ich dies in der Programmierung noch akzeptieren kann, bei Linux hab ich manchmal das Gefühl, dass selbst die LTS Versionen furchtbar buggy sind und jede neue Version auf den Schund der alten Versionen aufbaut. Aber nun gut, der Linux Kurs folgt im 2. Lehrjahr, vielleicht versteh ich danach mehr davon.


Der Code funktioniert nun soweit, bis auf eine echt merkwürdige Eigenart, die ich filmen musste. Ich konnte dafür nur mein Handy nehmen, Qualität ist also entsprechend bescheiden.

Datei von filehorst.de laden

Ich hoffe der FileHoster ist ok

edit:

Diese Codepassage hier ist Schuld daran gewesen:

Java:
//        if (currentDrawingImage.getWidth() != width || currentDrawingImage.getHeight() != height)
            currentDrawingImage = createImage();

Wenn man ich die If Bedingung ausgraue, dann funktioniert alles so wie es sein sollte.
Also muss er das currentDrawingImage immer wieder neu erstellen nach jedem Durchgang. Ich werde die if Bedingung mal so abändern, dass er es auch nicht nur dann neu erstellt, wenn sich die Fenstergröße ändert, sondern auch, wenn der Charakter sich bewegt.
 
Zuletzt bearbeitet:

KonradN

Super-Moderator
Mitarbeiter
Wenn man ich die If Bedingung ausgraue, dann funktioniert alles so wie es sein sollte.
Was gab es denn für Probleme? Gab es eine NullPointerException? Dann würde eine zusätzliche Bedingung curentDrawingImage == null || ..... ausreichen. Eigentlich sollten 3 Images ausreichend sein und nicht mehr benötigt werden, aber ggf. sind die möglichen Abfolgen nicht gut genug durchdacht gewesen. Immer ein neues Bild zu erzeugen ist aber nicht ratsam, denn diese haben eine gewisse Größe und das bringt eine unnötige Last auch auf den GC.
 

Hag2bard

Bekanntes Mitglied
Es gab das Problem, dass der Charakter einen Schweif mit sich gezogen hat, welcher aber in sich nochmal animiert war, schwer zu erklären, erinnert mich irgendwie an einen Wasserfall oder an die Stelle in Avengers, als der Hulk Doctor Strange in der Vergangenheit aufsucht und seine Metagestalt von der Großmeisterin aus seinem Körper geschlagen wird. :D
1655675900068.png
edit: Die createImage() Methode musste ich übrigens zu einem BufferedImage vom Typ ARGB umstellen, sonst wurde das Hintergrund Bild mit Schwarz überschrieben.
 
Zuletzt bearbeitet:

KonradN

Super-Moderator
Mitarbeiter
Kann das evtl. daran liegen, dass du nicht das ganze Bild neu gemalt hast? Das klingt zumindest etwas danach, dass du Flächen hast, die dann nicht gemalt werden und daher den alten Wert hatten.

Der erste Schritt ist also sozusagen immer das „löschen“ des Bildinhaltes.
 

Hag2bard

Bekanntes Mitglied
Ja das stimmt, ich wusste nicht dass das eine Rolle spielt. Wie lösche ich denn den Bildinhalt? Ich würde das so umsetzen, dass nur die Bereiche gelöscht werden, in denen gezeichnet wurde, wenn man denn einzelne Bildbereiche löschen kann.
 

KonradN

Super-Moderator
Mitarbeiter
Ich würde es da gar nicht so komplex machen - Du hast eine Hintergrundfarbe und die setzt Du einfach per Graphics.setColor um dann ein Graphics.fillRect aufzurufen für das ganze Bild.

Wenn Du aber ein fixen Hintergrund hast, dann kannst Du den auch einfach einmal erstellen in einem Bild und dann malst Du einfach dieses Bild. Das ist aber dann eine Optimierung und die kannst Du Dir natürlich auch überlegen - aber das würde ich nicht machen, so lange die einfache Variante ohne Optimierungen noch Probleme macht.
 

Hag2bard

Bekanntes Mitglied
Hallo, danke für die Antwort.
Ich geh jetzt einfach mal davon aus, auch wenn ich das eigentlich hasse, wenn man sowas sagt, dass man Super Mario World kennen sollte.
Dieses Level was ich da bisher nachgebaut habe ist das einzige was ein fixes Hintergrundbild hat.
Später wird sich der Hintergrund natürlich bewegen, wenn sich der Charakter durch einen Level bewegt.
Da dies der Fall ist, wäre die sinnvollere Lösung in dieses "eine" BufferedImage immer wieder die Map (den Hintergrund) zu zeichnen und darauf dann den Charakter, wie ich das vorher in paintComponent ja auch getan habe.

Zuerst probierte ich mit FillRect das CharakterBufferedImage mit einer rein transparenten Farbe zu füllen.
Also
Color c = new Color(0,0,0,127);
...
Leider war dies ein Misserfolg, der mir aber trotz der neuen, wahrscheinlich besseren Lösung sauer aufstößt.
Der Misserfolg äußerte sich darin, dass der Hintergrund schwarz war, sich der Schweif des Charakters aber allmählich auflöste.

Mit einer Color 0,0,0,255 war der Hintergrund schwarz, der Charakter allerdings, hatte keinen Schweif.

Mit welchen Übergabeparametern kann ich den Hintergrund nun transparent überschreiben?
 

Blender3D

Top Contributor
Später wird sich der Hintergrund natürlich bewegen, wenn sich der Charakter durch einen Level bewegt.
Da dies der Fall ist, wäre die sinnvollere Lösung in dieses "eine" BufferedImage immer wieder die Map (den Hintergrund) zu zeichnen und darauf dann den Charakter, wie ich das vorher in paintComponent ja auch getan habe.
Nein paintComponent ist einfach ein sehr schlechter Ansatz für so etwas.
Zu bewegtem Hintergrund habe ich dir bereits eine Test.jar zur Verfügung gestellt, um aktives Rendern auch bei einem bewegten Hintergrund zu demonstrieren. Siehe https://www.java-forum.org/thema/2d-plattformer-springen-physik.197673/#post-1312552
Dort wird die Klasse Display benutzt. Ich verwende hier meine Klasse CircleImage. Die stellt das Hintergrund Image je nach Position zur Verfügung.
 

Neumi5694

Top Contributor
Hallo, danke für die Antwort.
Ich geh jetzt einfach mal davon aus, auch wenn ich das eigentlich hasse, wenn man sowas sagt, dass man Super Mario World kennen sollte.
Dieses Level was ich da bisher nachgebaut habe ist das einzige was ein fixes Hintergrundbild hat.
Später wird sich der Hintergrund natürlich bewegen, wenn sich der Charakter durch einen Level bewegt.
Da dies der Fall ist, wäre die sinnvollere Lösung in dieses "eine" BufferedImage immer wieder die Map (den Hintergrund) zu zeichnen und darauf dann den Charakter, wie ich das vorher in paintComponent ja auch getan habe.

Zuerst probierte ich mit FillRect das CharakterBufferedImage mit einer rein transparenten Farbe zu füllen.
Also
Color c = new Color(0,0,0,127);
...
Leider war dies ein Misserfolg, der mir aber trotz der neuen, wahrscheinlich besseren Lösung sauer aufstößt.
Der Misserfolg äußerte sich darin, dass der Hintergrund schwarz war, sich der Schweif des Charakters aber allmählich auflöste.

Mit einer Color 0,0,0,255 war der Hintergrund schwarz, der Charakter allerdings, hatte keinen Schweif.

Mit welchen Übergabeparametern kann ich den Hintergrund nun transparent überschreiben?
Das geht so nicht ("transparent überschreiben" ... was sollte das denn überhaupt heißen?), du musst wirklich das Bild wiederherstellen, das vorher zu sehen war, der Weg über das Buffered Image ist der richtige. Die modernen Rechner (auch Smartphones) sind mehr als schnell genug, dass du den gesamten Hintergrund neu zeichnen kannst, du kannst natürlich aber auch nur den Rechteckausschnitt neu zeichnen lassen.

Würde es heute immer noch Hardware-Sprites geben, müsstest du gar nichts tun.

Was verwendest du eigentlich als Zeichenfläche? Ein Canvas? Das wäre meinem Kenntnisstand nach die schnellste Komponente, die wirklich gar nichts kann und macht außer zeichnen.
 

Hag2bard

Bekanntes Mitglied
Als Zeichenfläche benutze ich ein JLabel. In der paintComponent Methode wird dann lediglich ein BufferedImage gezeichnet, welches in einem anderen Thread gezeichnet wird, nach der Idee von KonradN.
Die Test.jar habe ich mir angeschaut, der Code dahinter ist nirgends zu finden.
 

Neumi5694

Top Contributor
Als Zeichenfläche benutze ich ein JLabel. In der paintComponent Methode wird dann lediglich ein BufferedImage gezeichnet, welches in einem anderen Thread gezeichnet wird, nach der Idee von KonradN.
Die Test.jar habe ich mir angeschaut, der Code dahinter ist nirgends zu finden.
Ok, für ein 2D Spiel wird das schon reichen, persönlich würde ich da eher zu AWT greifen als zu Swing.

Hab mal testweise für meine Software eine 3D-Darstellung mit Java3D eingebaut und es gab einmal die Möglichkeit, das per JCanvas zu machen oder Canvas (AWT) zu verwenden. Obwohl der Renderer selbst bei beiden der Selbe war, war der effektive Geschwindigkeitsunterschied enorm. Swing führt noch einen Riesenbatzen an Prüfungen durch, erlaubt Popupmenüs usw. Verwendet man hingegen ein AWT-Canvas entfällt das alles, die Rechteckfläche wird gezeichnet und fertig. Der Vorteil des Swing-Elements war natürlich, dass ich Buttons einfach darüber legen und verwenden konnte anstatt das über rechteckige Flächen im Render mit Abfrage der Mausposition zu machen.

In jedem Fall brauchst du 2 BufferedImages: Eines für allein den Hintergrund und eines mit dem fertigen zu zeichnenden Bild inklusive Spielfiguren.
(1) immer dann neu erstellt, wenn sich etwas am Hintergrund ändert (Blöcke haben z.B. statische Positionen, können als Hintergrund behandelt werden). (2) ist das, welches in das Label gezeichnet wird und wird bei jeder Änderung von egal was neu befüllt. Zuerst wird darauf der Hintgrund (1) reinkopiert, dann die Sprites. Vielleicht gibt es eine effiziente Methode, um alle Pixels eines Images auf ein anderes gleicher Größe zu übertragen, aber drawImage sollte eigentlich reichen.

Edit: Es geht natürlich auch ohne das Hintergrundbild (1), wenn du selbigen für die Erstellung von (2) jedesmal neu erstellen lassen willst.
Aber wenn sich der Hintergrund nur vergleichsweise selten ändert (nicht so oft, wie Figuren bewegt werden), bietet es sich an, selbigen zu cachen.

Falls du Flimmern verhindern willst (kann passieren, wenn (2) durch die repaint-Methode neu gezeichnet werden soll, während es noch erstellt wird), dann brauchst du noch ein drittes Image. (2) und (3) wechseln sich dann ab. Je eins von beiden wird ins JLabel gezeichnet, während das andere neu berechnet wird.

Wenn du mal so was wie Parallax-Scrolling implementierst, brauchst du natürlich mehrere Hintergrundebenen mit Transparenz. Das Prinzip ist aber immer das Gleiche. Ein Element "entfernt" man nicht einfach, sondern zeichnet an der Stelle (oder dank massig Rechenpower seit Mitte der 90er überall) den Hintergrund neu.
 

KonradN

Super-Moderator
Mitarbeiter
@Neumi5694 Das mit den Bildern hat er ja von mir bekommen - nur eben malt er das Bild nicht komplett und so hat er halt immer auch alte Dinge mit drauf.

@Hag2bard Dann sollte der Thread, der auch das rendern macht, einfach den Hintergrund einmal in einem eigenen Bild aufbauen. Und der erste Schritt ist dann immer: Übermal das Bild mit dem Hintergrundbild.

Wenn es dann zum scrollen kommt, dann kann das Hintergrundbild deutlich größer sein oder aus mehreren Bildern bestehen und Du übernimmst nur Ausschnitte daraus. So kann dann nicht nur eine Bewegung nach Links und Rechts sondern auch nach oben und unten möglich gemacht werden.

Aber der erste Schritt sollte immer sein, einen möglichst einfachen Algorithmus zu erstellen, der eben die Anforderungen erfüllt. Dann kann man später schauen, ob man da etwas optimieren kann. Aber es von Anfang an gleich optimieren zu wollen führt sehr leicht zu Problemen und Frust - wie Du wohl gerade selbst feststellst.
 

Neumi5694

Top Contributor
Hab nur seine Antwort gelesen und die klang halt etwas anders, er hat von einem einzelnen geredet. Er hat's dann wohl nicht verstanden, oder ich hab in die Antwort mehr reininterpretiert. Begriffe wie "löschen" oder "transparent überschreiben" gehen an dem vorbei, was man tatsächlich macht, deshalb nochmal meine Erklärung.
 
Zuletzt bearbeitet:

Ähnliche Java Themen

Neue Themen


Oben