Witam!
Moja przygoda z Javą właściwie dopiero się zaczyna i wiele aspektów tego języka stanowi dla mnie tajemnicę. Jedną z nich jest niepoprawne działanie małego apleciku, który napisałem. Właściwie nie przedstawia on nic sensownego, pisanie go było dla mnie formą treningu. Aplet składa się z panelu i dwóch przycisków. Naciśnięcie przycisku "Add ball" powoduje, że uruchamiany jest nowy wątek, który rysuje na panelu piłkę i w pętli powoduje jej ruch i przerysowanie panelu. Piłka swobodnie sobie "lata" i odbija się od krawędzi. Drugi przycisk "Clear" powoduje przerwanie tego wątku i usunięcie z panelu piłek. W środowisku Netbeans w przeglądarce apletów wszystko działa poprawnie, ale już w przeglądarkach internetowych nie: naciśnięcie guzika "Clear" nie ma żadnego efektu - wątek nie jest przerywany a piłeczki dalej sobie latają.
Stąd właśnie moja prośba do was drodzy koledzy i koleżanki - czy ktoś mógłby zerknąć na mój kod źródłowy i znaleźć błąd, który powoduje niepoprawne zachowanie się programu? Będę bardzo wdzięczny.
Plik Ball.java:
package bouncingballs;
import java.awt.Color;
import java.awt.geom.Ellipse2D;
public class Ball extends Ellipse2D.Double {
/* Kąt pomiędzy osią OX a wektorem ruchu piłki.*/
private double motionDir = 3.0/2.0 * Math.PI;
/* Kolor wypełnienia koła */
private Color fillingColor;
/* Kolor konturu koła */
private Color contourColor;
/**
* Tworzy piłkę o zadanych rozmiarach.
* @param x współrzędna X lewego górnego rogu prostokąta opisanego na kole
* @param y współrzędna Y lewego górnego rogu prostokąta opisanego na kole
* @param radius promień koła
* @param fillingColor kolor wypełnienia koła
* @param contourColor kolor konturu koła
*/
public Ball(double x, double y, double radius, Color fillingColor, Color contourColor) {
super(x, y, radius, radius);
this.fillingColor = fillingColor;
this.contourColor = contourColor;
}
/**
* Ustala nowy kierunek poruszania się koła na podstawie podanego kąta.
* @param radians kąt pomiędzy osią oX a wektorem ruchu piłki.
*/
public void setMotionDir(double radians) {
motionDir = radians;
}
/**
* Zwraca kąt, pod jakim porusza się piłka względem osi oX.
* @return kąt ruchu piłki
*/
public double getMotionDir() {
return motionDir;
}
/**
* Zwraca kolor konturu piłki
* @return kolor konturu piłki
*/
public Color getContourColor() {
return contourColor;
}
/**
* Ustala nowy kolor konturu piłki
* @param contourColor kolor konturu piłki
*/
public void setContourColor(Color contourColor) {
this.contourColor = contourColor;
}
/**
* Zwraca kolor wypełnianie piłki.
* @return kolor wypełnienia piłki
*/
public Color getFillingColor() {
return fillingColor;
}
/**
* Ustala nowy kolor wypełnienia piłki
* @param fillingColor kolor wypełnienia piłki
*/
public void setFillingColor(Color fillingColor) {
this.fillingColor = fillingColor;
}
/**
* Przemieszcza piłkę o odległość
distance wzdłuż wektora przecinającego
* oś oX pod kątem zwracanym przez metodę
getMotionDir()<code/>.
* @param distance odległość, o jaką ma się przemieścić piłka
*/
public void move(double distance) {
//Przesunięcie wzdłuż osi y
double dy = distance * Math.sin(motionDir);
//Przesunięci wzdłuż osi x = sqrt(distance^2 - dy^2)
double dx = distance * Math.cos(motionDir);
dy = -dy;
setFrame(getX() + dx, getY() + dy, getWidth(), getWidth());
}
}
Plik BallPanel.java:
package bouncingballs;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import javax.swing.*;
public class BallPanel extends JPanel {
/* Lista wszystkich piłek dodanych do panelu */
private ArrayList<Ball> balls = new ArrayList<>();
/* Odległość, o jaką zostaje przesunięta piłka w czasie odrysowania */
private Double dist = null;
/* Maksymalna ilość piłek, które można dodać do panelu */
private static final int BALLS_LIMIT = 10;
/* Domyślna odległość, o jaką zostaje przesunięta piłka w czasie odrysowania */
private static final int DEFAULT_MOVEMENT_DISTANCE = 2;
/**
* Tworzy panel o układzie krawędziowym.
*/
public BallPanel() {
super(new BorderLayout());
setBackground(Color.white);
}
/**
* Dodaje piłkę do panelu i powoduje jego odrysowanie.
* @param ball piłka
* @return
false, jeśli piłka nie została dodana, tzn. jeśli w
* panelu znajduje się już maksymalna ilość piłek; w przeciwnym wypadku
*
true<code/>
*/
public boolean addBall(Ball ball) {
if(balls.size() == BALLS_LIMIT)
return false;
balls.add(ball);
return true;
}
/**
* Usuwa wszystkie piłki i przerysowuje panel.
*/
public void removeAllBalls() {
balls.clear();
repaint();
}
/**
* Przesuwa wszystkie piłki na zdefinowaną odległość a następnie przerysowuje panel.
* Jeżeli odległość nie została określona, piłka jest przesuwana o domyślną odległość
* równą 1.
* @param g kontekst graficzny
*/
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
for(Ball ball : balls) {
//Rysuje kontur piłki
g2.setPaint(ball.getContourColor());
g2.draw(ball);
//Wypełnia piłkę kolorem
g2.setPaint(ball.getFillingColor());
g2.fill(ball);
}
}
/**
* Przemieszcza wszystkie piłki o określoną odległość. W przypadku, gdy odległość
* nie została określona, piłki przesuwane są o domyślną odległość równą 1.
*/
public void moveBalls() {
for(Ball ball : balls) {
int boundNr = intersectsBound(ball);
if(boundNr != 0) {
double newAngle = 0;
if(boundNr == 1 || boundNr == 3)
newAngle = Math.PI - ball.getMotionDir();
else if(boundNr == 2 || boundNr == 4)
newAngle = -ball.getMotionDir();
newAngle = newAngle < 0 ? 2*Math.PI + newAngle : newAngle;
ball.setMotionDir(newAngle);
}
ball.move(dist == null ? DEFAULT_MOVEMENT_DISTANCE : dist);
}
}
/**
* Sprawdza, czy panel w całości zawiera daną piłkę.
* @param ball piłka
* @return
true, jeśli piłka w całości może być narysowana na
* panelu,
false<code/> gdy piłka wykracza poza granice tego panelu.
*/
public boolean containsBall(Ball ball) {
//Prostokąt ograniczający piłkę
Rectangle2D framingRect = ball.getFrame();
//Prostokąt wyznaczający granice panelu
Rectangle bounds = getBounds();
return bounds.contains(framingRect);
}
/**
* Metoda sprawdza, do której granicy panelu dotarła piłka.
* @param ball badana piłka
* @return 1 - lewa granica,<br/> 2 - górna granica,<br/> 3 - prawa granica,<br/>
* 4 - dolna granica,<br/> 0 - piłka nie dotarła do żadnej granicy.
*/
public int intersectsBound(Ball ball) {
Rectangle bounds = getBounds();
Rectangle2D framingRect = ball.getFrame();
//Sprawdza, czy piłka dotarła do lewej granicy panelu
Line2D line1 = new Line2D.Double(bounds.getMinX(), bounds.getMinY(),
bounds.getMinX(), bounds.getMaxY());
Line2D line2 = new Line2D.Double(framingRect.getMinX(), framingRect.getMinY(),
framingRect.getMaxX(), framingRect.getMinY());
if(line1.intersectsLine(line2))
return 1;
//Sprawdza, czy piłka dotarła do górnej granicy panelu
line1.setLine(bounds.getMinX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMinY());
line2.setLine(framingRect.getMinX(), framingRect.getMinY(), framingRect.getMinX(), framingRect.getMaxY());
if(line1.intersectsLine(line2))
return 2;
//Sprawdza, czy piłka dotarła do prawej granicy panelu
line1.setLine(bounds.getMaxX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMaxY());
line2.setLine(framingRect.getMinX(), framingRect.getMinY(), framingRect.getMaxX(), framingRect.getMinY());
if(line1.intersectsLine(line2))
return 3;
//Sprawdza, czy piłka dotarła do dolnej granicy panelu
line1.setLine(bounds.getMinX(), bounds.getMaxY(), bounds.getMaxX(), bounds.getMaxY());
line2.setLine(framingRect.getMinX(), framingRect.getMinY(), framingRect.getMinX(), framingRect.getMaxY());
if(line1.intersectsLine(line2))
return 4;
return 0;
}
/**
* Ustala odległość, o jaką zostaje przesunięta piłka w czasie odrysowania
* @param dist odleglość
*/
public void setMovementDistance(double dist) {
this.dist = new Double(dist);
}
/**
* Zwraca domyślną odlełość, o jaką zostaną przesunięte piłki, gdy inna
* odległość nie została określona.
* @return
*/
public double getDefaultMovementDistance() {
return DEFAULT_MOVEMENT_DISTANCE;
}
/**
* Zwraca zdefiniowaną odległość, o jaką przesunie się piłka podczas odrysowywania.
* @return ogległość lub
null, gdy odległość nie została zdefiniowana
*/
public Double getMovementDistance() {
return dist == null ? dist : new Double(dist);
}
}
Plik ControlPanel.java:
```java
package bouncingballs;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.swing.*;
public class ControlPanel extends JPanel {
private JButton addBall = new JButton("Add ball");
private JButton clear = new JButton("Clear");
private final BallPanel ballPanel = new BallPanel();
private boolean firstBall = true;
private static Random rand = new Random();
private ExecutorService executor;
public ControlPanel() {
super(new BorderLayout());
setBorder(BorderFactory.createLineBorder(Color.black));
Box buttonPanel = new Box(BoxLayout.X_AXIS);
buttonPanel.add(Box.createHorizontalGlue());
buttonPanel.add(addBall);
buttonPanel.add(Box.createHorizontalStrut(15));
buttonPanel.add(clear);
buttonPanel.add(Box.createHorizontalGlue());
add(ballPanel, BorderLayout.CENTER);
add(buttonPanel, BorderLayout.SOUTH);
addListeners();
}
private void addListeners() {
addBall.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if(firstBall) {
executor = Executors.newCachedThreadPool();
firstBall = false;
executor.execute(new BouncingBallTask(ballPanel));
}
synchronized(ballPanel) {
Ball ball = new Ball(
ballPanel.getWidth() / 2.0,
ballPanel.getHeight() / 2.0,
15,
Color.red,
Color.black);
ball.setMotionDir(Math.toRadians(rand.nextInt(360) + 1));
ballPanel.addBall(ball);
}
}
});
clear.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
executor.shutdownNow();
synchronized(ballPanel) {
ballPanel.removeAllBalls();
repaint();
}
firstBall = true;
}
});
}
}
Plik BouncingBallTask.java:
package bouncingballs;
import java.util.concurrent.TimeUnit;
public class BouncingBallTask implements Runnable {
private final BallPanel ballPanel;
public BouncingBallTask(BallPanel ballPanel) {
this.ballPanel = ballPanel;
}
@Override
public void run() {
try {
while(!Thread.currentThread().isInterrupted()) {
synchronized(ballPanel) {
TimeUnit.MILLISECONDS.sleep(5);
ballPanel.moveBalls();
ballPanel.repaint();
}
}
}
catch(InterruptedException ignore) {}
}
}
package bouncingballs;
import javax.swing.*;
public class BouncingBalls extends JApplet {
@Override
public void init() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
add(new ControlPanel());
}
});
}
}
Plik BouncingBalls.java: