Kollisionserkennung in Bildern.

Kai008

Erfahrenes Mitglied
Hi.
Ich arbeite erst seit kurzen mit Java, und habe mich jetzt gefragt ob es vielleicht eine Funktion gibt, mit der Java automatisch die Koordinaten eines Bildes (mit ignorierung von transparenten Teilen) ausließt und man so eine einfache Kollisionserkennung hat. Oder eine andere Möglichkeit eine Kollisionserkennung zu realisieren. Ich habe unter anderen ein Ping Pong und ein Break Out geschrieben, und z. B. if(ballx == irgendwas && bally <= playery && bally > playery + playerlaenge) beim Ping Pong funktioniert nicht so gut.
Ich benutze Paint mit Doppelbuffer.
 

zeja

Erfahrenes Mitglied
So ganz verstanden habe ich nicht was du möchtest.

Was hast du momentan? Was möchtest du? Warum funktioniert dein Codestück nicht?
 

Kai008

Erfahrenes Mitglied
Ich habe jetzt ein paar drawRect-Blöcke und ein Bild. Der Ball fliegt rauf und runter, wie es bei einen BreakOut nun mal so ist.
aysfgue42w1uo0qrx.png


Der Ball ist nur so weit rechts weil ich nicht schnell genug auf Control + Print gekommen bin.
Manchmal fällt der Ball aber einfach durch den Player durch.
So wird gezeichnet:

Code:
if(playerx < 704)
	g.drawRect(playerx, 350, 50, 10);
else
{
	playerx = 704;
	g.drawRect(704, 350, 50, 10);
}

Die IF sorgt dafür das der Player rechts nicht weiter kann.
So wird abgefragt:

Code:
if(bally == 340 && ballx >= playerx - 5 && ballx <= playerx + 55 && ballrichtung == 1)
ballrichtung = 3;
usw...

"ballrichtung" ist eine Integer die festlegt ob der Ball nach rechts unten, links unten, rechts oben oder links oben fliegt.
Ich habe sogar die "Länge" wärend der Prüfung um 5 px. erhöht, aber hat nichts geändert.
Und jetzt wollte ich ein größeres Projekt beginnen, wo es weit mehr Bilder gibt, die auch kaum Quader sind, deshalb habe ich mich gefragt, ob Java eine Möglichkeit gibt zu erkennen wie ein Bild "geformt" ist, womit man einfach erkennen könnte ob sich 2 Bilder überschneiden oder berühren.
 

zeja

Erfahrenes Mitglied
Ich würde bei deinem Spiel erstmal alles was gezeichnet wird auch tatsächlich als Rectangle ablegen.

Mit intersects kann man dann nämlich prüfen ob sich zwei Shapes schneiden.

Danach solltest du mal ein paar Konstanten hinterlegen für deine numerischen Werten:
Wie weit ist das Player-Rectangle vom Boden entfernt?
private static final int PLAYER_MARGIN_BOTTOM = 50;
Wie hoch ist der Player:
private static final int PLAYER_HIGHT = 20;
Wie breit ist der
private static final int PLAYER_WIDTH = 50;

usw.

Genauso für die Ballrichtung:
private static final int NORTH = 1;
private static final int NORTH_EAST = 2;

Ab Java 1.5 besser als Enum:
Java:
public enum Direction {
NORTH, NORTH_EAST;
}
 

Kai008

Erfahrenes Mitglied
Könntest du mir bitte einen kurzen Beispielcode geben?
Ich arbeite wie gesagt erst kurz mit Java und habe noch nicht rausgefunden wie man die Referenz von Sun richtig benutzt. Zumindest wenn ich es versuche funktioniert es dann nicht richtig.
 

zeja

Erfahrenes Mitglied
Zur Benutzung der API habe ich mal ein Tutorial geschrieben: http://www.tutorials.de/forum/java-tutorials/306065-benutzung-der-java-dokumentation.html

Hier mal nen Beispiel wo der Ball zwischen Player und Steinen hin- und herprallt. Nur mal um zu zeigen wie ich mir das in etwa gedacht habe.

Java:
package de.tutorials;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;

public class BreakOutExample extends JFrame {

	private static final int FIELD_WIDTH = 400;
	private static final int FIELD_HEIGHT = 200;
	private static final int BRICK_HEIGHT = 10;
	private static final int BRICK_WIDTH = 30;
	private static final int PLAYER_HEIGHT = 10;
	private static final int PLAYER_WIDTH = 40;
	private static final int BALL_SIZE = 10;
	private static final int PLAYER_MARGIN = 20;

	private int ballIncrementY = BALL_SIZE / -2;
	private int ballIncrementX = 0;

