# Fehlverhalten einer Spielfigur



## Jeff93 (30. Jan 2010)

Hallo liebe Community,

ich habe vor kurzem mit der Java Programmierung begonnen und muss zugeben, dass mir die Sprache sehr gefällt, alles so schön OO. Ich habe mir jetzt sogar ein kleines Spiel programmiert. Im Spiel gibt es eine Figur die sich nach links und rechts bewegen muss um Rechtecken, die von oben herabfallen auszuweischen. Dies funktioniert soweit, doch ich habe das Gefühl dass sich die Spielfigur schneller nach links als nach rechts bewegt. Ich habe keinen Anhaltspunkt wieso.

Hier der Code:

```
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.Vector;
import javax.swing.*;

public class MovableImage extends Rectangle  {
	
	/********************************************************************************************************
		VARIABLEN - DEKLARATION
	*********************************************************************************************************/
		
	protected int speedX=0;
	protected int speedY=0;
	
	protected JComponent jcomponent = null;
	protected BufferedImage Image=null;


	/********************************************************************************************************
		KONSTRUKTOREN
	*********************************************************************************************************/
	
	public MovableImage () { }
	
	public MovableImage (int x, int y, BufferedImage image,JComponent jp) {
		setJComponent(jp);
		setX(x);
		setY(y);
		setBufferedImage(image);
	}
	
	public MovableImage (int x, int y, int speedX, int speedY, BufferedImage image,JComponent jp) {
		this(x,y,image,jp);
		setSpeedX(speedX);
		setSpeedY(speedY);
		
	}
	
	
	/********************************************************************************************************
		SETTER - METHODEN
	*********************************************************************************************************/
	
	public void setJComponent (JComponent jp) {
		this.jcomponent = jp;
	}
	
	public void setY (int y) {
		this.y = y;
	}
	
	public void setX (int x) {
		this.x = x;
	}
	
	public void setBufferedImage (BufferedImage image) {
		this.Image = image;
		if (image!=null) {
			this.height = image.getHeight();
			this.width = image.getWidth();
		}
	}
	
	public void setSpeedX (int speedX) {
		this.speedX = speedX;
	}
	
	public void setSpeedY (int speedY) {
		this.speedY = speedY;
	}
	

	/********************************************************************************************************
		GETTER - METHODEN
	*********************************************************************************************************/
	
	public BufferedImage getBufferedImage () {
		return this.Image;
	}
	
	/********************************************************************************************************
		METHODEN
	*********************************************************************************************************/
	
	public void drawImage(Graphics g) {
		if (this.Image!=null && this.jcomponent!=null)
			g.drawImage(this.Image,this.x,this.y,this.jcomponent);
	}
	
	public void move (double delta) {
		if (speedX!=0)
			setX((int)(this.x+this.speedX*(delta/1e9)));
		if (speedY!=0)
			setY((int)(this.y+this.speedY*(delta/1e9)));
		
	}
	
}
```


```
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.Vector;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.util.ArrayList;

public class GamePanel extends JPanel implements KeyListener,Runnable {

	/********************************************************************************************************
		VARIABLEN - DEKLARATION
	*********************************************************************************************************/

	boolean game_running;
	boolean game_over;
	
	private long delta;
	private long last;
	private long first;
	private long fps;
	private long seconds;
	private boolean new_sec;
	
	private int speedMarsMan;
	private int speedComet;
	private int speedCometInkr;

	
	private BufferedImage bg_img;
	
	private boolean left = false;
	private boolean right = false;
	
	private ArrayList<Comet> comets;
	
	protected MarsMan marsman = null;

	/********************************************************************************************************
		KONSTRUKTOREN
	*********************************************************************************************************/
	
	public GamePanel (int w, int h) {
		// set Dimensions of the Panel
		this.setPreferredSize(new Dimension(w,h));
		// create JFrame
		JFrame frame = new JFrame("the MarsMan");
		frame.setLocation(100,100);
		frame.setDefaultCloseOperation(frame.EXIT_ON_CLOSE);
		frame.add(this);
		frame.addKeyListener(this);
		frame.setResizable(false);
		// Adapt size of Frame to size of Panel
		frame.pack();
		frame.setVisible(true);
		// do Initializations
		doInit();
	}

	/********************************************************************************************************
		METHODEN
	*********************************************************************************************************/
	
	public static void main (String[] args) {
		GamePanel gamepanel = new GamePanel (300,300);
	}
	
	private void doInit () {
		BufferedImage marsman_img = (new BufferedImageLoader()).LoadImage("pics/MarsMan.gif");
		//
		first = last = System.nanoTime();
		delta = fps=seconds=delta=0;
		new_sec = game_over= left=right=false;		
		
		speedComet = 150;
		speedCometInkr = 20;
		speedMarsMan=100;
		
		bg_img = (new BufferedImageLoader()).LoadImage("pics/bgimg.jpg");
		comets = new ArrayList<Comet>();
		
		marsman = new MarsMan((int)((getWidth()-marsman_img.getWidth())/2), getHeight()-marsman_img.getHeight(),
			marsman_img,this);

		createComet();

		// start Thread
		game_running=true;
		Thread th = new Thread(this);
		th.start();
		
	}
	
	public void run () {
		// GameLoop
		while (game_running) {
			
			if (!game_over) {
				ComputeDelta ();
				CheckKeys();
				doLogic();
				Move();
			}
			repaint();
			try {
				Thread.sleep(10);
			} catch (InterruptedException ex) { }
		}
	}
	
	private void ComputeDelta () {
		long tmp;
		delta = System.nanoTime()-last;
		last = System.nanoTime();
		fps = ((long) (1e9))/delta;
		tmp=(System.nanoTime()-first)/((long) (1e9));
		new_sec = (tmp>seconds);
		seconds=tmp;
	}
	
	public void paintComponent (Graphics g) {
		super.paintComponent(g);
		if (game_running) {
			g.drawImage(bg_img,0,0,this);
			marsman.drawImage(g);
			for (Comet c :comets)
				c.drawImage(g);			
			if (game_over) {
				g.setColor(Color.red);
				g.setFont(new Font("",Font.BOLD,20));
				g.drawString("GAME OVER",50,100);
				g.setFont(new Font("",Font.BOLD,16));
				g.drawString("You survived "+seconds+" seconds",60,130);
				g.drawString("Klick ENTER to try again",70,150);
			}
			else {
			
			}
		}
	}

	private boolean CometsIntersecting (Comet com) {
		for (Comet c:comets)  {
			if (c.intersects(com))
				return true;
		}
		return false;
	}
	private void createComet () {
		BufferedImage tmp = (new BufferedImageLoader()).LoadImage(((int)(Math.random()*2))==1?"pics/comet1.gif":"pics/comet2.gif");		
		Comet c = new Comet((int)(Math.random()*(this.getWidth()-tmp.getWidth())), 1-tmp.getHeight(),
			0,speedComet,tmp,this);
		while (CometsIntersecting(c)) {
			c.setX((int)(Math.random()*(this.getWidth()-tmp.getWidth())));
		}
		comets.add(c);
	}
	
	private void doLogic () {
		// boost speed of comets
		if (seconds%3==0 && new_sec)
			speedComet+=speedCometInkr;
		if (seconds%8==0 && new_sec)
			createComet();
		
		
		for (int i=0;i<comets.size();++i) {
			if (!(comets.get(i)).intersects(this.getVisibleRect())) {
				comets.remove(i);
				createComet();
				--i;
			}
			else if ((comets.get(i)).intersects(marsman)) {
				game_over = true;
			}
		}
		
	}
	
	private void Move () {
		marsman.move(delta);
		for (Comet c:comets) {
			c.move(delta);
		}
	}
	
	private void CheckKeys () {
		if (right)
			marsman.setSpeedX(speedMarsMan);
		if (left)
			marsman.setSpeedX(-speedMarsMan);

		if (left==right)
			marsman.setSpeedX(0);
			
	}

	public void keyPressed(KeyEvent e) {
		
		if(e.getKeyCode()==KeyEvent.VK_A){
			left = true;
		}

		if(e.getKeyCode()==KeyEvent.VK_D){
			right = true;
		}
		
		if(e.getKeyCode()==KeyEvent.VK_ENTER && game_over){
			doInit();
		}
		
	}
		
	public void keyReleased(KeyEvent e) {
		

		if(e.getKeyCode()==KeyEvent.VK_A){
			left = false;
		}

		if(e.getKeyCode()==KeyEvent.VK_D){
			right = false;
		}
	
	}

	public void keyTyped(KeyEvent e) {

	}

}
```


```
import java.awt.image.BufferedImage;
import javax.swing.*;

public class MarsMan extends MovableImage {
	public MarsMan (int x, int y, BufferedImage image,JComponent jp) {
		super (x,y,image,jp);
	}
	
	/********************************************************************************************************
		SETTER - METHODEN
	*********************************************************************************************************/	
	public void setX (int x) {
		if (jcomponent==null || Image==null)
			this.x=x;
		else if (x<0)
			this.x = this.jcomponent.getWidth()-Image.getWidth();
		else if (x>=this.jcomponent.getWidth()-Image.getWidth())
			this.x = 0;
		else
			this.x = x;
	}
}
```
Wenn ihr keine Lust drauf habt euch das ganze Vieh herunterzuladen, könntet ihr mir ja eure Ideen posten.

Hoffe ihr könnt mir helfen, mfg, Jeff

PS: Die main ist in *GamePanel*, das Spiel wird gesteuert mit *A* und *D*


----------



## Empire Phoenix (31. Jan 2010)

Hm, also gefühl habe ich auch, ja, zudem:
nach einmal sterben respawn, danach sind die Blöke nurnoch gefallen wenn ich eine taste gedrückt habe und ich konnte mich nicht mehr bewegen. Auf einen Blick kann ich dir allerdings nicht sagen, warum das so unterschiedlich schnell ist, aber:

