# Freier-Fall mit drawImage



## SirJakob (17. Mrz 2017)

Hallo,
ich versuche derzeit ein Spiel zu programmieren, das Ähnlichkeiten mit FlappyBird hat.
Ich weiß allerdings nicht, wie ich meinen Astronauten zum Fallen bringe.
Bis jetzt konnte ich diesen lediglich mit der drawImage Methode zum Spielfeld hinzufügen.
Hier sind meine beiden Klassen:

```
package com.jakobstieve.rocketescape;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class Frame extends JFrame implements ActionListener{

    private JButton schliessen;
    private JButton ende;
    static int width = Toolkit.getDefaultToolkit().getScreenSize().width;
    static int height = Toolkit.getDefaultToolkit().getScreenSize().height;
    static JLabel Background = new JLabel();
    static ImageIcon BackgroundImage = new ImageIcon("MenuBackground.png");
   
    public static void main(String[] args) {
        Frame frame = new Frame("RocketEscape");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(400,400);
        frame.setLayout(null);
        frame.setVisible(true);
        frame.setResizable(false);
        frame.add(Background);
       
        Background.setBounds(0, 0, 400, 400);
        Background.setIcon(BackgroundImage);
}
   
    public Frame (String title){
        super(title);
       
        schliessen = new JButton("Spielen");
        schliessen.setBounds(120,200,160,40);
        schliessen.addActionListener(this);
        add(schliessen);
       
        ende = new JButton("Beenden");
        ende.setBounds(120,275,160,40);
        ende.addActionListener(this);
        add(ende);
    }
   
    public void actionPerformed(ActionEvent e) {
        if (e.getSource()== schliessen){
            fenster();
           
        }
        if (e.getSource()== ende){
            System.exit(0);
        }
    }
   
    public static void fenster(){
        JFrame fenster = new JFrame();
        fenster.setTitle("RocketEscape");
        fenster.setSize(width,height);
        fenster.setVisible(true);
        fenster.setResizable(true);
        fenster.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        fenster.setExtendedState(java.awt.Frame.MAXIMIZED_BOTH);
        fenster.add(new Gui());
    }       

}
```


```
package com.jakobstieve.rocketescape;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Toolkit;

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

public class Gui extends JPanel{
   
    Image img;
    Image img2;
    Image img3;
    static int width = Toolkit.getDefaultToolkit().getScreenSize().width;
    static int height = Toolkit.getDefaultToolkit().getScreenSize().height;
   
    public Gui(){
        setFocusable(true);
        ImageIcon u = new ImageIcon("BackgroundImage.png");
        ImageIcon s = new ImageIcon("Astronaut.png");
        ImageIcon Clicktostart = new ImageIcon("Cts.png");
        img = u.getImage();
        img2 = s.getImage();
        img3 = Clicktostart.getImage();
    }
   
    public void paint(Graphics g){
       
        super.paint(g);
        Graphics2D f2 = (Graphics2D)g;
       
        f2.drawImage(img,0,0,null);
        f2.drawImage(img2, width/2-53, height/3-90, null);
        f2.drawImage(img3, width/2-188, height-height/3, null);
    }
   
    public void fall(){
            double dy = 0;
            double g = 1.3;
            dy = dy + g;
            //Y Position des Astronauten um dy verändern.
       
    }

}
```
Wenn man im Menü auf den "Spielen" Button klickt, gelangt man in die "Spielwelt"(Frame).
Wenn man dort ist und einmal klickt (egal wohin), soll img3 (enhält den Schriftzug "click to start") verschwinden und der Astronaut nach unten fallen. Hat jemand eine Idee, wie ich das umsetzen könnte? Wenn sich jemand kurz Zeit nehmen würde, um mein Problem zu lösen, würde es mich sehr freuen.

LG Jakob

Ps:. Ich bin neu hier im Forum, kenne mich deshalb noch nicht wirklich aus. Ich bitte um Entschuldigung, falls ich etwas falsch gemacht habe.


----------



## Blender3D (20. Mrz 2017)

1) Du brauchst einen Timer oder Thread um die Animation zu realisieren.
2) Es genügt ein JFrame Objekt, um die GUI zu realisieren.
Hier ein kleines Beispiel wie Du in die richtige Richtung gehst.
Der Astronaut wird im GamePanel animiert.

```
import java.awt.Image;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JComponent;
import javax.swing.JFrame;

@SuppressWarnings("serial")
public class MainGUI extends JFrame implements GameCtrl {
    public final static String imageLocation = "./images/"; // image's directory
    private JComponent currentView = null;
    static int width = 400;
    static int height = 400;
   

    public static void main(String[] args) {
        System.setProperty("sun.java2d.opengl", "true");
        MainGUI frame = new MainGUI("RocketEscape");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(width, height);
        frame.setVisible(true);
        frame.setResizable(false);
    }

    public MainGUI(String title) {
        super(title);
        setToMenu();
    }

    @Override
    public void setToGame() {
        if (currentView != null)
            remove(currentView);
        currentView = new GamePanel(width, height, this);
        add(currentView);
        pack();
    }

    @Override
    public void setToMenu() {
        if (currentView != null)
            remove(currentView);
        currentView = new MenuPanel(width, height, this);
        add(currentView);
        pack();
    }

    /**
     * Loads image.
     *
     * @param file
     *            Image's location.
     * @return Image or null
     */
    public static Image loadImage(String file) {
        Image ret = null;
        try {
            ret = ImageIO.read(new File(file));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return ret;
    }
}
```


```
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JPanel;
import javax.swing.Timer;

@SuppressWarnings("serial")
public class GamePanel extends JPanel implements ActionListener {
    private Point astronautPos = null; // astronaut's position
    private Timer astronautAnimator = null; // animates astronaut
    public final static int BACKGROUND = 0;
    public final static int ASTRONAUT = 1;
    private String[] imageNames = { "BackgroundImage", "Astronaut" };
    private Image[] images = new Image[imageNames.length];
    private GameCtrl ctrl;

    public GamePanel(int width, int height, GameCtrl ctrl) {
        setPreferredSize(new Dimension(width, height));
        this.ctrl = ctrl;
        for (int i = 0; i < imageNames.length; i++)
            images[i] =MainGUI.loadImage(MainGUI.imageLocation + imageNames[i] + ".png");
        astronautPos = new Point(width / 2, images[ASTRONAUT].getHeight(null));
        start();
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        if (astronautPos.y >= getHeight() - images[ASTRONAUT].getHeight(null)) {
            stop(); // stop austronaut's animation if ground is reached
            ctrl.setToMenu();

        } else
            astronautPos.y++; // increase astronauts y-position
        repaint(astronautPos.x, astronautPos.y, images[ASTRONAUT].getWidth(null), images[ASTRONAUT].getHeight(null));
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawImage(images[BACKGROUND], 0, 0, null);
        g.drawImage(images[ASTRONAUT], astronautPos.x, astronautPos.y, null);
    }

    public void start() {
        astronautAnimator = new Timer(10, this);
        astronautAnimator.start();
    }

    public void stop() {
        astronautAnimator.stop();
        astronautAnimator = null;
    }
}
```


```
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JPanel;

@SuppressWarnings("serial")
public class MenuPanel extends JPanel implements ActionListener {
    private GameCtrl ctrl = null; // interface to switch between menu and game
    private JButton start = new JButton("Spielen");
    private JButton end = new JButton("Beenden");
    private Image backImage = null;

    public MenuPanel(int width, int height, GameCtrl ctrl) {
        setPreferredSize(new Dimension(width, height));
        this.ctrl = ctrl;
        setLayout(null);
        start.setBounds(120, 200, 160, 40);
        start.addActionListener(this);
        add(start);
        end.setBounds(120, 275, 160, 40);
        end.addActionListener(this);
        add(end);
        backImage = MainGUI.loadImage(MainGUI.imageLocation+ "MenuBackground.png");
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        Object src = e.getSource();
        if (src == start)
            ctrl.setToGame();
        if (src == end)
            System.exit(0);    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (backImage != null)
            g.drawImage(backImage, 0, 0, null);
    }

}
```


----------



## Blender3D (20. Mrz 2017)