	private Rectangle field;
	private List<Rectangle> bricks = new ArrayList<Rectangle>();
	private Rectangle player;
	private Ellipse2D.Double ball;
	protected boolean windowClosed = false;

	public BreakOutExample() {
		super("Break Out");
		this.setSize(FIELD_WIDTH + 50, FIELD_HEIGHT + 50);
		this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
		init();
	}

	private void init() {
		createField();
		createWallBricks();
		createPlayer();
		createBall();
		this.addWindowListener(new WindowAdapter() {

			@Override
			public void windowClosing(WindowEvent evt) {
				BreakOutExample.this.windowClosed = true;
			}
		});
		startGame();
	}

	private void startGame() {
		Thread t = new Thread() {

			@Override
			public void run() {
				while (windowIsOpen()) {
					controller();
					try {
						TimeUnit.MILLISECONDS.sleep(100);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}

		};
		t.start();
	}

	protected boolean windowIsOpen() {
		return !windowClosed;
	}

	private void createBall() {
		ball = new Ellipse2D.Double(player.getCenterX() - BALL_SIZE, player
				.getY()
				- BALL_SIZE, BALL_SIZE, BALL_SIZE);
	}

	private void createPlayer() {
		player = new Rectangle((int) field.getCenterX(), field.height
				- PLAYER_MARGIN - PLAYER_HEIGHT, PLAYER_WIDTH, PLAYER_HEIGHT);
	}

	private void createField() {
		field = new Rectangle(1, 1, FIELD_WIDTH, FIELD_HEIGHT);

		JPanel main = new JPanel() {

			@Override
			protected void paintComponent(Graphics g) {
				super.paintComponent(g);
				paintField(g);
			}

		};
		main.setBounds(field);
		this.add(main);
	}

	private boolean isInField(Rectangle rect) {
		return field.contains(rect);
	}

	public enum TouchLocation {
		RIGHT, LEFT, TOP, BOTTOM, NONE;
	}

	private TouchLocation ballTouchesBrick(Rectangle rect) {
		Rectangle biggerBall = new Rectangle((int) ball.x - 1,
				(int) ball.y - 1, (int) ball.width + 2, (int) ball.height + 2);

		if (biggerBall.intersects(rect)) {
			return TouchLocation.TOP;
		}

		// return biggerBall.intersects(rect);

		return TouchLocation.NONE;
	}

	private TouchLocation ballTouchesBricks() {
		for (Rectangle brick : bricks) {
			final TouchLocation location = ballTouchesBrick(brick);
			if (location != TouchLocation.NONE) {
				return location;
			}
		}
		return TouchLocation.NONE;
	}

	private boolean ballBelowPlayer() {
		return ball.getY() < player.getY();
	}

	private boolean ballTouchesPlayer() {
		final boolean touchesY = player.getY() <= ((int) ball.getMaxY() + 1);
		final boolean isInXRange = ball.getCenterX() >= player.getX()
				&& ball.getCenterX() <= player.getMaxX();
		return touchesY && isInXRange;
	}

	private void createWallBricks() {
		final int maxCols = (field.width / BRICK_WIDTH) - 2;
		final int maxRows = ((field.height / 2) / BRICK_HEIGHT) - 2;
		for (int y = 0; y < maxRows; y++) {
			for (int x = 0; x < maxCols; x++) {
				bricks.add(new Rectangle(10 + BRICK_WIDTH * x, 10
						+ BRICK_HEIGHT * y, BRICK_WIDTH, BRICK_HEIGHT));
			}
		}
	}

	protected void paintField(Graphics g) {
		g.setColor(Color.RED);
		g.drawRect(field.x, field.y, field.width, field.height);

		for (Rectangle brick : bricks) {
			g.fillRect(brick.x, brick.y, brick.width, brick.height);
		}
		g.setColor(Color.BLACK);
		for (Rectangle brick : bricks) {
			g.drawRect(brick.x, brick.y, brick.width, brick.height);
		}

		g.setColor(Color.BLUE);
		g.fillRect(player.x, player.y, player.width, player.height);

		g.setColor(Color.MAGENTA);
		g.fillOval((int) ball.x, (int) ball.y, (int) ball.width,
				(int) ball.height);
	}

	protected void controller() {
		ball.y += ballIncrementY;
		ball.x += ballIncrementX;

		SwingUtilities.invokeLater(new Runnable() {

			@Override
			public void run() {
				BreakOutExample.this.repaint();
			}

		});

		final TouchLocation touchLocation = ballTouchesBricks();
		if (touchLocation != TouchLocation.NONE) {
			switch (touchLocation) {
			case TOP:
				ballIncrementY *= -1;
			}
		} else if (ballTouchesPlayer()) {
			ballIncrementY *= -1;
		}

	}

	public static void main(String[] args) {
		BreakOutExample breakOut = new BreakOutExample();
		breakOut.setVisible(true);

	}

}
 

Kai008

Erfahrenes Mitglied
Danke. Und Respekt, das ging wirklich schnell.Ich brauchte für meins einen halben Tag, obwohl das ja nicht wesendlich mehr macht. Aber da der Code doch ein wenig lang ist brauche ich bitte noch ein wenig Hilfe beim verstehen.

Das final Variablen nach der Deklaration konstant macht und enum sowas wie ein Array ist habe ich schon mal alleine rausgefunden.
private List<Rectangle> bricks = new ArrayList<Rectangle>();
Deklariert glaub ich ein Array. Nur kannst du mir mehr über diese Art sagen? Ich kenne nur String[] Name = new String[Länge];
Und warum hat die private TouchLocation ballTouchesBrick(Rectangle rect) mehrere Namen?
 

zeja

Erfahrenes Mitglied
Mit 6 Jahren Erfahrung im Java Programmieren die ich nun habe geht das ganz fix. Also nicht verzweifeln, die Übung machts.

Zu deinen Fragen:
Das final Variablen nach der Deklaration konstant macht und enum sowas wie ein Array ist habe ich schon mal alleine rausgefunden.

Das mit dem final hast du richtig erkannt. Mit dem enum ist es ein wenig anders.

Es handelt sich dabei um eine Typsichere Aufzählung. Diese gibt es seit Java 1.5. Früher mußte man das alles über Konstanten regeln:

Java:
package de.tutorials.swt;

public class EnumExample {
	private static final int MESSAGE1 = 1;
	private static final int MESSAGE2 = 2;

	private static String getMessage(int messageType) {
		switch (messageType) {
		case MESSAGE1:
			return "Nachricht 1";
		case MESSAGE2:
			return "Nachricht 2";
		default:
			return "unbekannter Typ: " + Integer.toString(messageType);
		}
	}

	public static void main(String[] args) {
		// Konstanten benutzen ist gut
		System.out.println(getMessage(MESSAGE1));
		System.out.println(getMessage(MESSAGE2));
		// aber man könnten auch einen einfachen int übergeben
		// dann ist das Ergebnis nicht wie gewünscht
		System.out.println(getMessage(10));
	}
}

Oben ist unschön, man kann irgendwelche ints übergeben. Unten im Beispiel kann man nur die im enum definierten Typen übergeben. So ist das in der API auch schöner weil ein Benutzer direkt erkennt was er übergeben darf, dass weiß man im ersten Beispiel nur wenn es in der Dokumentation dazu geschrieben wird.

Java:
package de.tutorials.swt;

public class EnumExample {

	public enum MessageType {
		MESSAGE1, MESSAGE2;
	}

	private static String getMessage(MessageType messageType) {
		switch (messageType) {
		case MESSAGE1:
			return "Nachricht 1";
		case MESSAGE2:
			return "Nachricht 2";
		default:
			return "unbekannter Typ: " + messageType;
		}
	}

	public static void main(String[] args) {
		// Hier kann man nur die im enum definierten Typen
		// angeben
		System.out.println(getMessage(MessageType.MESSAGE1));
		System.out.println(getMessage(MessageType.MESSAGE2));
	}
}


private List<Rectangle> bricks = new ArrayList<Rectangle>();
Deklariert glaub ich ein Array. Nur kannst du mir mehr über diese Art sagen? Ich kenne nur String[] Name = new String[Länge];

Das ist kein Array sondern eine Liste. Bei einem Array mußt du ja immer die Länge angeben. Wenn du die Länge nicht vorher weißt kannst du eine Liste nehmen. Um den Typ der Element der Liste festzulegen schreibt man diesen in spitze Klammern (hier <Rectangle>). List nennt man einen generischen Typ.

Und warum hat die private TouchLocation ballTouchesBrick(Rectangle rect) mehrere Namen?

Hmm was meinst du damit?

Dass es zwei Methoden gibt?
private TouchLocation ballTouchesBricks()
private TouchLocation ballTouchesBrick(Rectangle rect) und

Die erste Iteriert über alle Bricks und prüft ob irgendeine von dem Ball berührt wird. Die zweite Methode wird innerhalb der ersten aufgerufen und prüft für einen konkreten Brick ob dieser den Ball berührt. Das habe ich gemacht um nicht alles in die Schleife reinschreiben zu müssen. Das ist nicht unbedingt schön zu lesen.
 

Kai008

Erfahrenes Mitglied
Danke, ich glaube ich habe es jetzt verstanden. Und wenn es doch Probleme gibt experimentiere ich halt mit deinen Skript herum.