Allerdings soweit wie ich das sehe sind 3 probleme vorhanden:
1. Du greifst direkt auf swing/awt Komponenten hinzu, das sollte man nicht tun wegen threadunsafe, gibt es ne relativ einfach Lösung aber da ich fast nie Swing benutze fällt mir der name nciht ein.
2. Du verlässt dich darauf, das dein Game immer gleich viele ms braucht für ein Frame. Stattdessen solltest du die wartezeit abhängig von der benötigte Zeit machen.
3. Du kannst dir wenn du dich auch eine fest Ticktime verlassen kannst eine Menge berechnungen sparen, weil du ja weißt wieviele ms vergangen sind und somit keine deltas berechnen musst (die oft genug Fehlerquellen darstellen können).


----------



## Jeff93 (31. Jan 2010)

Vielen Dank,

ich habe mir die Klass Timer angesehen und in meinen Code eingebaut, jetzt funktioniert alles wie es soll.

Jeff


----------



## Quaxli (2. Feb 2010)

Du solltest mit etwas größerer Genauigkeit rechnen, als mit int, dann geht's  (vgl. Tutorial, da wird auch Rectangle2D verwendet.

Dein Code oben hat bei mir funktioniert, nachdem ich folgende Änderungen eingebaut habe:

1. MarsMan erbt nicht mehr von Rectangle, sondern von Rectangle2D.Double
2. Anpassung der Move-Methode


```
public void move (double delta) {
		if (speedX!=0){
			x = x+speedX*(delta/1e9);
		}
		
		if (speedY!=0){
			y = y+speedY*(delta/1e9);
		}
	}
```

Am Timer kann es nicht gelegen haben, da Du ja das Delta des Schleifendurchlaufs errechnest und die Figuren in Abhängigkeit davon bewegst. Von "auf gleichförmige Bewegung" verlassen, kann daher gar nicht die Rede sein.
Ursache war offensichtlich eine nicht ausreichend große Genauigkeit durch Rundungsfehler, die durch o. a. Modifikation beseitigt wird. Warum dies nur eine Richtung auftrat habe ich jetzt nicht genauer verfolgt.

2 Punkte noch, die ich persönlich als unschön empfinde:

1.  Ich würde if-Bedingungen grundsätzlich mit geblockten Bedingunge versehen {}. Es erhöht sich die Lesbarkeit und reduziert mögliche Fehlerquellen.

2. Von diesen ganzen (imho unnötigen) this kriege ich Augenkrebs  Ich finde das nicht sehr lesbar - rein subjektiv gesehen.


----------



## Jeff93 (2. Feb 2010)

Quaxli hat gesagt.:


> Du solltest mit etwas größerer Genauigkeit rechnen, als mit int, dann geht's  (vgl. Tutorial, da wird auch Rectangle2D verwendet.
> 
> Am Timer kann es nicht gelegen haben, da Du ja das Delta des Schleifendurchlaufs errechnest und die Figuren in Abhängigkeit davon bewegst. Von "auf gleichförmige Bewegung" verlassen, kann daher gar nicht die Rede sein.



Kann sein, denn als ich es mit den Timern versucht habe, habe ich das mit dem Delta sein gelassen.

Jeff

P.S: Gute Besserung Quaxli ^^


----------



## Quaxli (2. Feb 2010)

Jeff93 hat gesagt.:


> P.S: Gute Besserung Quaxli ^^



[DUKE]lol[/DUKE]


----------



## Empire Phoenix (3. Feb 2010)

this is jetzt aber auch nicht schlecht, weil beim lesen dadurch auch eindeutig klar wird das es sich nicht um eine statische variable handelt. -> Tatsächlich schreibe ich sogar immer this hin wenns geht (hat auch den Vorteil das eclipse mir alle möglichen variablen anbietet ^^ und ich somit zeit spare wegen rechtschreibfehlern)

BTW hatte ich ja nicht ganz unrecht mit der Fehlerquelle der deltaberechnungen ^^


----------



## Quaxli (3. Feb 2010)

Empire Phoenix hat gesagt.:


> this is jetzt aber auch nicht schlecht, weil beim lesen dadurch auch eindeutig klar wird das es sich nicht um eine statische variable handelt. -> Tatsächlich schreibe ich sogar immer this hin wenns geht (hat auch den Vorteil das eclipse mir alle möglichen variablen anbietet ^^ und ich somit zeit spare wegen rechtschreibfehlern)



Deswegen hatte ich auch hingeschrieben, daß ich das subjektiv sehe.



Empire Phoenix hat gesagt.:


> BTW hatte ich ja nicht ganz unrecht mit der Fehlerquelle der deltaberechnungen ^^



Doch. 
Es ist ein Unterschied, ob es nicht da ist oder nur fehlerhaft.


----------