Außerdem benutze niemals paint(), sondern paintComponent(),
paint() wird von Swing zum richtigen Zeitpunkt aufgerufen.


----------



## Blender3D (20. Mrz 2017)

```
/**
* Interface to switch between game and menu
*
* @see MainGUI
*
*/
public interface GameCtrl {
    public void setToGame();

    public void setToMenu();
}
```
Hier noch das Interface GameCtrl


----------



## SirJakob (22. Mrz 2017)

Vielen Dank, @Blender3D !
Du hast mir sehr weitergeholfen.


----------



## SirJakob (22. Mrz 2017)

Eine Frage habe ich allerdings noch, @Blender3D 
Sobald ich die Fall-Geschwindigkeit erhöhe, bleiben "Rückstände" von den zuvor gezeichneten Astronauten zu sehen. Ist es möglich, dies zu vermeiden?


----------



## Blender3D (22. Mrz 2017)

Blender3D hat gesagt.:


> public void actionPerformed(ActionEvent e) {
> if (astronautPos.y >= getHeight() - images[ASTRONAUT].getHeight(null)) {
> stop(); // stop austronaut's animation if ground is reached
> ctrl.setToMenu();
> ...


In dem Beispiel wird repaint genau an der Position des Astronauten mit einer Rechteckgröße von Astronauthöhe und Astronautweite ausgelöst.
Das bedeutet, dass nur dieser Bereich neu gezeichnet wird. Wenn du aber die Geschwindigkeit erhöhst, indem du z.B. die y Koordinate nicht um 1 sondern um 2 oder mehr erhöhst, wird ein Bereich wo zuvor dein Astronaut war nicht neu gezeichnet. --> Artefakte.
Wenn Du  nur repaint() benutzt hast Du keine Artefakte mehr. Nachteil: Es werden Bereiche neu gezeichnet die nicht neu gezeichnet werden müssten. --> Perfomance schlechter.
Lösung: führe eine Varaible für die Geschwindigkeit ein.
z.B. int astronautSpeed = 3;

```
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JPanel;
import javax.swing.Timer;

@SuppressWarnings("serial")
public class GamePanel extends JPanel implements ActionListener {
    private Point astronautPos = null; // astronaut's position
    private Timer astronautAnimator = null; // animates astronaut
    private int astronautSpeed = 5; // astronaut's speed
    public final static int BACKGROUND = 0;
    public final static int ASTRONAUT = 1;
    private String[] imageNames = { "BackgroundImage", "Astronaut" };
    private Image[] images = new Image[imageNames.length];
    private GameCtrl ctrl;

    public GamePanel(int width, int height, GameCtrl ctrl) {
        setPreferredSize(new Dimension(width, height));
        this.ctrl = ctrl;
        for (int i = 0; i < imageNames.length; i++)
            images[i] = MainGUI.loadImage(MainGUI.imageLocation + imageNames[i] + ".png");
        astronautPos = new Point(width / 2, images[ASTRONAUT].getHeight(null));
        start();
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        if (astronautPos.y >= getHeight() - images[ASTRONAUT].getHeight(null)) {
            stop(); // stop austronaut's animation if ground is reached
            ctrl.setToMenu();

        } else
            astronautPos.y += astronautSpeed; // increase astronauts y-position
        repaint(astronautPos.x - astronautSpeed, astronautPos.y - astronautSpeed,
                images[ASTRONAUT].getWidth(null) + astronautSpeed, images[ASTRONAUT].getHeight(null) + astronautSpeed);
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawImage(images[BACKGROUND], 0, 0, null);
        g.drawImage(images[ASTRONAUT], astronautPos.x, astronautPos.y, null);
    }

    public void start() {
        astronautAnimator = new Timer(10, this);
        astronautAnimator.start();
    }

    public void stop() {
        astronautAnimator.stop();
        astronautAnimator = null;
    }
}
```
Für feinere Geschwindigkeitsunterschiede nimmt man statt int float oder double.


----------



## SirJakob (22. Mrz 2017)

Perfekt! 
Danke nochmals, jetzt funktioniert es. @Blender3D


----------

